From 31d9dc35395d495fa896532bdffb4529c136ce41 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 4 Dec 2017 22:05:27 -0500 Subject: [PATCH 01/69] read a file --- src-self-hosted/main.zig | 288 +++------------------------------------ std/io.zig | 14 ++ 2 files changed, 34 insertions(+), 268 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 71180b2001..28f0c7b7a7 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -2,286 +2,38 @@ const builtin = @import("builtin"); const io = @import("std").io; const os = @import("std").os; const heap = @import("std").heap; +const warn = @import("std").debug.warn; -// TODO: sync up CLI with c++ code -// TODO: concurrency -error InvalidArgument; -error MissingArg0; +const Token = struct { -var arg0: []u8 = undefined; +}; + +const Tokenizer = struct { + + pub fn next() -> Token { + + } + +}; -var stderr_file: io.File = undefined; -const stderr = &stderr_file.out_stream; pub fn main() -> %void { - stderr_file = %return io.getStdErr(); - if (internal_main()) |_| { - return; - } else |err| { - if (err == error.InvalidArgument) { - stderr.print("\n") %% return err; - printUsage(stderr) %% return err; - } else { - stderr.print("{}\n", err) %% return err; - } - return err; - } -} - -pub fn internal_main() -> %void { - var args_it = os.args(); - - var incrementing_allocator = heap.IncrementingAllocator.init(10 * 1024 * 1024) %% |err| { - io.stderr.printf("Unable to allocate memory") %% {}; + main2() %% |err| { + warn("{}\n", @errorName(err)); return err; }; +} + +pub fn main2() -> %void { + var incrementing_allocator = %return heap.IncrementingAllocator.init(10 * 1024 * 1024); defer incrementing_allocator.deinit(); const allocator = &incrementing_allocator.allocator; - - arg0 = %return (args_it.next(allocator) ?? error.MissingArg0); - defer allocator.free(arg0); - var build_mode = builtin.Mode.Debug; - var strip = false; - var is_static = false; - var verbose = false; - var verbose_link = false; - var verbose_ir = false; - var mwindows = false; - var mconsole = false; + const target_file = "input.zig"; // TODO - while (args_it.next()) |arg_or_err| { - const arg = %return arg_or_err; + const target_file_buf = %return io.readFileAlloc(target_file, allocator); - if (arg[0] == '-') { - if (strcmp(arg, "--release-fast") == 0) { - build_mode = builtin.Mode.ReleaseFast; - } else if (strcmp(arg, "--release-safe") == 0) { - build_mode = builtin.Mode.ReleaseSafe; - } else if (strcmp(arg, "--strip") == 0) { - strip = true; - } else if (strcmp(arg, "--static") == 0) { - is_static = true; - } else if (strcmp(arg, "--verbose") == 0) { - verbose = true; - } else if (strcmp(arg, "--verbose-link") == 0) { - verbose_link = true; - } else if (strcmp(arg, "--verbose-ir") == 0) { - verbose_ir = true; - } else if (strcmp(arg, "-mwindows") == 0) { - mwindows = true; - } else if (strcmp(arg, "-mconsole") == 0) { - mconsole = true; - } else if (strcmp(arg, "-municode") == 0) { - municode = true; - } else if (strcmp(arg, "-rdynamic") == 0) { - rdynamic = true; - } else if (strcmp(arg, "--each-lib-rpath") == 0) { - each_lib_rpath = true; - } else if (strcmp(arg, "--enable-timing-info") == 0) { - timing_info = true; - } else if (strcmp(arg, "--test-cmd-bin") == 0) { - test_exec_args.append(nullptr); - } else if (arg[1] == 'L' && arg[2] != 0) { - // alias for --library-path - lib_dirs.append(&arg[2]); - } else if (strcmp(arg, "--pkg-begin") == 0) { - if (i + 2 >= argc) { - fprintf(stderr, "Expected 2 arguments after --pkg-begin\n"); - return usage(arg0); - } - CliPkg *new_cur_pkg = allocate(1); - i += 1; - new_cur_pkg->name = argv[i]; - i += 1; - new_cur_pkg->path = argv[i]; - new_cur_pkg->parent = cur_pkg; - cur_pkg->children.append(new_cur_pkg); - cur_pkg = new_cur_pkg; - } else if (strcmp(arg, "--pkg-end") == 0) { - if (cur_pkg->parent == nullptr) { - fprintf(stderr, "Encountered --pkg-end with no matching --pkg-begin\n"); - return EXIT_FAILURE; - } - cur_pkg = cur_pkg->parent; - } else if (i + 1 >= argc) { - fprintf(stderr, "Expected another argument after %s\n", arg); - return usage(arg0); - } else { - i += 1; - if (strcmp(arg, "--output") == 0) { - out_file = argv[i]; - } else if (strcmp(arg, "--output-h") == 0) { - out_file_h = argv[i]; - } else if (strcmp(arg, "--color") == 0) { - if (strcmp(argv[i], "auto") == 0) { - color = ErrColorAuto; - } else if (strcmp(argv[i], "on") == 0) { - color = ErrColorOn; - } else if (strcmp(argv[i], "off") == 0) { - color = ErrColorOff; - } else { - fprintf(stderr, "--color options are 'auto', 'on', or 'off'\n"); - return usage(arg0); - } - } else if (strcmp(arg, "--name") == 0) { - out_name = argv[i]; - } else if (strcmp(arg, "--libc-lib-dir") == 0) { - libc_lib_dir = argv[i]; - } else if (strcmp(arg, "--libc-static-lib-dir") == 0) { - libc_static_lib_dir = argv[i]; - } else if (strcmp(arg, "--libc-include-dir") == 0) { - libc_include_dir = argv[i]; - } else if (strcmp(arg, "--msvc-lib-dir") == 0) { - msvc_lib_dir = argv[i]; - } else if (strcmp(arg, "--kernel32-lib-dir") == 0) { - kernel32_lib_dir = argv[i]; - } else if (strcmp(arg, "--zig-install-prefix") == 0) { - zig_install_prefix = argv[i]; - } else if (strcmp(arg, "--dynamic-linker") == 0) { - dynamic_linker = argv[i]; - } else if (strcmp(arg, "-isystem") == 0) { - clang_argv.append("-isystem"); - clang_argv.append(argv[i]); - } else if (strcmp(arg, "-dirafter") == 0) { - clang_argv.append("-dirafter"); - clang_argv.append(argv[i]); - } else if (strcmp(arg, "-mllvm") == 0) { - clang_argv.append("-mllvm"); - clang_argv.append(argv[i]); - - llvm_argv.append(argv[i]); - } else if (strcmp(arg, "--library-path") == 0 || strcmp(arg, "-L") == 0) { - lib_dirs.append(argv[i]); - } else if (strcmp(arg, "--library") == 0) { - link_libs.append(argv[i]); - } else if (strcmp(arg, "--object") == 0) { - objects.append(argv[i]); - } else if (strcmp(arg, "--assembly") == 0) { - asm_files.append(argv[i]); - } else if (strcmp(arg, "--cache-dir") == 0) { - cache_dir = argv[i]; - } else if (strcmp(arg, "--target-arch") == 0) { - target_arch = argv[i]; - } else if (strcmp(arg, "--target-os") == 0) { - target_os = argv[i]; - } else if (strcmp(arg, "--target-environ") == 0) { - target_environ = argv[i]; - } else if (strcmp(arg, "-mmacosx-version-min") == 0) { - mmacosx_version_min = argv[i]; - } else if (strcmp(arg, "-mios-version-min") == 0) { - mios_version_min = argv[i]; - } else if (strcmp(arg, "-framework") == 0) { - frameworks.append(argv[i]); - } else if (strcmp(arg, "--linker-script") == 0) { - linker_script = argv[i]; - } else if (strcmp(arg, "-rpath") == 0) { - rpath_list.append(argv[i]); - } else if (strcmp(arg, "--test-filter") == 0) { - test_filter = argv[i]; - } else if (strcmp(arg, "--test-name-prefix") == 0) { - test_name_prefix = argv[i]; - } else if (strcmp(arg, "--ver-major") == 0) { - ver_major = atoi(argv[i]); - } else if (strcmp(arg, "--ver-minor") == 0) { - ver_minor = atoi(argv[i]); - } else if (strcmp(arg, "--ver-patch") == 0) { - ver_patch = atoi(argv[i]); - } else if (strcmp(arg, "--test-cmd") == 0) { - test_exec_args.append(argv[i]); - } else { - fprintf(stderr, "Invalid argument: %s\n", arg); - return usage(arg0); - } - } - } - } + warn("{}", target_file_buf); } - -fn printUsage(outstream: &io.OutStream) -> %void { - %return outstream.print("Usage: {} [command] [options]\n", arg0); - %return outstream.write( - \\Commands: - \\ build build project from build.zig - \\ build-exe [source] create executable from source or object files - \\ build-lib [source] create library from source or object files - \\ build-obj [source] create object from source or assembly - \\ translate-c [source] convert c code to zig code - \\ targets list available compilation targets - \\ test [source] create and run a test build - \\ version print version number and exit - \\ zen print zen of zig and exit - \\Compile Options: - \\ --assembly [source] add assembly file to build - \\ --cache-dir [path] override the cache directory - \\ --color [auto|off|on] enable or disable colored error messages - \\ --enable-timing-info print timing diagnostics - \\ --libc-include-dir [path] directory where libc stdlib.h resides - \\ --name [name] override output name - \\ --output [file] override destination path - \\ --output-h [file] override generated header file path - \\ --pkg-begin [name] [path] make package available to import and push current pkg - \\ --pkg-end pop current pkg - \\ --release-fast build with optimizations on and safety off - \\ --release-safe build with optimizations on and safety on - \\ --static output will be statically linked - \\ --strip exclude debug symbols - \\ --target-arch [name] specify target architecture - \\ --target-environ [name] specify target environment - \\ --target-os [name] specify target operating system - \\ --verbose turn on compiler debug output - \\ --verbose-link turn on compiler debug output for linking only - \\ --verbose-ir turn on compiler debug output for IR only - \\ --zig-install-prefix [path] override directory where zig thinks it is installed - \\ -dirafter [dir] same as -isystem but do it last - \\ -isystem [dir] add additional search path for other .h files - \\ -mllvm [arg] additional arguments to forward to LLVM's option processing - \\Link Options: - \\ --ar-path [path] set the path to ar - \\ --dynamic-linker [path] set the path to ld.so - \\ --each-lib-rpath add rpath for each used dynamic library - \\ --libc-lib-dir [path] directory where libc crt1.o resides - \\ --libc-static-lib-dir [path] directory where libc crtbegin.o resides - \\ --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides - \\ --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides - \\ --library [lib] link against lib - \\ --library-path [dir] add a directory to the library search path - \\ --linker-script [path] use a custom linker script - \\ --object [obj] add object file to build - \\ -L[dir] alias for --library-path - \\ -rdynamic add all symbols to the dynamic symbol table - \\ -rpath [path] add directory to the runtime library search path - \\ -mconsole (windows) --subsystem console to the linker - \\ -mwindows (windows) --subsystem windows to the linker - \\ -municode (windows) link with unicode - \\ -framework [name] (darwin) link against framework - \\ -mios-version-min [ver] (darwin) set iOS deployment target - \\ -mmacosx-version-min [ver] (darwin) set Mac OS X deployment target - \\ --ver-major [ver] dynamic library semver major version - \\ --ver-minor [ver] dynamic library semver minor version - \\ --ver-patch [ver] dynamic library semver patch version - \\Test Options: - \\ --test-filter [text] skip tests that do not match filter - \\ --test-name-prefix [text] add prefix to all tests - \\ --test-cmd [arg] specify test execution command one arg at a time - \\ --test-cmd-bin appends test binary path to test cmd args - \\ - ); -} - -const ZIG_ZEN = - \\ * Communicate intent precisely. - \\ * Edge cases matter. - \\ * Favor reading code over writing code. - \\ * Only one obvious way to do things. - \\ * Runtime crashes are better than bugs. - \\ * Compile errors are better than runtime crashes. - \\ * Incremental improvements. - \\ * Avoid local maximums. - \\ * Reduce the amount one must remember. - \\ * Minimize energy spent on coding style. - \\ * Together we serve end users. - \\ -; diff --git a/std/io.zig b/std/io.zig index c86ebed326..87a46b05eb 100644 --- a/std/io.zig +++ b/std/io.zig @@ -493,6 +493,20 @@ pub fn writeFile(path: []const u8, data: []const u8, allocator: ?&mem.Allocator) %return file.write(data); } +/// On success, caller owns returned buffer. +pub fn readFileAlloc(path: []const u8, allocator: &mem.Allocator) -> %[]u8 { + var file = %return File.openRead(path, allocator); + defer file.close(); + + const size = %return file.getEndPos(); + const buf = %return allocator.alloc(u8, size); + %defer allocator.free(buf); + + var adapter = FileInStream.init(&file); + %return adapter.stream.readNoEof(buf); + return buf; +} + pub const BufferedInStream = BufferedInStreamCustom(os.page_size); pub fn BufferedInStreamCustom(comptime buffer_size: usize) -> type { From 798dbe487b678a85c7e10b78c2424c4b3508475b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 4 Dec 2017 23:09:03 -0500 Subject: [PATCH 02/69] simple tokenization --- src-self-hosted/main.zig | 131 ++++++++++++++++++++++++++++++++++++++- src/ir.cpp | 45 ++++++++------ 2 files changed, 153 insertions(+), 23 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 28f0c7b7a7..654d9443b1 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -3,18 +3,134 @@ const io = @import("std").io; const os = @import("std").os; const heap = @import("std").heap; const warn = @import("std").debug.warn; - +const assert = @import("std").debug.assert; +const mem = @import("std").mem; const Token = struct { + id: Id, + start: usize, + end: usize, + const Keyword = enum { + @"align", + @"and", + @"asm", + @"break", + @"coldcc", + @"comptime", + @"const", + @"continue", + @"defer", + @"else", + @"enum", + @"error", + @"export", + @"extern", + @"false", + @"fn", + @"for", + @"goto", + @"if", + @"inline", + @"nakedcc", + @"noalias", + @"null", + @"or", + @"packed", + @"pub", + @"return", + @"stdcallcc", + @"struct", + @"switch", + @"test", + @"this", + @"true", + @"undefined", + @"union", + @"unreachable", + @"use", + @"var", + @"volatile", + @"while", + }; + + fn getKeyword(bytes: []const u8) -> ?Keyword { + comptime var i = 0; + inline while (i < @memberCount(Keyword)) : (i += 1) { + if (mem.eql(u8, @memberName(Keyword, i), bytes)) { + return Keyword(i); + } + } + return null; + } + + + const Id = union(enum) { + Invalid, + Identifier, + Keyword: Keyword, + Eof, + }; }; const Tokenizer = struct { + buffer: []const u8, + index: usize, - pub fn next() -> Token { - + pub fn dump(self: &Tokenizer, token: &const Token) { + warn("{} \"{}\"\n", @tagName(token.id), self.buffer[token.start..token.end]); } + pub fn init(buffer: []const u8) -> Tokenizer { + return Tokenizer { + .buffer = buffer, + .index = 0, + }; + } + + const State = enum { + Start, + Identifier, + }; + + pub fn next(self: &Tokenizer) -> Token { + var state = State.Start; + var result = Token { + .id = Token.Id { .Eof = {} }, + .start = self.index, + .end = undefined, + }; + while (self.index < self.buffer.len) : (self.index += 1) { + const c = self.buffer[self.index]; + switch (state) { + State.Start => switch (c) { + ' ', '\n' => { + result.start = self.index + 1; + }, + 'a'...'z', 'A'...'Z', '_' => { + state = State.Identifier; + result.id = Token.Id { .Identifier = {} }; + }, + else => { + result.id = Token.Id { .Invalid = {} }; + self.index += 1; + break; + }, + }, + State.Identifier => switch (c) { + 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + else => { + if (Token.getKeyword(self.buffer[result.start..self.index])) |keyword_id| { + result.id = Token.Id { .Keyword = keyword_id }; + } + break; + }, + }, + } + } + result.end = self.index; + return result; + } }; @@ -36,4 +152,13 @@ pub fn main2() -> %void { const target_file_buf = %return io.readFileAlloc(target_file, allocator); warn("{}", target_file_buf); + + var tokenizer = Tokenizer.init(target_file_buf); + while (true) { + const token = tokenizer.next(); + tokenizer.dump(token); + if (@TagType(Token.Id)(token.id) == Token.Id.Eof) { + break; + } + } } diff --git a/src/ir.cpp b/src/ir.cpp index facd7087f0..6ad6492a0c 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -8422,16 +8422,6 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour if (type_is_invalid(wanted_type)) return ira->codegen->invalid_instruction; - if (actual_type != wanted_type->data.enumeration.tag_int_type) { - ir_add_error(ira, source_instr, - buf_sprintf("integer to enum cast from '%s' instead of its tag type, '%s'", - buf_ptr(&actual_type->name), - buf_ptr(&wanted_type->data.enumeration.tag_int_type->name))); - return ira->codegen->invalid_instruction; - } - - assert(actual_type->id == TypeTableEntryIdInt); - if (instr_is_comptime(target)) { ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); if (!val) @@ -8453,6 +8443,17 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour return result; } + if (actual_type != wanted_type->data.enumeration.tag_int_type) { + ir_add_error(ira, source_instr, + buf_sprintf("integer to enum cast from '%s' instead of its tag type, '%s'", + buf_ptr(&actual_type->name), + buf_ptr(&wanted_type->data.enumeration.tag_int_type->name))); + return ira->codegen->invalid_instruction; + } + + assert(actual_type->id == TypeTableEntryIdInt); + + IrInstruction *result = ir_build_int_to_enum(&ira->new_irb, source_instr->scope, source_instr->source_node, target); result->value.type = wanted_type; @@ -8822,6 +8823,20 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } + // explicit cast from integer to enum type with no payload + if ((actual_type->id == TypeTableEntryIdInt || actual_type->id == TypeTableEntryIdNumLitInt) && + wanted_type->id == TypeTableEntryIdEnum) + { + return ir_analyze_int_to_enum(ira, source_instr, value, wanted_type); + } + + // explicit cast from enum type with no payload to integer + if ((wanted_type->id == TypeTableEntryIdInt || wanted_type->id == TypeTableEntryIdNumLitInt) && + actual_type->id == TypeTableEntryIdEnum) + { + return ir_analyze_enum_to_int(ira, source_instr, value, wanted_type); + } + // explicit cast from number literal to another type // explicit cast from number literal to &const integer if (actual_type->id == TypeTableEntryIdNumLitFloat || @@ -8886,16 +8901,6 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_analyze_int_to_err(ira, source_instr, value); } - // explicit cast from integer to enum type with no payload - if (actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdEnum) { - return ir_analyze_int_to_enum(ira, source_instr, value, wanted_type); - } - - // explicit cast from enum type with no payload to integer - if (wanted_type->id == TypeTableEntryIdInt && actual_type->id == TypeTableEntryIdEnum) { - return ir_analyze_enum_to_int(ira, source_instr, value, wanted_type); - } - // explicit cast from union to the enum type of the union if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) { type_ensure_zero_bits_known(ira->codegen, actual_type); From 07898cc0dfae880b35bededa726c7e16a3c45f3d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 4 Dec 2017 23:25:59 -0500 Subject: [PATCH 03/69] tokenizing string literals --- src-self-hosted/main.zig | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 654d9443b1..969b2a0bb4 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -64,12 +64,15 @@ const Token = struct { return null; } + const StrLitKind = enum {Normal, C}; const Id = union(enum) { Invalid, Identifier, Keyword: Keyword, + StringLiteral: StrLitKind, Eof, + Builtin, }; }; @@ -91,6 +94,10 @@ const Tokenizer = struct { const State = enum { Start, Identifier, + Builtin, + C, + StringLiteral, + StringLiteralBackslash, }; pub fn next(self: &Tokenizer) -> Token { @@ -107,10 +114,22 @@ const Tokenizer = struct { ' ', '\n' => { result.start = self.index + 1; }, - 'a'...'z', 'A'...'Z', '_' => { + 'c' => { + state = State.C; + result.id = Token.Id { .Identifier = {} }; + }, + '"' => { + state = State.StringLiteral; + result.id = Token.Id { .StringLiteral = Token.StrLitKind.Normal }; + }, + 'a'...'b', 'd'...'z', 'A'...'Z', '_' => { state = State.Identifier; result.id = Token.Id { .Identifier = {} }; }, + '@' => { + state = State.Builtin; + result.id = Token.Id { .Builtin = {} }; + }, else => { result.id = Token.Id { .Invalid = {} }; self.index += 1; @@ -126,6 +145,36 @@ const Tokenizer = struct { break; }, }, + State.Builtin => switch (c) { + 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + else => break, + }, + State.C => switch (c) { + '\\' => @panic("TODO"), + '"' => { + state = State.StringLiteral; + result.id = Token.Id { .StringLiteral = Token.StrLitKind.C }; + }, + 'a'...'z', 'A'...'Z', '_', '0'...'9' => { + state = State.Identifier; + }, + else => break, + }, + State.StringLiteral => switch (c) { + '\\' => { + state = State.StringLiteralBackslash; + }, + '"' => { + self.index += 1; + break; + }, + '\n' => break, // Look for this error later. + else => {}, + }, + State.StringLiteralBackslash => switch (c) { + '\n' => break, // Look for this error later. + else => {}, + }, } } result.end = self.index; From 7297baa9c6f3f1b894b47fe7b06b5ae2007d0f3c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 4 Dec 2017 23:29:39 -0500 Subject: [PATCH 04/69] tokenizing basic operators --- src-self-hosted/main.zig | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 969b2a0bb4..263a4c9f7d 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -73,6 +73,10 @@ const Token = struct { StringLiteral: StrLitKind, Eof, Builtin, + Equal, + LParen, + RParen, + Semicolon, }; }; @@ -130,6 +134,26 @@ const Tokenizer = struct { state = State.Builtin; result.id = Token.Id { .Builtin = {} }; }, + '=' => { + result.id = Token.Id { .Equal = {} }; + self.index += 1; + break; + }, + '(' => { + result.id = Token.Id { .LParen = {} }; + self.index += 1; + break; + }, + ')' => { + result.id = Token.Id { .RParen = {} }; + self.index += 1; + break; + }, + ';' => { + result.id = Token.Id { .Semicolon = {} }; + self.index += 1; + break; + }, else => { result.id = Token.Id { .Invalid = {} }; self.index += 1; From 3976981ab3370d099535d79d42bd82d5924df7f5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 4 Dec 2017 23:40:33 -0500 Subject: [PATCH 05/69] tokenizing hello world --- src-self-hosted/main.zig | 47 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 263a4c9f7d..4e040840b7 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -77,6 +77,12 @@ const Token = struct { LParen, RParen, Semicolon, + Percent, + LBrace, + RBrace, + Period, + Minus, + Arrow, }; }; @@ -102,6 +108,7 @@ const Tokenizer = struct { C, StringLiteral, StringLiteralBackslash, + Minus, }; pub fn next(self: &Tokenizer) -> Token { @@ -154,6 +161,29 @@ const Tokenizer = struct { self.index += 1; break; }, + '%' => { + result.id = Token.Id { .Percent = {} }; + self.index += 1; + break; + }, + '{' => { + result.id = Token.Id { .LBrace = {} }; + self.index += 1; + break; + }, + '}' => { + result.id = Token.Id { .RBrace = {} }; + self.index += 1; + break; + }, + '.' => { + result.id = Token.Id { .Period = {} }; + self.index += 1; + break; + }, + '-' => { + state = State.Minus; + }, else => { result.id = Token.Id { .Invalid = {} }; self.index += 1; @@ -195,9 +225,24 @@ const Tokenizer = struct { '\n' => break, // Look for this error later. else => {}, }, + State.StringLiteralBackslash => switch (c) { '\n' => break, // Look for this error later. - else => {}, + else => { + state = State.StringLiteral; + }, + }, + + State.Minus => switch (c) { + '>' => { + result.id = Token.Id { .Arrow = {} }; + self.index += 1; + break; + }, + else => { + result.id = Token.Id { .Minus = {} }; + break; + }, }, } } From 08d531143f0b373cbc54e037fa526fb00d9db398 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 00:20:23 -0500 Subject: [PATCH 06/69] parser skeleton --- src-self-hosted/main.zig | 228 +++++++++++++++++++++++++++++---------- 1 file changed, 173 insertions(+), 55 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 4e040840b7..de44a4652f 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -5,60 +5,66 @@ const heap = @import("std").heap; const warn = @import("std").debug.warn; const assert = @import("std").debug.assert; const mem = @import("std").mem; +const ArrayList = @import("std").ArrayList; + const Token = struct { id: Id, start: usize, end: usize, - const Keyword = enum { - @"align", - @"and", - @"asm", - @"break", - @"coldcc", - @"comptime", - @"const", - @"continue", - @"defer", - @"else", - @"enum", - @"error", - @"export", - @"extern", - @"false", - @"fn", - @"for", - @"goto", - @"if", - @"inline", - @"nakedcc", - @"noalias", - @"null", - @"or", - @"packed", - @"pub", - @"return", - @"stdcallcc", - @"struct", - @"switch", - @"test", - @"this", - @"true", - @"undefined", - @"union", - @"unreachable", - @"use", - @"var", - @"volatile", - @"while", + const KeywordId = struct { + bytes: []const u8, + id: Id, }; - fn getKeyword(bytes: []const u8) -> ?Keyword { - comptime var i = 0; - inline while (i < @memberCount(Keyword)) : (i += 1) { - if (mem.eql(u8, @memberName(Keyword, i), bytes)) { - return Keyword(i); + const keywords = []KeywordId { + KeywordId{.bytes="align", .id = Id {.Keyword_align = {}}}, + KeywordId{.bytes="and", .id = Id {.Keyword_and = {}}}, + KeywordId{.bytes="asm", .id = Id {.Keyword_asm = {}}}, + KeywordId{.bytes="break", .id = Id {.Keyword_break = {}}}, + KeywordId{.bytes="coldcc", .id = Id {.Keyword_coldcc = {}}}, + KeywordId{.bytes="comptime", .id = Id {.Keyword_comptime = {}}}, + KeywordId{.bytes="const", .id = Id {.Keyword_const = {}}}, + KeywordId{.bytes="continue", .id = Id {.Keyword_continue = {}}}, + KeywordId{.bytes="defer", .id = Id {.Keyword_defer = {}}}, + KeywordId{.bytes="else", .id = Id {.Keyword_else = {}}}, + KeywordId{.bytes="enum", .id = Id {.Keyword_enum = {}}}, + KeywordId{.bytes="error", .id = Id {.Keyword_error = {}}}, + KeywordId{.bytes="export", .id = Id {.Keyword_export = {}}}, + KeywordId{.bytes="extern", .id = Id {.Keyword_extern = {}}}, + KeywordId{.bytes="false", .id = Id {.Keyword_false = {}}}, + KeywordId{.bytes="fn", .id = Id {.Keyword_fn = {}}}, + KeywordId{.bytes="for", .id = Id {.Keyword_for = {}}}, + KeywordId{.bytes="goto", .id = Id {.Keyword_goto = {}}}, + KeywordId{.bytes="if", .id = Id {.Keyword_if = {}}}, + KeywordId{.bytes="inline", .id = Id {.Keyword_inline = {}}}, + KeywordId{.bytes="nakedcc", .id = Id {.Keyword_nakedcc = {}}}, + KeywordId{.bytes="noalias", .id = Id {.Keyword_noalias = {}}}, + KeywordId{.bytes="null", .id = Id {.Keyword_null = {}}}, + KeywordId{.bytes="or", .id = Id {.Keyword_or = {}}}, + KeywordId{.bytes="packed", .id = Id {.Keyword_packed = {}}}, + KeywordId{.bytes="pub", .id = Id {.Keyword_pub = {}}}, + KeywordId{.bytes="return", .id = Id {.Keyword_return = {}}}, + KeywordId{.bytes="stdcallcc", .id = Id {.Keyword_stdcallcc = {}}}, + KeywordId{.bytes="struct", .id = Id {.Keyword_struct = {}}}, + KeywordId{.bytes="switch", .id = Id {.Keyword_switch = {}}}, + KeywordId{.bytes="test", .id = Id {.Keyword_test = {}}}, + KeywordId{.bytes="this", .id = Id {.Keyword_this = {}}}, + KeywordId{.bytes="true", .id = Id {.Keyword_true = {}}}, + KeywordId{.bytes="undefined", .id = Id {.Keyword_undefined = {}}}, + KeywordId{.bytes="union", .id = Id {.Keyword_union = {}}}, + KeywordId{.bytes="unreachable", .id = Id {.Keyword_unreachable = {}}}, + KeywordId{.bytes="use", .id = Id {.Keyword_use = {}}}, + KeywordId{.bytes="var", .id = Id {.Keyword_var = {}}}, + KeywordId{.bytes="volatile", .id = Id {.Keyword_volatile = {}}}, + KeywordId{.bytes="while", .id = Id {.Keyword_while = {}}}, + }; + + fn getKeyword(bytes: []const u8) -> ?Id { + for (keywords) |kw| { + if (mem.eql(u8, kw.bytes, bytes)) { + return kw.id; } } return null; @@ -69,7 +75,6 @@ const Token = struct { const Id = union(enum) { Invalid, Identifier, - Keyword: Keyword, StringLiteral: StrLitKind, Eof, Builtin, @@ -83,6 +88,46 @@ const Token = struct { Period, Minus, Arrow, + Keyword_align, + Keyword_and, + Keyword_asm, + Keyword_break, + Keyword_coldcc, + Keyword_comptime, + Keyword_const, + Keyword_continue, + Keyword_defer, + Keyword_else, + Keyword_enum, + Keyword_error, + Keyword_export, + Keyword_extern, + Keyword_false, + Keyword_fn, + Keyword_for, + Keyword_goto, + Keyword_if, + Keyword_inline, + Keyword_nakedcc, + Keyword_noalias, + Keyword_null, + Keyword_or, + Keyword_packed, + Keyword_pub, + Keyword_return, + Keyword_stdcallcc, + Keyword_struct, + Keyword_switch, + Keyword_test, + Keyword_this, + Keyword_true, + Keyword_undefined, + Keyword_union, + Keyword_unreachable, + Keyword_use, + Keyword_var, + Keyword_volatile, + Keyword_while, }; }; @@ -193,8 +238,8 @@ const Tokenizer = struct { State.Identifier => switch (c) { 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, else => { - if (Token.getKeyword(self.buffer[result.start..self.index])) |keyword_id| { - result.id = Token.Id { .Keyword = keyword_id }; + if (Token.getKeyword(self.buffer[result.start..self.index])) |id| { + result.id = id; } break; }, @@ -251,6 +296,73 @@ const Tokenizer = struct { } }; +const AstNode = struct { + +}; + +const Parser = struct { + tokenizer: &Tokenizer, + allocator: &mem.Allocator, + + fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator) -> Parser { + return Parser { + .tokenizer = tokenizer, + .allocator = allocator, + }; + } + + const StackFrame = struct { + + }; + + const State = enum { + TopLevel, + Expression, + }; + + fn parse(self: &Parser) -> %void { + var stack = ArrayList(StackFrame).init(self.allocator); + defer stack.deinit(); + + var state = State.TopLevel; + while (true) { + const token = self.tokenizer.next(); + switch (state) { + State.TopLevel => switch (token.id) { + Token.Id.Keyword_pub => { + const next_token = self.tokenizer.next(); + switch (next_token.id) { + Token.Id.Keyword_fn => { + const fn_name = self.tokenizer.next(); + if (@TagType(Token.Id)(fn_name.id) != Token.Id.Identifier) { + @panic("parse error"); + } + + const lparen = self.tokenizer.next(); + if (@TagType(Token.Id)(lparen.id) != Token.Id.LParen) { + @panic("parse error"); + } + }, + Token.Id.Keyword_const => @panic("TODO"), + Token.Id.Keyword_var => @panic("TODO"), + Token.Id.Keyword_use => @panic("TODO"), + else => @panic("parse error"), + } + }, + Token.Id.Keyword_const => @panic("TODO"), + Token.Id.Keyword_var => @panic("TODO"), + Token.Id.Keyword_fn => @panic("TODO"), + Token.Id.Keyword_export => @panic("TODO"), + Token.Id.Keyword_use => @panic("TODO"), + Token.Id.Keyword_comptime => @panic("TODO"), + else => @panic("parse error"), + }, + State.Expression => @panic("TODO"), + } + } + } +}; + pub fn main() -> %void { main2() %% |err| { @@ -271,12 +383,18 @@ pub fn main2() -> %void { warn("{}", target_file_buf); - var tokenizer = Tokenizer.init(target_file_buf); - while (true) { - const token = tokenizer.next(); - tokenizer.dump(token); - if (@TagType(Token.Id)(token.id) == Token.Id.Eof) { - break; + { + var tokenizer = Tokenizer.init(target_file_buf); + while (true) { + const token = tokenizer.next(); + tokenizer.dump(token); + if (@TagType(Token.Id)(token.id) == Token.Id.Eof) { + break; + } } } + + var tokenizer = Tokenizer.init(target_file_buf); + var parser = Parser.init(&tokenizer, allocator); + %return parser.parse(); } From 74cea89fce2af41d2af7379ff526d7046c4d8d77 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 12:28:59 -0500 Subject: [PATCH 07/69] translate-c: fix not printing clang errors --- src/codegen.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index f84636502d..35974a73dd 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5298,18 +5298,19 @@ void codegen_translate_c(CodeGen *g, Buf *full_path) { ZigList errors = {0}; int err = parse_h_file(import, &errors, buf_ptr(full_path), g, nullptr); - if (err) { - fprintf(stderr, "unable to parse C file: %s\n", err_str(err)); - exit(1); - } - if (errors.length > 0) { + if (err == ErrorCCompileErrors && errors.length > 0) { for (size_t i = 0; i < errors.length; i += 1) { ErrorMsg *err_msg = errors.at(i); print_err_msg(err_msg, g->err_color); } exit(1); } + + if (err) { + fprintf(stderr, "unable to parse C file: %s\n", err_str(err)); + exit(1); + } } static ImportTableEntry *add_special_code(CodeGen *g, PackageTableEntry *package, const char *basename) { From 63a2f9a8b2251ffdc37d5f28dfbd3f6be1bd7908 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 18:09:22 -0500 Subject: [PATCH 08/69] fix casting integer literal to enum --- src/ir.cpp | 26 +++++++++++++++++++------- test/cases/enum.zig | 5 +++++ test/compile_errors.zig | 12 ++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index facd7087f0..7c933ae950 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -8436,14 +8436,16 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); if (!val) return ira->codegen->invalid_instruction; - BigInt enum_member_count; - bigint_init_unsigned(&enum_member_count, wanted_type->data.enumeration.src_field_count); - if (bigint_cmp(&val->data.x_bigint, &enum_member_count) != CmpLT) { + + TypeEnumField *field = find_enum_field_by_tag(wanted_type, &val->data.x_bigint); + if (field == nullptr) { Buf *val_buf = buf_alloc(); bigint_append_buf(val_buf, &val->data.x_bigint, 10); - ir_add_error(ira, source_instr, - buf_sprintf("integer value %s too big for enum '%s' which has %" PRIu32 " fields", - buf_ptr(val_buf), buf_ptr(&wanted_type->name), wanted_type->data.enumeration.src_field_count)); + ErrorMsg *msg = ir_add_error(ira, source_instr, + buf_sprintf("enum '%s' has no tag matching integer value %s", + buf_ptr(&wanted_type->name), buf_ptr(val_buf))); + add_error_note(ira->codegen, msg, wanted_type->data.enumeration.decl_node, + buf_sprintf("'%s' declared here", buf_ptr(&wanted_type->name))); return ira->codegen->invalid_instruction; } @@ -8827,7 +8829,17 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst if (actual_type->id == TypeTableEntryIdNumLitFloat || actual_type->id == TypeTableEntryIdNumLitInt) { - if (wanted_type->id == TypeTableEntryIdPointer && + if (wanted_type->id == TypeTableEntryIdEnum) { + IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.enumeration.tag_int_type, value); + if (type_is_invalid(cast1->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + if (type_is_invalid(cast2->value.type)) + return ira->codegen->invalid_instruction; + + return cast2; + } else if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.is_const) { IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value); diff --git a/test/cases/enum.zig b/test/cases/enum.zig index ec900511eb..d15614f9fd 100644 --- a/test/cases/enum.zig +++ b/test/cases/enum.zig @@ -344,3 +344,8 @@ fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) { MultipleChoice2.Unspecified5 => 9, }); } + +test "cast integer literal to enum" { + assert(MultipleChoice2(0) == MultipleChoice2.Unspecified1); + assert(MultipleChoice2(40) == MultipleChoice2.B); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5d13ed8d48..162109f4d8 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2684,4 +2684,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { , ".tmp_source.zig:11:13: error: switch on union which has no attached enum", ".tmp_source.zig:1:17: note: consider 'union(enum)' here"); + + cases.add("enum in field count range but not matching tag", + \\const Foo = enum(u32) { + \\ A = 10, + \\ B = 11, + \\}; + \\export fn entry() { + \\ var x = Foo(0); + \\} + , + ".tmp_source.zig:6:16: error: enum 'Foo' has no tag matching integer value 0", + ".tmp_source.zig:1:13: note: 'Foo' declared here"); } From 960914a073c367883c9fdf54e900890a6aefc05f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 20:46:58 -0500 Subject: [PATCH 09/69] add implicit cast from enum to union when the enum is the tag type of the union and is comptime known to be of a void field of the union See #642 --- src/analyze.cpp | 15 +++++--- src/codegen.cpp | 5 ++- src/ir.cpp | 79 +++++++++++++++++++++++++++++++++++++++++ test/cases/union.zig | 7 ++++ test/compile_errors.zig | 31 ++++++++++++++++ 5 files changed, 129 insertions(+), 8 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 96f0eb44db..61f7bedcbb 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2244,12 +2244,10 @@ static void resolve_union_zero_bits(CodeGen *g, TypeTableEntry *union_type) { TypeTableEntry *enum_type = analyze_type_expr(g, scope, enum_type_node); if (type_is_invalid(enum_type)) { union_type->data.unionation.is_invalid = true; - union_type->data.unionation.embedded_in_current = false; return; } if (enum_type->id != TypeTableEntryIdEnum) { union_type->data.unionation.is_invalid = true; - union_type->data.unionation.embedded_in_current = false; add_node_error(g, enum_type_node, buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&enum_type->name))); return; @@ -3319,7 +3317,7 @@ TypeStructField *find_struct_type_field(TypeTableEntry *type_entry, Buf *name) { TypeUnionField *find_union_type_field(TypeTableEntry *type_entry, Buf *name) { assert(type_entry->id == TypeTableEntryIdUnion); - assert(type_entry->data.unionation.complete); + assert(type_entry->data.unionation.zero_bits_known); for (uint32_t i = 0; i < type_entry->data.unionation.src_field_count; i += 1) { TypeUnionField *field = &type_entry->data.unionation.fields[i]; if (buf_eql_buf(field->enum_field->name, name)) { @@ -3331,7 +3329,7 @@ TypeUnionField *find_union_type_field(TypeTableEntry *type_entry, Buf *name) { TypeUnionField *find_union_field_by_tag(TypeTableEntry *type_entry, const BigInt *tag) { assert(type_entry->id == TypeTableEntryIdUnion); - assert(type_entry->data.unionation.complete); + assert(type_entry->data.unionation.zero_bits_known); assert(type_entry->data.unionation.gen_tag_index != SIZE_MAX); for (uint32_t i = 0; i < type_entry->data.unionation.src_field_count; i += 1) { TypeUnionField *field = &type_entry->data.unionation.fields[i]; @@ -3888,7 +3886,6 @@ bool handle_is_ptr(TypeTableEntry *type_entry) { return false; case TypeTableEntryIdArray: case TypeTableEntryIdStruct: - case TypeTableEntryIdUnion: return type_has_bits(type_entry); case TypeTableEntryIdErrorUnion: return type_has_bits(type_entry->data.error.child_type); @@ -3896,6 +3893,14 @@ bool handle_is_ptr(TypeTableEntry *type_entry) { return type_has_bits(type_entry->data.maybe.child_type) && type_entry->data.maybe.child_type->id != TypeTableEntryIdPointer && type_entry->data.maybe.child_type->id != TypeTableEntryIdFn; + case TypeTableEntryIdUnion: + assert(type_entry->data.unionation.complete); + if (type_entry->data.unionation.gen_field_count == 0) + return false; + if (!type_has_bits(type_entry)) + return false; + return true; + } zig_unreachable(); } diff --git a/src/codegen.cpp b/src/codegen.cpp index 35974a73dd..9941238f8f 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3946,8 +3946,6 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { case TypeTableEntryIdUnion: { LLVMTypeRef union_type_ref = type_entry->data.unionation.union_type_ref; - ConstExprValue *payload_value = const_val->data.x_union.payload; - assert(payload_value != nullptr); if (type_entry->data.unionation.gen_field_count == 0) { if (type_entry->data.unionation.gen_tag_index == SIZE_MAX) { @@ -3960,7 +3958,8 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { LLVMValueRef union_value_ref; bool make_unnamed_struct; - if (!type_has_bits(payload_value->type)) { + ConstExprValue *payload_value = const_val->data.x_union.payload; + if (payload_value == nullptr || !type_has_bits(payload_value->type)) { if (type_entry->data.unionation.gen_tag_index == SIZE_MAX) return LLVMGetUndef(type_entry->type_ref); diff --git a/src/ir.cpp b/src/ir.cpp index 7c933ae950..86fa8c3566 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -7468,6 +7468,17 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, } } + // implicit enum to union which has the enum as the tag type + if (expected_type->id == TypeTableEntryIdUnion && actual_type->id == TypeTableEntryIdEnum && + (expected_type->data.unionation.decl_node->data.container_decl.auto_enum || + expected_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)) + { + type_ensure_zero_bits_known(ira->codegen, expected_type); + if (expected_type->data.unionation.tag_type == actual_type) { + return ImplicitCastMatchResultYes; + } + } + // implicit undefined literal to anything if (actual_type->id == TypeTableEntryIdUndefLit) { return ImplicitCastMatchResultYes; @@ -8370,6 +8381,63 @@ static IrInstruction *ir_analyze_undefined_to_anything(IrAnalyze *ira, IrInstruc return result; } +static IrInstruction *ir_analyze_enum_to_union(IrAnalyze *ira, IrInstruction *source_instr, + IrInstruction *target, TypeTableEntry *wanted_type) +{ + assert(wanted_type->id == TypeTableEntryIdUnion); + assert(target->value.type->id == TypeTableEntryIdEnum); + + if (instr_is_comptime(target)) { + ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); + if (!val) + return ira->codegen->invalid_instruction; + TypeUnionField *union_field = find_union_field_by_tag(wanted_type, &val->data.x_enum_tag); + assert(union_field != nullptr); + type_ensure_zero_bits_known(ira->codegen, union_field->type_entry); + if (!union_field->type_entry->zero_bits) { + AstNode *field_node = wanted_type->data.unionation.decl_node->data.container_decl.fields.at( + union_field->enum_field->decl_index); + ErrorMsg *msg = ir_add_error(ira, source_instr, + buf_sprintf("cast to union '%s' must initialize '%s' field '%s'", + buf_ptr(&wanted_type->name), + buf_ptr(&union_field->type_entry->name), + buf_ptr(union_field->name))); + add_error_note(ira->codegen, msg, field_node, + buf_sprintf("field '%s' declared here", buf_ptr(union_field->name))); + return ira->codegen->invalid_instruction; + } + IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, + source_instr->source_node, wanted_type); + result->value.special = ConstValSpecialStatic; + result->value.type = wanted_type; + bigint_init_bigint(&result->value.data.x_union.tag, &val->data.x_enum_tag); + return result; + } + + // if the union has all fields 0 bits, we can do it + // and in fact it's a noop cast because the union value is just the enum value + if (wanted_type->data.unionation.gen_field_count == 0) { + IrInstruction *result = ir_build_cast(&ira->new_irb, target->scope, target->source_node, wanted_type, target, CastOpNoop); + result->value.type = wanted_type; + return result; + } + + ErrorMsg *msg = ir_add_error(ira, source_instr, + buf_sprintf("runtime cast to union '%s' which has non-void fields", + buf_ptr(&wanted_type->name))); + for (uint32_t i = 0; i < wanted_type->data.unionation.src_field_count; i += 1) { + TypeUnionField *union_field = &wanted_type->data.unionation.fields[i]; + if (type_has_bits(union_field->type_entry)) { + AstNode *field_node = wanted_type->data.unionation.decl_node->data.container_decl.fields.at(i); + add_error_note(ira->codegen, msg, field_node, + buf_sprintf("field '%s' has type '%s'", + buf_ptr(union_field->name), + buf_ptr(&union_field->type_entry->name))); + } + } + return ira->codegen->invalid_instruction; +} + static IrInstruction *ir_analyze_widen_or_shorten(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target, TypeTableEntry *wanted_type) { @@ -8919,6 +8987,17 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } + // explicit enum to union which has the enum as the tag type + if (wanted_type->id == TypeTableEntryIdUnion && actual_type->id == TypeTableEntryIdEnum && + (wanted_type->data.unionation.decl_node->data.container_decl.auto_enum || + wanted_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)) + { + type_ensure_zero_bits_known(ira->codegen, wanted_type); + if (wanted_type->data.unionation.tag_type == actual_type) { + return ir_analyze_enum_to_union(ira, source_instr, value, wanted_type); + } + } + // explicit cast from undefined to anything if (actual_type->id == TypeTableEntryIdUndefLit) { return ir_analyze_undefined_to_anything(ira, source_instr, value, wanted_type); diff --git a/test/cases/union.zig b/test/cases/union.zig index 1db9a1bef1..cd7fad4c88 100644 --- a/test/cases/union.zig +++ b/test/cases/union.zig @@ -190,3 +190,10 @@ test "cast union to tag type of union" { fn testCastUnionToTagType(x: &const TheUnion) { assert(TheTag(*x) == TheTag.B); } + +test "cast tag type of union to union" { + var x: Value2 = Letter2.B; + assert(Letter2(x) == Letter2.B); +} +const Letter2 = enum { A, B, C }; +const Value2 = union(Letter2) { A: i32, B, C, }; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 162109f4d8..8dbb8171c2 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2696,4 +2696,35 @@ pub fn addCases(cases: &tests.CompileErrorContext) { , ".tmp_source.zig:6:16: error: enum 'Foo' has no tag matching integer value 0", ".tmp_source.zig:1:13: note: 'Foo' declared here"); + + cases.add("comptime cast enum to union but field has payload", + \\const Letter = enum { A, B, C }; + \\const Value = union(Letter) { + \\ A: i32, + \\ B, + \\ C, + \\}; + \\export fn entry() { + \\ var x: Value = Letter.A; + \\} + , + ".tmp_source.zig:8:26: error: cast to union 'Value' must initialize 'i32' field 'A'", + ".tmp_source.zig:3:5: note: field 'A' declared here"); + + cases.add("runtime cast to union which has non-void fields", + \\const Letter = enum { A, B, C }; + \\const Value = union(Letter) { + \\ A: i32, + \\ B, + \\ C, + \\}; + \\export fn entry() { + \\ foo(Letter.A); + \\} + \\fn foo(l: Letter) { + \\ var x: Value = l; + \\} + , + ".tmp_source.zig:11:20: error: runtime cast to union 'Value' which has non-void fields", + ".tmp_source.zig:3:5: note: field 'A' has type 'i32'"); } From b66fb7ceae9029935e5cf3c3752d240a74b6cd7c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 20:51:49 -0500 Subject: [PATCH 10/69] revert to master branch ir.cpp, fixes issue better than this branch --- src/ir.cpp | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index e31cd2d1bf..86fa8c3566 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -8490,6 +8490,16 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour if (type_is_invalid(wanted_type)) return ira->codegen->invalid_instruction; + if (actual_type != wanted_type->data.enumeration.tag_int_type) { + ir_add_error(ira, source_instr, + buf_sprintf("integer to enum cast from '%s' instead of its tag type, '%s'", + buf_ptr(&actual_type->name), + buf_ptr(&wanted_type->data.enumeration.tag_int_type->name))); + return ira->codegen->invalid_instruction; + } + + assert(actual_type->id == TypeTableEntryIdInt); + if (instr_is_comptime(target)) { ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); if (!val) @@ -8513,17 +8523,6 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour return result; } - if (actual_type != wanted_type->data.enumeration.tag_int_type) { - ir_add_error(ira, source_instr, - buf_sprintf("integer to enum cast from '%s' instead of its tag type, '%s'", - buf_ptr(&actual_type->name), - buf_ptr(&wanted_type->data.enumeration.tag_int_type->name))); - return ira->codegen->invalid_instruction; - } - - assert(actual_type->id == TypeTableEntryIdInt); - - IrInstruction *result = ir_build_int_to_enum(&ira->new_irb, source_instr->scope, source_instr->source_node, target); result->value.type = wanted_type; @@ -8893,20 +8892,6 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } - // explicit cast from integer to enum type with no payload - if ((actual_type->id == TypeTableEntryIdInt || actual_type->id == TypeTableEntryIdNumLitInt) && - wanted_type->id == TypeTableEntryIdEnum) - { - return ir_analyze_int_to_enum(ira, source_instr, value, wanted_type); - } - - // explicit cast from enum type with no payload to integer - if ((wanted_type->id == TypeTableEntryIdInt || wanted_type->id == TypeTableEntryIdNumLitInt) && - actual_type->id == TypeTableEntryIdEnum) - { - return ir_analyze_enum_to_int(ira, source_instr, value, wanted_type); - } - // explicit cast from number literal to another type // explicit cast from number literal to &const integer if (actual_type->id == TypeTableEntryIdNumLitFloat || @@ -8981,6 +8966,16 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_analyze_int_to_err(ira, source_instr, value); } + // explicit cast from integer to enum type with no payload + if (actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdEnum) { + return ir_analyze_int_to_enum(ira, source_instr, value, wanted_type); + } + + // explicit cast from enum type with no payload to integer + if (wanted_type->id == TypeTableEntryIdInt && actual_type->id == TypeTableEntryIdEnum) { + return ir_analyze_enum_to_int(ira, source_instr, value, wanted_type); + } + // explicit cast from union to the enum type of the union if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) { type_ensure_zero_bits_known(ira->codegen, actual_type); From 2715f6fdb8bca1c88c58dac047889711359e193b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 21:10:47 -0500 Subject: [PATCH 11/69] allow implicit cast from union to its enum tag type closes #642 --- src/ir.cpp | 11 +++++++++++ test/cases/union.zig | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index 86fa8c3566..0ab63a02fe 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -7468,6 +7468,17 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, } } + // implicit union to its enum tag type + if (expected_type->id == TypeTableEntryIdEnum && actual_type->id == TypeTableEntryIdUnion && + (actual_type->data.unionation.decl_node->data.container_decl.auto_enum || + actual_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)) + { + type_ensure_zero_bits_known(ira->codegen, actual_type); + if (actual_type->data.unionation.tag_type == expected_type) { + return ImplicitCastMatchResultYes; + } + } + // implicit enum to union which has the enum as the tag type if (expected_type->id == TypeTableEntryIdUnion && actual_type->id == TypeTableEntryIdEnum && (expected_type->data.unionation.decl_node->data.container_decl.auto_enum || diff --git a/test/cases/union.zig b/test/cases/union.zig index cd7fad4c88..5657767dce 100644 --- a/test/cases/union.zig +++ b/test/cases/union.zig @@ -197,3 +197,11 @@ test "cast tag type of union to union" { } const Letter2 = enum { A, B, C }; const Value2 = union(Letter2) { A: i32, B, C, }; + +test "implicit cast union to its tag type" { + var x: Value2 = Letter2.B; + giveMeLetterB(x); +} +fn giveMeLetterB(x: Letter2) { + assert(x == Value2.B); +} From c49ee9f632dd5ee7f341e9093234f39c19a32115 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 21:33:24 -0500 Subject: [PATCH 12/69] allow union and its tag type to peer resolve to the tag type --- src/ir.cpp | 132 ++++++++++++++++++++++++++++++++----------- test/cases/union.zig | 1 + 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 0ab63a02fe..d9d70346bd 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -7519,33 +7519,52 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod IrInstruction *cur_inst = instructions[i]; TypeTableEntry *cur_type = cur_inst->value.type; TypeTableEntry *prev_type = prev_inst->value.type; + if (type_is_invalid(cur_type)) { return cur_type; - } else if (prev_type->id == TypeTableEntryIdUnreachable) { + } + + if (prev_type->id == TypeTableEntryIdUnreachable) { prev_inst = cur_inst; - } else if (cur_type->id == TypeTableEntryIdUnreachable) { + } + + if (cur_type->id == TypeTableEntryIdUnreachable) { continue; - } else if (prev_type->id == TypeTableEntryIdPureError) { + } + + if (prev_type->id == TypeTableEntryIdPureError) { prev_inst = cur_inst; continue; - } else if (prev_type->id == TypeTableEntryIdNullLit) { + } + + if (prev_type->id == TypeTableEntryIdNullLit) { prev_inst = cur_inst; continue; - } else if (cur_type->id == TypeTableEntryIdPureError) { + } + + if (cur_type->id == TypeTableEntryIdPureError) { if (prev_type->id == TypeTableEntryIdArray) { convert_to_const_slice = true; } any_are_pure_error = true; continue; - } else if (cur_type->id == TypeTableEntryIdNullLit) { + } + + if (cur_type->id == TypeTableEntryIdNullLit) { any_are_null = true; continue; - } else if (types_match_const_cast_only(prev_type, cur_type)) { + } + + if (types_match_const_cast_only(prev_type, cur_type)) { continue; - } else if (types_match_const_cast_only(cur_type, prev_type)) { + } + + if (types_match_const_cast_only(cur_type, prev_type)) { prev_inst = cur_inst; continue; - } else if (prev_type->id == TypeTableEntryIdInt && + } + + if (prev_type->id == TypeTableEntryIdInt && cur_type->id == TypeTableEntryIdInt && prev_type->data.integral.is_signed == cur_type->data.integral.is_signed) { @@ -7553,36 +7572,52 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod prev_inst = cur_inst; } continue; - } else if (prev_type->id == TypeTableEntryIdFloat && + } + + if (prev_type->id == TypeTableEntryIdFloat && cur_type->id == TypeTableEntryIdFloat) { if (cur_type->data.floating.bit_count > prev_type->data.floating.bit_count) { prev_inst = cur_inst; } - } else if (prev_type->id == TypeTableEntryIdErrorUnion && + } + + if (prev_type->id == TypeTableEntryIdErrorUnion && types_match_const_cast_only(prev_type->data.error.child_type, cur_type)) { continue; - } else if (cur_type->id == TypeTableEntryIdErrorUnion && + } + + if (cur_type->id == TypeTableEntryIdErrorUnion && types_match_const_cast_only(cur_type->data.error.child_type, prev_type)) { prev_inst = cur_inst; continue; - } else if (prev_type->id == TypeTableEntryIdMaybe && + } + + if (prev_type->id == TypeTableEntryIdMaybe && types_match_const_cast_only(prev_type->data.maybe.child_type, cur_type)) { continue; - } else if (cur_type->id == TypeTableEntryIdMaybe && + } + + if (cur_type->id == TypeTableEntryIdMaybe && types_match_const_cast_only(cur_type->data.maybe.child_type, prev_type)) { prev_inst = cur_inst; continue; - } else if (cur_type->id == TypeTableEntryIdUndefLit) { + } + + if (cur_type->id == TypeTableEntryIdUndefLit) { continue; - } else if (prev_type->id == TypeTableEntryIdUndefLit) { + } + + if (prev_type->id == TypeTableEntryIdUndefLit) { prev_inst = cur_inst; continue; - } else if (prev_type->id == TypeTableEntryIdNumLitInt || + } + + if (prev_type->id == TypeTableEntryIdNumLitInt || prev_type->id == TypeTableEntryIdNumLitFloat) { if (ir_num_lit_fits_in_other_type(ira, prev_inst, cur_type, false)) { @@ -7591,7 +7626,9 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod } else { return ira->codegen->builtin_types.entry_invalid; } - } else if (cur_type->id == TypeTableEntryIdNumLitInt || + } + + if (cur_type->id == TypeTableEntryIdNumLitInt || cur_type->id == TypeTableEntryIdNumLitFloat) { if (ir_num_lit_fits_in_other_type(ira, cur_inst, prev_type, false)) { @@ -7599,20 +7636,26 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod } else { return ira->codegen->builtin_types.entry_invalid; } - } else if (cur_type->id == TypeTableEntryIdArray && prev_type->id == TypeTableEntryIdArray && + } + + if (cur_type->id == TypeTableEntryIdArray && prev_type->id == TypeTableEntryIdArray && cur_type->data.array.len != prev_type->data.array.len && types_match_const_cast_only(cur_type->data.array.child_type, prev_type->data.array.child_type)) { convert_to_const_slice = true; prev_inst = cur_inst; continue; - } else if (cur_type->id == TypeTableEntryIdArray && prev_type->id == TypeTableEntryIdArray && + } + + if (cur_type->id == TypeTableEntryIdArray && prev_type->id == TypeTableEntryIdArray && cur_type->data.array.len != prev_type->data.array.len && types_match_const_cast_only(prev_type->data.array.child_type, cur_type->data.array.child_type)) { convert_to_const_slice = true; continue; - } else if (cur_type->id == TypeTableEntryIdArray && is_slice(prev_type) && + } + + if (cur_type->id == TypeTableEntryIdArray && is_slice(prev_type) && (prev_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const || cur_type->data.array.len == 0) && types_match_const_cast_only(prev_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type, @@ -7620,7 +7663,9 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod { convert_to_const_slice = false; continue; - } else if (prev_type->id == TypeTableEntryIdArray && is_slice(cur_type) && + } + + if (prev_type->id == TypeTableEntryIdArray && is_slice(cur_type) && (cur_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const || prev_type->data.array.len == 0) && types_match_const_cast_only(cur_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type, @@ -7629,17 +7674,40 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod prev_inst = cur_inst; convert_to_const_slice = false; continue; - } else { - ErrorMsg *msg = ir_add_error_node(ira, source_node, - buf_sprintf("incompatible types: '%s' and '%s'", - buf_ptr(&prev_type->name), buf_ptr(&cur_type->name))); - add_error_note(ira->codegen, msg, prev_inst->source_node, - buf_sprintf("type '%s' here", buf_ptr(&prev_type->name))); - add_error_note(ira->codegen, msg, cur_inst->source_node, - buf_sprintf("type '%s' here", buf_ptr(&cur_type->name))); - - return ira->codegen->builtin_types.entry_invalid; } + + if (prev_type->id == TypeTableEntryIdEnum && cur_type->id == TypeTableEntryIdUnion && + (cur_type->data.unionation.decl_node->data.container_decl.auto_enum || cur_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)) + { + type_ensure_zero_bits_known(ira->codegen, cur_type); + if (type_is_invalid(cur_type)) + return ira->codegen->builtin_types.entry_invalid; + if (cur_type->data.unionation.tag_type == prev_type) { + continue; + } + } + + if (cur_type->id == TypeTableEntryIdEnum && prev_type->id == TypeTableEntryIdUnion && + (prev_type->data.unionation.decl_node->data.container_decl.auto_enum || prev_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)) + { + type_ensure_zero_bits_known(ira->codegen, prev_type); + if (type_is_invalid(prev_type)) + return ira->codegen->builtin_types.entry_invalid; + if (prev_type->data.unionation.tag_type == cur_type) { + prev_inst = cur_inst; + continue; + } + } + + ErrorMsg *msg = ir_add_error_node(ira, source_node, + buf_sprintf("incompatible types: '%s' and '%s'", + buf_ptr(&prev_type->name), buf_ptr(&cur_type->name))); + add_error_note(ira->codegen, msg, prev_inst->source_node, + buf_sprintf("type '%s' here", buf_ptr(&prev_type->name))); + add_error_note(ira->codegen, msg, cur_inst->source_node, + buf_sprintf("type '%s' here", buf_ptr(&cur_type->name))); + + return ira->codegen->builtin_types.entry_invalid; } if (convert_to_const_slice) { assert(prev_inst->value.type->id == TypeTableEntryIdArray); diff --git a/test/cases/union.zig b/test/cases/union.zig index 5657767dce..7c1c04c711 100644 --- a/test/cases/union.zig +++ b/test/cases/union.zig @@ -200,6 +200,7 @@ const Value2 = union(Letter2) { A: i32, B, C, }; test "implicit cast union to its tag type" { var x: Value2 = Letter2.B; + assert(x == Letter2.B); giveMeLetterB(x); } fn giveMeLetterB(x: Letter2) { From bb6b4f8db2bf793f4118ee68a05ff0b4df52c318 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 22:15:33 -0500 Subject: [PATCH 13/69] fix enum with 1 member causing segfault closes #647 --- src/ir.cpp | 9 +++++++-- test/cases/enum.zig | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index d9d70346bd..0eaf6bdf01 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -9504,6 +9504,10 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, bin_op_instruction->base.source_node, instructions, 2); if (type_is_invalid(resolved_type)) return resolved_type; + type_ensure_zero_bits_known(ira->codegen, resolved_type); + if (type_is_invalid(resolved_type)) + return resolved_type; + AstNode *source_node = bin_op_instruction->base.source_node; switch (resolved_type->id) { @@ -9568,7 +9572,8 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp ConstExprValue *op1_val = &casted_op1->value; ConstExprValue *op2_val = &casted_op2->value; - if ((value_is_comptime(op1_val) && value_is_comptime(op2_val)) || resolved_type->id == TypeTableEntryIdVoid) { + bool one_possible_value = !type_requires_comptime(resolved_type) && !type_has_bits(resolved_type); + if (one_possible_value || (value_is_comptime(op1_val) && value_is_comptime(op2_val))) { bool answer; if (resolved_type->id == TypeTableEntryIdNumLitFloat || resolved_type->id == TypeTableEntryIdFloat) { Cmp cmp_result = float_cmp(op1_val, op2_val); @@ -9577,7 +9582,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp Cmp cmp_result = bigint_cmp(&op1_val->data.x_bigint, &op2_val->data.x_bigint); answer = resolve_cmp_op_id(op_id, cmp_result); } else { - bool are_equal = resolved_type->id == TypeTableEntryIdVoid || const_values_equal(op1_val, op2_val); + bool are_equal = one_possible_value || const_values_equal(op1_val, op2_val); if (op_id == IrBinOpCmpEq) { answer = are_equal; } else if (op_id == IrBinOpCmpNotEq) { diff --git a/test/cases/enum.zig b/test/cases/enum.zig index d15614f9fd..cad3289c39 100644 --- a/test/cases/enum.zig +++ b/test/cases/enum.zig @@ -349,3 +349,20 @@ test "cast integer literal to enum" { assert(MultipleChoice2(0) == MultipleChoice2.Unspecified1); assert(MultipleChoice2(40) == MultipleChoice2.B); } + +const EnumWithOneMember = enum { + Eof, +}; + +fn doALoopThing(id: EnumWithOneMember) { + while (true) { + if (id == EnumWithOneMember.Eof) { + break; + } + @compileError("above if condition should be comptime"); + } +} + +test "comparison operator on enum with one member is comptime known" { + doALoopThing(EnumWithOneMember.Eof); +} From f464fe14f4ece387935dbe2bb6b73ecf466c3f83 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 22:26:17 -0500 Subject: [PATCH 14/69] switch on enum which only has 1 field is comptime known closes #593 --- src/ir.cpp | 10 ++++++++++ test/cases/enum.zig | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index 0eaf6bdf01..6cd8b678c9 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -13174,6 +13174,16 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira, return tag_type; } case TypeTableEntryIdEnum: { + type_ensure_zero_bits_known(ira->codegen, target_type); + if (type_is_invalid(target_type)) + return ira->codegen->builtin_types.entry_invalid; + if (target_type->data.enumeration.src_field_count < 2) { + TypeEnumField *only_field = &target_type->data.enumeration.fields[0]; + ConstExprValue *out_val = ir_build_const_from(ira, &switch_target_instruction->base); + bigint_init_bigint(&out_val->data.x_enum_tag, &only_field->value); + return target_type; + } + if (pointee_val) { ConstExprValue *out_val = ir_build_const_from(ira, &switch_target_instruction->base); bigint_init_bigint(&out_val->data.x_enum_tag, &pointee_val->data.x_enum_tag); diff --git a/test/cases/enum.zig b/test/cases/enum.zig index cad3289c39..6352a23afa 100644 --- a/test/cases/enum.zig +++ b/test/cases/enum.zig @@ -366,3 +366,14 @@ fn doALoopThing(id: EnumWithOneMember) { test "comparison operator on enum with one member is comptime known" { doALoopThing(EnumWithOneMember.Eof); } + +const State = enum { + Start, +}; +test "switch on enum with one member is comptime known" { + var state = State.Start; + switch (state) { + State.Start => return, + } + @compileError("analysis should not reach here"); +} From 249cb2aa30bbdd0c30f24ef18097e3b1cd3e0da5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2017 22:39:36 -0500 Subject: [PATCH 15/69] fix regressions from previous commit c49ee9f632dd5ee7f341e9093234f39c19a32115 broke the tests and this fixes them --- src/ir.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 6cd8b678c9..43ea16c976 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -7526,6 +7526,7 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod if (prev_type->id == TypeTableEntryIdUnreachable) { prev_inst = cur_inst; + continue; } if (cur_type->id == TypeTableEntryIdUnreachable) { @@ -7574,12 +7575,11 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod continue; } - if (prev_type->id == TypeTableEntryIdFloat && - cur_type->id == TypeTableEntryIdFloat) - { + if (prev_type->id == TypeTableEntryIdFloat && cur_type->id == TypeTableEntryIdFloat) { if (cur_type->data.floating.bit_count > prev_type->data.floating.bit_count) { prev_inst = cur_inst; } + continue; } if (prev_type->id == TypeTableEntryIdErrorUnion && From 62c25af8021fc399c9a8c667dd986a458b40a7dd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 6 Dec 2017 18:12:05 -0500 Subject: [PATCH 16/69] add higher level arg-parsing API + misc. changes * add @noInlineCall - see #640 This fixes a crash in --release-safe and --release-fast modes where the optimizer inlines everything into _start and clobbers the command line argument data. If we were able to verify that the user's code never reads command line args, we could leave off this "no inline" attribute. * add i29 and u29 primitive types. u29 is the type of alignment, so it makes sense to be a primitive. probably in the future we'll make any `i` or `u` followed by digits into a primitive. * add `aligned` functions to Allocator interface * add `os.argsAlloc` and `os.argsFree` so that you can get a `[]const []u8`, do whatever arg parsing you want, and then free it. For now this uses the other API under the hood, but it could be reimplemented to do a single allocation. * add tests to make sure command line argument parsing works. --- src/all_types.hpp | 5 ++- src/analyze.cpp | 8 ++-- src/codegen.cpp | 22 ++++++++--- src/ir.cpp | 28 ++++++------- src/zig_llvm.cpp | 13 +++++-- src/zig_llvm.hpp | 7 +++- std/debug.zig | 4 +- std/heap.zig | 8 ++-- std/mem.zig | 54 ++++++++++++++++---------- std/os/index.zig | 48 +++++++++++++++++++++++ std/special/bootstrap.zig | 4 +- test/compare_output.zig | 82 +++++++++++++++++++++++++++++++++++++++ test/tests.zig | 24 ++++++++++-- 13 files changed, 249 insertions(+), 58 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index 086ad7db49..c86b99d35c 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1270,6 +1270,7 @@ enum BuiltinFnId { BuiltinFnIdFieldParentPtr, BuiltinFnIdOffsetOf, BuiltinFnIdInlineCall, + BuiltinFnIdNoInlineCall, BuiltinFnIdTypeId, BuiltinFnIdShlExact, BuiltinFnIdShrExact, @@ -1439,7 +1440,7 @@ struct CodeGen { struct { TypeTableEntry *entry_bool; - TypeTableEntry *entry_int[2][11]; // [signed,unsigned][2,3,4,5,6,7,8,16,32,64,128] + TypeTableEntry *entry_int[2][12]; // [signed,unsigned][2,3,4,5,6,7,8,16,29,32,64,128] TypeTableEntry *entry_c_int[CIntTypeCount]; TypeTableEntry *entry_c_longdouble; TypeTableEntry *entry_c_void; @@ -2102,7 +2103,7 @@ struct IrInstructionCall { IrInstruction **args; bool is_comptime; LLVMValueRef tmp_ptr; - bool is_inline; + FnInline fn_inline; }; struct IrInstructionConst { diff --git a/src/analyze.cpp b/src/analyze.cpp index 61f7bedcbb..431b64f984 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -3818,12 +3818,14 @@ TypeTableEntry **get_int_type_ptr(CodeGen *g, bool is_signed, uint32_t size_in_b index = 6; } else if (size_in_bits == 16) { index = 7; - } else if (size_in_bits == 32) { + } else if (size_in_bits == 29) { index = 8; - } else if (size_in_bits == 64) { + } else if (size_in_bits == 32) { index = 9; - } else if (size_in_bits == 128) { + } else if (size_in_bits == 64) { index = 10; + } else if (size_in_bits == 128) { + index = 11; } else { return nullptr; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 9941238f8f..d052ef159a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -839,7 +839,7 @@ static void gen_panic(CodeGen *g, LLVMValueRef msg_arg) { assert(g->panic_fn != nullptr); LLVMValueRef fn_val = fn_llvm_value(g, g->panic_fn); LLVMCallConv llvm_cc = get_llvm_cc(g, g->panic_fn->type_entry->data.fn.fn_type_id.cc); - ZigLLVMBuildCall(g->builder, fn_val, &msg_arg, 1, llvm_cc, false, ""); + ZigLLVMBuildCall(g->builder, fn_val, &msg_arg, 1, llvm_cc, ZigLLVM_FnInlineAuto, ""); LLVMBuildUnreachable(g->builder); } @@ -988,7 +988,7 @@ static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) { static void gen_debug_safety_crash_for_err(CodeGen *g, LLVMValueRef err_val) { LLVMValueRef safety_crash_err_fn = get_safety_crash_err_fn(g); ZigLLVMBuildCall(g->builder, safety_crash_err_fn, &err_val, 1, get_llvm_cc(g, CallingConventionUnspecified), - false, ""); + ZigLLVM_FnInlineAuto, ""); LLVMBuildUnreachable(g->builder); } @@ -2316,12 +2316,22 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr } } - bool want_always_inline = (instruction->fn_entry != nullptr && - instruction->fn_entry->fn_inline == FnInlineAlways) || instruction->is_inline; + ZigLLVM_FnInline fn_inline; + switch (instruction->fn_inline) { + case FnInlineAuto: + fn_inline = ZigLLVM_FnInlineAuto; + break; + case FnInlineAlways: + fn_inline = (instruction->fn_entry == nullptr) ? ZigLLVM_FnInlineAuto : ZigLLVM_FnInlineAlways; + break; + case FnInlineNever: + fn_inline = ZigLLVM_FnInlineNever; + break; + } LLVMCallConv llvm_cc = get_llvm_cc(g, fn_type->data.fn.fn_type_id.cc); LLVMValueRef result = ZigLLVMBuildCall(g->builder, fn_val, - gen_param_values, (unsigned)gen_param_index, llvm_cc, want_always_inline, ""); + gen_param_values, (unsigned)gen_param_index, llvm_cc, fn_inline, ""); for (size_t param_i = 0; param_i < fn_type_id->param_count; param_i += 1) { FnGenParamInfo *gen_info = &fn_type->data.fn.gen_param_info[param_i]; @@ -4634,6 +4644,7 @@ static const uint8_t int_sizes_in_bits[] = { 7, 8, 16, + 29, 32, 64, 128, @@ -4971,6 +4982,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdRem, "rem", 2); create_builtin_fn(g, BuiltinFnIdMod, "mod", 2); create_builtin_fn(g, BuiltinFnIdInlineCall, "inlineCall", SIZE_MAX); + create_builtin_fn(g, BuiltinFnIdNoInlineCall, "noInlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdTypeId, "typeId", 1); create_builtin_fn(g, BuiltinFnIdShlExact, "shlExact", 2); create_builtin_fn(g, BuiltinFnIdShrExact, "shrExact", 2); diff --git a/src/ir.cpp b/src/ir.cpp index 43ea16c976..0512ab5e36 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -928,13 +928,13 @@ static IrInstruction *ir_build_union_field_ptr_from(IrBuilder *irb, IrInstructio static IrInstruction *ir_build_call(IrBuilder *irb, Scope *scope, AstNode *source_node, FnTableEntry *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args, - bool is_comptime, bool is_inline) + bool is_comptime, FnInline fn_inline) { IrInstructionCall *call_instruction = ir_build_instruction(irb, scope, source_node); call_instruction->fn_entry = fn_entry; call_instruction->fn_ref = fn_ref; call_instruction->is_comptime = is_comptime; - call_instruction->is_inline = is_inline; + call_instruction->fn_inline = fn_inline; call_instruction->args = args; call_instruction->arg_count = arg_count; @@ -948,10 +948,10 @@ static IrInstruction *ir_build_call(IrBuilder *irb, Scope *scope, AstNode *sourc static IrInstruction *ir_build_call_from(IrBuilder *irb, IrInstruction *old_instruction, FnTableEntry *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args, - bool is_comptime, bool is_inline) + bool is_comptime, FnInline fn_inline) { IrInstruction *new_instruction = ir_build_call(irb, old_instruction->scope, - old_instruction->source_node, fn_entry, fn_ref, arg_count, args, is_comptime, is_inline); + old_instruction->source_node, fn_entry, fn_ref, arg_count, args, is_comptime, fn_inline); ir_link_new_instruction(new_instruction, old_instruction); return new_instruction; } @@ -4672,6 +4672,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_offset_of(irb, scope, node, arg0_value, arg1_value); } case BuiltinFnIdInlineCall: + case BuiltinFnIdNoInlineCall: { if (node->data.fn_call_expr.params.length == 0) { add_node_error(irb->codegen, node, buf_sprintf("expected at least 1 argument, found 0")); @@ -4692,8 +4693,9 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo if (args[i] == irb->codegen->invalid_instruction) return args[i]; } + FnInline fn_inline = (builtin_fn->id == BuiltinFnIdInlineCall) ? FnInlineAlways : FnInlineNever; - return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, true); + return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, fn_inline); } case BuiltinFnIdTypeId: { @@ -4804,7 +4806,7 @@ static IrInstruction *ir_gen_fn_call(IrBuilder *irb, Scope *scope, AstNode *node return args[i]; } - return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, false); + return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto); } static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode *node) { @@ -10617,7 +10619,7 @@ no_mem_slot: static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *call_instruction, FnTableEntry *fn_entry, TypeTableEntry *fn_type, IrInstruction *fn_ref, - IrInstruction *first_arg_ptr, bool comptime_fn_call, bool inline_fn_call) + IrInstruction *first_arg_ptr, bool comptime_fn_call, FnInline fn_inline) { FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; size_t first_arg_1_or_0 = first_arg_ptr ? 1 : 0; @@ -10876,7 +10878,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal if (type_requires_comptime(return_type)) { // Throw out our work and call the function as if it were comptime. - return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true, false); + return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true, FnInlineAuto); } } @@ -10900,7 +10902,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal size_t impl_param_count = impl_fn->type_entry->data.fn.fn_type_id.param_count; IrInstruction *new_call_instruction = ir_build_call_from(&ira->new_irb, &call_instruction->base, - impl_fn, nullptr, impl_param_count, casted_args, false, inline_fn_call); + impl_fn, nullptr, impl_param_count, casted_args, false, fn_inline); TypeTableEntry *return_type = impl_fn->type_entry->data.fn.fn_type_id.return_type; ir_add_alloca(ira, new_call_instruction, return_type); @@ -10959,7 +10961,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal return ira->codegen->builtin_types.entry_invalid; IrInstruction *new_call_instruction = ir_build_call_from(&ira->new_irb, &call_instruction->base, - fn_entry, fn_ref, call_param_count, casted_args, false, inline_fn_call); + fn_entry, fn_ref, call_param_count, casted_args, false, fn_inline); ir_add_alloca(ira, new_call_instruction, return_type); return ir_finish_anal(ira, return_type); @@ -10998,13 +11000,13 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction } else if (fn_ref->value.type->id == TypeTableEntryIdFn) { FnTableEntry *fn_table_entry = ir_resolve_fn(ira, fn_ref); return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry, - fn_ref, nullptr, is_comptime, call_instruction->is_inline); + fn_ref, nullptr, is_comptime, call_instruction->fn_inline); } else if (fn_ref->value.type->id == TypeTableEntryIdBoundFn) { assert(fn_ref->value.special == ConstValSpecialStatic); FnTableEntry *fn_table_entry = fn_ref->value.data.x_bound_fn.fn; IrInstruction *first_arg_ptr = fn_ref->value.data.x_bound_fn.first_arg; return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry, - nullptr, first_arg_ptr, is_comptime, call_instruction->is_inline); + nullptr, first_arg_ptr, is_comptime, call_instruction->fn_inline); } else { ir_add_error_node(ira, fn_ref->source_node, buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name))); @@ -11014,7 +11016,7 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction if (fn_ref->value.type->id == TypeTableEntryIdFn) { return ir_analyze_fn_call(ira, call_instruction, nullptr, fn_ref->value.type, - fn_ref, nullptr, false, false); + fn_ref, nullptr, false, FnInlineAuto); } else { ir_add_error_node(ira, fn_ref->source_node, buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name))); diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index fa352147cc..0c3e711dd5 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -175,12 +175,19 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args, - unsigned NumArgs, unsigned CC, bool always_inline, const char *Name) + unsigned NumArgs, unsigned CC, ZigLLVM_FnInline fn_inline, const char *Name) { CallInst *call_inst = CallInst::Create(unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Name); call_inst->setCallingConv(CC); - if (always_inline) { - call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::AlwaysInline); + switch (fn_inline) { + case ZigLLVM_FnInlineAuto: + break; + case ZigLLVM_FnInlineAlways: + call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::AlwaysInline); + break; + case ZigLLVM_FnInlineNever: + call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::NoInline); + break; } return wrap(unwrap(B)->Insert(call_inst)); } diff --git a/src/zig_llvm.hpp b/src/zig_llvm.hpp index b72b6a889f..404154835e 100644 --- a/src/zig_llvm.hpp +++ b/src/zig_llvm.hpp @@ -45,8 +45,13 @@ enum ZigLLVM_EmitOutputType { bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, const char *filename, ZigLLVM_EmitOutputType output_type, char **error_message, bool is_debug); +enum ZigLLVM_FnInline { + ZigLLVM_FnInlineAuto, + ZigLLVM_FnInlineAlways, + ZigLLVM_FnInlineNever, +}; LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args, - unsigned NumArgs, unsigned CC, bool always_inline, const char *Name); + unsigned NumArgs, unsigned CC, ZigLLVM_FnInline fn_inline, const char *Name); LLVMValueRef ZigLLVMBuildCmpXchg(LLVMBuilderRef builder, LLVMValueRef ptr, LLVMValueRef cmp, LLVMValueRef new_val, LLVMAtomicOrdering success_ordering, diff --git a/std/debug.zig b/std/debug.zig index a2bea9eddd..c520e98b15 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -977,7 +977,7 @@ var some_mem_index: usize = 0; error OutOfMemory; -fn globalAlloc(self: &mem.Allocator, n: usize, alignment: usize) -> %[]u8 { +fn globalAlloc(self: &mem.Allocator, n: usize, alignment: u29) -> %[]u8 { const addr = @ptrToInt(&some_mem[some_mem_index]); const rem = @rem(addr, alignment); const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); @@ -991,7 +991,7 @@ fn globalAlloc(self: &mem.Allocator, n: usize, alignment: usize) -> %[]u8 { return result; } -fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { +fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { if (new_size <= old_mem.len) { return old_mem[0..new_size]; } else { diff --git a/std/heap.zig b/std/heap.zig index d0bf8ab871..d54d921856 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -16,7 +16,7 @@ pub var c_allocator = Allocator { .freeFn = cFree, }; -fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 { +fn cAlloc(self: &Allocator, n: usize, alignment: u29) -> %[]u8 { if (c.malloc(usize(n))) |buf| { @ptrCast(&u8, buf)[0..n] } else { @@ -24,7 +24,7 @@ fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 { } } -fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { +fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { if (new_size <= old_mem.len) { old_mem[0..new_size] } else { @@ -106,7 +106,7 @@ pub const IncrementingAllocator = struct { return self.bytes.len - self.end_index; } - fn alloc(allocator: &Allocator, n: usize, alignment: usize) -> %[]u8 { + fn alloc(allocator: &Allocator, n: usize, alignment: u29) -> %[]u8 { const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); const addr = @ptrToInt(&self.bytes[self.end_index]); const rem = @rem(addr, alignment); @@ -121,7 +121,7 @@ pub const IncrementingAllocator = struct { return result; } - fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { if (new_size <= old_mem.len) { return old_mem[0..new_size]; } else { diff --git a/std/mem.zig b/std/mem.zig index 815e122812..e99675d248 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -7,22 +7,24 @@ pub const Cmp = math.Cmp; pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the - /// slicer's pointer aligned at least to alignment bytes. - allocFn: fn (self: &Allocator, byte_count: usize, alignment: usize) -> %[]u8, + /// slice's pointer aligned at least to alignment bytes. + allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) -> %[]u8, - /// Guaranteed: `old_mem.len` is the same as what was returned from allocFn or reallocFn. - /// Guaranteed: alignment >= alignment of old_mem.ptr + /// If `new_byte_count > old_mem.len`: + /// * `old_mem.len` is the same as what was returned from allocFn or reallocFn. + /// * alignment >= alignment of old_mem.ptr /// - /// If `new_byte_count` is less than or equal to `old_mem.len` this function must - /// return successfully. - reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: usize) -> %[]u8, + /// If `new_byte_count <= old_mem.len`: + /// * this function must return successfully. + /// * alignment <= alignment of old_mem.ptr + reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) -> %[]u8, /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn` freeFn: fn (self: &Allocator, old_mem: []u8), fn create(self: &Allocator, comptime T: type) -> %&T { const slice = %return self.alloc(T, 1); - &slice[0] + return &slice[0]; } fn destroy(self: &Allocator, ptr: var) { @@ -30,28 +32,43 @@ pub const Allocator = struct { } fn alloc(self: &Allocator, comptime T: type, n: usize) -> %[]T { + return self.alignedAlloc(T, @alignOf(T), n); + } + + fn alignedAlloc(self: &Allocator, comptime T: type, comptime alignment: u29, + n: usize) -> %[]align(alignment) T + { const byte_count = %return math.mul(usize, @sizeOf(T), n); - const byte_slice = %return self.allocFn(self, byte_count, @alignOf(T)); - ([]T)(@alignCast(@alignOf(T), byte_slice)) + const byte_slice = %return self.allocFn(self, byte_count, alignment); + return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) -> %[]T { + return self.alignedRealloc(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n); + } + + fn alignedRealloc(self: &Allocator, comptime T: type, comptime alignment: u29, + old_mem: []align(alignment) T, n: usize) -> %[]align(alignment) T + { if (old_mem.len == 0) { return self.alloc(T, n); } - // Assert that old_mem.ptr is properly aligned. - const aligned_old_mem = @alignCast(@alignOf(T), old_mem); - const byte_count = %return math.mul(usize, @sizeOf(T), n); - const byte_slice = %return self.reallocFn(self, ([]u8)(aligned_old_mem), byte_count, @alignOf(T)); - return ([]T)(@alignCast(@alignOf(T), byte_slice)); + const byte_slice = %return self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); + return ([]T)(@alignCast(alignment, byte_slice)); } /// Reallocate, but `n` must be less than or equal to `old_mem.len`. /// Unlike `realloc`, this function cannot fail. /// Shrinking to 0 is the same as calling `free`. fn shrink(self: &Allocator, comptime T: type, old_mem: []T, n: usize) -> []T { + return self.alignedShrink(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n); + } + + fn alignedShrink(self: &Allocator, comptime T: type, comptime alignment: u29, + old_mem: []align(alignment) T, n: usize) -> []align(alignment) T + { if (n == 0) { self.free(old_mem); return old_mem[0..0]; @@ -59,15 +76,12 @@ pub const Allocator = struct { assert(n <= old_mem.len); - // Assert that old_mem.ptr is properly aligned. - const aligned_old_mem = @alignCast(@alignOf(T), old_mem); - // Here we skip the overflow checking on the multiplication because // n <= old_mem.len and the multiplication didn't overflow for that operation. const byte_count = @sizeOf(T) * n; - const byte_slice = %%self.reallocFn(self, ([]u8)(aligned_old_mem), byte_count, @alignOf(T)); - return ([]T)(@alignCast(@alignOf(T), byte_slice)); + const byte_slice = %%self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); + return ([]T)(@alignCast(alignment, byte_slice)); } fn free(self: &Allocator, memory: var) { diff --git a/std/os/index.zig b/std/os/index.zig index 361750aedc..d26daed9fe 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -1422,6 +1422,54 @@ pub fn args() -> ArgIterator { return ArgIterator.init(); } +/// Caller must call freeArgs on result. +pub fn argsAlloc(allocator: &mem.Allocator) -> %[]const []u8 { + // TODO refactor to only make 1 allocation. + var it = args(); + var contents = %return Buffer.initSize(allocator, 0); + defer contents.deinit(); + + var slice_list = ArrayList(usize).init(allocator); + defer slice_list.deinit(); + + while (it.next(allocator)) |arg_or_err| { + const arg = %return arg_or_err; + defer allocator.free(arg); + %return contents.append(arg); + %return slice_list.append(arg.len); + } + + const contents_slice = contents.toSliceConst(); + const slice_sizes = slice_list.toSliceConst(); + const slice_list_bytes = %return math.mul(usize, @sizeOf([]u8), slice_sizes.len); + const total_bytes = %return math.add(usize, slice_list_bytes, contents_slice.len); + const buf = %return allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes); + %defer allocator.free(buf); + + const result_slice_list = ([][]u8)(buf[0..slice_list_bytes]); + const result_contents = buf[slice_list_bytes..]; + mem.copy(u8, result_contents, contents_slice); + + var contents_index: usize = 0; + for (slice_sizes) |len, i| { + const new_index = contents_index + len; + result_slice_list[i] = result_contents[contents_index..new_index]; + contents_index = new_index; + } + + return result_slice_list; +} + +pub fn argsFree(allocator: &mem.Allocator, args_alloc: []const []u8) { + var total_bytes: usize = 0; + for (args_alloc) |arg| { + total_bytes += @sizeOf([]u8) + arg.len; + } + const unaligned_allocated_buf = @ptrCast(&u8, args_alloc.ptr)[0..total_bytes]; + const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf); + return allocator.free(aligned_allocated_buf); +} + test "windows arg parsing" { testWindowsCmdLine(c"a b\tc d", [][]const u8{"a", "b", "c", "d"}); testWindowsCmdLine(c"\"abc\" d e", [][]const u8{"abc", "d", "e"}); diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index 924e537ddd..99bea09726 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -28,7 +28,9 @@ export nakedcc fn _start() -> noreturn { }, else => @compileError("unsupported arch"), } - posixCallMainAndExit() + // If LLVM inlines stack variables into _start, they will overwrite + // the command line argument data. + @noInlineCall(posixCallMainAndExit); } export fn WinMainCRTStartup() -> noreturn { diff --git a/test/compare_output.zig b/test/compare_output.zig index 1a7b8b51dd..ad9c91ff20 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -444,4 +444,86 @@ pub fn addCases(cases: &tests.CompareOutputContext) { tc }); + + cases.addCase({ + var tc = cases.create("parsing args", + \\const std = @import("std"); + \\const io = std.io; + \\const os = std.os; + \\const allocator = std.debug.global_allocator; + \\ + \\pub fn main() -> %void { + \\ var args_it = os.args(); + \\ var stdout_file = %return io.getStdOut(); + \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ const stdout = &stdout_adapter.stream; + \\ var index: usize = 0; + \\ _ = args_it.skip(); + \\ while (args_it.next(allocator)) |arg_or_err| : (index += 1) { + \\ const arg = %return arg_or_err; + \\ %return stdout.print("{}: {}\n", index, arg); + \\ } + \\} + , + \\0: first arg + \\1: 'a' 'b' \ + \\2: bare + \\3: ba""re + \\4: " + \\5: last arg + \\ + ); + + tc.setCommandLineArgs([][]const u8 { + "first arg", + "'a' 'b' \\", + "bare", + "ba\"\"re", + "\"", + "last arg", + }); + + tc + }); + + cases.addCase({ + var tc = cases.create("parsing args new API", + \\const std = @import("std"); + \\const io = std.io; + \\const os = std.os; + \\const allocator = std.debug.global_allocator; + \\ + \\pub fn main() -> %void { + \\ var args_it = os.args(); + \\ var stdout_file = %return io.getStdOut(); + \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ const stdout = &stdout_adapter.stream; + \\ var index: usize = 0; + \\ _ = args_it.skip(); + \\ while (args_it.next(allocator)) |arg_or_err| : (index += 1) { + \\ const arg = %return arg_or_err; + \\ %return stdout.print("{}: {}\n", index, arg); + \\ } + \\} + , + \\0: first arg + \\1: 'a' 'b' \ + \\2: bare + \\3: ba""re + \\4: " + \\5: last arg + \\ + ); + + tc.setCommandLineArgs([][]const u8 { + "first arg", + "'a' 'b' \\", + "bare", + "ba\"\"re", + "\"", + "last arg", + }); + + tc + }); } diff --git a/test/tests.zig b/test/tests.zig index e74afa1755..a5eb9d4db9 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -189,6 +189,7 @@ pub const CompareOutputContext = struct { expected_output: []const u8, link_libc: bool, special: Special, + cli_args: []const []const u8, const SourceFile = struct { filename: []const u8, @@ -201,6 +202,10 @@ pub const CompareOutputContext = struct { .source = source, }); } + + pub fn setCommandLineArgs(self: &TestCase, args: []const []const u8) { + self.cli_args = args; + } }; const RunCompareOutputStep = struct { @@ -210,9 +215,11 @@ pub const CompareOutputContext = struct { name: []const u8, expected_output: []const u8, test_index: usize, + cli_args: []const []const u8, pub fn create(context: &CompareOutputContext, exe_path: []const u8, - name: []const u8, expected_output: []const u8) -> &RunCompareOutputStep + name: []const u8, expected_output: []const u8, + cli_args: []const []const u8) -> &RunCompareOutputStep { const allocator = context.b.allocator; const ptr = %%allocator.create(RunCompareOutputStep); @@ -223,6 +230,7 @@ pub const CompareOutputContext = struct { .expected_output = expected_output, .test_index = context.test_index, .step = build.Step.init("RunCompareOutput", allocator, make), + .cli_args = cli_args, }; context.test_index += 1; return ptr; @@ -233,10 +241,17 @@ pub const CompareOutputContext = struct { const b = self.context.b; const full_exe_path = b.pathFromRoot(self.exe_path); + var args = ArrayList([]const u8).init(b.allocator); + defer args.deinit(); + + %%args.append(full_exe_path); + for (self.cli_args) |arg| { + %%args.append(arg); + } warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); - const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator); + const child = %%os.ChildProcess.init(args.toSliceConst(), b.allocator); defer child.deinit(); child.stdin_behavior = StdIo.Ignore; @@ -364,6 +379,7 @@ pub const CompareOutputContext = struct { .expected_output = expected_output, .link_libc = false, .special = special, + .cli_args = []const []const u8{}, }; const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; tc.addSourceFile(root_src_name, source); @@ -420,7 +436,7 @@ pub const CompareOutputContext = struct { } const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(), annotated_case_name, - case.expected_output); + case.expected_output, case.cli_args); run_and_cmp_output.step.dependOn(&exe.step); self.step.dependOn(&run_and_cmp_output.step); @@ -447,7 +463,7 @@ pub const CompareOutputContext = struct { } const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(), - annotated_case_name, case.expected_output); + annotated_case_name, case.expected_output, case.cli_args); run_and_cmp_output.step.dependOn(&exe.step); self.step.dependOn(&run_and_cmp_output.step); From 18b8a625f55f1c9cd325ec1ffb8d6d666c440b86 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 6 Dec 2017 18:22:52 -0500 Subject: [PATCH 17/69] upgrade to new args api --- src-self-hosted/main.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index de44a4652f..c714a40af1 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -377,7 +377,10 @@ pub fn main2() -> %void { const allocator = &incrementing_allocator.allocator; - const target_file = "input.zig"; // TODO + const args = %return os.argsAlloc(allocator); + defer os.argsFree(allocator, args); + + const target_file = args[1]; const target_file_buf = %return io.readFileAlloc(target_file, allocator); From 37fbf01755ad8a87e9ffa5b2cd6592b523da0d8e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 6 Dec 2017 21:41:38 -0500 Subject: [PATCH 18/69] awkward void union field syntax no longer needed --- src-self-hosted/main.zig | 116 +++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index c714a40af1..d8b403c066 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -19,46 +19,46 @@ const Token = struct { }; const keywords = []KeywordId { - KeywordId{.bytes="align", .id = Id {.Keyword_align = {}}}, - KeywordId{.bytes="and", .id = Id {.Keyword_and = {}}}, - KeywordId{.bytes="asm", .id = Id {.Keyword_asm = {}}}, - KeywordId{.bytes="break", .id = Id {.Keyword_break = {}}}, - KeywordId{.bytes="coldcc", .id = Id {.Keyword_coldcc = {}}}, - KeywordId{.bytes="comptime", .id = Id {.Keyword_comptime = {}}}, - KeywordId{.bytes="const", .id = Id {.Keyword_const = {}}}, - KeywordId{.bytes="continue", .id = Id {.Keyword_continue = {}}}, - KeywordId{.bytes="defer", .id = Id {.Keyword_defer = {}}}, - KeywordId{.bytes="else", .id = Id {.Keyword_else = {}}}, - KeywordId{.bytes="enum", .id = Id {.Keyword_enum = {}}}, - KeywordId{.bytes="error", .id = Id {.Keyword_error = {}}}, - KeywordId{.bytes="export", .id = Id {.Keyword_export = {}}}, - KeywordId{.bytes="extern", .id = Id {.Keyword_extern = {}}}, - KeywordId{.bytes="false", .id = Id {.Keyword_false = {}}}, - KeywordId{.bytes="fn", .id = Id {.Keyword_fn = {}}}, - KeywordId{.bytes="for", .id = Id {.Keyword_for = {}}}, - KeywordId{.bytes="goto", .id = Id {.Keyword_goto = {}}}, - KeywordId{.bytes="if", .id = Id {.Keyword_if = {}}}, - KeywordId{.bytes="inline", .id = Id {.Keyword_inline = {}}}, - KeywordId{.bytes="nakedcc", .id = Id {.Keyword_nakedcc = {}}}, - KeywordId{.bytes="noalias", .id = Id {.Keyword_noalias = {}}}, - KeywordId{.bytes="null", .id = Id {.Keyword_null = {}}}, - KeywordId{.bytes="or", .id = Id {.Keyword_or = {}}}, - KeywordId{.bytes="packed", .id = Id {.Keyword_packed = {}}}, - KeywordId{.bytes="pub", .id = Id {.Keyword_pub = {}}}, - KeywordId{.bytes="return", .id = Id {.Keyword_return = {}}}, - KeywordId{.bytes="stdcallcc", .id = Id {.Keyword_stdcallcc = {}}}, - KeywordId{.bytes="struct", .id = Id {.Keyword_struct = {}}}, - KeywordId{.bytes="switch", .id = Id {.Keyword_switch = {}}}, - KeywordId{.bytes="test", .id = Id {.Keyword_test = {}}}, - KeywordId{.bytes="this", .id = Id {.Keyword_this = {}}}, - KeywordId{.bytes="true", .id = Id {.Keyword_true = {}}}, - KeywordId{.bytes="undefined", .id = Id {.Keyword_undefined = {}}}, - KeywordId{.bytes="union", .id = Id {.Keyword_union = {}}}, - KeywordId{.bytes="unreachable", .id = Id {.Keyword_unreachable = {}}}, - KeywordId{.bytes="use", .id = Id {.Keyword_use = {}}}, - KeywordId{.bytes="var", .id = Id {.Keyword_var = {}}}, - KeywordId{.bytes="volatile", .id = Id {.Keyword_volatile = {}}}, - KeywordId{.bytes="while", .id = Id {.Keyword_while = {}}}, + KeywordId{.bytes="align", .id = Id.Keyword_align}, + KeywordId{.bytes="and", .id = Id.Keyword_and}, + KeywordId{.bytes="asm", .id = Id.Keyword_asm}, + KeywordId{.bytes="break", .id = Id.Keyword_break}, + KeywordId{.bytes="coldcc", .id = Id.Keyword_coldcc}, + KeywordId{.bytes="comptime", .id = Id.Keyword_comptime}, + KeywordId{.bytes="const", .id = Id.Keyword_const}, + KeywordId{.bytes="continue", .id = Id.Keyword_continue}, + KeywordId{.bytes="defer", .id = Id.Keyword_defer}, + KeywordId{.bytes="else", .id = Id.Keyword_else}, + KeywordId{.bytes="enum", .id = Id.Keyword_enum}, + KeywordId{.bytes="error", .id = Id.Keyword_error}, + KeywordId{.bytes="export", .id = Id.Keyword_export}, + KeywordId{.bytes="extern", .id = Id.Keyword_extern}, + KeywordId{.bytes="false", .id = Id.Keyword_false}, + KeywordId{.bytes="fn", .id = Id.Keyword_fn}, + KeywordId{.bytes="for", .id = Id.Keyword_for}, + KeywordId{.bytes="goto", .id = Id.Keyword_goto}, + KeywordId{.bytes="if", .id = Id.Keyword_if}, + KeywordId{.bytes="inline", .id = Id.Keyword_inline}, + KeywordId{.bytes="nakedcc", .id = Id.Keyword_nakedcc}, + KeywordId{.bytes="noalias", .id = Id.Keyword_noalias}, + KeywordId{.bytes="null", .id = Id.Keyword_null}, + KeywordId{.bytes="or", .id = Id.Keyword_or}, + KeywordId{.bytes="packed", .id = Id.Keyword_packed}, + KeywordId{.bytes="pub", .id = Id.Keyword_pub}, + KeywordId{.bytes="return", .id = Id.Keyword_return}, + KeywordId{.bytes="stdcallcc", .id = Id.Keyword_stdcallcc}, + KeywordId{.bytes="struct", .id = Id.Keyword_struct}, + KeywordId{.bytes="switch", .id = Id.Keyword_switch}, + KeywordId{.bytes="test", .id = Id.Keyword_test}, + KeywordId{.bytes="this", .id = Id.Keyword_this}, + KeywordId{.bytes="true", .id = Id.Keyword_true}, + KeywordId{.bytes="undefined", .id = Id.Keyword_undefined}, + KeywordId{.bytes="union", .id = Id.Keyword_union}, + KeywordId{.bytes="unreachable", .id = Id.Keyword_unreachable}, + KeywordId{.bytes="use", .id = Id.Keyword_use}, + KeywordId{.bytes="var", .id = Id.Keyword_var}, + KeywordId{.bytes="volatile", .id = Id.Keyword_volatile}, + KeywordId{.bytes="while", .id = Id.Keyword_while}, }; fn getKeyword(bytes: []const u8) -> ?Id { @@ -159,7 +159,7 @@ const Tokenizer = struct { pub fn next(self: &Tokenizer) -> Token { var state = State.Start; var result = Token { - .id = Token.Id { .Eof = {} }, + .id = Token.Id.Eof, .start = self.index, .end = undefined, }; @@ -172,7 +172,7 @@ const Tokenizer = struct { }, 'c' => { state = State.C; - result.id = Token.Id { .Identifier = {} }; + result.id = Token.Id.Identifier; }, '"' => { state = State.StringLiteral; @@ -180,49 +180,49 @@ const Tokenizer = struct { }, 'a'...'b', 'd'...'z', 'A'...'Z', '_' => { state = State.Identifier; - result.id = Token.Id { .Identifier = {} }; + result.id = Token.Id.Identifier; }, '@' => { state = State.Builtin; - result.id = Token.Id { .Builtin = {} }; + result.id = Token.Id.Builtin; }, '=' => { - result.id = Token.Id { .Equal = {} }; + result.id = Token.Id.Equal; self.index += 1; break; }, '(' => { - result.id = Token.Id { .LParen = {} }; + result.id = Token.Id.LParen; self.index += 1; break; }, ')' => { - result.id = Token.Id { .RParen = {} }; + result.id = Token.Id.RParen; self.index += 1; break; }, ';' => { - result.id = Token.Id { .Semicolon = {} }; + result.id = Token.Id.Semicolon; self.index += 1; break; }, '%' => { - result.id = Token.Id { .Percent = {} }; + result.id = Token.Id.Percent; self.index += 1; break; }, '{' => { - result.id = Token.Id { .LBrace = {} }; + result.id = Token.Id.LBrace; self.index += 1; break; }, '}' => { - result.id = Token.Id { .RBrace = {} }; + result.id = Token.Id.RBrace; self.index += 1; break; }, '.' => { - result.id = Token.Id { .Period = {} }; + result.id = Token.Id.Period; self.index += 1; break; }, @@ -230,7 +230,7 @@ const Tokenizer = struct { state = State.Minus; }, else => { - result.id = Token.Id { .Invalid = {} }; + result.id = Token.Id.Invalid; self.index += 1; break; }, @@ -280,12 +280,12 @@ const Tokenizer = struct { State.Minus => switch (c) { '>' => { - result.id = Token.Id { .Arrow = {} }; + result.id = Token.Id.Arrow; self.index += 1; break; }, else => { - result.id = Token.Id { .Minus = {} }; + result.id = Token.Id.Minus; break; }, }, @@ -334,12 +334,12 @@ const Parser = struct { switch (next_token.id) { Token.Id.Keyword_fn => { const fn_name = self.tokenizer.next(); - if (@TagType(Token.Id)(fn_name.id) != Token.Id.Identifier) { + if (fn_name.id != Token.Id.Identifier) { @panic("parse error"); } const lparen = self.tokenizer.next(); - if (@TagType(Token.Id)(lparen.id) != Token.Id.LParen) { + if (lparen.id != Token.Id.LParen) { @panic("parse error"); } }, @@ -391,7 +391,7 @@ pub fn main2() -> %void { while (true) { const token = tokenizer.next(); tokenizer.dump(token); - if (@TagType(Token.Id)(token.id) == Token.Id.Eof) { + if (token.id == Token.Id.Eof) { break; } } From dc502042d58ccfb3ad05d68c865a9079fe9015ec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 7 Dec 2017 11:52:52 -0500 Subject: [PATCH 19/69] translate-c: refactor prefix and suffix op C macro parsing --- src/c_tokenizer.cpp | 15 +++++++ src/c_tokenizer.hpp | 3 ++ src/translate_c.cpp | 98 +++++++++++++++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 29 deletions(-) diff --git a/src/c_tokenizer.cpp b/src/c_tokenizer.cpp index 6be2cf991e..3746cf5853 100644 --- a/src/c_tokenizer.cpp +++ b/src/c_tokenizer.cpp @@ -121,6 +121,9 @@ static void begin_token(CTokenize *ctok, CTokId id) { case CTokIdRParen: case CTokIdEOF: case CTokIdDot: + case CTokIdAsterisk: + case CTokIdBang: + case CTokIdTilde: break; } } @@ -228,10 +231,22 @@ void tokenize_c_macro(CTokenize *ctok, const uint8_t *c) { begin_token(ctok, CTokIdRParen); end_token(ctok); break; + case '*': + begin_token(ctok, CTokIdAsterisk); + end_token(ctok); + break; case '-': begin_token(ctok, CTokIdMinus); end_token(ctok); break; + case '!': + begin_token(ctok, CTokIdBang); + end_token(ctok); + break; + case '~': + begin_token(ctok, CTokIdTilde); + end_token(ctok); + break; default: return mark_error(ctok); } diff --git a/src/c_tokenizer.hpp b/src/c_tokenizer.hpp index a3df2b94af..d7c9e53bcf 100644 --- a/src/c_tokenizer.hpp +++ b/src/c_tokenizer.hpp @@ -22,6 +22,9 @@ enum CTokId { CTokIdRParen, CTokIdEOF, CTokIdDot, + CTokIdAsterisk, + CTokIdBang, + CTokIdTilde, }; enum CNumLitSuffix { diff --git a/src/translate_c.cpp b/src/translate_c.cpp index 5082c37a61..9e73c08b84 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -4000,6 +4000,10 @@ static void render_macros(Context *c) { } } +static AstNode *parse_ctok_primary_expr(Context *c, CTokenize *ctok, size_t *tok_i); +static AstNode *parse_ctok_expr(Context *c, CTokenize *ctok, size_t *tok_i); +static AstNode *parse_ctok_prefix_op_expr(Context *c, CTokenize *ctok, size_t *tok_i); + static AstNode *parse_ctok_num_lit(Context *c, CTokenize *ctok, size_t *tok_i, bool negate) { CTok *tok = &ctok->tokens.at(*tok_i); if (tok->id == CTokIdNumLitInt) { @@ -4027,7 +4031,7 @@ static AstNode *parse_ctok_num_lit(Context *c, CTokenize *ctok, size_t *tok_i, b return nullptr; } -static AstNode *parse_ctok(Context *c, CTokenize *ctok, size_t *tok_i) { +static AstNode *parse_ctok_primary_expr(Context *c, CTokenize *ctok, size_t *tok_i) { CTok *tok = &ctok->tokens.at(*tok_i); switch (tok->id) { case CTokIdCharLit: @@ -4044,38 +4048,17 @@ static AstNode *parse_ctok(Context *c, CTokenize *ctok, size_t *tok_i) { return parse_ctok_num_lit(c, ctok, tok_i, false); case CTokIdSymbol: { - bool need_symbol = false; - CTokId curr_id = CTokIdSymbol; + *tok_i += 1; Buf *symbol_name = buf_create_from_buf(&tok->data.symbol); - AstNode *curr_node = trans_create_node_symbol(c, symbol_name); - AstNode *parent_node = curr_node; - do { - *tok_i += 1; - CTok* curr_tok = &ctok->tokens.at(*tok_i); - if (need_symbol) { - if (curr_tok->id == CTokIdSymbol) { - symbol_name = buf_create_from_buf(&curr_tok->data.symbol); - curr_node = trans_create_node_field_access(c, parent_node, buf_create_from_buf(symbol_name)); - parent_node = curr_node; - need_symbol = false; - } else { - return nullptr; - } - } else { - if (curr_tok->id == CTokIdDot) { - need_symbol = true; - continue; - } else { - break; - } - } - } while (curr_id != CTokIdEOF); - return curr_node; + return trans_create_node_symbol(c, symbol_name); } case CTokIdLParen: { *tok_i += 1; - AstNode *inner_node = parse_ctok(c, ctok, tok_i); + AstNode *inner_node = parse_ctok_expr(c, ctok, tok_i); + if (inner_node == nullptr) { + return nullptr; + } CTok *next_tok = &ctok->tokens.at(*tok_i); if (next_tok->id != CTokIdRParen) { @@ -4087,12 +4070,69 @@ static AstNode *parse_ctok(Context *c, CTokenize *ctok, size_t *tok_i) { case CTokIdDot: case CTokIdEOF: case CTokIdRParen: + case CTokIdAsterisk: + case CTokIdBang: + case CTokIdTilde: // not able to make sense of this return nullptr; } zig_unreachable(); } +static AstNode *parse_ctok_expr(Context *c, CTokenize *ctok, size_t *tok_i) { + return parse_ctok_prefix_op_expr(c, ctok, tok_i); +} + +static AstNode *parse_ctok_suffix_op_expr(Context *c, CTokenize *ctok, size_t *tok_i) { + AstNode *node = parse_ctok_primary_expr(c, ctok, tok_i); + if (node == nullptr) + return nullptr; + + while (true) { + CTok *first_tok = &ctok->tokens.at(*tok_i); + if (first_tok->id == CTokIdDot) { + *tok_i += 1; + + CTok *name_tok = &ctok->tokens.at(*tok_i); + if (name_tok->id != CTokIdSymbol) { + return nullptr; + } + *tok_i += 1; + + node = trans_create_node_field_access(c, node, buf_create_from_buf(&name_tok->data.symbol)); + } else if (first_tok->id == CTokIdAsterisk) { + *tok_i += 1; + + node = trans_create_node_addr_of(c, false, false, node); + } else { + return node; + } + } +} + +static PrefixOp ctok_to_prefix_op(CTok *token) { + switch (token->id) { + case CTokIdBang: return PrefixOpBoolNot; + case CTokIdMinus: return PrefixOpNegation; + case CTokIdTilde: return PrefixOpBinNot; + case CTokIdAsterisk: return PrefixOpDereference; + default: return PrefixOpInvalid; + } +} +static AstNode *parse_ctok_prefix_op_expr(Context *c, CTokenize *ctok, size_t *tok_i) { + CTok *op_tok = &ctok->tokens.at(*tok_i); + PrefixOp prefix_op = ctok_to_prefix_op(op_tok); + if (prefix_op == PrefixOpInvalid) { + return parse_ctok_suffix_op_expr(c, ctok, tok_i); + } + *tok_i += 1; + + AstNode *prefix_op_expr = parse_ctok_prefix_op_expr(c, ctok, tok_i); + if (prefix_op_expr == nullptr) + return nullptr; + return trans_create_node_prefix_op(c, prefix_op, prefix_op_expr); +} + static void process_macro(Context *c, CTokenize *ctok, Buf *name, const char *char_ptr) { tokenize_c_macro(ctok, (const uint8_t *)char_ptr); @@ -4105,7 +4145,7 @@ static void process_macro(Context *c, CTokenize *ctok, Buf *name, const char *ch assert(name_tok->id == CTokIdSymbol && buf_eql_buf(&name_tok->data.symbol, name)); tok_i += 1; - AstNode *result_node = parse_ctok(c, ctok, &tok_i); + AstNode *result_node = parse_ctok_suffix_op_expr(c, ctok, &tok_i); if (result_node == nullptr) { return; } From 182cf5b8de375080bff6f2bb91cd398e776da16c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 7 Dec 2017 12:27:29 -0500 Subject: [PATCH 20/69] translate-c: support macros with pointer casting --- src/ast_render.cpp | 8 ++++++-- src/translate_c.cpp | 23 +++++++++++++++++++++-- test/translate_c.zig | 10 ++++++++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 4f4dc1decd..d6a23e5f85 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -584,12 +584,15 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { PrefixOp op = node->data.prefix_op_expr.prefix_op; fprintf(ar->f, "%s", prefix_op_str(op)); - render_node_ungrouped(ar, node->data.prefix_op_expr.primary_expr); + AstNode *child_node = node->data.prefix_op_expr.primary_expr; + bool new_grouped = child_node->type == NodeTypePrefixOpExpr || child_node->type == NodeTypeAddrOfExpr; + render_node_extra(ar, child_node, new_grouped); if (!grouped) fprintf(ar->f, ")"); break; } case NodeTypeAddrOfExpr: { + if (!grouped) fprintf(ar->f, "("); fprintf(ar->f, "&"); if (node->data.addr_of_expr.align_expr != nullptr) { fprintf(ar->f, "align("); @@ -617,6 +620,7 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { } render_node_ungrouped(ar, node->data.addr_of_expr.op_expr); + if (!grouped) fprintf(ar->f, ")"); break; } case NodeTypeFnCallExpr: @@ -625,7 +629,7 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { fprintf(ar->f, "@"); } AstNode *fn_ref_node = node->data.fn_call_expr.fn_ref_expr; - bool grouped = (fn_ref_node->type != NodeTypePrefixOpExpr); + bool grouped = (fn_ref_node->type != NodeTypePrefixOpExpr && fn_ref_node->type != NodeTypeAddrOfExpr); render_node_extra(ar, fn_ref_node, grouped); fprintf(ar->f, "("); for (size_t i = 0; i < node->data.fn_call_expr.params.length; i += 1) { diff --git a/src/translate_c.cpp b/src/translate_c.cpp index 9e73c08b84..f8f5b5ba62 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -4061,11 +4061,30 @@ static AstNode *parse_ctok_primary_expr(Context *c, CTokenize *ctok, size_t *tok } CTok *next_tok = &ctok->tokens.at(*tok_i); - if (next_tok->id != CTokIdRParen) { + if (next_tok->id == CTokIdRParen) { + *tok_i += 1; + return inner_node; + } + + AstNode *node_to_cast = parse_ctok_expr(c, ctok, tok_i); + if (node_to_cast == nullptr) { + return nullptr; + } + + CTok *next_tok2 = &ctok->tokens.at(*tok_i); + if (next_tok2->id != CTokIdRParen) { return nullptr; } *tok_i += 1; - return inner_node; + + if (inner_node->type == NodeTypeAddrOfExpr) { + AstNode *call_node = trans_create_node_builtin_fn_call_str(c, "ptrCast"); + call_node->data.fn_call_expr.params.append(inner_node); + call_node->data.fn_call_expr.params.append(node_to_cast); + return call_node; + } else { + return trans_create_node_cast(c, inner_node, node_to_cast); + } } case CTokIdDot: case CTokIdEOF: diff --git a/test/translate_c.zig b/test/translate_c.zig index d4974109da..bbdd56db0d 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -902,7 +902,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\} , \\export fn foo(x: ?&c_int) { - \\ (*(??x)) = 1; + \\ (*??x) = 1; \\} ); @@ -930,7 +930,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\pub fn foo() -> c_int { \\ var x: c_int = 1234; \\ var ptr: ?&c_int = &x; - \\ return *(??ptr); + \\ return *??ptr; \\} ); @@ -1188,4 +1188,10 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ const v2: &const u8 = c"2.2.2"; \\} ); + + cases.add("macro pointer cast", + \\#define NRF_GPIO ((NRF_GPIO_Type *) NRF_GPIO_BASE) + , + \\pub const NRF_GPIO = @ptrCast(&NRF_GPIO_Type, NRF_GPIO_BASE); + ); } From 3577a80bb67923c0c165bd3c6e57ef3248e1b59c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 8 Dec 2017 12:28:21 -0500 Subject: [PATCH 21/69] translate-c: more complex logic for translating a C cast in a macro --- src/analyze.hpp | 1 + src/codegen.cpp | 4 ++++ src/ir.cpp | 3 +++ src/translate_c.cpp | 45 ++++++++++++++++++++++++++++++++++++-------- test/translate_c.zig | 2 +- 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/analyze.hpp b/src/analyze.hpp index e6100c692c..e12c4cda44 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -180,5 +180,6 @@ void add_link_lib_symbol(CodeGen *g, Buf *lib_name, Buf *symbol_name); uint32_t get_abi_alignment(CodeGen *g, TypeTableEntry *type_entry); TypeTableEntry *get_align_amt_type(CodeGen *g); +PackageTableEntry *new_anonymous_package(void); #endif diff --git a/src/codegen.cpp b/src/codegen.cpp index d052ef159a..44b3df3526 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -55,6 +55,10 @@ static PackageTableEntry *new_package(const char *root_src_dir, const char *root return entry; } +PackageTableEntry *new_anonymous_package(void) { + return new_package("", ""); +} + CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode, Buf *zig_lib_dir) { diff --git a/src/ir.cpp b/src/ir.cpp index 0512ab5e36..62927f46a0 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -14052,6 +14052,9 @@ static TypeTableEntry *ir_analyze_instruction_c_import(IrAnalyze *ira, IrInstruc ImportTableEntry *child_import = allocate(1); child_import->decls_scope = create_decls_scope(node, nullptr, nullptr, child_import); child_import->c_import_node = node; + child_import->package = new_anonymous_package(); + child_import->package->package_table.put(buf_create_from_str("builtin"), ira->codegen->compile_var_package); + child_import->package->package_table.put(buf_create_from_str("std"), ira->codegen->std_package); ZigList errors = {0}; diff --git a/src/translate_c.cpp b/src/translate_c.cpp index f8f5b5ba62..4575d4ee56 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -173,6 +173,14 @@ static AstNode * trans_create_node(Context *c, NodeType id) { return node; } +static AstNode *trans_create_node_if(Context *c, AstNode *cond_node, AstNode *then_node, AstNode *else_node) { + AstNode *node = trans_create_node(c, NodeTypeIfBoolExpr); + node->data.if_bool_expr.condition = cond_node; + node->data.if_bool_expr.then_block = then_node; + node->data.if_bool_expr.else_node = else_node; + return node; +} + static AstNode *trans_create_node_float_lit(Context *c, double value) { AstNode *node = trans_create_node(c, NodeTypeFloatLiteral); node->data.float_literal.bigfloat = allocate(1); @@ -4077,14 +4085,35 @@ static AstNode *parse_ctok_primary_expr(Context *c, CTokenize *ctok, size_t *tok } *tok_i += 1; - if (inner_node->type == NodeTypeAddrOfExpr) { - AstNode *call_node = trans_create_node_builtin_fn_call_str(c, "ptrCast"); - call_node->data.fn_call_expr.params.append(inner_node); - call_node->data.fn_call_expr.params.append(node_to_cast); - return call_node; - } else { - return trans_create_node_cast(c, inner_node, node_to_cast); - } + + //if (@typeId(@typeOf(x)) == @import("builtin").TypeId.Pointer) + // @ptrCast(dest, x) + //else if (@typeId(@typeOf(x)) == @import("builtin").TypeId.Integer) + // @intToPtr(dest, x) + //else + // (dest)(x) + + AstNode *import_builtin = trans_create_node_builtin_fn_call_str(c, "import"); + import_builtin->data.fn_call_expr.params.append(trans_create_node_str_lit_non_c(c, buf_create_from_str("builtin"))); + AstNode *typeid_type = trans_create_node_field_access_str(c, import_builtin, "TypeId"); + AstNode *typeid_pointer = trans_create_node_field_access_str(c, typeid_type, "Pointer"); + AstNode *typeid_integer = trans_create_node_field_access_str(c, typeid_type, "Int"); + AstNode *typeof_x = trans_create_node_builtin_fn_call_str(c, "typeOf"); + typeof_x->data.fn_call_expr.params.append(node_to_cast); + AstNode *typeid_value = trans_create_node_builtin_fn_call_str(c, "typeId"); + typeid_value->data.fn_call_expr.params.append(typeof_x); + + AstNode *outer_if_cond = trans_create_node_bin_op(c, typeid_value, BinOpTypeCmpEq, typeid_pointer); + AstNode *inner_if_cond = trans_create_node_bin_op(c, typeid_value, BinOpTypeCmpEq, typeid_integer); + AstNode *inner_if_then = trans_create_node_builtin_fn_call_str(c, "intToPtr"); + inner_if_then->data.fn_call_expr.params.append(inner_node); + inner_if_then->data.fn_call_expr.params.append(node_to_cast); + AstNode *inner_if_else = trans_create_node_cast(c, inner_node, node_to_cast); + AstNode *inner_if = trans_create_node_if(c, inner_if_cond, inner_if_then, inner_if_else); + AstNode *outer_if_then = trans_create_node_builtin_fn_call_str(c, "ptrCast"); + outer_if_then->data.fn_call_expr.params.append(inner_node); + outer_if_then->data.fn_call_expr.params.append(node_to_cast); + return trans_create_node_if(c, outer_if_cond, outer_if_then, inner_if); } case CTokIdDot: case CTokIdEOF: diff --git a/test/translate_c.zig b/test/translate_c.zig index bbdd56db0d..d50c7b9691 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1192,6 +1192,6 @@ pub fn addCases(cases: &tests.TranslateCContext) { cases.add("macro pointer cast", \\#define NRF_GPIO ((NRF_GPIO_Type *) NRF_GPIO_BASE) , - \\pub const NRF_GPIO = @ptrCast(&NRF_GPIO_Type, NRF_GPIO_BASE); + \\pub const NRF_GPIO = if (@typeId(@typeOf(NRF_GPIO_BASE)) == @import("builtin").TypeId.Pointer) @ptrCast(&NRF_GPIO_Type, NRF_GPIO_BASE) else if (@typeId(@typeOf(NRF_GPIO_BASE)) == @import("builtin").TypeId.Int) @intToPtr(&NRF_GPIO_Type, NRF_GPIO_BASE) else (&NRF_GPIO_Type)(NRF_GPIO_BASE); ); } From 756a218e27831a20395cc370fb3d667666dee20d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 8 Dec 2017 17:49:14 -0500 Subject: [PATCH 22/69] add implicit cast from enum tag type of union to const ptr to the union closes #654 --- src/ir.cpp | 34 ++++++++++++++++++++++++++++++++++ test/cases/union.zig | 14 ++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index 62927f46a0..40f853531f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -7492,6 +7492,19 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, } } + // implicit enum to &const union which has the enum as the tag type + if (actual_type->id == TypeTableEntryIdEnum && expected_type->id == TypeTableEntryIdPointer) { + TypeTableEntry *union_type = expected_type->data.pointer.child_type; + if (union_type->data.unionation.decl_node->data.container_decl.auto_enum || + union_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr) + { + type_ensure_zero_bits_known(ira->codegen, union_type); + if (union_type->data.unionation.tag_type == actual_type) { + return ImplicitCastMatchResultYes; + } + } + } + // implicit undefined literal to anything if (actual_type->id == TypeTableEntryIdUndefLit) { return ImplicitCastMatchResultYes; @@ -9079,6 +9092,27 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } + // explicit enum to &const union which has the enum as the tag type + if (actual_type->id == TypeTableEntryIdEnum && wanted_type->id == TypeTableEntryIdPointer) { + TypeTableEntry *union_type = wanted_type->data.pointer.child_type; + if (union_type->data.unionation.decl_node->data.container_decl.auto_enum || + union_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr) + { + type_ensure_zero_bits_known(ira->codegen, union_type); + if (union_type->data.unionation.tag_type == actual_type) { + IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, union_type, value); + if (type_is_invalid(cast1->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + if (type_is_invalid(cast2->value.type)) + return ira->codegen->invalid_instruction; + + return cast2; + } + } + } + // explicit cast from undefined to anything if (actual_type->id == TypeTableEntryIdUndefLit) { return ir_analyze_undefined_to_anything(ira, source_instr, value, wanted_type); diff --git a/test/cases/union.zig b/test/cases/union.zig index 7c1c04c711..90d869289f 100644 --- a/test/cases/union.zig +++ b/test/cases/union.zig @@ -206,3 +206,17 @@ test "implicit cast union to its tag type" { fn giveMeLetterB(x: Letter2) { assert(x == Value2.B); } + +test "implicit cast from @EnumTagType(TheUnion) to &const TheUnion" { + assertIsTheUnion2Item1(TheUnion2.Item1); +} + +const TheUnion2 = union(enum) { + Item1, + Item2: i32, +}; + +fn assertIsTheUnion2Item1(value: &const TheUnion2) { + assert(*value == TheUnion2.Item1); +} + From d431b0fb99342f1e2629f5dd8bf1ca578a5e5d9a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 8 Dec 2017 23:15:43 -0500 Subject: [PATCH 23/69] parse a simple variable declaration --- src-self-hosted/main.zig | 440 +++++++++++++++++++++++++++++++++++---- std/mem.zig | 11 + 2 files changed, 406 insertions(+), 45 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index d8b403c066..680de4d041 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -88,6 +88,8 @@ const Token = struct { Period, Minus, Arrow, + Colon, + Slash, Keyword_align, Keyword_and, Keyword_asm, @@ -135,6 +137,37 @@ const Tokenizer = struct { buffer: []const u8, index: usize, + pub const Location = struct { + line: usize, + column: usize, + line_start: usize, + line_end: usize, + }; + + pub fn getTokenLocation(self: &Tokenizer, token: &const Token) -> Location { + var loc = Location { + .line = 0, + .column = 0, + .line_start = 0, + .line_end = 0, + }; + for (self.buffer) |c, i| { + if (i == token.start) { + loc.line_end = i; + while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {} + return loc; + } + if (c == '\n') { + loc.line += 1; + loc.column = 0; + loc.line_start = i; + } else { + loc.column += 1; + } + } + return loc; + } + pub fn dump(self: &Tokenizer, token: &const Token) { warn("{} \"{}\"\n", @tagName(token.id), self.buffer[token.start..token.end]); } @@ -154,6 +187,8 @@ const Tokenizer = struct { StringLiteral, StringLiteralBackslash, Minus, + Slash, + LineComment, }; pub fn next(self: &Tokenizer) -> Token { @@ -206,6 +241,11 @@ const Tokenizer = struct { self.index += 1; break; }, + ':' => { + result.id = Token.Id.Colon; + self.index += 1; + break; + }, '%' => { result.id = Token.Id.Percent; self.index += 1; @@ -229,6 +269,9 @@ const Tokenizer = struct { '-' => { state = State.Minus; }, + '/' => { + state = State.Slash; + }, else => { result.id = Token.Id.Invalid; self.index += 1; @@ -289,78 +332,363 @@ const Tokenizer = struct { break; }, }, + State.Slash => switch (c) { + '/' => { + result.id = undefined; + state = State.LineComment; + }, + else => { + result.id = Token.Id.Slash; + break; + }, + }, + State.LineComment => switch (c) { + '\n' => { + state = State.Start; + result = Token { + .id = Token.Id.Eof, + .start = self.index + 1, + .end = undefined, + }; + }, + else => {}, + }, } } result.end = self.index; + // TODO check state when returning EOF return result; } }; -const AstNode = struct { - +const Visibility = enum { + Private, + Pub, + Export, }; +const Mutability = enum { + Const, + Var, +}; + +const AstNode = struct { + id: Id, + + const Id = enum { + Root, + VarDecl, + Identifier, + }; + + fn iterate(base: &AstNode, index: usize) -> ?&AstNode { + return switch (base.id) { + Id.Root => @fieldParentPtr(AstNodeRoot, "base", base).iterate(index), + Id.VarDecl => @fieldParentPtr(AstNodeVarDecl, "base", base).iterate(index), + Id.Identifier => @fieldParentPtr(AstNodeIdentifier, "base", base).iterate(index), + }; + } +}; + +const AstNodeRoot = struct { + base: AstNode, + decls: ArrayList(&AstNode), + + fn iterate(self: &AstNodeRoot, index: usize) -> ?&AstNode { + if (index < self.decls.len) { + return self.decls.items[index]; + } + return null; + } +}; + +const AstNodeVarDecl = struct { + base: AstNode, + visib: Visibility, + name_token: Token, + eq_token: Token, + mut: Mutability, + is_comptime: bool, + type_node: ?&AstNode, + align_node: ?&AstNode, + init_node: ?&AstNode, + + fn iterate(self: &AstNodeVarDecl, index: usize) -> ?&AstNode { + var i = index; + + if (self.type_node) |type_node| { + if (i < 1) return type_node; + i -= 1; + } + + if (self.align_node) |align_node| { + if (i < 1) return align_node; + i -= 1; + } + + if (self.init_node) |init_node| { + if (i < 1) return init_node; + i -= 1; + } + + return null; + } +}; + +const AstNodeIdentifier = struct { + base: AstNode, + name_token: Token, + + fn iterate(self: &AstNodeIdentifier, index: usize) -> ?&AstNode { + return null; + } +}; + +error ParseError; + const Parser = struct { tokenizer: &Tokenizer, allocator: &mem.Allocator, + put_back_tokens: [1]Token, + put_back_count: usize, + source_file_name: []const u8, - fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator) -> Parser { + fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator, source_file_name: []const u8) -> Parser { return Parser { .tokenizer = tokenizer, .allocator = allocator, + .put_back_tokens = undefined, + .put_back_count = 0, + .source_file_name = source_file_name, }; } - const StackFrame = struct { - - }; - - const State = enum { + const State = union(enum) { TopLevel, - Expression, + TopLevelModifier: Visibility, + Expression: &?&AstNode, + GroupedExpression: &?&AstNode, + PrimaryExpression: &?&AstNode, + TypeExpr: &?&AstNode, + VarDecl: &AstNodeVarDecl, + VarDeclAlign: &AstNodeVarDecl, + VarDeclEq: &AstNodeVarDecl, + ExpectSemicolon, }; - fn parse(self: &Parser) -> %void { - var stack = ArrayList(StackFrame).init(self.allocator); + pub fn parse(self: &Parser) -> %&AstNode { + var stack = ArrayList(State).init(self.allocator); defer stack.deinit(); - var state = State.TopLevel; - while (true) { - const token = self.tokenizer.next(); - switch (state) { - State.TopLevel => switch (token.id) { - Token.Id.Keyword_pub => { - const next_token = self.tokenizer.next(); - switch (next_token.id) { - Token.Id.Keyword_fn => { - const fn_name = self.tokenizer.next(); - if (fn_name.id != Token.Id.Identifier) { - @panic("parse error"); - } + %return stack.append(State.TopLevel); - const lparen = self.tokenizer.next(); - if (lparen.id != Token.Id.LParen) { - @panic("parse error"); - } - }, - Token.Id.Keyword_const => @panic("TODO"), - Token.Id.Keyword_var => @panic("TODO"), - Token.Id.Keyword_use => @panic("TODO"), - else => @panic("parse error"), - } - }, - Token.Id.Keyword_const => @panic("TODO"), - Token.Id.Keyword_var => @panic("TODO"), - Token.Id.Keyword_fn => @panic("TODO"), - Token.Id.Keyword_export => @panic("TODO"), - Token.Id.Keyword_use => @panic("TODO"), - Token.Id.Keyword_comptime => @panic("TODO"), - else => @panic("parse error"), + const root_node = %return self.createRoot(); + // TODO %defer self.freeAst(); + + while (true) { + // This gives us 1 free append that can't fail + const state = stack.pop(); + + switch (state) { + State.TopLevel => { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_pub => { + stack.append(State {.TopLevelModifier = Visibility.Pub }) %% unreachable; + continue; + }, + Token.Id.Keyword_export => { + stack.append(State {.TopLevelModifier = Visibility.Export }) %% unreachable; + continue; + }, + Token.Id.Keyword_const => { + stack.append(State.TopLevel) %% unreachable; + const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Const, false); + %return root_node.decls.append(&var_decl_node.base); + %return stack.append(State { .VarDecl = var_decl_node }); + continue; + }, + Token.Id.Keyword_var => { + stack.append(State.TopLevel) %% unreachable; + const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Var, false); + %return root_node.decls.append(&var_decl_node.base); + %return stack.append(State { .VarDecl = var_decl_node }); + continue; + }, + Token.Id.Eof => return &root_node.base, + else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), + } }, - State.Expression => @panic("TODO"), + State.TopLevelModifier => |visib| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_const => { + stack.append(State.TopLevel) %% unreachable; + const var_decl_node = %return self.createVarDecl(visib, Mutability.Const, false); + %return root_node.decls.append(&var_decl_node.base); + %return stack.append(State { .VarDecl = var_decl_node }); + continue; + }, + Token.Id.Keyword_var => { + stack.append(State.TopLevel) %% unreachable; + const var_decl_node = %return self.createVarDecl(visib, Mutability.Var, false); + %return root_node.decls.append(&var_decl_node.base); + %return stack.append(State { .VarDecl = var_decl_node }); + continue; + }, + else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), + } + }, + State.VarDecl => |var_decl| { + var_decl.name_token = %return self.eatToken(Token.Id.Identifier); + stack.append(State { .VarDeclAlign = var_decl }) %% unreachable; + + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Colon) { + %return stack.append(State { .TypeExpr = &var_decl.type_node }); + continue; + } + + self.putBackToken(next_token); + continue; + }, + State.VarDeclAlign => |var_decl| { + stack.append(State { .VarDeclEq = var_decl }) %% unreachable; + + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Keyword_align) { + %return stack.append(State { .GroupedExpression = &var_decl.align_node }); + continue; + } + + self.putBackToken(next_token); + continue; + }, + State.VarDeclEq => |var_decl| { + var_decl.eq_token = %return self.eatToken(Token.Id.Equal); + stack.append(State.ExpectSemicolon) %% unreachable; + %return stack.append(State { + .Expression = &var_decl.init_node, + }); + continue; + }, + State.ExpectSemicolon => { + _ = %return self.eatToken(Token.Id.Semicolon); + continue; + }, + State.Expression => |result_ptr| { + // TODO this should not jump straight to primary expression + stack.append(State {.PrimaryExpression = result_ptr}) %% unreachable; + continue; + }, + State.PrimaryExpression => |result_ptr| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Identifier => { + const identifier = %return self.createIdentifier(token); + *result_ptr = &identifier.base; + continue; + }, + else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), + } + }, + State.TypeExpr => @panic("TODO"), + State.GroupedExpression => @panic("TODO"), } + unreachable; } } + + fn createRoot(self: &Parser) -> %&AstNodeRoot { + const node = %return self.allocator.create(AstNodeRoot); + %defer self.allocator.destroy(node); + + *node = AstNodeRoot { + .base = AstNode {.id = AstNode.Id.Root}, + .decls = ArrayList(&AstNode).init(self.allocator), + }; + return node; + } + + fn createVarDecl(self: &Parser, visib: Visibility, mut: Mutability, is_comptime: bool) -> %&AstNodeVarDecl { + const node = %return self.allocator.create(AstNodeVarDecl); + %defer self.allocator.destroy(node); + + *node = AstNodeVarDecl { + .base = AstNode {.id = AstNode.Id.VarDecl}, + .visib = visib, + .mut = mut, + .is_comptime = is_comptime, + .type_node = null, + .align_node = null, + .init_node = null, + // initialized later + .name_token = undefined, + .eq_token = undefined, + }; + return node; + } + + fn createIdentifier(self: &Parser, name_token: &const Token) -> %&AstNodeIdentifier { + const node = %return self.allocator.create(AstNodeIdentifier); + %defer self.allocator.destroy(node); + + *node = AstNodeIdentifier { + .base = AstNode {.id = AstNode.Id.Identifier}, + .name_token = *name_token, + }; + return node; + } + + fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) -> error { + const loc = self.tokenizer.getTokenLocation(token); + warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args); + warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]); + { + var i: usize = 0; + while (i < loc.column) : (i += 1) { + warn(" "); + } + } + { + const caret_count = token.end - token.start; + var i: usize = 0; + while (i < caret_count) : (i += 1) { + warn("~"); + } + } + warn("\n"); + return error.ParseError; + } + + fn expectToken(self: &Parser, token: &const Token, id: @TagType(Token.Id)) -> %void { + if (token.id != id) { + return self.parseError(token, "expected {}, found {}", @tagName(id), @tagName(token.id)); + } + } + + fn eatToken(self: &Parser, id: @TagType(Token.Id)) -> %Token { + const token = self.getNextToken(); + %return self.expectToken(token, id); + return token; + } + + fn putBackToken(self: &Parser, token: &const Token) { + self.put_back_tokens[self.put_back_count] = *token; + self.put_back_count += 1; + } + + fn getNextToken(self: &Parser) -> Token { + return if (self.put_back_count != 0) { + const put_back_index = self.put_back_count - 1; + const put_back_token = self.put_back_tokens[put_back_index]; + self.put_back_count = put_back_index; + put_back_token + } else { + self.tokenizer.next() + }; + } + }; @@ -384,8 +712,11 @@ pub fn main2() -> %void { const target_file_buf = %return io.readFileAlloc(target_file, allocator); + warn("====input:====\n"); + warn("{}", target_file_buf); + warn("====tokenization:====\n"); { var tokenizer = Tokenizer.init(target_file_buf); while (true) { @@ -397,7 +728,26 @@ pub fn main2() -> %void { } } + warn("====parse:====\n"); + var tokenizer = Tokenizer.init(target_file_buf); - var parser = Parser.init(&tokenizer, allocator); - %return parser.parse(); + var parser = Parser.init(&tokenizer, allocator, target_file); + const node = %return parser.parse(); + + + render(node, 0); +} + +fn render(node: &AstNode, indent: usize) { + { + var i: usize = 0; + while (i < indent) : (i += 1) { + warn(" "); + } + } + warn("{}\n", @tagName(node.id)); + var i: usize = 0; + while (node.iterate(i)) |child| : (i += 1) { + render(child, indent + 2); + } } diff --git a/std/mem.zig b/std/mem.zig index e99675d248..b62451687f 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -8,6 +8,7 @@ pub const Cmp = math.Cmp; pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the /// slice's pointer aligned at least to alignment bytes. + /// The returned newly allocated memory is undefined. allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) -> %[]u8, /// If `new_byte_count > old_mem.len`: @@ -17,6 +18,8 @@ pub const Allocator = struct { /// If `new_byte_count <= old_mem.len`: /// * this function must return successfully. /// * alignment <= alignment of old_mem.ptr + /// + /// The returned newly allocated memory is undefined. reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) -> %[]u8, /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn` @@ -40,6 +43,10 @@ pub const Allocator = struct { { const byte_count = %return math.mul(usize, @sizeOf(T), n); const byte_slice = %return self.allocFn(self, byte_count, alignment); + // This loop should get optimized out in ReleaseFast mode + for (byte_slice) |*byte| { + *byte = undefined; + } return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } @@ -56,6 +63,10 @@ pub const Allocator = struct { const byte_count = %return math.mul(usize, @sizeOf(T), n); const byte_slice = %return self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); + // This loop should get optimized out in ReleaseFast mode + for (byte_slice[old_mem.len..]) |*byte| { + *byte = undefined; + } return ([]T)(@alignCast(alignment, byte_slice)); } From f466e539ef27d1cf90e2b163d5afbcf0bffc0aa5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 8 Dec 2017 23:56:07 -0500 Subject: [PATCH 24/69] tokenizing libc hello world --- src-self-hosted/main.zig | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 680de4d041..a826780bc1 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -90,6 +90,10 @@ const Token = struct { Arrow, Colon, Slash, + Comma, + Ampersand, + AmpersandEqual, + NumberLiteral, Keyword_align, Keyword_and, Keyword_asm, @@ -189,6 +193,13 @@ const Tokenizer = struct { Minus, Slash, LineComment, + Zero, + NumberLiteral, + NumberDot, + FloatFraction, + FloatExponentUnsigned, + FloatExponentNumber, + Ampersand, }; pub fn next(self: &Tokenizer) -> Token { @@ -241,6 +252,11 @@ const Tokenizer = struct { self.index += 1; break; }, + ',' => { + result.id = Token.Id.Comma; + self.index += 1; + break; + }, ':' => { result.id = Token.Id.Colon; self.index += 1; @@ -272,12 +288,34 @@ const Tokenizer = struct { '/' => { state = State.Slash; }, + '&' => { + state = State.Ampersand; + }, + '0' => { + state = State.Zero; + result.id = Token.Id.NumberLiteral; + }, + '1'...'9' => { + state = State.NumberLiteral; + result.id = Token.Id.NumberLiteral; + }, else => { result.id = Token.Id.Invalid; self.index += 1; break; }, }, + State.Ampersand => switch (c) { + '=' => { + result.id = Token.Id.AmpersandEqual; + self.index += 1; + break; + }, + else => { + result.id = Token.Id.Ampersand; + break; + }, + }, State.Identifier => switch (c) { 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, else => { @@ -353,6 +391,58 @@ const Tokenizer = struct { }, else => {}, }, + State.Zero => switch (c) { + 'b', 'o', 'x' => { + state = State.NumberLiteral; + }, + else => { + // reinterpret as a normal number + self.index -= 1; + state = State.NumberLiteral; + }, + }, + State.NumberLiteral => switch (c) { + '.' => { + state = State.NumberDot; + }, + 'p', 'P', 'e', 'E' => { + state = State.FloatExponentUnsigned; + }, + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => break, + }, + State.NumberDot => switch (c) { + '.' => { + self.index -= 1; + state = State.Start; + break; + }, + else => { + self.index -= 1; + state = State.FloatFraction; + }, + }, + State.FloatFraction => switch (c) { + 'p', 'P', 'e', 'E' => { + state = State.FloatExponentUnsigned; + }, + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => break, + }, + State.FloatExponentUnsigned => switch (c) { + '+', '-' => { + state = State.FloatExponentNumber; + }, + else => { + // reinterpret as a normal exponent number + self.index -= 1; + state = State.FloatExponentNumber; + } + }, + State.FloatExponentNumber => switch (c) { + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => break, + }, } } result.end = self.index; From e9efa74333b4890b2598c96dc7df4761965e6819 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 9 Dec 2017 20:01:13 -0500 Subject: [PATCH 25/69] partial parameter decl parsing --- src-self-hosted/main.zig | 464 ++++++++++++++++++++++++++++++++++++--- src/analyze.cpp | 2 +- 2 files changed, 433 insertions(+), 33 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index a826780bc1..1ba5a5a69d 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -86,6 +86,8 @@ const Token = struct { LBrace, RBrace, Period, + Ellipsis2, + Ellipsis3, Minus, Arrow, Colon, @@ -200,6 +202,8 @@ const Tokenizer = struct { FloatExponentUnsigned, FloatExponentNumber, Ampersand, + Period, + Period2, }; pub fn next(self: &Tokenizer) -> Token { @@ -278,9 +282,7 @@ const Tokenizer = struct { break; }, '.' => { - result.id = Token.Id.Period; - self.index += 1; - break; + state = State.Period; }, '-' => { state = State.Minus; @@ -370,6 +372,29 @@ const Tokenizer = struct { break; }, }, + + State.Period => switch (c) { + '.' => { + state = State.Period2; + }, + else => { + result.id = Token.Id.Period; + break; + }, + }, + + State.Period2 => switch (c) { + '.' => { + result.id = Token.Id.Ellipsis3; + self.index += 1; + break; + }, + else => { + result.id = Token.Id.Ellipsis2; + break; + }, + }, + State.Slash => switch (c) { '/' => { result.id = undefined; @@ -451,15 +476,30 @@ const Tokenizer = struct { } }; +const Comptime = enum { No, Yes }; +const NoAlias = enum { No, Yes }; +const Extern = enum { No, Yes }; +const VarArgs = enum { No, Yes }; +const Mutability = enum { Const, Var }; + +const Inline = enum { + Auto, + Always, + Never, +}; + const Visibility = enum { Private, Pub, Export, }; -const Mutability = enum { - Const, - Var, +const CallingConvention = enum { + Auto, + C, + Cold, + Naked, + Stdcall, }; const AstNode = struct { @@ -469,6 +509,8 @@ const AstNode = struct { Root, VarDecl, Identifier, + FnProto, + ParamDecl, }; fn iterate(base: &AstNode, index: usize) -> ?&AstNode { @@ -476,6 +518,8 @@ const AstNode = struct { Id.Root => @fieldParentPtr(AstNodeRoot, "base", base).iterate(index), Id.VarDecl => @fieldParentPtr(AstNodeVarDecl, "base", base).iterate(index), Id.Identifier => @fieldParentPtr(AstNodeIdentifier, "base", base).iterate(index), + Id.FnProto => @fieldParentPtr(AstNodeFnProto, "base", base).iterate(index), + Id.ParamDecl => @fieldParentPtr(AstNodeParamDecl, "base", base).iterate(index), }; } }; @@ -498,7 +542,9 @@ const AstNodeVarDecl = struct { name_token: Token, eq_token: Token, mut: Mutability, - is_comptime: bool, + is_comptime: Comptime, + is_extern: Extern, + lib_name: ?&AstNode, type_node: ?&AstNode, align_node: ?&AstNode, init_node: ?&AstNode, @@ -534,12 +580,75 @@ const AstNodeIdentifier = struct { } }; +const AstNodeFnProto = struct { + base: AstNode, + visib: Visibility, + fn_token: Token, + name_token: ?Token, + params: ArrayList(&AstNode), + return_type: ?&AstNode, + var_args: VarArgs, + is_extern: Extern, + is_inline: Inline, + cc: CallingConvention, + fn_def_node: ?&AstNode, + lib_name: ?&AstNode, // populated if this is an extern declaration + align_expr: ?&AstNode, // populated if align(A) is present + + fn iterate(self: &AstNodeFnProto, index: usize) -> ?&AstNode { + var i = index; + + if (i < self.params.len) return self.params.items[i]; + i -= self.params.len; + + if (self.return_type) |return_type| { + if (i < 1) return return_type; + i -= 1; + } + + if (self.fn_def_node) |fn_def_node| { + if (i < 1) return fn_def_node; + i -= 1; + } + + if (self.lib_name) |lib_name| { + if (i < 1) return lib_name; + i -= 1; + } + + if (self.align_expr) |align_expr| { + if (i < 1) return align_expr; + i -= 1; + } + + return null; + } +}; + +const AstNodeParamDecl = struct { + base: AstNode, + comptime_token: ?Token, + noalias_token: ?Token, + name_token: ?Token, + type_node: &AstNode, + var_args_token: ?Token, + + fn iterate(self: &AstNodeParamDecl, index: usize) -> ?&AstNode { + var i = index; + + if (i < 1) return self.type_node; + i -= 1; + + return null; + } +}; + error ParseError; const Parser = struct { tokenizer: &Tokenizer, allocator: &mem.Allocator, - put_back_tokens: [1]Token, + put_back_tokens: [2]Token, put_back_count: usize, source_file_name: []const u8, @@ -556,14 +665,32 @@ const Parser = struct { const State = union(enum) { TopLevel, TopLevelModifier: Visibility, - Expression: &?&AstNode, - GroupedExpression: &?&AstNode, - PrimaryExpression: &?&AstNode, - TypeExpr: &?&AstNode, + TopLevelExtern: Visibility, + Expression: &&AstNode, + GroupedExpression: &&AstNode, + UnwrapExpression: &&AstNode, + BoolOrExpression: &&AstNode, + BoolAndExpression: &&AstNode, + ComparisonExpression: &&AstNode, + BinaryOrExpression: &&AstNode, + BinaryXorExpression: &&AstNode, + BinaryAndExpression: &&AstNode, + BitShiftExpression: &&AstNode, + AdditionExpression: &&AstNode, + MultiplyExpression: &&AstNode, + BraceSuffixExpression: &&AstNode, + PrefixOpExpression: &&AstNode, + SuffixOpExpression: &&AstNode, + PrimaryExpression: &&AstNode, + TypeExpr: &&AstNode, VarDecl: &AstNodeVarDecl, VarDeclAlign: &AstNodeVarDecl, VarDeclEq: &AstNodeVarDecl, - ExpectSemicolon, + ExpectToken: @TagType(Token.Id), + FnProto: &AstNodeFnProto, + FnProtoAlign: &AstNodeFnProto, + ParamDecl: &AstNodeFnProto, + ParamDeclComma, }; pub fn parse(self: &Parser) -> %&AstNode { @@ -593,19 +720,31 @@ const Parser = struct { }, Token.Id.Keyword_const => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Const, false); - %return root_node.decls.append(&var_decl_node.base); + const var_decl_node = { + const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Const, Comptime.No, Extern.No); + %defer self.allocator.destroy(var_decl_node); + %return root_node.decls.append(&var_decl_node.base); + var_decl_node + }; %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_var => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Var, false); - %return root_node.decls.append(&var_decl_node.base); + const var_decl_node = { + const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Var, Comptime.No, Extern.No); + %defer self.allocator.destroy(var_decl_node); + %return root_node.decls.append(&var_decl_node.base); + var_decl_node + }; %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Eof => return &root_node.base, + Token.Id.Keyword_extern => { + stack.append(State { .TopLevelExtern = Visibility.Private }) %% unreachable; + continue; + }, else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), } }, @@ -614,28 +753,81 @@ const Parser = struct { switch (token.id) { Token.Id.Keyword_const => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = %return self.createVarDecl(visib, Mutability.Const, false); - %return root_node.decls.append(&var_decl_node.base); + const var_decl_node = { + const var_decl_node = %return self.createVarDecl(visib, Mutability.Const, Comptime.No, Extern.No); + %defer self.allocator.destroy(var_decl_node); + %return root_node.decls.append(&var_decl_node.base); + var_decl_node + }; %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_var => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = %return self.createVarDecl(visib, Mutability.Var, false); - %return root_node.decls.append(&var_decl_node.base); + const var_decl_node = { + const var_decl_node = %return self.createVarDecl(visib, Mutability.Var, Comptime.No, Extern.No); + %defer self.allocator.destroy(var_decl_node); + %return root_node.decls.append(&var_decl_node.base); + var_decl_node + }; %return stack.append(State { .VarDecl = var_decl_node }); continue; }, + Token.Id.Keyword_extern => { + stack.append(State { .TopLevelExtern = visib }) %% unreachable; + continue; + }, else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), } }, + State.TopLevelExtern => |visib| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_var => { + stack.append(State.TopLevel) %% unreachable; + const var_decl_node = { + const var_decl_node = %return self.createVarDecl(visib, Mutability.Var, Comptime.No, Extern.Yes); + %defer self.allocator.destroy(var_decl_node); + %return root_node.decls.append(&var_decl_node.base); + var_decl_node + }; + %return stack.append(State { .VarDecl = var_decl_node }); + continue; + }, + Token.Id.Keyword_fn => { + stack.append(State.TopLevel) %% unreachable; + const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, token, + Extern.Yes, CallingConvention.Auto, visib, Inline.Auto); + %return stack.append(State { .FnProto = fn_proto_node }); + continue; + }, + Token.Id.StringLiteral => { + @panic("TODO extern with string literal"); + }, + Token.Id.Keyword_coldcc, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { + stack.append(State.TopLevel) %% unreachable; + const cc = switch (token.id) { + Token.Id.Keyword_coldcc => CallingConvention.Cold, + Token.Id.Keyword_nakedcc => CallingConvention.Naked, + Token.Id.Keyword_stdcallcc => CallingConvention.Stdcall, + else => unreachable, + }; + const fn_token = %return self.eatToken(Token.Id.Keyword_fn); + const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, fn_token, + Extern.Yes, cc, visib, Inline.Auto); + %return stack.append(State { .FnProto = fn_proto_node }); + continue; + }, + else => return self.parseError(token, "expected variable declaration or function, found {}", @tagName(token.id)), + } + }, State.VarDecl => |var_decl| { var_decl.name_token = %return self.eatToken(Token.Id.Identifier); stack.append(State { .VarDeclAlign = var_decl }) %% unreachable; const next_token = self.getNextToken(); if (next_token.id == Token.Id.Colon) { - %return stack.append(State { .TypeExpr = &var_decl.type_node }); + %return stack.append(State { .TypeExpr = removeNullCast(&var_decl.type_node) }); continue; } @@ -647,7 +839,7 @@ const Parser = struct { const next_token = self.getNextToken(); if (next_token.id == Token.Id.Keyword_align) { - %return stack.append(State { .GroupedExpression = &var_decl.align_node }); + %return stack.append(State { .GroupedExpression = removeNullCast(&var_decl.align_node) }); continue; } @@ -656,21 +848,86 @@ const Parser = struct { }, State.VarDeclEq => |var_decl| { var_decl.eq_token = %return self.eatToken(Token.Id.Equal); - stack.append(State.ExpectSemicolon) %% unreachable; + stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; %return stack.append(State { - .Expression = &var_decl.init_node, + .Expression = removeNullCast(&var_decl.init_node), }); continue; }, - State.ExpectSemicolon => { - _ = %return self.eatToken(Token.Id.Semicolon); + State.ExpectToken => |token_id| { + _ = %return self.eatToken(token_id); continue; }, State.Expression => |result_ptr| { - // TODO this should not jump straight to primary expression - stack.append(State {.PrimaryExpression = result_ptr}) %% unreachable; + stack.append(State {.UnwrapExpression = result_ptr}) %% unreachable; continue; }, + + State.UnwrapExpression => |result_ptr| { + stack.append(State {.BoolOrExpression = result_ptr}) %% unreachable; + continue; + }, + + State.BoolOrExpression => |result_ptr| { + stack.append(State {.BoolAndExpression = result_ptr}) %% unreachable; + continue; + }, + + State.BoolAndExpression => |result_ptr| { + stack.append(State {.ComparisonExpression = result_ptr}) %% unreachable; + continue; + }, + + State.ComparisonExpression => |result_ptr| { + stack.append(State {.BinaryOrExpression = result_ptr}) %% unreachable; + continue; + }, + + State.BinaryOrExpression => |result_ptr| { + stack.append(State {.BinaryXorExpression = result_ptr}) %% unreachable; + continue; + }, + + State.BinaryXorExpression => |result_ptr| { + stack.append(State {.BinaryAndExpression = result_ptr}) %% unreachable; + continue; + }, + + State.BinaryAndExpression => |result_ptr| { + stack.append(State {.BitShiftExpression = result_ptr}) %% unreachable; + continue; + }, + + State.BitShiftExpression => |result_ptr| { + stack.append(State {.AdditionExpression = result_ptr}) %% unreachable; + continue; + }, + + State.AdditionExpression => |result_ptr| { + stack.append(State {.AdditionExpression = result_ptr}) %% unreachable; + continue; + }, + + State.MultiplyExpression => |result_ptr| { + stack.append(State {.BraceSuffixExpression = result_ptr}) %% unreachable; + continue; + }, + + State.BraceSuffixExpression => |result_ptr| { + stack.append(State {.PrefixOpExpression = result_ptr}) %% unreachable; + continue; + }, + + State.PrefixOpExpression => |result_ptr| { + stack.append(State { .SuffixOpExpression = result_ptr }) %% unreachable; + continue; + }, + + State.SuffixOpExpression => |result_ptr| { + stack.append(State { .PrimaryExpression = result_ptr }) %% unreachable; + continue; + }, + State.PrimaryExpression => |result_ptr| { const token = self.getNextToken(); switch (token.id) { @@ -682,7 +939,84 @@ const Parser = struct { else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), } }, - State.TypeExpr => @panic("TODO"), + + State.TypeExpr => |result_ptr| { + const token = self.getNextToken(); + if (token.id == Token.Id.Keyword_var) { + @panic("TODO param with type var"); + } + self.putBackToken(token); + + stack.append(State { .PrefixOpExpression = result_ptr }) %% unreachable; + continue; + }, + + State.FnProto => |fn_proto| { + stack.append(State { .FnProtoAlign = fn_proto }) %% unreachable; + %return stack.append(State { .ParamDecl = fn_proto }); + %return stack.append(State { .ExpectToken = Token.Id.LParen }); + + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Identifier) { + fn_proto.name_token = next_token; + continue; + } + self.putBackToken(next_token); + continue; + }, + + State.FnProtoAlign => |fn_proto| { + @panic("TODO fn proto align"); + //continue; + }, + + State.ParamDecl => |fn_proto| { + var token = self.getNextToken(); + if (token.id == Token.Id.RParen) { + continue; + } + const param_decl = %return self.createAttachParamDecl(&fn_proto.params); + if (token.id == Token.Id.Keyword_comptime) { + param_decl.comptime_token = token; + token = self.getNextToken(); + } else if (token.id == Token.Id.Keyword_noalias) { + param_decl.noalias_token = token; + token = self.getNextToken(); + }; + if (token.id == Token.Id.Identifier) { + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Colon) { + param_decl.name_token = token; + token = self.getNextToken(); + } else { + self.putBackToken(next_token); + } + } + if (token.id == Token.Id.Ellipsis3) { + param_decl.var_args_token = token; + } else { + self.putBackToken(token); + } + + stack.append(State { .ParamDecl = fn_proto }) %% unreachable; + %return stack.append(State.ParamDeclComma); + %return stack.append(State { .TypeExpr = ¶m_decl.type_node }); + continue; + }, + + State.ParamDeclComma => { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.RParen => { + _ = stack.pop(); // pop off the ParamDecl + continue; + }, + Token.Id.Comma => continue, + else => return self.parseError(token, "expected ',' or ')', found {}", @tagName(token.id)), + } + }, + + State.GroupedExpression => @panic("TODO"), } unreachable; @@ -700,7 +1034,9 @@ const Parser = struct { return node; } - fn createVarDecl(self: &Parser, visib: Visibility, mut: Mutability, is_comptime: bool) -> %&AstNodeVarDecl { + fn createVarDecl(self: &Parser, visib: Visibility, mut: Mutability, is_comptime: Comptime, + is_extern: Extern) -> %&AstNodeVarDecl + { const node = %return self.allocator.create(AstNodeVarDecl); %defer self.allocator.destroy(node); @@ -709,9 +1045,11 @@ const Parser = struct { .visib = visib, .mut = mut, .is_comptime = is_comptime, + .is_extern = is_extern, .type_node = null, .align_node = null, .init_node = null, + .lib_name = null, // initialized later .name_token = undefined, .eq_token = undefined, @@ -730,6 +1068,61 @@ const Parser = struct { return node; } + fn createFnProto(self: &Parser, fn_token: &const Token, is_extern: Extern, + cc: CallingConvention, visib: Visibility, is_inline: Inline) -> %&AstNodeFnProto + { + const node = %return self.allocator.create(AstNodeFnProto); + %defer self.allocator.destroy(node); + + *node = AstNodeFnProto { + .base = AstNode {.id = AstNode.Id.FnProto}, + .visib = visib, + .name_token = null, + .fn_token = *fn_token, + .params = ArrayList(&AstNode).init(self.allocator), + .return_type = null, + .var_args = VarArgs.No, + .is_extern = is_extern, + .is_inline = is_inline, + .cc = cc, + .fn_def_node = null, + .lib_name = null, + .align_expr = null, + }; + return node; + } + + fn createParamDecl(self: &Parser) -> %&AstNodeParamDecl { + const node = %return self.allocator.create(AstNodeParamDecl); + %defer self.allocator.destroy(node); + + *node = AstNodeParamDecl { + .base = AstNode {.id = AstNode.Id.ParamDecl}, + .comptime_token = null, + .noalias_token = null, + .name_token = null, + .type_node = undefined, + .var_args_token = null, + }; + return node; + } + + fn createAttachParamDecl(self: &Parser, list: &ArrayList(&AstNode)) -> %&AstNodeParamDecl { + const node = %return self.createParamDecl(); + %defer self.allocator.destroy(node); + %return list.append(&node.base); + return node; + } + + fn createAttachFnProto(self: &Parser, list: &ArrayList(&AstNode), fn_token: &const Token, + is_extern: Extern, cc: CallingConvention, visib: Visibility, is_inline: Inline) -> %&AstNodeFnProto + { + const node = %return self.createFnProto(fn_token, is_extern, cc, visib, is_inline); + %defer self.allocator.destroy(node); + %return list.append(&node.base); + return node; + } + fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) -> error { const loc = self.tokenizer.getTokenLocation(token); warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args); @@ -778,7 +1171,6 @@ const Parser = struct { self.tokenizer.next() }; } - }; @@ -841,3 +1233,11 @@ fn render(node: &AstNode, indent: usize) { render(child, indent + 2); } } + +fn removeNullCast(x: var) -> {const InnerPtr = @typeOf(x).Child.Child; &InnerPtr} { + comptime assert(@typeId(@typeOf(x)) == builtin.TypeId.Pointer); + comptime assert(@typeId(@typeOf(x).Child) == builtin.TypeId.Nullable); + comptime assert(@typeId(@typeOf(x).Child.Child) == builtin.TypeId.Pointer); + const InnerPtr = @typeOf(x).Child.Child; + return @ptrCast(&InnerPtr, x); +} diff --git a/src/analyze.cpp b/src/analyze.cpp index 431b64f984..1d5d5e4790 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2227,7 +2227,7 @@ static void resolve_union_zero_bits(CodeGen *g, TypeTableEntry *union_type) { tag_type = new_type_table_entry(TypeTableEntryIdEnum); buf_resize(&tag_type->name, 0); - buf_appendf(&tag_type->name, "@EnumTagType(%s)", buf_ptr(&union_type->name)); + buf_appendf(&tag_type->name, "@TagType(%s)", buf_ptr(&union_type->name)); tag_type->is_copyable = true; tag_type->type_ref = tag_int_type->type_ref; tag_type->zero_bits = tag_int_type->zero_bits; From 62ead3a2ee3670e2eba8a8e8fe5b2cb32630d787 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 9 Dec 2017 20:50:31 -0500 Subject: [PATCH 26/69] parsing an extern fn declaration --- src-self-hosted/main.zig | 90 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 1ba5a5a69d..db4d6f8cd9 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -481,6 +481,7 @@ const NoAlias = enum { No, Yes }; const Extern = enum { No, Yes }; const VarArgs = enum { No, Yes }; const Mutability = enum { Const, Var }; +const Volatile = enum { No, Yes }; const Inline = enum { Auto, @@ -511,6 +512,7 @@ const AstNode = struct { Identifier, FnProto, ParamDecl, + AddrOfExpr, }; fn iterate(base: &AstNode, index: usize) -> ?&AstNode { @@ -520,6 +522,7 @@ const AstNode = struct { Id.Identifier => @fieldParentPtr(AstNodeIdentifier, "base", base).iterate(index), Id.FnProto => @fieldParentPtr(AstNodeFnProto, "base", base).iterate(index), Id.ParamDecl => @fieldParentPtr(AstNodeParamDecl, "base", base).iterate(index), + Id.AddrOfExpr => @fieldParentPtr(AstNodeAddrOfExpr, "base", base).iterate(index), }; } }; @@ -643,6 +646,31 @@ const AstNodeParamDecl = struct { } }; +const AstNodeAddrOfExpr = struct { + base: AstNode, + align_expr: ?&AstNode, + op_token: Token, + bit_offset_start_token: ?Token, + bit_offset_end_token: ?Token, + const_token: ?Token, + volatile_token: ?Token, + op_expr: &AstNode, + + fn iterate(self: &AstNodeAddrOfExpr, index: usize) -> ?&AstNode { + var i = index; + + if (self.align_expr) |align_expr| { + if (i < 1) return align_expr; + i -= 1; + } + + if (i < 1) return self.op_expr; + i -= 1; + + return null; + } +}; + error ParseError; const Parser = struct { @@ -796,6 +824,7 @@ const Parser = struct { }, Token.Id.Keyword_fn => { stack.append(State.TopLevel) %% unreachable; + %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, token, Extern.Yes, CallingConvention.Auto, visib, Inline.Auto); %return stack.append(State { .FnProto = fn_proto_node }); @@ -806,6 +835,7 @@ const Parser = struct { }, Token.Id.Keyword_coldcc, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { stack.append(State.TopLevel) %% unreachable; + %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); const cc = switch (token.id) { Token.Id.Keyword_coldcc => CallingConvention.Cold, Token.Id.Keyword_nakedcc => CallingConvention.Naked, @@ -904,7 +934,7 @@ const Parser = struct { }, State.AdditionExpression => |result_ptr| { - stack.append(State {.AdditionExpression = result_ptr}) %% unreachable; + stack.append(State {.MultiplyExpression = result_ptr}) %% unreachable; continue; }, @@ -919,6 +949,27 @@ const Parser = struct { }, State.PrefixOpExpression => |result_ptr| { + const first_token = self.getNextToken(); + if (first_token.id == Token.Id.Ampersand) { + const addr_of_expr = %return self.createAttachAddrOfExpr(result_ptr, first_token); + var token = self.getNextToken(); + if (token.id == Token.Id.Keyword_align) { + @panic("TODO align"); + } + if (token.id == Token.Id.Keyword_const) { + addr_of_expr.const_token = token; + token = self.getNextToken(); + } + if (token.id == Token.Id.Keyword_volatile) { + addr_of_expr.volatile_token = token; + token = self.getNextToken(); + } + self.putBackToken(token); + stack.append(State { .PrefixOpExpression = &addr_of_expr.op_expr }) %% unreachable; + continue; + } + + self.putBackToken(first_token); stack.append(State { .SuffixOpExpression = result_ptr }) %% unreachable; continue; }, @@ -966,8 +1017,17 @@ const Parser = struct { }, State.FnProtoAlign => |fn_proto| { - @panic("TODO fn proto align"); - //continue; + const token = self.getNextToken(); + if (token.id == Token.Id.Keyword_align) { + @panic("TODO fn proto align"); + } + if (token.id == Token.Id.Arrow) { + stack.append(State { .TypeExpr = removeNullCast(&fn_proto.return_type) }) %% unreachable; + continue; + } else { + self.putBackToken(token); + continue; + } }, State.ParamDecl => |fn_proto| { @@ -1107,6 +1167,30 @@ const Parser = struct { return node; } + fn createAddrOfExpr(self: &Parser, op_token: &const Token) -> %&AstNodeAddrOfExpr { + const node = %return self.allocator.create(AstNodeAddrOfExpr); + %defer self.allocator.destroy(node); + + *node = AstNodeAddrOfExpr { + .base = AstNode {.id = AstNode.Id.AddrOfExpr}, + .align_expr = null, + .op_token = *op_token, + .bit_offset_start_token = null, + .bit_offset_end_token = null, + .const_token = null, + .volatile_token = null, + .op_expr = undefined, + }; + return node; + } + + fn createAttachAddrOfExpr(self: &Parser, result_ptr: &&AstNode, op_token: &const Token) -> %&AstNodeAddrOfExpr { + const node = %return self.createAddrOfExpr(op_token); + %defer self.allocator.destroy(node); + *result_ptr = &node.base; + return node; + } + fn createAttachParamDecl(self: &Parser, list: &ArrayList(&AstNode)) -> %&AstNodeParamDecl { const node = %return self.createParamDecl(); %defer self.allocator.destroy(node); From 990db3c35a798a6e51dbbbc1696f3fe78c7a0faf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 10 Dec 2017 15:03:57 -0500 Subject: [PATCH 27/69] rename @EnumTagType to @TagType in type names --- src/analyze.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 431b64f984..1d5d5e4790 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2227,7 +2227,7 @@ static void resolve_union_zero_bits(CodeGen *g, TypeTableEntry *union_type) { tag_type = new_type_table_entry(TypeTableEntryIdEnum); buf_resize(&tag_type->name, 0); - buf_appendf(&tag_type->name, "@EnumTagType(%s)", buf_ptr(&union_type->name)); + buf_appendf(&tag_type->name, "@TagType(%s)", buf_ptr(&union_type->name)); tag_type->is_copyable = true; tag_type->type_ref = tag_int_type->type_ref; tag_type->zero_bits = tag_int_type->zero_bits; From 22dc713a2f6b19ccba31434cfa2bc619c1a2c170 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 10 Dec 2017 15:38:05 -0500 Subject: [PATCH 28/69] mem.Allocator initializes bytes to undefined --- std/mem.zig | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/std/mem.zig b/std/mem.zig index e99675d248..2e6bd6ec85 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -8,6 +8,7 @@ pub const Cmp = math.Cmp; pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the /// slice's pointer aligned at least to alignment bytes. + /// The returned newly allocated memory is undefined. allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) -> %[]u8, /// If `new_byte_count > old_mem.len`: @@ -17,6 +18,8 @@ pub const Allocator = struct { /// If `new_byte_count <= old_mem.len`: /// * this function must return successfully. /// * alignment <= alignment of old_mem.ptr + /// + /// The returned newly allocated memory is undefined. reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) -> %[]u8, /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn` @@ -40,6 +43,10 @@ pub const Allocator = struct { { const byte_count = %return math.mul(usize, @sizeOf(T), n); const byte_slice = %return self.allocFn(self, byte_count, alignment); + // This loop should get optimized out in ReleaseFast mode + for (byte_slice) |*byte| { + *byte = undefined; + } return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } @@ -54,8 +61,13 @@ pub const Allocator = struct { return self.alloc(T, n); } + const old_byte_slice = ([]u8)(old_mem); const byte_count = %return math.mul(usize, @sizeOf(T), n); - const byte_slice = %return self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); + const byte_slice = %return self.reallocFn(self, old_byte_slice, byte_count, alignment); + // This loop should get optimized out in ReleaseFast mode + for (byte_slice[old_byte_slice.len..]) |*byte| { + *byte = undefined; + } return ([]T)(@alignCast(alignment, byte_slice)); } From dc2e3465c78411c67eb6d1efd4944b19b0b279f2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 10 Dec 2017 19:40:46 -0500 Subject: [PATCH 29/69] rendering source code without recursion --- src-self-hosted/main.zig | 383 ++++++++++++++++++++++++++++++--------- src/analyze.cpp | 2 +- std/array_list.zig | 30 ++- std/index.zig | 13 +- std/mem.zig | 13 +- 5 files changed, 326 insertions(+), 115 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index db4d6f8cd9..705281e441 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1,11 +1,14 @@ +const std = @import("std"); const builtin = @import("builtin"); -const io = @import("std").io; -const os = @import("std").os; -const heap = @import("std").heap; -const warn = @import("std").debug.warn; -const assert = @import("std").debug.assert; -const mem = @import("std").mem; -const ArrayList = @import("std").ArrayList; +const io = std.io; +const os = std.os; +const heap = std.heap; +const warn = std.debug.warn; +const assert = std.debug.assert; +const mem = std.mem; +const ArrayList = std.ArrayList; +const AlignedArrayList = std.AlignedArrayList; +const math = std.math; const Token = struct { @@ -474,6 +477,10 @@ const Tokenizer = struct { // TODO check state when returning EOF return result; } + + pub fn getTokenSlice(self: &const Tokenizer, token: &const Token) -> []const u8 { + return self.buffer[token.start..token.end]; + } }; const Comptime = enum { No, Yes }; @@ -525,6 +532,17 @@ const AstNode = struct { Id.AddrOfExpr => @fieldParentPtr(AstNodeAddrOfExpr, "base", base).iterate(index), }; } + + fn destroy(base: &AstNode, allocator: &mem.Allocator) { + return switch (base.id) { + Id.Root => allocator.destroy(@fieldParentPtr(AstNodeRoot, "base", base)), + Id.VarDecl => allocator.destroy(@fieldParentPtr(AstNodeVarDecl, "base", base)), + Id.Identifier => allocator.destroy(@fieldParentPtr(AstNodeIdentifier, "base", base)), + Id.FnProto => allocator.destroy(@fieldParentPtr(AstNodeFnProto, "base", base)), + Id.ParamDecl => allocator.destroy(@fieldParentPtr(AstNodeParamDecl, "base", base)), + Id.AddrOfExpr => allocator.destroy(@fieldParentPtr(AstNodeAddrOfExpr, "base", base)), + }; + } }; const AstNodeRoot = struct { @@ -541,7 +559,7 @@ const AstNodeRoot = struct { const AstNodeVarDecl = struct { base: AstNode, - visib: Visibility, + visib_token: ?Token, name_token: Token, eq_token: Token, mut: Mutability, @@ -585,7 +603,7 @@ const AstNodeIdentifier = struct { const AstNodeFnProto = struct { base: AstNode, - visib: Visibility, + visib_token: ?Token, fn_token: Token, name_token: ?Token, params: ArrayList(&AstNode), @@ -648,8 +666,8 @@ const AstNodeParamDecl = struct { const AstNodeAddrOfExpr = struct { base: AstNode, - align_expr: ?&AstNode, op_token: Token, + align_expr: ?&AstNode, bit_offset_start_token: ?Token, bit_offset_end_token: ?Token, const_token: ?Token, @@ -674,26 +692,47 @@ const AstNodeAddrOfExpr = struct { error ParseError; const Parser = struct { - tokenizer: &Tokenizer, allocator: &mem.Allocator, + tokenizer: &Tokenizer, put_back_tokens: [2]Token, put_back_count: usize, source_file_name: []const u8, - fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator, source_file_name: []const u8) -> Parser { + // This memory contents are used only during a function call. It's used to repurpose memory; + // specifically so that freeAst can be guaranteed to succeed. + const utility_bytes_align = @alignOf( union { a: RenderAstFrame, b: State, c: RenderState } ); + utility_bytes: []align(utility_bytes_align) u8, + + fn initUtilityArrayList(self: &Parser, comptime T: type) -> ArrayList(T) { + const new_byte_count = self.utility_bytes.len - self.utility_bytes.len % @sizeOf(T); + self.utility_bytes = self.allocator.alignedShrink(u8, utility_bytes_align, self.utility_bytes, new_byte_count); + const typed_slice = ([]T)(self.utility_bytes); + return ArrayList(T).fromOwnedSlice(self.allocator, typed_slice); + } + + fn deinitUtilityArrayList(self: &Parser, list: var) { + self.utility_bytes = ([]align(utility_bytes_align) u8)(list.toOwnedSlice()); + } + + pub fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator, source_file_name: []const u8) -> Parser { return Parser { - .tokenizer = tokenizer, .allocator = allocator, + .tokenizer = tokenizer, .put_back_tokens = undefined, .put_back_count = 0, .source_file_name = source_file_name, + .utility_bytes = []align(utility_bytes_align) u8{}, }; } + pub fn deinit(self: &Parser) { + self.allocator.free(self.utility_bytes); + } + const State = union(enum) { - TopLevel, - TopLevelModifier: Visibility, - TopLevelExtern: Visibility, + TopLevel, + TopLevelModifier: ?Token, + TopLevelExtern: ?Token, Expression: &&AstNode, GroupedExpression: &&AstNode, UnwrapExpression: &&AstNode, @@ -721,14 +760,36 @@ const Parser = struct { ParamDeclComma, }; - pub fn parse(self: &Parser) -> %&AstNode { - var stack = ArrayList(State).init(self.allocator); - defer stack.deinit(); + pub fn freeAst(self: &Parser, root_node: &AstNodeRoot) { + // utility_bytes is big enough to do this iteration since we were able to do + // the parsing in the first place + comptime assert(@sizeOf(State) >= @sizeOf(&AstNode)); - %return stack.append(State.TopLevel); + var stack = self.initUtilityArrayList(&AstNode); + defer self.deinitUtilityArrayList(stack); + + stack.append(&root_node.base) %% unreachable; + while (stack.popOrNull()) |node| { + var i: usize = 0; + while (node.iterate(i)) |child| : (i += 1) { + if (child.iterate(0) != null) { + stack.append(child) %% unreachable; + } else { + child.destroy(self.allocator); + } + } + node.destroy(self.allocator); + } + } + + pub fn parse(self: &Parser) -> %&AstNodeRoot { + var stack = self.initUtilityArrayList(State); + defer self.deinitUtilityArrayList(stack); const root_node = %return self.createRoot(); - // TODO %defer self.freeAst(); + %defer self.allocator.destroy(root_node); + %return stack.append(State.TopLevel); + %defer self.freeAst(root_node); while (true) { // This gives us 1 free append that can't fail @@ -738,95 +799,74 @@ const Parser = struct { State.TopLevel => { const token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_pub => { - stack.append(State {.TopLevelModifier = Visibility.Pub }) %% unreachable; - continue; - }, - Token.Id.Keyword_export => { - stack.append(State {.TopLevelModifier = Visibility.Export }) %% unreachable; + Token.Id.Keyword_pub, Token.Id.Keyword_export => { + stack.append(State { .TopLevelModifier = token }) %% unreachable; continue; }, Token.Id.Keyword_const => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = { - const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Const, Comptime.No, Extern.No); - %defer self.allocator.destroy(var_decl_node); - %return root_node.decls.append(&var_decl_node.base); - var_decl_node - }; + // TODO shouldn't need this cast + const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, (?Token)(null), + Mutability.Const, Comptime.No, Extern.No); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_var => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = { - const var_decl_node = %return self.createVarDecl(Visibility.Private, Mutability.Var, Comptime.No, Extern.No); - %defer self.allocator.destroy(var_decl_node); - %return root_node.decls.append(&var_decl_node.base); - var_decl_node - }; + // TODO shouldn't need this cast + const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, (?Token)(null), + Mutability.Var, Comptime.No, Extern.No); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, - Token.Id.Eof => return &root_node.base, + Token.Id.Eof => return root_node, Token.Id.Keyword_extern => { - stack.append(State { .TopLevelExtern = Visibility.Private }) %% unreachable; + stack.append(State { .TopLevelExtern = null }) %% unreachable; continue; }, else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), } }, - State.TopLevelModifier => |visib| { + State.TopLevelModifier => |visib_token| { const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_const => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = { - const var_decl_node = %return self.createVarDecl(visib, Mutability.Const, Comptime.No, Extern.No); - %defer self.allocator.destroy(var_decl_node); - %return root_node.decls.append(&var_decl_node.base); - var_decl_node - }; + const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, visib_token, + Mutability.Const, Comptime.No, Extern.No); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_var => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = { - const var_decl_node = %return self.createVarDecl(visib, Mutability.Var, Comptime.No, Extern.No); - %defer self.allocator.destroy(var_decl_node); - %return root_node.decls.append(&var_decl_node.base); - var_decl_node - }; + const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, visib_token, + Mutability.Var, Comptime.No, Extern.No); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_extern => { - stack.append(State { .TopLevelExtern = visib }) %% unreachable; + stack.append(State { .TopLevelExtern = visib_token }) %% unreachable; continue; }, else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), } }, - State.TopLevelExtern => |visib| { + State.TopLevelExtern => |visib_token| { const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_var => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = { - const var_decl_node = %return self.createVarDecl(visib, Mutability.Var, Comptime.No, Extern.Yes); - %defer self.allocator.destroy(var_decl_node); - %return root_node.decls.append(&var_decl_node.base); - var_decl_node - }; + const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, visib_token, + Mutability.Var, Comptime.No, Extern.Yes); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_fn => { stack.append(State.TopLevel) %% unreachable; %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); + // TODO shouldn't need this cast const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, token, - Extern.Yes, CallingConvention.Auto, visib, Inline.Auto); + Extern.Yes, CallingConvention.Auto, (?Token)(null), Inline.Auto); %return stack.append(State { .FnProto = fn_proto_node }); continue; }, @@ -843,8 +883,9 @@ const Parser = struct { else => unreachable, }; const fn_token = %return self.eatToken(Token.Id.Keyword_fn); + // TODO shouldn't need this cast const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, fn_token, - Extern.Yes, cc, visib, Inline.Auto); + Extern.Yes, cc, (?Token)(null), Inline.Auto); %return stack.append(State { .FnProto = fn_proto_node }); continue; }, @@ -1054,6 +1095,8 @@ const Parser = struct { } if (token.id == Token.Id.Ellipsis3) { param_decl.var_args_token = token; + stack.append(State { .ExpectToken = Token.Id.RParen }) %% unreachable; + continue; } else { self.putBackToken(token); } @@ -1094,7 +1137,7 @@ const Parser = struct { return node; } - fn createVarDecl(self: &Parser, visib: Visibility, mut: Mutability, is_comptime: Comptime, + fn createVarDecl(self: &Parser, visib_token: &const ?Token, mut: Mutability, is_comptime: Comptime, is_extern: Extern) -> %&AstNodeVarDecl { const node = %return self.allocator.create(AstNodeVarDecl); @@ -1102,7 +1145,7 @@ const Parser = struct { *node = AstNodeVarDecl { .base = AstNode {.id = AstNode.Id.VarDecl}, - .visib = visib, + .visib_token = *visib_token, .mut = mut, .is_comptime = is_comptime, .is_extern = is_extern, @@ -1129,14 +1172,14 @@ const Parser = struct { } fn createFnProto(self: &Parser, fn_token: &const Token, is_extern: Extern, - cc: CallingConvention, visib: Visibility, is_inline: Inline) -> %&AstNodeFnProto + cc: CallingConvention, visib_token: &const ?Token, is_inline: Inline) -> %&AstNodeFnProto { const node = %return self.allocator.create(AstNodeFnProto); %defer self.allocator.destroy(node); *node = AstNodeFnProto { .base = AstNode {.id = AstNode.Id.FnProto}, - .visib = visib, + .visib_token = *visib_token, .name_token = null, .fn_token = *fn_token, .params = ArrayList(&AstNode).init(self.allocator), @@ -1199,9 +1242,18 @@ const Parser = struct { } fn createAttachFnProto(self: &Parser, list: &ArrayList(&AstNode), fn_token: &const Token, - is_extern: Extern, cc: CallingConvention, visib: Visibility, is_inline: Inline) -> %&AstNodeFnProto + is_extern: Extern, cc: CallingConvention, visib_token: &const ?Token, is_inline: Inline) -> %&AstNodeFnProto { - const node = %return self.createFnProto(fn_token, is_extern, cc, visib, is_inline); + const node = %return self.createFnProto(fn_token, is_extern, cc, visib_token, is_inline); + %defer self.allocator.destroy(node); + %return list.append(&node.base); + return node; + } + + fn createAttachVarDecl(self: &Parser, list: &ArrayList(&AstNode), visib_token: &const ?Token, mut: Mutability, + is_comptime: Comptime, is_extern: Extern) -> %&AstNodeVarDecl + { + const node = %return self.createVarDecl(visib_token, mut, is_comptime, is_extern); %defer self.allocator.destroy(node); %return list.append(&node.base); return node; @@ -1255,8 +1307,171 @@ const Parser = struct { self.tokenizer.next() }; } -}; + const RenderAstFrame = struct { + node: &AstNode, + indent: usize, + }; + + pub fn renderAst(self: &Parser, stream: &std.io.OutStream, root_node: &AstNodeRoot) -> %void { + var stack = self.initUtilityArrayList(RenderAstFrame); + defer self.deinitUtilityArrayList(stack); + + %return stack.append(RenderAstFrame { + .node = &root_node.base, + .indent = 0, + }); + + while (stack.popOrNull()) |frame| { + { + var i: usize = 0; + while (i < frame.indent) : (i += 1) { + %return stream.print(" "); + } + } + %return stream.print("{}\n", @tagName(frame.node.id)); + var child_i: usize = 0; + while (frame.node.iterate(child_i)) |child| : (child_i += 1) { + %return stack.append(RenderAstFrame { + .node = child, + .indent = frame.indent + 2, + }); + } + } + } + + + pub const RenderState = union(enum) { + TopLevelDecl: &AstNode, + FnProtoRParen: &AstNodeFnProto, + ParamDecl: &AstNode, + Text: []const u8, + Expression: &AstNode, + AddrOfExprBit: &AstNodeAddrOfExpr, + }; + + pub fn renderSource(self: &Parser, stream: &std.io.OutStream, root_node: &AstNodeRoot) -> %void { + var stack = self.initUtilityArrayList(RenderState); + defer self.deinitUtilityArrayList(stack); + + { + var i = root_node.decls.len; + while (i != 0) { + i -= 1; + const decl = root_node.decls.items[i]; + %return stack.append(RenderState {.TopLevelDecl = decl}); + } + } + + while (stack.popOrNull()) |state| { + switch (state) { + RenderState.TopLevelDecl => |decl| { + switch (decl.id) { + AstNode.Id.FnProto => { + const fn_proto = @fieldParentPtr(AstNodeFnProto, "base", decl); + if (fn_proto.visib_token) |visib_token| { + switch (visib_token.id) { + Token.Id.Keyword_pub => %return stream.print("pub "), + Token.Id.Keyword_export => %return stream.print("export "), + else => unreachable, + }; + } + if (fn_proto.is_extern == Extern.Yes) { + %return stream.print("extern "); + } + %return stream.print("fn"); + + if (fn_proto.name_token) |name_token| { + %return stream.print(" {}", self.tokenizer.getTokenSlice(name_token)); + } + + %return stream.print("("); + + if (fn_proto.fn_def_node == null) { + %return stack.append(RenderState { .Text = ";" }); + } + + %return stack.append(RenderState { .FnProtoRParen = fn_proto}); + var i = fn_proto.params.len; + while (i != 0) { + i -= 1; + const param_decl_node = fn_proto.params.items[i]; + %return stack.append(RenderState { .ParamDecl = param_decl_node}); + if (i != 0) { + %return stack.append(RenderState { .Text = ", " }); + } + } + }, + else => unreachable, + } + }, + RenderState.ParamDecl => |base| { + const param_decl = @fieldParentPtr(AstNodeParamDecl, "base", base); + if (param_decl.comptime_token) |comptime_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); + } + if (param_decl.noalias_token) |noalias_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(noalias_token)); + } + if (param_decl.name_token) |name_token| { + %return stream.print("{}: ", self.tokenizer.getTokenSlice(name_token)); + } + if (param_decl.var_args_token) |var_args_token| { + %return stream.print("{}", self.tokenizer.getTokenSlice(var_args_token)); + } else { + %return stack.append(RenderState { .Expression = param_decl.type_node}); + } + }, + RenderState.Text => |bytes| { + %return stream.write(bytes); + }, + RenderState.Expression => |base| switch (base.id) { + AstNode.Id.Identifier => { + const identifier = @fieldParentPtr(AstNodeIdentifier, "base", base); + %return stream.print("{}", self.tokenizer.getTokenSlice(identifier.name_token)); + }, + AstNode.Id.AddrOfExpr => { + const addr_of_expr = @fieldParentPtr(AstNodeAddrOfExpr, "base", base); + %return stream.print("{}", self.tokenizer.getTokenSlice(addr_of_expr.op_token)); + %return stack.append(RenderState { .AddrOfExprBit = addr_of_expr}); + + if (addr_of_expr.align_expr) |align_expr| { + %return stream.print("align("); + %return stack.append(RenderState { .Text = ")"}); + %return stack.append(RenderState { .Expression = align_expr}); + } + }, + else => unreachable, + }, + RenderState.AddrOfExprBit => |addr_of_expr| { + if (addr_of_expr.bit_offset_start_token) |bit_offset_start_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_start_token)); + } + if (addr_of_expr.bit_offset_end_token) |bit_offset_end_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_end_token)); + } + if (addr_of_expr.const_token) |const_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(const_token)); + } + if (addr_of_expr.volatile_token) |volatile_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(volatile_token)); + } + %return stack.append(RenderState { .Expression = addr_of_expr.op_expr}); + }, + RenderState.FnProtoRParen => |fn_proto| { + %return stream.print(")"); + if (fn_proto.align_expr != null) { + @panic("TODO"); + } + if (fn_proto.return_type) |return_type| { + %return stream.print(" -> "); + %return stack.append(RenderState { .Expression = return_type}); + } + }, + } + } + } +}; pub fn main() -> %void { main2() %% |err| { @@ -1277,6 +1492,11 @@ pub fn main2() -> %void { const target_file = args[1]; const target_file_buf = %return io.readFileAlloc(target_file, allocator); + defer allocator.free(target_file_buf); + + var stderr_file = %return std.io.getStdErr(); + var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file); + const out_stream = &stderr_file_out_stream.stream; warn("====input:====\n"); @@ -1298,24 +1518,15 @@ pub fn main2() -> %void { var tokenizer = Tokenizer.init(target_file_buf); var parser = Parser.init(&tokenizer, allocator, target_file); - const node = %return parser.parse(); + defer parser.deinit(); + const root_node = %return parser.parse(); + defer parser.freeAst(root_node); - render(node, 0); -} + %return parser.renderAst(out_stream, root_node); -fn render(node: &AstNode, indent: usize) { - { - var i: usize = 0; - while (i < indent) : (i += 1) { - warn(" "); - } - } - warn("{}\n", @tagName(node.id)); - var i: usize = 0; - while (node.iterate(i)) |child| : (i += 1) { - render(child, indent + 2); - } + warn("====fmt:====\n"); + %return parser.renderSource(out_stream, root_node); } fn removeNullCast(x: var) -> {const InnerPtr = @typeOf(x).Child.Child; &InnerPtr} { diff --git a/src/analyze.cpp b/src/analyze.cpp index 1d5d5e4790..431b64f984 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2227,7 +2227,7 @@ static void resolve_union_zero_bits(CodeGen *g, TypeTableEntry *union_type) { tag_type = new_type_table_entry(TypeTableEntryIdEnum); buf_resize(&tag_type->name, 0); - buf_appendf(&tag_type->name, "@TagType(%s)", buf_ptr(&union_type->name)); + buf_appendf(&tag_type->name, "@EnumTagType(%s)", buf_ptr(&union_type->name)); tag_type->is_copyable = true; tag_type->type_ref = tag_int_type->type_ref; tag_type->zero_bits = tag_int_type->zero_bits; diff --git a/std/array_list.zig b/std/array_list.zig index 65b7e023e2..5d043075c3 100644 --- a/std/array_list.zig +++ b/std/array_list.zig @@ -3,21 +3,25 @@ const assert = debug.assert; const mem = @import("mem.zig"); const Allocator = mem.Allocator; -pub fn ArrayList(comptime T: type) -> type{ +pub fn ArrayList(comptime T: type) -> type { + return AlignedArrayList(T, @alignOf(T)); +} + +pub fn AlignedArrayList(comptime T: type, comptime A: u29) -> type{ struct { const Self = this; /// Use toSlice instead of slicing this directly, because if you don't /// specify the end position of the slice, this will potentially give /// you uninitialized memory. - items: []T, + items: []align(A) T, len: usize, allocator: &Allocator, /// Deinitialize with `deinit` or use `toOwnedSlice`. pub fn init(allocator: &Allocator) -> Self { Self { - .items = []T{}, + .items = []align(A) T{}, .len = 0, .allocator = allocator, } @@ -27,18 +31,18 @@ pub fn ArrayList(comptime T: type) -> type{ l.allocator.free(l.items); } - pub fn toSlice(l: &Self) -> []T { + pub fn toSlice(l: &Self) -> []align(A) T { return l.items[0..l.len]; } - pub fn toSliceConst(l: &const Self) -> []const T { + pub fn toSliceConst(l: &const Self) -> []align(A) const T { return l.items[0..l.len]; } /// ArrayList takes ownership of the passed in slice. The slice must have been /// allocated with `allocator`. /// Deinitialize with `deinit` or use `toOwnedSlice`. - pub fn fromOwnedSlice(allocator: &Allocator, slice: []T) -> Self { + pub fn fromOwnedSlice(allocator: &Allocator, slice: []align(A) T) -> Self { return Self { .items = slice, .len = slice.len, @@ -47,9 +51,9 @@ pub fn ArrayList(comptime T: type) -> type{ } /// The caller owns the returned memory. ArrayList becomes empty. - pub fn toOwnedSlice(self: &Self) -> []T { + pub fn toOwnedSlice(self: &Self) -> []align(A) T { const allocator = self.allocator; - const result = allocator.shrink(T, self.items, self.len); + const result = allocator.alignedShrink(T, A, self.items, self.len); *self = init(allocator); return result; } @@ -59,7 +63,7 @@ pub fn ArrayList(comptime T: type) -> type{ *new_item_ptr = *item; } - pub fn appendSlice(l: &Self, items: []const T) -> %void { + pub fn appendSlice(l: &Self, items: []align(A) const T) -> %void { %return l.ensureCapacity(l.len + items.len); mem.copy(T, l.items[l.len..], items); l.len += items.len; @@ -82,7 +86,7 @@ pub fn ArrayList(comptime T: type) -> type{ better_capacity += better_capacity / 2 + 8; if (better_capacity >= new_capacity) break; } - l.items = %return l.allocator.realloc(T, l.items, better_capacity); + l.items = %return l.allocator.alignedRealloc(T, A, l.items, better_capacity); } pub fn addOne(l: &Self) -> %&T { @@ -97,6 +101,12 @@ pub fn ArrayList(comptime T: type) -> type{ self.len -= 1; return self.items[self.len]; } + + pub fn popOrNull(self: &Self) -> ?T { + if (self.len == 0) + return null; + return self.pop(); + } } } diff --git a/std/index.zig b/std/index.zig index 8033eee142..13ae25f914 100644 --- a/std/index.zig +++ b/std/index.zig @@ -1,4 +1,5 @@ pub const ArrayList = @import("array_list.zig").ArrayList; +pub const AlignedArrayList = @import("array_list.zig").AlignedArrayList; pub const BufMap = @import("buf_map.zig").BufMap; pub const BufSet = @import("buf_set.zig").BufSet; pub const Buffer = @import("buffer.zig").Buffer; @@ -26,12 +27,12 @@ pub const sort = @import("sort.zig"); test "std" { // run tests from these - _ = @import("array_list.zig").ArrayList; - _ = @import("buf_map.zig").BufMap; - _ = @import("buf_set.zig").BufSet; - _ = @import("buffer.zig").Buffer; - _ = @import("hash_map.zig").HashMap; - _ = @import("linked_list.zig").LinkedList; + _ = @import("array_list.zig"); + _ = @import("buf_map.zig"); + _ = @import("buf_set.zig"); + _ = @import("buffer.zig"); + _ = @import("hash_map.zig"); + _ = @import("linked_list.zig"); _ = @import("base64.zig"); _ = @import("build.zig"); diff --git a/std/mem.zig b/std/mem.zig index b62451687f..b1e7a608b2 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -8,7 +8,6 @@ pub const Cmp = math.Cmp; pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the /// slice's pointer aligned at least to alignment bytes. - /// The returned newly allocated memory is undefined. allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) -> %[]u8, /// If `new_byte_count > old_mem.len`: @@ -18,8 +17,6 @@ pub const Allocator = struct { /// If `new_byte_count <= old_mem.len`: /// * this function must return successfully. /// * alignment <= alignment of old_mem.ptr - /// - /// The returned newly allocated memory is undefined. reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) -> %[]u8, /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn` @@ -43,10 +40,6 @@ pub const Allocator = struct { { const byte_count = %return math.mul(usize, @sizeOf(T), n); const byte_slice = %return self.allocFn(self, byte_count, alignment); - // This loop should get optimized out in ReleaseFast mode - for (byte_slice) |*byte| { - *byte = undefined; - } return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } @@ -63,10 +56,6 @@ pub const Allocator = struct { const byte_count = %return math.mul(usize, @sizeOf(T), n); const byte_slice = %return self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); - // This loop should get optimized out in ReleaseFast mode - for (byte_slice[old_mem.len..]) |*byte| { - *byte = undefined; - } return ([]T)(@alignCast(alignment, byte_slice)); } @@ -92,7 +81,7 @@ pub const Allocator = struct { const byte_count = @sizeOf(T) * n; const byte_slice = %%self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); - return ([]T)(@alignCast(alignment, byte_slice)); + return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } fn free(self: &Allocator, memory: var) { From f210f17d306ed1298901d38a29c1e50a1ee38ae5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 10 Dec 2017 21:26:28 -0500 Subject: [PATCH 30/69] add self-hosted parsing and rendering to main tests --- build.zig | 4 ++++ src-self-hosted/main.zig | 35 +++++++++++++++++++++++++++++++++++ std/buffer.zig | 3 +++ std/debug.zig | 15 ++++++++------- std/index.zig | 1 + std/io.zig | 21 +++++++++++++++++++++ 6 files changed, 72 insertions(+), 7 deletions(-) diff --git a/build.zig b/build.zig index 1c1d4f832e..84c3b05eda 100644 --- a/build.zig +++ b/build.zig @@ -53,6 +53,10 @@ pub fn build(b: &Builder) { "std/special/compiler_rt/index.zig", "compiler-rt", "Run the compiler_rt tests", with_lldb)); + test_step.dependOn(tests.addPkgTests(b, test_filter, + "src-self-hosted/main.zig", "fmt", "Run the fmt tests", + with_lldb)); + test_step.dependOn(tests.addCompareOutputTests(b, test_filter)); test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 705281e441..209ee829e5 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1387,6 +1387,7 @@ const Parser = struct { %return stream.print("("); + %return stack.append(RenderState { .Text = "\n" }); if (fn_proto.fn_def_node == null) { %return stack.append(RenderState { .Text = ";" }); } @@ -1536,3 +1537,37 @@ fn removeNullCast(x: var) -> {const InnerPtr = @typeOf(x).Child.Child; &InnerPtr const InnerPtr = @typeOf(x).Child.Child; return @ptrCast(&InnerPtr, x); } + + + +fn testCanonical(source: []const u8) { + const allocator = std.debug.global_allocator; + std.debug.global_allocator_index = 0; + + var tokenizer = Tokenizer.init(source); + var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); + defer parser.deinit(); + + const root_node = parser.parse() %% unreachable; + defer parser.freeAst(root_node); + + var buffer = std.Buffer.initSize(allocator, 0) %% unreachable; + var buffer_out_stream = io.BufferOutStream.init(&buffer); + parser.renderSource(&buffer_out_stream.stream, root_node) %% unreachable; + + if (!mem.eql(u8, buffer.toSliceConst(), source)) { + warn("\n====== expected this output: =========\n"); + warn("{}", source); + warn("\n======== instead found this: =========\n"); + warn("{}", buffer.toSliceConst()); + warn("\n======================================\n"); + @panic("test failed"); + } +} + +test "zig fmt" { + testCanonical( + \\extern fn puts(s: &const u8) -> c_int; + \\ + ); +} diff --git a/std/buffer.zig b/std/buffer.zig index 96abaeb762..73a38fff5b 100644 --- a/std/buffer.zig +++ b/std/buffer.zig @@ -98,14 +98,17 @@ pub const Buffer = struct { mem.copy(u8, self.list.toSlice()[old_len..], m); } + // TODO: remove, use OutStream for this pub fn appendFormat(self: &Buffer, comptime format: []const u8, args: ...) -> %void { return fmt.format(self, append, format, args); } + // TODO: remove, use OutStream for this pub fn appendByte(self: &Buffer, byte: u8) -> %void { return self.appendByteNTimes(byte, 1); } + // TODO: remove, use OutStream for this pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) -> %void { var prev_size: usize = self.len(); %return self.resize(prev_size + count); diff --git a/std/debug.zig b/std/debug.zig index c520e98b15..3e7a38d043 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -966,28 +966,29 @@ fn readILeb128(in_stream: &io.InStream) -> %i64 { } pub const global_allocator = &global_allocator_state; + var global_allocator_state = mem.Allocator { .allocFn = globalAlloc, .reallocFn = globalRealloc, .freeFn = globalFree, }; -var some_mem: [100 * 1024]u8 = undefined; -var some_mem_index: usize = 0; +pub var global_allocator_mem: [100 * 1024]u8 = undefined; +pub var global_allocator_index: usize = 0; error OutOfMemory; fn globalAlloc(self: &mem.Allocator, n: usize, alignment: u29) -> %[]u8 { - const addr = @ptrToInt(&some_mem[some_mem_index]); + const addr = @ptrToInt(&global_allocator_mem[global_allocator_index]); const rem = @rem(addr, alignment); const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); - const adjusted_index = some_mem_index + march_forward_bytes; + const adjusted_index = global_allocator_index + march_forward_bytes; const end_index = adjusted_index + n; - if (end_index > some_mem.len) { + if (end_index > global_allocator_mem.len) { return error.OutOfMemory; } - const result = some_mem[adjusted_index .. end_index]; - some_mem_index = end_index; + const result = global_allocator_mem[adjusted_index .. end_index]; + global_allocator_index = end_index; return result; } diff --git a/std/index.zig b/std/index.zig index 13ae25f914..323eee203e 100644 --- a/std/index.zig +++ b/std/index.zig @@ -3,6 +3,7 @@ pub const AlignedArrayList = @import("array_list.zig").AlignedArrayList; pub const BufMap = @import("buf_map.zig").BufMap; pub const BufSet = @import("buf_set.zig").BufSet; pub const Buffer = @import("buffer.zig").Buffer; +pub const BufferOutStream = @import("buffer.zig").BufferOutStream; pub const HashMap = @import("hash_map.zig").HashMap; pub const LinkedList = @import("linked_list.zig").LinkedList; diff --git a/std/io.zig b/std/io.zig index 87a46b05eb..b677e457f7 100644 --- a/std/io.zig +++ b/std/io.zig @@ -633,3 +633,24 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize) -> type { } }; } + +/// Implementation of OutStream trait for Buffer +pub const BufferOutStream = struct { + buffer: &Buffer, + stream: OutStream, + + pub fn init(buffer: &Buffer) -> BufferOutStream { + return BufferOutStream { + .buffer = buffer, + .stream = OutStream { + .writeFn = writeFn, + }, + }; + } + + fn writeFn(out_stream: &OutStream, bytes: []const u8) -> %void { + const self = @fieldParentPtr(BufferOutStream, "stream", out_stream); + return self.buffer.append(bytes); + } +}; + From 53d58684a6cb09f6c8463b162e5c059a45ca56eb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 10 Dec 2017 22:44:04 -0500 Subject: [PATCH 31/69] self-hosted: parse var decls --- src-self-hosted/main.zig | 149 +++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 60 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 209ee829e5..e3026a2968 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -562,9 +562,9 @@ const AstNodeVarDecl = struct { visib_token: ?Token, name_token: Token, eq_token: Token, - mut: Mutability, - is_comptime: Comptime, - is_extern: Extern, + mut_token: Token, + comptime_token: ?Token, + extern_token: ?Token, lib_name: ?&AstNode, type_node: ?&AstNode, align_node: ?&AstNode, @@ -608,10 +608,10 @@ const AstNodeFnProto = struct { name_token: ?Token, params: ArrayList(&AstNode), return_type: ?&AstNode, - var_args: VarArgs, - is_extern: Extern, - is_inline: Inline, - cc: CallingConvention, + var_args_token: ?Token, + extern_token: ?Token, + inline_token: ?Token, + cc_token: ?Token, fn_def_node: ?&AstNode, lib_name: ?&AstNode, // populated if this is an extern declaration align_expr: ?&AstNode, // populated if align(A) is present @@ -729,10 +729,15 @@ const Parser = struct { self.allocator.free(self.utility_bytes); } + const TopLevelExternCtx = struct { + visib_token: ?Token, + extern_token: Token, + }; + const State = union(enum) { TopLevel, TopLevelModifier: ?Token, - TopLevelExtern: ?Token, + TopLevelExtern: TopLevelExternCtx, Expression: &&AstNode, GroupedExpression: &&AstNode, UnwrapExpression: &&AstNode, @@ -803,25 +808,22 @@ const Parser = struct { stack.append(State { .TopLevelModifier = token }) %% unreachable; continue; }, - Token.Id.Keyword_const => { + Token.Id.Keyword_const, Token.Id.Keyword_var => { stack.append(State.TopLevel) %% unreachable; // TODO shouldn't need this cast const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, (?Token)(null), - Mutability.Const, Comptime.No, Extern.No); - %return stack.append(State { .VarDecl = var_decl_node }); - continue; - }, - Token.Id.Keyword_var => { - stack.append(State.TopLevel) %% unreachable; - // TODO shouldn't need this cast - const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, (?Token)(null), - Mutability.Var, Comptime.No, Extern.No); + token, (?Token)(null), (?Token)(null)); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Eof => return root_node, Token.Id.Keyword_extern => { - stack.append(State { .TopLevelExtern = null }) %% unreachable; + stack.append(State { + .TopLevelExtern = TopLevelExternCtx { + .visib_token = null, + .extern_token = token, + } + }) %% unreachable; continue; }, else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), @@ -830,43 +832,43 @@ const Parser = struct { State.TopLevelModifier => |visib_token| { const token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_const => { + Token.Id.Keyword_const, Token.Id.Keyword_var => { stack.append(State.TopLevel) %% unreachable; + // TODO shouldn't need the casts here const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, visib_token, - Mutability.Const, Comptime.No, Extern.No); - %return stack.append(State { .VarDecl = var_decl_node }); - continue; - }, - Token.Id.Keyword_var => { - stack.append(State.TopLevel) %% unreachable; - const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, visib_token, - Mutability.Var, Comptime.No, Extern.No); + token, (?Token)(null), (?Token)(null)); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_extern => { - stack.append(State { .TopLevelExtern = visib_token }) %% unreachable; + stack.append(State { + .TopLevelExtern = TopLevelExternCtx { + .visib_token = visib_token, + .extern_token = token, + }, + }) %% unreachable; continue; }, else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), } }, - State.TopLevelExtern => |visib_token| { + State.TopLevelExtern => |ctx| { const token = self.getNextToken(); switch (token.id) { - Token.Id.Keyword_var => { + Token.Id.Keyword_var, Token.Id.Keyword_const => { stack.append(State.TopLevel) %% unreachable; - const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, visib_token, - Mutability.Var, Comptime.No, Extern.Yes); + // TODO shouldn't need these casts + const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, ctx.visib_token, + token, (?Token)(null), (?Token)(ctx.extern_token)); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, Token.Id.Keyword_fn => { stack.append(State.TopLevel) %% unreachable; %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); - // TODO shouldn't need this cast + // TODO shouldn't need these casts const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, token, - Extern.Yes, CallingConvention.Auto, (?Token)(null), Inline.Auto); + (?Token)(ctx.extern_token), (?Token)(null), (?Token)(null), (?Token)(null)); %return stack.append(State { .FnProto = fn_proto_node }); continue; }, @@ -876,16 +878,10 @@ const Parser = struct { Token.Id.Keyword_coldcc, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { stack.append(State.TopLevel) %% unreachable; %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); - const cc = switch (token.id) { - Token.Id.Keyword_coldcc => CallingConvention.Cold, - Token.Id.Keyword_nakedcc => CallingConvention.Naked, - Token.Id.Keyword_stdcallcc => CallingConvention.Stdcall, - else => unreachable, - }; const fn_token = %return self.eatToken(Token.Id.Keyword_fn); // TODO shouldn't need this cast const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, fn_token, - Extern.Yes, cc, (?Token)(null), Inline.Auto); + (?Token)(ctx.extern_token), (?Token)(token), (?Token)(null), (?Token)(null)); %return stack.append(State { .FnProto = fn_proto_node }); continue; }, @@ -1137,8 +1133,8 @@ const Parser = struct { return node; } - fn createVarDecl(self: &Parser, visib_token: &const ?Token, mut: Mutability, is_comptime: Comptime, - is_extern: Extern) -> %&AstNodeVarDecl + fn createVarDecl(self: &Parser, visib_token: &const ?Token, mut_token: &const Token, comptime_token: &const ?Token, + extern_token: &const ?Token) -> %&AstNodeVarDecl { const node = %return self.allocator.create(AstNodeVarDecl); %defer self.allocator.destroy(node); @@ -1146,9 +1142,9 @@ const Parser = struct { *node = AstNodeVarDecl { .base = AstNode {.id = AstNode.Id.VarDecl}, .visib_token = *visib_token, - .mut = mut, - .is_comptime = is_comptime, - .is_extern = is_extern, + .mut_token = *mut_token, + .comptime_token = *comptime_token, + .extern_token = *extern_token, .type_node = null, .align_node = null, .init_node = null, @@ -1171,8 +1167,8 @@ const Parser = struct { return node; } - fn createFnProto(self: &Parser, fn_token: &const Token, is_extern: Extern, - cc: CallingConvention, visib_token: &const ?Token, is_inline: Inline) -> %&AstNodeFnProto + fn createFnProto(self: &Parser, fn_token: &const Token, extern_token: &const ?Token, + cc_token: &const ?Token, visib_token: &const ?Token, inline_token: &const ?Token) -> %&AstNodeFnProto { const node = %return self.allocator.create(AstNodeFnProto); %defer self.allocator.destroy(node); @@ -1184,10 +1180,10 @@ const Parser = struct { .fn_token = *fn_token, .params = ArrayList(&AstNode).init(self.allocator), .return_type = null, - .var_args = VarArgs.No, - .is_extern = is_extern, - .is_inline = is_inline, - .cc = cc, + .var_args_token = null, + .extern_token = *extern_token, + .inline_token = *inline_token, + .cc_token = *cc_token, .fn_def_node = null, .lib_name = null, .align_expr = null, @@ -1242,18 +1238,19 @@ const Parser = struct { } fn createAttachFnProto(self: &Parser, list: &ArrayList(&AstNode), fn_token: &const Token, - is_extern: Extern, cc: CallingConvention, visib_token: &const ?Token, is_inline: Inline) -> %&AstNodeFnProto + extern_token: &const ?Token, cc_token: &const ?Token, visib_token: &const ?Token, + inline_token: &const ?Token) -> %&AstNodeFnProto { - const node = %return self.createFnProto(fn_token, is_extern, cc, visib_token, is_inline); + const node = %return self.createFnProto(fn_token, extern_token, cc_token, visib_token, inline_token); %defer self.allocator.destroy(node); %return list.append(&node.base); return node; } - fn createAttachVarDecl(self: &Parser, list: &ArrayList(&AstNode), visib_token: &const ?Token, mut: Mutability, - is_comptime: Comptime, is_extern: Extern) -> %&AstNodeVarDecl + fn createAttachVarDecl(self: &Parser, list: &ArrayList(&AstNode), visib_token: &const ?Token, + mut_token: &const Token, comptime_token: &const ?Token, extern_token: &const ?Token) -> %&AstNodeVarDecl { - const node = %return self.createVarDecl(visib_token, mut, is_comptime, is_extern); + const node = %return self.createVarDecl(visib_token, mut_token, comptime_token, extern_token); %defer self.allocator.destroy(node); %return list.append(&node.base); return node; @@ -1376,8 +1373,8 @@ const Parser = struct { else => unreachable, }; } - if (fn_proto.is_extern == Extern.Yes) { - %return stream.print("extern "); + if (fn_proto.extern_token) |extern_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); } %return stream.print("fn"); @@ -1403,6 +1400,30 @@ const Parser = struct { } } }, + AstNode.Id.VarDecl => { + const var_decl = @fieldParentPtr(AstNodeVarDecl, "base", decl); + if (var_decl.visib_token) |visib_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); + } + if (var_decl.extern_token) |extern_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); + if (var_decl.lib_name != null) { + @panic("TODO"); + } + } + if (var_decl.comptime_token) |comptime_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); + } + %return stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token)); + %return stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token)); + + %return stream.print(" = "); + + %return stack.append(RenderState { .Text = ";\n" }); + if (var_decl.init_node) |init_node| { + %return stack.append(RenderState { .Expression = init_node }); + } + }, else => unreachable, } }, @@ -1570,4 +1591,12 @@ test "zig fmt" { \\extern fn puts(s: &const u8) -> c_int; \\ ); + + testCanonical( + \\const a = b; + \\pub const a = b; + \\var a = b; + \\pub var a = b; + \\ + ); } From f951bcf01b54d0cf16251d14fd3dee92b2dd9a10 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 10 Dec 2017 23:02:45 -0500 Subject: [PATCH 32/69] self-hosted: parse variable declarations with types --- src-self-hosted/main.zig | 50 +++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index e3026a2968..d04d52adc6 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -914,12 +914,19 @@ const Parser = struct { continue; }, State.VarDeclEq => |var_decl| { - var_decl.eq_token = %return self.eatToken(Token.Id.Equal); - stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; - %return stack.append(State { - .Expression = removeNullCast(&var_decl.init_node), - }); - continue; + const token = self.getNextToken(); + if (token.id == Token.Id.Equal) { + var_decl.eq_token = token; + stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; + %return stack.append(State { + .Expression = removeNullCast(&var_decl.init_node), + }); + continue; + } + if (token.id == Token.Id.Semicolon) { + continue; + } + return self.parseError(token, "expected '=' or ';', found {}", @tagName(token.id)); }, State.ExpectToken => |token_id| { _ = %return self.eatToken(token_id); @@ -1345,6 +1352,7 @@ const Parser = struct { Text: []const u8, Expression: &AstNode, AddrOfExprBit: &AstNodeAddrOfExpr, + VarDeclAlign: &AstNodeVarDecl, }; pub fn renderSource(self: &Parser, stream: &std.io.OutStream, root_node: &AstNodeRoot) -> %void { @@ -1417,16 +1425,27 @@ const Parser = struct { %return stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token)); %return stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token)); - %return stream.print(" = "); - - %return stack.append(RenderState { .Text = ";\n" }); - if (var_decl.init_node) |init_node| { - %return stack.append(RenderState { .Expression = init_node }); + %return stack.append(RenderState { .VarDeclAlign = var_decl }); + if (var_decl.type_node) |type_node| { + %return stream.print(": "); + %return stack.append(RenderState { .Expression = type_node }); } }, else => unreachable, } }, + + RenderState.VarDeclAlign => |var_decl| { + if (var_decl.align_node != null) { + @panic("TODO"); + } + %return stack.append(RenderState { .Text = ";\n" }); + if (var_decl.init_node) |init_node| { + %return stream.print(" = "); + %return stack.append(RenderState { .Expression = init_node }); + } + }, + RenderState.ParamDecl => |base| { const param_decl = @fieldParentPtr(AstNodeParamDecl, "base", base); if (param_decl.comptime_token) |comptime_token| { @@ -1597,6 +1616,15 @@ test "zig fmt" { \\pub const a = b; \\var a = b; \\pub var a = b; + \\const a: i32 = b; + \\pub const a: i32 = b; + \\var a: i32 = b; + \\pub var a: i32 = b; + \\ + ); + + testCanonical( + \\extern var foo: c_int; \\ ); } From 9a51091a5c3d7547989d705e3feebad6252019a6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 10 Dec 2017 23:19:01 -0500 Subject: [PATCH 33/69] self-hosted: clean up parser --- src-self-hosted/main.zig | 75 ++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index d04d52adc6..869ed4ff6a 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -729,15 +729,15 @@ const Parser = struct { self.allocator.free(self.utility_bytes); } - const TopLevelExternCtx = struct { + const TopLevelDeclCtx = struct { visib_token: ?Token, - extern_token: Token, + extern_token: ?Token, }; const State = union(enum) { TopLevel, - TopLevelModifier: ?Token, - TopLevelExtern: TopLevelExternCtx, + TopLevelExtern: ?Token, + TopLevelDecl: TopLevelDeclCtx, Expression: &&AstNode, GroupedExpression: &&AstNode, UnwrapExpression: &&AstNode, @@ -805,61 +805,46 @@ const Parser = struct { const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_pub, Token.Id.Keyword_export => { - stack.append(State { .TopLevelModifier = token }) %% unreachable; - continue; - }, - Token.Id.Keyword_const, Token.Id.Keyword_var => { - stack.append(State.TopLevel) %% unreachable; - // TODO shouldn't need this cast - const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, (?Token)(null), - token, (?Token)(null), (?Token)(null)); - %return stack.append(State { .VarDecl = var_decl_node }); + stack.append(State { .TopLevelExtern = token }) %% unreachable; continue; }, Token.Id.Eof => return root_node, - Token.Id.Keyword_extern => { - stack.append(State { - .TopLevelExtern = TopLevelExternCtx { - .visib_token = null, - .extern_token = token, - } - }) %% unreachable; + else => { + self.putBackToken(token); + // TODO shouldn't need this cast + stack.append(State { .TopLevelExtern = null }) %% unreachable; continue; }, - else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), } }, - State.TopLevelModifier => |visib_token| { + State.TopLevelExtern => |visib_token| { const token = self.getNextToken(); - switch (token.id) { - Token.Id.Keyword_const, Token.Id.Keyword_var => { - stack.append(State.TopLevel) %% unreachable; - // TODO shouldn't need the casts here - const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, visib_token, - token, (?Token)(null), (?Token)(null)); - %return stack.append(State { .VarDecl = var_decl_node }); - continue; - }, - Token.Id.Keyword_extern => { - stack.append(State { - .TopLevelExtern = TopLevelExternCtx { - .visib_token = visib_token, - .extern_token = token, - }, - }) %% unreachable; - continue; - }, - else => return self.parseError(token, "expected top level declaration, found {}", @tagName(token.id)), + if (token.id == Token.Id.Keyword_extern) { + stack.append(State { + .TopLevelDecl = TopLevelDeclCtx { + .visib_token = visib_token, + .extern_token = token, + }, + }) %% unreachable; + continue; } + self.putBackToken(token); + stack.append(State { + .TopLevelDecl = TopLevelDeclCtx { + .visib_token = visib_token, + .extern_token = null, + }, + }) %% unreachable; + continue; }, - State.TopLevelExtern => |ctx| { + State.TopLevelDecl => |ctx| { const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_var, Token.Id.Keyword_const => { stack.append(State.TopLevel) %% unreachable; // TODO shouldn't need these casts const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, ctx.visib_token, - token, (?Token)(null), (?Token)(ctx.extern_token)); + token, (?Token)(null), ctx.extern_token); %return stack.append(State { .VarDecl = var_decl_node }); continue; }, @@ -868,7 +853,7 @@ const Parser = struct { %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); // TODO shouldn't need these casts const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, token, - (?Token)(ctx.extern_token), (?Token)(null), (?Token)(null), (?Token)(null)); + ctx.extern_token, (?Token)(null), (?Token)(null), (?Token)(null)); %return stack.append(State { .FnProto = fn_proto_node }); continue; }, @@ -881,7 +866,7 @@ const Parser = struct { const fn_token = %return self.eatToken(Token.Id.Keyword_fn); // TODO shouldn't need this cast const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, fn_token, - (?Token)(ctx.extern_token), (?Token)(token), (?Token)(null), (?Token)(null)); + ctx.extern_token, (?Token)(token), (?Token)(null), (?Token)(null)); %return stack.append(State { .FnProto = fn_proto_node }); continue; }, From fd6a36a23509fb956e05f0052b8ab5a80ead3380 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Dec 2017 09:21:06 -0500 Subject: [PATCH 34/69] self-hosted: parsing and rendering blocks --- src-self-hosted/main.zig | 391 ++++++++++++++++++++++++++++----------- std/io.zig | 8 + 2 files changed, 292 insertions(+), 107 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 869ed4ff6a..991e20805e 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -169,7 +169,7 @@ const Tokenizer = struct { if (c == '\n') { loc.line += 1; loc.column = 0; - loc.line_start = i; + loc.line_start = i + 1; } else { loc.column += 1; } @@ -520,6 +520,7 @@ const AstNode = struct { FnProto, ParamDecl, AddrOfExpr, + Block, }; fn iterate(base: &AstNode, index: usize) -> ?&AstNode { @@ -530,6 +531,7 @@ const AstNode = struct { Id.FnProto => @fieldParentPtr(AstNodeFnProto, "base", base).iterate(index), Id.ParamDecl => @fieldParentPtr(AstNodeParamDecl, "base", base).iterate(index), Id.AddrOfExpr => @fieldParentPtr(AstNodeAddrOfExpr, "base", base).iterate(index), + Id.Block => @fieldParentPtr(AstNodeBlock, "base", base).iterate(index), }; } @@ -541,6 +543,7 @@ const AstNode = struct { Id.FnProto => allocator.destroy(@fieldParentPtr(AstNodeFnProto, "base", base)), Id.ParamDecl => allocator.destroy(@fieldParentPtr(AstNodeParamDecl, "base", base)), Id.AddrOfExpr => allocator.destroy(@fieldParentPtr(AstNodeAddrOfExpr, "base", base)), + Id.Block => allocator.destroy(@fieldParentPtr(AstNodeBlock, "base", base)), }; } }; @@ -551,7 +554,7 @@ const AstNodeRoot = struct { fn iterate(self: &AstNodeRoot, index: usize) -> ?&AstNode { if (index < self.decls.len) { - return self.decls.items[index]; + return self.decls.items[self.decls.len - index - 1]; } return null; } @@ -612,36 +615,36 @@ const AstNodeFnProto = struct { extern_token: ?Token, inline_token: ?Token, cc_token: ?Token, - fn_def_node: ?&AstNode, + body_node: ?&AstNode, lib_name: ?&AstNode, // populated if this is an extern declaration align_expr: ?&AstNode, // populated if align(A) is present fn iterate(self: &AstNodeFnProto, index: usize) -> ?&AstNode { var i = index; - if (i < self.params.len) return self.params.items[i]; - i -= self.params.len; + if (self.body_node) |body_node| { + if (i < 1) return body_node; + i -= 1; + } if (self.return_type) |return_type| { if (i < 1) return return_type; i -= 1; } - if (self.fn_def_node) |fn_def_node| { - if (i < 1) return fn_def_node; + if (self.align_expr) |align_expr| { + if (i < 1) return align_expr; i -= 1; } + if (i < self.params.len) return self.params.items[self.params.len - i - 1]; + i -= self.params.len; + if (self.lib_name) |lib_name| { if (i < 1) return lib_name; i -= 1; } - if (self.align_expr) |align_expr| { - if (i < 1) return align_expr; - i -= 1; - } - return null; } }; @@ -689,6 +692,22 @@ const AstNodeAddrOfExpr = struct { } }; +const AstNodeBlock = struct { + base: AstNode, + begin_token: Token, + end_token: Token, + statements: ArrayList(&AstNode), + + fn iterate(self: &AstNodeBlock, index: usize) -> ?&AstNode { + var i = index; + + if (i < self.statements.len) return self.statements.items[i]; + i -= self.statements.len; + + return null; + } +}; + error ParseError; const Parser = struct { @@ -734,27 +753,41 @@ const Parser = struct { extern_token: ?Token, }; + const DestPtr = union(enum) { + Field: &&AstNode, + NullableField: &?&AstNode, + List: &ArrayList(&AstNode), + + pub fn store(self: &const DestPtr, value: &AstNode) -> %void { + switch (*self) { + DestPtr.Field => |ptr| *ptr = value, + DestPtr.NullableField => |ptr| *ptr = value, + DestPtr.List => |list| %return list.append(value), + } + } + }; + const State = union(enum) { TopLevel, TopLevelExtern: ?Token, TopLevelDecl: TopLevelDeclCtx, - Expression: &&AstNode, - GroupedExpression: &&AstNode, - UnwrapExpression: &&AstNode, - BoolOrExpression: &&AstNode, - BoolAndExpression: &&AstNode, - ComparisonExpression: &&AstNode, - BinaryOrExpression: &&AstNode, - BinaryXorExpression: &&AstNode, - BinaryAndExpression: &&AstNode, - BitShiftExpression: &&AstNode, - AdditionExpression: &&AstNode, - MultiplyExpression: &&AstNode, - BraceSuffixExpression: &&AstNode, - PrefixOpExpression: &&AstNode, - SuffixOpExpression: &&AstNode, - PrimaryExpression: &&AstNode, - TypeExpr: &&AstNode, + Expression: DestPtr, + GroupedExpression: DestPtr, + UnwrapExpression: DestPtr, + BoolOrExpression: DestPtr, + BoolAndExpression: DestPtr, + ComparisonExpression: DestPtr, + BinaryOrExpression: DestPtr, + BinaryXorExpression: DestPtr, + BinaryAndExpression: DestPtr, + BitShiftExpression: DestPtr, + AdditionExpression: DestPtr, + MultiplyExpression: DestPtr, + BraceSuffixExpression: DestPtr, + PrefixOpExpression: DestPtr, + SuffixOpExpression: DestPtr, + PrimaryExpression: DestPtr, + TypeExpr: DestPtr, VarDecl: &AstNodeVarDecl, VarDeclAlign: &AstNodeVarDecl, VarDeclEq: &AstNodeVarDecl, @@ -763,6 +796,9 @@ const Parser = struct { FnProtoAlign: &AstNodeFnProto, ParamDecl: &AstNodeFnProto, ParamDeclComma, + FnDef: &AstNodeFnProto, + Block: &AstNodeBlock, + Statement: &AstNodeBlock, }; pub fn freeAst(self: &Parser, root_node: &AstNodeRoot) { @@ -797,6 +833,18 @@ const Parser = struct { %defer self.freeAst(root_node); while (true) { + //{ + // const token = self.getNextToken(); + // warn("{} ", @tagName(token.id)); + // self.putBackToken(token); + // var i: usize = stack.len; + // while (i != 0) { + // i -= 1; + // warn("{} ", @tagName(stack.items[i])); + // } + // warn("\n"); + //} + // This gives us 1 free append that can't fail const state = stack.pop(); @@ -850,11 +898,11 @@ const Parser = struct { }, Token.Id.Keyword_fn => { stack.append(State.TopLevel) %% unreachable; - %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); // TODO shouldn't need these casts - const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, token, + const fn_proto = %return self.createAttachFnProto(&root_node.decls, token, ctx.extern_token, (?Token)(null), (?Token)(null), (?Token)(null)); - %return stack.append(State { .FnProto = fn_proto_node }); + %return stack.append(State { .FnDef = fn_proto }); + %return stack.append(State { .FnProto = fn_proto }); continue; }, Token.Id.StringLiteral => { @@ -862,12 +910,12 @@ const Parser = struct { }, Token.Id.Keyword_coldcc, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { stack.append(State.TopLevel) %% unreachable; - %return stack.append(State { .ExpectToken = Token.Id.Semicolon }); const fn_token = %return self.eatToken(Token.Id.Keyword_fn); // TODO shouldn't need this cast - const fn_proto_node = %return self.createAttachFnProto(&root_node.decls, fn_token, + const fn_proto = %return self.createAttachFnProto(&root_node.decls, fn_token, ctx.extern_token, (?Token)(token), (?Token)(null), (?Token)(null)); - %return stack.append(State { .FnProto = fn_proto_node }); + %return stack.append(State { .FnDef = fn_proto }); + %return stack.append(State { .FnProto = fn_proto }); continue; }, else => return self.parseError(token, "expected variable declaration or function, found {}", @tagName(token.id)), @@ -879,7 +927,7 @@ const Parser = struct { const next_token = self.getNextToken(); if (next_token.id == Token.Id.Colon) { - %return stack.append(State { .TypeExpr = removeNullCast(&var_decl.type_node) }); + %return stack.append(State { .TypeExpr = DestPtr {.NullableField = &var_decl.type_node} }); continue; } @@ -891,7 +939,11 @@ const Parser = struct { const next_token = self.getNextToken(); if (next_token.id == Token.Id.Keyword_align) { - %return stack.append(State { .GroupedExpression = removeNullCast(&var_decl.align_node) }); + %return stack.append(State { + .GroupedExpression = DestPtr { + .NullableField = &var_decl.align_node + } + }); continue; } @@ -904,7 +956,7 @@ const Parser = struct { var_decl.eq_token = token; stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; %return stack.append(State { - .Expression = removeNullCast(&var_decl.init_node), + .Expression = DestPtr {.NullableField = &var_decl.init_node}, }); continue; } @@ -917,70 +969,70 @@ const Parser = struct { _ = %return self.eatToken(token_id); continue; }, - State.Expression => |result_ptr| { - stack.append(State {.UnwrapExpression = result_ptr}) %% unreachable; + State.Expression => |dest_ptr| { + stack.append(State {.UnwrapExpression = dest_ptr}) %% unreachable; continue; }, - State.UnwrapExpression => |result_ptr| { - stack.append(State {.BoolOrExpression = result_ptr}) %% unreachable; + State.UnwrapExpression => |dest_ptr| { + stack.append(State {.BoolOrExpression = dest_ptr}) %% unreachable; continue; }, - State.BoolOrExpression => |result_ptr| { - stack.append(State {.BoolAndExpression = result_ptr}) %% unreachable; + State.BoolOrExpression => |dest_ptr| { + stack.append(State {.BoolAndExpression = dest_ptr}) %% unreachable; continue; }, - State.BoolAndExpression => |result_ptr| { - stack.append(State {.ComparisonExpression = result_ptr}) %% unreachable; + State.BoolAndExpression => |dest_ptr| { + stack.append(State {.ComparisonExpression = dest_ptr}) %% unreachable; continue; }, - State.ComparisonExpression => |result_ptr| { - stack.append(State {.BinaryOrExpression = result_ptr}) %% unreachable; + State.ComparisonExpression => |dest_ptr| { + stack.append(State {.BinaryOrExpression = dest_ptr}) %% unreachable; continue; }, - State.BinaryOrExpression => |result_ptr| { - stack.append(State {.BinaryXorExpression = result_ptr}) %% unreachable; + State.BinaryOrExpression => |dest_ptr| { + stack.append(State {.BinaryXorExpression = dest_ptr}) %% unreachable; continue; }, - State.BinaryXorExpression => |result_ptr| { - stack.append(State {.BinaryAndExpression = result_ptr}) %% unreachable; + State.BinaryXorExpression => |dest_ptr| { + stack.append(State {.BinaryAndExpression = dest_ptr}) %% unreachable; continue; }, - State.BinaryAndExpression => |result_ptr| { - stack.append(State {.BitShiftExpression = result_ptr}) %% unreachable; + State.BinaryAndExpression => |dest_ptr| { + stack.append(State {.BitShiftExpression = dest_ptr}) %% unreachable; continue; }, - State.BitShiftExpression => |result_ptr| { - stack.append(State {.AdditionExpression = result_ptr}) %% unreachable; + State.BitShiftExpression => |dest_ptr| { + stack.append(State {.AdditionExpression = dest_ptr}) %% unreachable; continue; }, - State.AdditionExpression => |result_ptr| { - stack.append(State {.MultiplyExpression = result_ptr}) %% unreachable; + State.AdditionExpression => |dest_ptr| { + stack.append(State {.MultiplyExpression = dest_ptr}) %% unreachable; continue; }, - State.MultiplyExpression => |result_ptr| { - stack.append(State {.BraceSuffixExpression = result_ptr}) %% unreachable; + State.MultiplyExpression => |dest_ptr| { + stack.append(State {.BraceSuffixExpression = dest_ptr}) %% unreachable; continue; }, - State.BraceSuffixExpression => |result_ptr| { - stack.append(State {.PrefixOpExpression = result_ptr}) %% unreachable; + State.BraceSuffixExpression => |dest_ptr| { + stack.append(State {.PrefixOpExpression = dest_ptr}) %% unreachable; continue; }, - State.PrefixOpExpression => |result_ptr| { + State.PrefixOpExpression => |dest_ptr| { const first_token = self.getNextToken(); if (first_token.id == Token.Id.Ampersand) { - const addr_of_expr = %return self.createAttachAddrOfExpr(result_ptr, first_token); + const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, first_token); var token = self.getNextToken(); if (token.id == Token.Id.Keyword_align) { @panic("TODO align"); @@ -994,40 +1046,42 @@ const Parser = struct { token = self.getNextToken(); } self.putBackToken(token); - stack.append(State { .PrefixOpExpression = &addr_of_expr.op_expr }) %% unreachable; + stack.append(State { + .PrefixOpExpression = DestPtr { .Field = &addr_of_expr.op_expr}, + }) %% unreachable; continue; } self.putBackToken(first_token); - stack.append(State { .SuffixOpExpression = result_ptr }) %% unreachable; + stack.append(State { .SuffixOpExpression = dest_ptr }) %% unreachable; continue; }, - State.SuffixOpExpression => |result_ptr| { - stack.append(State { .PrimaryExpression = result_ptr }) %% unreachable; + State.SuffixOpExpression => |dest_ptr| { + stack.append(State { .PrimaryExpression = dest_ptr }) %% unreachable; continue; }, - State.PrimaryExpression => |result_ptr| { + State.PrimaryExpression => |dest_ptr| { const token = self.getNextToken(); switch (token.id) { Token.Id.Identifier => { const identifier = %return self.createIdentifier(token); - *result_ptr = &identifier.base; + %return dest_ptr.store(&identifier.base); continue; }, else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), } }, - State.TypeExpr => |result_ptr| { + State.TypeExpr => |dest_ptr| { const token = self.getNextToken(); if (token.id == Token.Id.Keyword_var) { @panic("TODO param with type var"); } self.putBackToken(token); - stack.append(State { .PrefixOpExpression = result_ptr }) %% unreachable; + stack.append(State { .PrefixOpExpression = dest_ptr }) %% unreachable; continue; }, @@ -1051,7 +1105,9 @@ const Parser = struct { @panic("TODO fn proto align"); } if (token.id == Token.Id.Arrow) { - stack.append(State { .TypeExpr = removeNullCast(&fn_proto.return_type) }) %% unreachable; + stack.append(State { + .TypeExpr = DestPtr {.NullableField = &fn_proto.return_type}, + }) %% unreachable; continue; } else { self.putBackToken(token); @@ -1091,7 +1147,9 @@ const Parser = struct { stack.append(State { .ParamDecl = fn_proto }) %% unreachable; %return stack.append(State.ParamDeclComma); - %return stack.append(State { .TypeExpr = ¶m_decl.type_node }); + %return stack.append(State { + .TypeExpr = DestPtr {.Field = ¶m_decl.type_node} + }); continue; }, @@ -1107,6 +1165,70 @@ const Parser = struct { } }, + State.FnDef => |fn_proto| { + const token = self.getNextToken(); + switch(token.id) { + Token.Id.LBrace => { + const block = %return self.createBlock(token); + fn_proto.body_node = &block.base; + stack.append(State { .Block = block }) %% unreachable; + continue; + }, + Token.Id.Semicolon => continue, + else => return self.parseError(token, "expected ';' or '{{', found {}", @tagName(token.id)), + } + }, + + State.Block => |block| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.RBrace => { + block.end_token = token; + continue; + }, + else => { + self.putBackToken(token); + stack.append(State { .Block = block }) %% unreachable; + %return stack.append(State { .Statement = block }); + continue; + }, + } + }, + + State.Statement => |block| { + { + // Look for comptime var, comptime const + const comptime_token = self.getNextToken(); + if (comptime_token.id == Token.Id.Keyword_comptime) { + const mut_token = self.getNextToken(); + if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) { + // TODO shouldn't need these casts + const var_decl = %return self.createAttachVarDecl(&block.statements, (?Token)(null), + mut_token, (?Token)(comptime_token), (?Token)(null)); + %return stack.append(State { .VarDecl = var_decl }); + continue; + } + self.putBackToken(mut_token); + } + self.putBackToken(comptime_token); + } + { + // Look for const, var + const mut_token = self.getNextToken(); + if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) { + // TODO shouldn't need these casts + const var_decl = %return self.createAttachVarDecl(&block.statements, (?Token)(null), + mut_token, (?Token)(null), (?Token)(null)); + %return stack.append(State { .VarDecl = var_decl }); + continue; + } + self.putBackToken(mut_token); + } + + stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; + %return stack.append(State { .Expression = DestPtr{.List = &block.statements} }); + continue; + }, State.GroupedExpression => @panic("TODO"), } @@ -1176,7 +1298,7 @@ const Parser = struct { .extern_token = *extern_token, .inline_token = *inline_token, .cc_token = *cc_token, - .fn_def_node = null, + .body_node = null, .lib_name = null, .align_expr = null, }; @@ -1215,10 +1337,23 @@ const Parser = struct { return node; } - fn createAttachAddrOfExpr(self: &Parser, result_ptr: &&AstNode, op_token: &const Token) -> %&AstNodeAddrOfExpr { + fn createBlock(self: &Parser, begin_token: &const Token) -> %&AstNodeBlock { + const node = %return self.allocator.create(AstNodeBlock); + %defer self.allocator.destroy(node); + + *node = AstNodeBlock { + .base = AstNode {.id = AstNode.Id.Block}, + .begin_token = *begin_token, + .end_token = undefined, + .statements = ArrayList(&AstNode).init(self.allocator), + }; + return node; + } + + fn createAttachAddrOfExpr(self: &Parser, dest_ptr: &const DestPtr, op_token: &const Token) -> %&AstNodeAddrOfExpr { const node = %return self.createAddrOfExpr(op_token); %defer self.allocator.destroy(node); - *result_ptr = &node.base; + %return dest_ptr.store(&node.base); return node; } @@ -1337,7 +1472,11 @@ const Parser = struct { Text: []const u8, Expression: &AstNode, AddrOfExprBit: &AstNodeAddrOfExpr, + VarDecl: &AstNodeVarDecl, VarDeclAlign: &AstNodeVarDecl, + Statement: &AstNode, + PrintIndent, + Indent: usize, }; pub fn renderSource(self: &Parser, stream: &std.io.OutStream, root_node: &AstNodeRoot) -> %void { @@ -1353,6 +1492,8 @@ const Parser = struct { } } + const indent_delta = 4; + var indent: usize = 0; while (stack.popOrNull()) |state| { switch (state) { RenderState.TopLevelDecl => |decl| { @@ -1378,7 +1519,7 @@ const Parser = struct { %return stream.print("("); %return stack.append(RenderState { .Text = "\n" }); - if (fn_proto.fn_def_node == null) { + if (fn_proto.body_node == null) { %return stack.append(RenderState { .Text = ";" }); } @@ -1395,36 +1536,42 @@ const Parser = struct { }, AstNode.Id.VarDecl => { const var_decl = @fieldParentPtr(AstNodeVarDecl, "base", decl); - if (var_decl.visib_token) |visib_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); - } - if (var_decl.extern_token) |extern_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); - if (var_decl.lib_name != null) { - @panic("TODO"); - } - } - if (var_decl.comptime_token) |comptime_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); - } - %return stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token)); - %return stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token)); + %return stack.append(RenderState { .Text = "\n"}); + %return stack.append(RenderState { .VarDecl = var_decl}); - %return stack.append(RenderState { .VarDeclAlign = var_decl }); - if (var_decl.type_node) |type_node| { - %return stream.print(": "); - %return stack.append(RenderState { .Expression = type_node }); - } }, else => unreachable, } }, + RenderState.VarDecl => |var_decl| { + if (var_decl.visib_token) |visib_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); + } + if (var_decl.extern_token) |extern_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); + if (var_decl.lib_name != null) { + @panic("TODO"); + } + } + if (var_decl.comptime_token) |comptime_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); + } + %return stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token)); + %return stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token)); + + %return stack.append(RenderState { .VarDeclAlign = var_decl }); + if (var_decl.type_node) |type_node| { + %return stream.print(": "); + %return stack.append(RenderState { .Expression = type_node }); + } + }, + RenderState.VarDeclAlign => |var_decl| { if (var_decl.align_node != null) { @panic("TODO"); } - %return stack.append(RenderState { .Text = ";\n" }); + %return stack.append(RenderState { .Text = ";" }); if (var_decl.init_node) |init_node| { %return stream.print(" = "); %return stack.append(RenderState { .Expression = init_node }); @@ -1467,6 +1614,23 @@ const Parser = struct { %return stack.append(RenderState { .Expression = align_expr}); } }, + AstNode.Id.Block => { + const block = @fieldParentPtr(AstNodeBlock, "base", base); + %return stream.write("{"); + %return stack.append(RenderState { .Text = "}"}); + %return stack.append(RenderState.PrintIndent); + %return stack.append(RenderState { .Indent = indent}); + %return stack.append(RenderState { .Text = "\n"}); + var i = block.statements.len; + while (i != 0) { + i -= 1; + const statement_node = block.statements.items[i]; + %return stack.append(RenderState { .Statement = statement_node}); + %return stack.append(RenderState.PrintIndent); + %return stack.append(RenderState { .Indent = indent + indent_delta}); + %return stack.append(RenderState { .Text = "\n" }); + } + }, else => unreachable, }, RenderState.AddrOfExprBit => |addr_of_expr| { @@ -1491,9 +1655,24 @@ const Parser = struct { } if (fn_proto.return_type) |return_type| { %return stream.print(" -> "); + if (fn_proto.body_node) |body_node| { + %return stack.append(RenderState { .Expression = body_node}); + %return stack.append(RenderState { .Text = " "}); + } %return stack.append(RenderState { .Expression = return_type}); } }, + RenderState.Statement => |base| { + switch (base.id) { + AstNode.Id.VarDecl => { + const var_decl = @fieldParentPtr(AstNodeVarDecl, "base", base); + %return stack.append(RenderState { .VarDecl = var_decl}); + }, + else => unreachable, + } + }, + RenderState.Indent => |new_indent| indent = new_indent, + RenderState.PrintIndent => %return stream.writeByteNTimes(' ', indent), } } } @@ -1555,15 +1734,6 @@ pub fn main2() -> %void { %return parser.renderSource(out_stream, root_node); } -fn removeNullCast(x: var) -> {const InnerPtr = @typeOf(x).Child.Child; &InnerPtr} { - comptime assert(@typeId(@typeOf(x)) == builtin.TypeId.Pointer); - comptime assert(@typeId(@typeOf(x).Child) == builtin.TypeId.Nullable); - comptime assert(@typeId(@typeOf(x).Child.Child) == builtin.TypeId.Pointer); - const InnerPtr = @typeOf(x).Child.Child; - return @ptrCast(&InnerPtr, x); -} - - fn testCanonical(source: []const u8) { const allocator = std.debug.global_allocator; @@ -1612,4 +1782,11 @@ test "zig fmt" { \\extern var foo: c_int; \\ ); + + testCanonical( + \\fn main(argc: c_int, argv: &&u8) -> c_int { + \\ const a = b; + \\} + \\ + ); } diff --git a/std/io.zig b/std/io.zig index b677e457f7..0ba3d06a01 100644 --- a/std/io.zig +++ b/std/io.zig @@ -481,6 +481,14 @@ pub const OutStream = struct { const slice = (&byte)[0..1]; return self.writeFn(self, slice); } + + pub fn writeByteNTimes(self: &OutStream, byte: u8, n: usize) -> %void { + const slice = (&byte)[0..1]; + var i: usize = 0; + while (i < n) : (i += 1) { + %return self.writeFn(self, slice); + } + } }; /// `path` may need to be copied in memory to add a null terminating byte. In this case From a3a590a32ab8165955583b6ea76f89f19bb21740 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Dec 2017 14:47:20 -0500 Subject: [PATCH 35/69] self-hosted: workaround for issue #537 --- src-self-hosted/main.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 991e20805e..0a1df91c6f 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1761,6 +1761,12 @@ fn testCanonical(source: []const u8) { } test "zig fmt" { + if (builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386) { + // TODO get this test passing + // https://github.com/zig-lang/zig/issues/537 + return; + } + testCanonical( \\extern fn puts(s: &const u8) -> c_int; \\ From d8d379faf1f656743d118e5e5cfa3dba1e537d65 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Dec 2017 16:18:06 -0500 Subject: [PATCH 36/69] self-hosted: refactor into multiple files add return expression add number literal --- src-self-hosted/ast.zig | 245 +++++ src-self-hosted/main.zig | 1684 +-------------------------------- src-self-hosted/parser.zig | 1074 +++++++++++++++++++++ src-self-hosted/tokenizer.zig | 479 ++++++++++ 4 files changed, 1809 insertions(+), 1673 deletions(-) create mode 100644 src-self-hosted/ast.zig create mode 100644 src-self-hosted/parser.zig create mode 100644 src-self-hosted/tokenizer.zig diff --git a/src-self-hosted/ast.zig b/src-self-hosted/ast.zig new file mode 100644 index 0000000000..40da01481c --- /dev/null +++ b/src-self-hosted/ast.zig @@ -0,0 +1,245 @@ +const std = @import("std"); +const assert = std.debug.assert; +const ArrayList = std.ArrayList; +const Token = @import("tokenizer.zig").Token; +const mem = std.mem; + +pub const Node = struct { + id: Id, + + pub const Id = enum { + Root, + VarDecl, + Identifier, + FnProto, + ParamDecl, + AddrOfExpr, + Block, + Return, + IntegerLiteral, + FloatLiteral, + }; + + pub fn iterate(base: &Node, index: usize) -> ?&Node { + return switch (base.id) { + Id.Root => @fieldParentPtr(NodeRoot, "base", base).iterate(index), + Id.VarDecl => @fieldParentPtr(NodeVarDecl, "base", base).iterate(index), + Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).iterate(index), + Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).iterate(index), + Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).iterate(index), + Id.AddrOfExpr => @fieldParentPtr(NodeAddrOfExpr, "base", base).iterate(index), + Id.Block => @fieldParentPtr(NodeBlock, "base", base).iterate(index), + Id.Return => @fieldParentPtr(NodeReturn, "base", base).iterate(index), + Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).iterate(index), + Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).iterate(index), + }; + } + + pub fn destroy(base: &Node, allocator: &mem.Allocator) { + return switch (base.id) { + Id.Root => allocator.destroy(@fieldParentPtr(NodeRoot, "base", base)), + Id.VarDecl => allocator.destroy(@fieldParentPtr(NodeVarDecl, "base", base)), + Id.Identifier => allocator.destroy(@fieldParentPtr(NodeIdentifier, "base", base)), + Id.FnProto => allocator.destroy(@fieldParentPtr(NodeFnProto, "base", base)), + Id.ParamDecl => allocator.destroy(@fieldParentPtr(NodeParamDecl, "base", base)), + Id.AddrOfExpr => allocator.destroy(@fieldParentPtr(NodeAddrOfExpr, "base", base)), + Id.Block => allocator.destroy(@fieldParentPtr(NodeBlock, "base", base)), + Id.Return => allocator.destroy(@fieldParentPtr(NodeReturn, "base", base)), + Id.IntegerLiteral => allocator.destroy(@fieldParentPtr(NodeIntegerLiteral, "base", base)), + Id.FloatLiteral => allocator.destroy(@fieldParentPtr(NodeFloatLiteral, "base", base)), + }; + } +}; + +pub const NodeRoot = struct { + base: Node, + decls: ArrayList(&Node), + + pub fn iterate(self: &NodeRoot, index: usize) -> ?&Node { + if (index < self.decls.len) { + return self.decls.items[self.decls.len - index - 1]; + } + return null; + } +}; + +pub const NodeVarDecl = struct { + base: Node, + visib_token: ?Token, + name_token: Token, + eq_token: Token, + mut_token: Token, + comptime_token: ?Token, + extern_token: ?Token, + lib_name: ?&Node, + type_node: ?&Node, + align_node: ?&Node, + init_node: ?&Node, + + pub fn iterate(self: &NodeVarDecl, index: usize) -> ?&Node { + var i = index; + + if (self.type_node) |type_node| { + if (i < 1) return type_node; + i -= 1; + } + + if (self.align_node) |align_node| { + if (i < 1) return align_node; + i -= 1; + } + + if (self.init_node) |init_node| { + if (i < 1) return init_node; + i -= 1; + } + + return null; + } +}; + +pub const NodeIdentifier = struct { + base: Node, + name_token: Token, + + pub fn iterate(self: &NodeIdentifier, index: usize) -> ?&Node { + return null; + } +}; + +pub const NodeFnProto = struct { + base: Node, + visib_token: ?Token, + fn_token: Token, + name_token: ?Token, + params: ArrayList(&Node), + return_type: ?&Node, + var_args_token: ?Token, + extern_token: ?Token, + inline_token: ?Token, + cc_token: ?Token, + body_node: ?&Node, + lib_name: ?&Node, // populated if this is an extern declaration + align_expr: ?&Node, // populated if align(A) is present + + pub fn iterate(self: &NodeFnProto, index: usize) -> ?&Node { + var i = index; + + if (self.body_node) |body_node| { + if (i < 1) return body_node; + i -= 1; + } + + if (self.return_type) |return_type| { + if (i < 1) return return_type; + i -= 1; + } + + if (self.align_expr) |align_expr| { + if (i < 1) return align_expr; + i -= 1; + } + + if (i < self.params.len) return self.params.items[self.params.len - i - 1]; + i -= self.params.len; + + if (self.lib_name) |lib_name| { + if (i < 1) return lib_name; + i -= 1; + } + + return null; + } +}; + +pub const NodeParamDecl = struct { + base: Node, + comptime_token: ?Token, + noalias_token: ?Token, + name_token: ?Token, + type_node: &Node, + var_args_token: ?Token, + + pub fn iterate(self: &NodeParamDecl, index: usize) -> ?&Node { + var i = index; + + if (i < 1) return self.type_node; + i -= 1; + + return null; + } +}; + +pub const NodeAddrOfExpr = struct { + base: Node, + op_token: Token, + align_expr: ?&Node, + bit_offset_start_token: ?Token, + bit_offset_end_token: ?Token, + const_token: ?Token, + volatile_token: ?Token, + op_expr: &Node, + + pub fn iterate(self: &NodeAddrOfExpr, index: usize) -> ?&Node { + var i = index; + + if (self.align_expr) |align_expr| { + if (i < 1) return align_expr; + i -= 1; + } + + if (i < 1) return self.op_expr; + i -= 1; + + return null; + } +}; + +pub const NodeBlock = struct { + base: Node, + begin_token: Token, + end_token: Token, + statements: ArrayList(&Node), + + pub fn iterate(self: &NodeBlock, index: usize) -> ?&Node { + var i = index; + + if (i < self.statements.len) return self.statements.items[i]; + i -= self.statements.len; + + return null; + } +}; + +pub const NodeReturn = struct { + base: Node, + return_token: Token, + expr: &Node, + + pub fn iterate(self: &NodeReturn, index: usize) -> ?&Node { + var i = index; + + if (i < 1) return self.expr; + i -= 1; + + return null; + } +}; + +pub const NodeIntegerLiteral = struct { + base: Node, + token: Token, + + pub fn iterate(self: &NodeIntegerLiteral, index: usize) -> ?&Node { + return null; + } +}; + +pub const NodeFloatLiteral = struct { + base: Node, + token: Token, + + pub fn iterate(self: &NodeFloatLiteral, index: usize) -> ?&Node { + return null; + } +}; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 0a1df91c6f..bba16dfb3f 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1,1682 +1,13 @@ const std = @import("std"); +const mem = std.mem; const builtin = @import("builtin"); const io = std.io; const os = std.os; const heap = std.heap; const warn = std.debug.warn; -const assert = std.debug.assert; -const mem = std.mem; -const ArrayList = std.ArrayList; -const AlignedArrayList = std.AlignedArrayList; -const math = std.math; - - -const Token = struct { - id: Id, - start: usize, - end: usize, - - const KeywordId = struct { - bytes: []const u8, - id: Id, - }; - - const keywords = []KeywordId { - KeywordId{.bytes="align", .id = Id.Keyword_align}, - KeywordId{.bytes="and", .id = Id.Keyword_and}, - KeywordId{.bytes="asm", .id = Id.Keyword_asm}, - KeywordId{.bytes="break", .id = Id.Keyword_break}, - KeywordId{.bytes="coldcc", .id = Id.Keyword_coldcc}, - KeywordId{.bytes="comptime", .id = Id.Keyword_comptime}, - KeywordId{.bytes="const", .id = Id.Keyword_const}, - KeywordId{.bytes="continue", .id = Id.Keyword_continue}, - KeywordId{.bytes="defer", .id = Id.Keyword_defer}, - KeywordId{.bytes="else", .id = Id.Keyword_else}, - KeywordId{.bytes="enum", .id = Id.Keyword_enum}, - KeywordId{.bytes="error", .id = Id.Keyword_error}, - KeywordId{.bytes="export", .id = Id.Keyword_export}, - KeywordId{.bytes="extern", .id = Id.Keyword_extern}, - KeywordId{.bytes="false", .id = Id.Keyword_false}, - KeywordId{.bytes="fn", .id = Id.Keyword_fn}, - KeywordId{.bytes="for", .id = Id.Keyword_for}, - KeywordId{.bytes="goto", .id = Id.Keyword_goto}, - KeywordId{.bytes="if", .id = Id.Keyword_if}, - KeywordId{.bytes="inline", .id = Id.Keyword_inline}, - KeywordId{.bytes="nakedcc", .id = Id.Keyword_nakedcc}, - KeywordId{.bytes="noalias", .id = Id.Keyword_noalias}, - KeywordId{.bytes="null", .id = Id.Keyword_null}, - KeywordId{.bytes="or", .id = Id.Keyword_or}, - KeywordId{.bytes="packed", .id = Id.Keyword_packed}, - KeywordId{.bytes="pub", .id = Id.Keyword_pub}, - KeywordId{.bytes="return", .id = Id.Keyword_return}, - KeywordId{.bytes="stdcallcc", .id = Id.Keyword_stdcallcc}, - KeywordId{.bytes="struct", .id = Id.Keyword_struct}, - KeywordId{.bytes="switch", .id = Id.Keyword_switch}, - KeywordId{.bytes="test", .id = Id.Keyword_test}, - KeywordId{.bytes="this", .id = Id.Keyword_this}, - KeywordId{.bytes="true", .id = Id.Keyword_true}, - KeywordId{.bytes="undefined", .id = Id.Keyword_undefined}, - KeywordId{.bytes="union", .id = Id.Keyword_union}, - KeywordId{.bytes="unreachable", .id = Id.Keyword_unreachable}, - KeywordId{.bytes="use", .id = Id.Keyword_use}, - KeywordId{.bytes="var", .id = Id.Keyword_var}, - KeywordId{.bytes="volatile", .id = Id.Keyword_volatile}, - KeywordId{.bytes="while", .id = Id.Keyword_while}, - }; - - fn getKeyword(bytes: []const u8) -> ?Id { - for (keywords) |kw| { - if (mem.eql(u8, kw.bytes, bytes)) { - return kw.id; - } - } - return null; - } - - const StrLitKind = enum {Normal, C}; - - const Id = union(enum) { - Invalid, - Identifier, - StringLiteral: StrLitKind, - Eof, - Builtin, - Equal, - LParen, - RParen, - Semicolon, - Percent, - LBrace, - RBrace, - Period, - Ellipsis2, - Ellipsis3, - Minus, - Arrow, - Colon, - Slash, - Comma, - Ampersand, - AmpersandEqual, - NumberLiteral, - Keyword_align, - Keyword_and, - Keyword_asm, - Keyword_break, - Keyword_coldcc, - Keyword_comptime, - Keyword_const, - Keyword_continue, - Keyword_defer, - Keyword_else, - Keyword_enum, - Keyword_error, - Keyword_export, - Keyword_extern, - Keyword_false, - Keyword_fn, - Keyword_for, - Keyword_goto, - Keyword_if, - Keyword_inline, - Keyword_nakedcc, - Keyword_noalias, - Keyword_null, - Keyword_or, - Keyword_packed, - Keyword_pub, - Keyword_return, - Keyword_stdcallcc, - Keyword_struct, - Keyword_switch, - Keyword_test, - Keyword_this, - Keyword_true, - Keyword_undefined, - Keyword_union, - Keyword_unreachable, - Keyword_use, - Keyword_var, - Keyword_volatile, - Keyword_while, - }; -}; - -const Tokenizer = struct { - buffer: []const u8, - index: usize, - - pub const Location = struct { - line: usize, - column: usize, - line_start: usize, - line_end: usize, - }; - - pub fn getTokenLocation(self: &Tokenizer, token: &const Token) -> Location { - var loc = Location { - .line = 0, - .column = 0, - .line_start = 0, - .line_end = 0, - }; - for (self.buffer) |c, i| { - if (i == token.start) { - loc.line_end = i; - while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {} - return loc; - } - if (c == '\n') { - loc.line += 1; - loc.column = 0; - loc.line_start = i + 1; - } else { - loc.column += 1; - } - } - return loc; - } - - pub fn dump(self: &Tokenizer, token: &const Token) { - warn("{} \"{}\"\n", @tagName(token.id), self.buffer[token.start..token.end]); - } - - pub fn init(buffer: []const u8) -> Tokenizer { - return Tokenizer { - .buffer = buffer, - .index = 0, - }; - } - - const State = enum { - Start, - Identifier, - Builtin, - C, - StringLiteral, - StringLiteralBackslash, - Minus, - Slash, - LineComment, - Zero, - NumberLiteral, - NumberDot, - FloatFraction, - FloatExponentUnsigned, - FloatExponentNumber, - Ampersand, - Period, - Period2, - }; - - pub fn next(self: &Tokenizer) -> Token { - var state = State.Start; - var result = Token { - .id = Token.Id.Eof, - .start = self.index, - .end = undefined, - }; - while (self.index < self.buffer.len) : (self.index += 1) { - const c = self.buffer[self.index]; - switch (state) { - State.Start => switch (c) { - ' ', '\n' => { - result.start = self.index + 1; - }, - 'c' => { - state = State.C; - result.id = Token.Id.Identifier; - }, - '"' => { - state = State.StringLiteral; - result.id = Token.Id { .StringLiteral = Token.StrLitKind.Normal }; - }, - 'a'...'b', 'd'...'z', 'A'...'Z', '_' => { - state = State.Identifier; - result.id = Token.Id.Identifier; - }, - '@' => { - state = State.Builtin; - result.id = Token.Id.Builtin; - }, - '=' => { - result.id = Token.Id.Equal; - self.index += 1; - break; - }, - '(' => { - result.id = Token.Id.LParen; - self.index += 1; - break; - }, - ')' => { - result.id = Token.Id.RParen; - self.index += 1; - break; - }, - ';' => { - result.id = Token.Id.Semicolon; - self.index += 1; - break; - }, - ',' => { - result.id = Token.Id.Comma; - self.index += 1; - break; - }, - ':' => { - result.id = Token.Id.Colon; - self.index += 1; - break; - }, - '%' => { - result.id = Token.Id.Percent; - self.index += 1; - break; - }, - '{' => { - result.id = Token.Id.LBrace; - self.index += 1; - break; - }, - '}' => { - result.id = Token.Id.RBrace; - self.index += 1; - break; - }, - '.' => { - state = State.Period; - }, - '-' => { - state = State.Minus; - }, - '/' => { - state = State.Slash; - }, - '&' => { - state = State.Ampersand; - }, - '0' => { - state = State.Zero; - result.id = Token.Id.NumberLiteral; - }, - '1'...'9' => { - state = State.NumberLiteral; - result.id = Token.Id.NumberLiteral; - }, - else => { - result.id = Token.Id.Invalid; - self.index += 1; - break; - }, - }, - State.Ampersand => switch (c) { - '=' => { - result.id = Token.Id.AmpersandEqual; - self.index += 1; - break; - }, - else => { - result.id = Token.Id.Ampersand; - break; - }, - }, - State.Identifier => switch (c) { - 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, - else => { - if (Token.getKeyword(self.buffer[result.start..self.index])) |id| { - result.id = id; - } - break; - }, - }, - State.Builtin => switch (c) { - 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, - else => break, - }, - State.C => switch (c) { - '\\' => @panic("TODO"), - '"' => { - state = State.StringLiteral; - result.id = Token.Id { .StringLiteral = Token.StrLitKind.C }; - }, - 'a'...'z', 'A'...'Z', '_', '0'...'9' => { - state = State.Identifier; - }, - else => break, - }, - State.StringLiteral => switch (c) { - '\\' => { - state = State.StringLiteralBackslash; - }, - '"' => { - self.index += 1; - break; - }, - '\n' => break, // Look for this error later. - else => {}, - }, - - State.StringLiteralBackslash => switch (c) { - '\n' => break, // Look for this error later. - else => { - state = State.StringLiteral; - }, - }, - - State.Minus => switch (c) { - '>' => { - result.id = Token.Id.Arrow; - self.index += 1; - break; - }, - else => { - result.id = Token.Id.Minus; - break; - }, - }, - - State.Period => switch (c) { - '.' => { - state = State.Period2; - }, - else => { - result.id = Token.Id.Period; - break; - }, - }, - - State.Period2 => switch (c) { - '.' => { - result.id = Token.Id.Ellipsis3; - self.index += 1; - break; - }, - else => { - result.id = Token.Id.Ellipsis2; - break; - }, - }, - - State.Slash => switch (c) { - '/' => { - result.id = undefined; - state = State.LineComment; - }, - else => { - result.id = Token.Id.Slash; - break; - }, - }, - State.LineComment => switch (c) { - '\n' => { - state = State.Start; - result = Token { - .id = Token.Id.Eof, - .start = self.index + 1, - .end = undefined, - }; - }, - else => {}, - }, - State.Zero => switch (c) { - 'b', 'o', 'x' => { - state = State.NumberLiteral; - }, - else => { - // reinterpret as a normal number - self.index -= 1; - state = State.NumberLiteral; - }, - }, - State.NumberLiteral => switch (c) { - '.' => { - state = State.NumberDot; - }, - 'p', 'P', 'e', 'E' => { - state = State.FloatExponentUnsigned; - }, - '0'...'9', 'a'...'f', 'A'...'F' => {}, - else => break, - }, - State.NumberDot => switch (c) { - '.' => { - self.index -= 1; - state = State.Start; - break; - }, - else => { - self.index -= 1; - state = State.FloatFraction; - }, - }, - State.FloatFraction => switch (c) { - 'p', 'P', 'e', 'E' => { - state = State.FloatExponentUnsigned; - }, - '0'...'9', 'a'...'f', 'A'...'F' => {}, - else => break, - }, - State.FloatExponentUnsigned => switch (c) { - '+', '-' => { - state = State.FloatExponentNumber; - }, - else => { - // reinterpret as a normal exponent number - self.index -= 1; - state = State.FloatExponentNumber; - } - }, - State.FloatExponentNumber => switch (c) { - '0'...'9', 'a'...'f', 'A'...'F' => {}, - else => break, - }, - } - } - result.end = self.index; - // TODO check state when returning EOF - return result; - } - - pub fn getTokenSlice(self: &const Tokenizer, token: &const Token) -> []const u8 { - return self.buffer[token.start..token.end]; - } -}; - -const Comptime = enum { No, Yes }; -const NoAlias = enum { No, Yes }; -const Extern = enum { No, Yes }; -const VarArgs = enum { No, Yes }; -const Mutability = enum { Const, Var }; -const Volatile = enum { No, Yes }; - -const Inline = enum { - Auto, - Always, - Never, -}; - -const Visibility = enum { - Private, - Pub, - Export, -}; - -const CallingConvention = enum { - Auto, - C, - Cold, - Naked, - Stdcall, -}; - -const AstNode = struct { - id: Id, - - const Id = enum { - Root, - VarDecl, - Identifier, - FnProto, - ParamDecl, - AddrOfExpr, - Block, - }; - - fn iterate(base: &AstNode, index: usize) -> ?&AstNode { - return switch (base.id) { - Id.Root => @fieldParentPtr(AstNodeRoot, "base", base).iterate(index), - Id.VarDecl => @fieldParentPtr(AstNodeVarDecl, "base", base).iterate(index), - Id.Identifier => @fieldParentPtr(AstNodeIdentifier, "base", base).iterate(index), - Id.FnProto => @fieldParentPtr(AstNodeFnProto, "base", base).iterate(index), - Id.ParamDecl => @fieldParentPtr(AstNodeParamDecl, "base", base).iterate(index), - Id.AddrOfExpr => @fieldParentPtr(AstNodeAddrOfExpr, "base", base).iterate(index), - Id.Block => @fieldParentPtr(AstNodeBlock, "base", base).iterate(index), - }; - } - - fn destroy(base: &AstNode, allocator: &mem.Allocator) { - return switch (base.id) { - Id.Root => allocator.destroy(@fieldParentPtr(AstNodeRoot, "base", base)), - Id.VarDecl => allocator.destroy(@fieldParentPtr(AstNodeVarDecl, "base", base)), - Id.Identifier => allocator.destroy(@fieldParentPtr(AstNodeIdentifier, "base", base)), - Id.FnProto => allocator.destroy(@fieldParentPtr(AstNodeFnProto, "base", base)), - Id.ParamDecl => allocator.destroy(@fieldParentPtr(AstNodeParamDecl, "base", base)), - Id.AddrOfExpr => allocator.destroy(@fieldParentPtr(AstNodeAddrOfExpr, "base", base)), - Id.Block => allocator.destroy(@fieldParentPtr(AstNodeBlock, "base", base)), - }; - } -}; - -const AstNodeRoot = struct { - base: AstNode, - decls: ArrayList(&AstNode), - - fn iterate(self: &AstNodeRoot, index: usize) -> ?&AstNode { - if (index < self.decls.len) { - return self.decls.items[self.decls.len - index - 1]; - } - return null; - } -}; - -const AstNodeVarDecl = struct { - base: AstNode, - visib_token: ?Token, - name_token: Token, - eq_token: Token, - mut_token: Token, - comptime_token: ?Token, - extern_token: ?Token, - lib_name: ?&AstNode, - type_node: ?&AstNode, - align_node: ?&AstNode, - init_node: ?&AstNode, - - fn iterate(self: &AstNodeVarDecl, index: usize) -> ?&AstNode { - var i = index; - - if (self.type_node) |type_node| { - if (i < 1) return type_node; - i -= 1; - } - - if (self.align_node) |align_node| { - if (i < 1) return align_node; - i -= 1; - } - - if (self.init_node) |init_node| { - if (i < 1) return init_node; - i -= 1; - } - - return null; - } -}; - -const AstNodeIdentifier = struct { - base: AstNode, - name_token: Token, - - fn iterate(self: &AstNodeIdentifier, index: usize) -> ?&AstNode { - return null; - } -}; - -const AstNodeFnProto = struct { - base: AstNode, - visib_token: ?Token, - fn_token: Token, - name_token: ?Token, - params: ArrayList(&AstNode), - return_type: ?&AstNode, - var_args_token: ?Token, - extern_token: ?Token, - inline_token: ?Token, - cc_token: ?Token, - body_node: ?&AstNode, - lib_name: ?&AstNode, // populated if this is an extern declaration - align_expr: ?&AstNode, // populated if align(A) is present - - fn iterate(self: &AstNodeFnProto, index: usize) -> ?&AstNode { - var i = index; - - if (self.body_node) |body_node| { - if (i < 1) return body_node; - i -= 1; - } - - if (self.return_type) |return_type| { - if (i < 1) return return_type; - i -= 1; - } - - if (self.align_expr) |align_expr| { - if (i < 1) return align_expr; - i -= 1; - } - - if (i < self.params.len) return self.params.items[self.params.len - i - 1]; - i -= self.params.len; - - if (self.lib_name) |lib_name| { - if (i < 1) return lib_name; - i -= 1; - } - - return null; - } -}; - -const AstNodeParamDecl = struct { - base: AstNode, - comptime_token: ?Token, - noalias_token: ?Token, - name_token: ?Token, - type_node: &AstNode, - var_args_token: ?Token, - - fn iterate(self: &AstNodeParamDecl, index: usize) -> ?&AstNode { - var i = index; - - if (i < 1) return self.type_node; - i -= 1; - - return null; - } -}; - -const AstNodeAddrOfExpr = struct { - base: AstNode, - op_token: Token, - align_expr: ?&AstNode, - bit_offset_start_token: ?Token, - bit_offset_end_token: ?Token, - const_token: ?Token, - volatile_token: ?Token, - op_expr: &AstNode, - - fn iterate(self: &AstNodeAddrOfExpr, index: usize) -> ?&AstNode { - var i = index; - - if (self.align_expr) |align_expr| { - if (i < 1) return align_expr; - i -= 1; - } - - if (i < 1) return self.op_expr; - i -= 1; - - return null; - } -}; - -const AstNodeBlock = struct { - base: AstNode, - begin_token: Token, - end_token: Token, - statements: ArrayList(&AstNode), - - fn iterate(self: &AstNodeBlock, index: usize) -> ?&AstNode { - var i = index; - - if (i < self.statements.len) return self.statements.items[i]; - i -= self.statements.len; - - return null; - } -}; - -error ParseError; - -const Parser = struct { - allocator: &mem.Allocator, - tokenizer: &Tokenizer, - put_back_tokens: [2]Token, - put_back_count: usize, - source_file_name: []const u8, - - // This memory contents are used only during a function call. It's used to repurpose memory; - // specifically so that freeAst can be guaranteed to succeed. - const utility_bytes_align = @alignOf( union { a: RenderAstFrame, b: State, c: RenderState } ); - utility_bytes: []align(utility_bytes_align) u8, - - fn initUtilityArrayList(self: &Parser, comptime T: type) -> ArrayList(T) { - const new_byte_count = self.utility_bytes.len - self.utility_bytes.len % @sizeOf(T); - self.utility_bytes = self.allocator.alignedShrink(u8, utility_bytes_align, self.utility_bytes, new_byte_count); - const typed_slice = ([]T)(self.utility_bytes); - return ArrayList(T).fromOwnedSlice(self.allocator, typed_slice); - } - - fn deinitUtilityArrayList(self: &Parser, list: var) { - self.utility_bytes = ([]align(utility_bytes_align) u8)(list.toOwnedSlice()); - } - - pub fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator, source_file_name: []const u8) -> Parser { - return Parser { - .allocator = allocator, - .tokenizer = tokenizer, - .put_back_tokens = undefined, - .put_back_count = 0, - .source_file_name = source_file_name, - .utility_bytes = []align(utility_bytes_align) u8{}, - }; - } - - pub fn deinit(self: &Parser) { - self.allocator.free(self.utility_bytes); - } - - const TopLevelDeclCtx = struct { - visib_token: ?Token, - extern_token: ?Token, - }; - - const DestPtr = union(enum) { - Field: &&AstNode, - NullableField: &?&AstNode, - List: &ArrayList(&AstNode), - - pub fn store(self: &const DestPtr, value: &AstNode) -> %void { - switch (*self) { - DestPtr.Field => |ptr| *ptr = value, - DestPtr.NullableField => |ptr| *ptr = value, - DestPtr.List => |list| %return list.append(value), - } - } - }; - - const State = union(enum) { - TopLevel, - TopLevelExtern: ?Token, - TopLevelDecl: TopLevelDeclCtx, - Expression: DestPtr, - GroupedExpression: DestPtr, - UnwrapExpression: DestPtr, - BoolOrExpression: DestPtr, - BoolAndExpression: DestPtr, - ComparisonExpression: DestPtr, - BinaryOrExpression: DestPtr, - BinaryXorExpression: DestPtr, - BinaryAndExpression: DestPtr, - BitShiftExpression: DestPtr, - AdditionExpression: DestPtr, - MultiplyExpression: DestPtr, - BraceSuffixExpression: DestPtr, - PrefixOpExpression: DestPtr, - SuffixOpExpression: DestPtr, - PrimaryExpression: DestPtr, - TypeExpr: DestPtr, - VarDecl: &AstNodeVarDecl, - VarDeclAlign: &AstNodeVarDecl, - VarDeclEq: &AstNodeVarDecl, - ExpectToken: @TagType(Token.Id), - FnProto: &AstNodeFnProto, - FnProtoAlign: &AstNodeFnProto, - ParamDecl: &AstNodeFnProto, - ParamDeclComma, - FnDef: &AstNodeFnProto, - Block: &AstNodeBlock, - Statement: &AstNodeBlock, - }; - - pub fn freeAst(self: &Parser, root_node: &AstNodeRoot) { - // utility_bytes is big enough to do this iteration since we were able to do - // the parsing in the first place - comptime assert(@sizeOf(State) >= @sizeOf(&AstNode)); - - var stack = self.initUtilityArrayList(&AstNode); - defer self.deinitUtilityArrayList(stack); - - stack.append(&root_node.base) %% unreachable; - while (stack.popOrNull()) |node| { - var i: usize = 0; - while (node.iterate(i)) |child| : (i += 1) { - if (child.iterate(0) != null) { - stack.append(child) %% unreachable; - } else { - child.destroy(self.allocator); - } - } - node.destroy(self.allocator); - } - } - - pub fn parse(self: &Parser) -> %&AstNodeRoot { - var stack = self.initUtilityArrayList(State); - defer self.deinitUtilityArrayList(stack); - - const root_node = %return self.createRoot(); - %defer self.allocator.destroy(root_node); - %return stack.append(State.TopLevel); - %defer self.freeAst(root_node); - - while (true) { - //{ - // const token = self.getNextToken(); - // warn("{} ", @tagName(token.id)); - // self.putBackToken(token); - // var i: usize = stack.len; - // while (i != 0) { - // i -= 1; - // warn("{} ", @tagName(stack.items[i])); - // } - // warn("\n"); - //} - - // This gives us 1 free append that can't fail - const state = stack.pop(); - - switch (state) { - State.TopLevel => { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Keyword_pub, Token.Id.Keyword_export => { - stack.append(State { .TopLevelExtern = token }) %% unreachable; - continue; - }, - Token.Id.Eof => return root_node, - else => { - self.putBackToken(token); - // TODO shouldn't need this cast - stack.append(State { .TopLevelExtern = null }) %% unreachable; - continue; - }, - } - }, - State.TopLevelExtern => |visib_token| { - const token = self.getNextToken(); - if (token.id == Token.Id.Keyword_extern) { - stack.append(State { - .TopLevelDecl = TopLevelDeclCtx { - .visib_token = visib_token, - .extern_token = token, - }, - }) %% unreachable; - continue; - } - self.putBackToken(token); - stack.append(State { - .TopLevelDecl = TopLevelDeclCtx { - .visib_token = visib_token, - .extern_token = null, - }, - }) %% unreachable; - continue; - }, - State.TopLevelDecl => |ctx| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Keyword_var, Token.Id.Keyword_const => { - stack.append(State.TopLevel) %% unreachable; - // TODO shouldn't need these casts - const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, ctx.visib_token, - token, (?Token)(null), ctx.extern_token); - %return stack.append(State { .VarDecl = var_decl_node }); - continue; - }, - Token.Id.Keyword_fn => { - stack.append(State.TopLevel) %% unreachable; - // TODO shouldn't need these casts - const fn_proto = %return self.createAttachFnProto(&root_node.decls, token, - ctx.extern_token, (?Token)(null), (?Token)(null), (?Token)(null)); - %return stack.append(State { .FnDef = fn_proto }); - %return stack.append(State { .FnProto = fn_proto }); - continue; - }, - Token.Id.StringLiteral => { - @panic("TODO extern with string literal"); - }, - Token.Id.Keyword_coldcc, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { - stack.append(State.TopLevel) %% unreachable; - const fn_token = %return self.eatToken(Token.Id.Keyword_fn); - // TODO shouldn't need this cast - const fn_proto = %return self.createAttachFnProto(&root_node.decls, fn_token, - ctx.extern_token, (?Token)(token), (?Token)(null), (?Token)(null)); - %return stack.append(State { .FnDef = fn_proto }); - %return stack.append(State { .FnProto = fn_proto }); - continue; - }, - else => return self.parseError(token, "expected variable declaration or function, found {}", @tagName(token.id)), - } - }, - State.VarDecl => |var_decl| { - var_decl.name_token = %return self.eatToken(Token.Id.Identifier); - stack.append(State { .VarDeclAlign = var_decl }) %% unreachable; - - const next_token = self.getNextToken(); - if (next_token.id == Token.Id.Colon) { - %return stack.append(State { .TypeExpr = DestPtr {.NullableField = &var_decl.type_node} }); - continue; - } - - self.putBackToken(next_token); - continue; - }, - State.VarDeclAlign => |var_decl| { - stack.append(State { .VarDeclEq = var_decl }) %% unreachable; - - const next_token = self.getNextToken(); - if (next_token.id == Token.Id.Keyword_align) { - %return stack.append(State { - .GroupedExpression = DestPtr { - .NullableField = &var_decl.align_node - } - }); - continue; - } - - self.putBackToken(next_token); - continue; - }, - State.VarDeclEq => |var_decl| { - const token = self.getNextToken(); - if (token.id == Token.Id.Equal) { - var_decl.eq_token = token; - stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; - %return stack.append(State { - .Expression = DestPtr {.NullableField = &var_decl.init_node}, - }); - continue; - } - if (token.id == Token.Id.Semicolon) { - continue; - } - return self.parseError(token, "expected '=' or ';', found {}", @tagName(token.id)); - }, - State.ExpectToken => |token_id| { - _ = %return self.eatToken(token_id); - continue; - }, - State.Expression => |dest_ptr| { - stack.append(State {.UnwrapExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.UnwrapExpression => |dest_ptr| { - stack.append(State {.BoolOrExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BoolOrExpression => |dest_ptr| { - stack.append(State {.BoolAndExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BoolAndExpression => |dest_ptr| { - stack.append(State {.ComparisonExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.ComparisonExpression => |dest_ptr| { - stack.append(State {.BinaryOrExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BinaryOrExpression => |dest_ptr| { - stack.append(State {.BinaryXorExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BinaryXorExpression => |dest_ptr| { - stack.append(State {.BinaryAndExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BinaryAndExpression => |dest_ptr| { - stack.append(State {.BitShiftExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BitShiftExpression => |dest_ptr| { - stack.append(State {.AdditionExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.AdditionExpression => |dest_ptr| { - stack.append(State {.MultiplyExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.MultiplyExpression => |dest_ptr| { - stack.append(State {.BraceSuffixExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BraceSuffixExpression => |dest_ptr| { - stack.append(State {.PrefixOpExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.PrefixOpExpression => |dest_ptr| { - const first_token = self.getNextToken(); - if (first_token.id == Token.Id.Ampersand) { - const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, first_token); - var token = self.getNextToken(); - if (token.id == Token.Id.Keyword_align) { - @panic("TODO align"); - } - if (token.id == Token.Id.Keyword_const) { - addr_of_expr.const_token = token; - token = self.getNextToken(); - } - if (token.id == Token.Id.Keyword_volatile) { - addr_of_expr.volatile_token = token; - token = self.getNextToken(); - } - self.putBackToken(token); - stack.append(State { - .PrefixOpExpression = DestPtr { .Field = &addr_of_expr.op_expr}, - }) %% unreachable; - continue; - } - - self.putBackToken(first_token); - stack.append(State { .SuffixOpExpression = dest_ptr }) %% unreachable; - continue; - }, - - State.SuffixOpExpression => |dest_ptr| { - stack.append(State { .PrimaryExpression = dest_ptr }) %% unreachable; - continue; - }, - - State.PrimaryExpression => |dest_ptr| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Identifier => { - const identifier = %return self.createIdentifier(token); - %return dest_ptr.store(&identifier.base); - continue; - }, - else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), - } - }, - - State.TypeExpr => |dest_ptr| { - const token = self.getNextToken(); - if (token.id == Token.Id.Keyword_var) { - @panic("TODO param with type var"); - } - self.putBackToken(token); - - stack.append(State { .PrefixOpExpression = dest_ptr }) %% unreachable; - continue; - }, - - State.FnProto => |fn_proto| { - stack.append(State { .FnProtoAlign = fn_proto }) %% unreachable; - %return stack.append(State { .ParamDecl = fn_proto }); - %return stack.append(State { .ExpectToken = Token.Id.LParen }); - - const next_token = self.getNextToken(); - if (next_token.id == Token.Id.Identifier) { - fn_proto.name_token = next_token; - continue; - } - self.putBackToken(next_token); - continue; - }, - - State.FnProtoAlign => |fn_proto| { - const token = self.getNextToken(); - if (token.id == Token.Id.Keyword_align) { - @panic("TODO fn proto align"); - } - if (token.id == Token.Id.Arrow) { - stack.append(State { - .TypeExpr = DestPtr {.NullableField = &fn_proto.return_type}, - }) %% unreachable; - continue; - } else { - self.putBackToken(token); - continue; - } - }, - - State.ParamDecl => |fn_proto| { - var token = self.getNextToken(); - if (token.id == Token.Id.RParen) { - continue; - } - const param_decl = %return self.createAttachParamDecl(&fn_proto.params); - if (token.id == Token.Id.Keyword_comptime) { - param_decl.comptime_token = token; - token = self.getNextToken(); - } else if (token.id == Token.Id.Keyword_noalias) { - param_decl.noalias_token = token; - token = self.getNextToken(); - }; - if (token.id == Token.Id.Identifier) { - const next_token = self.getNextToken(); - if (next_token.id == Token.Id.Colon) { - param_decl.name_token = token; - token = self.getNextToken(); - } else { - self.putBackToken(next_token); - } - } - if (token.id == Token.Id.Ellipsis3) { - param_decl.var_args_token = token; - stack.append(State { .ExpectToken = Token.Id.RParen }) %% unreachable; - continue; - } else { - self.putBackToken(token); - } - - stack.append(State { .ParamDecl = fn_proto }) %% unreachable; - %return stack.append(State.ParamDeclComma); - %return stack.append(State { - .TypeExpr = DestPtr {.Field = ¶m_decl.type_node} - }); - continue; - }, - - State.ParamDeclComma => { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.RParen => { - _ = stack.pop(); // pop off the ParamDecl - continue; - }, - Token.Id.Comma => continue, - else => return self.parseError(token, "expected ',' or ')', found {}", @tagName(token.id)), - } - }, - - State.FnDef => |fn_proto| { - const token = self.getNextToken(); - switch(token.id) { - Token.Id.LBrace => { - const block = %return self.createBlock(token); - fn_proto.body_node = &block.base; - stack.append(State { .Block = block }) %% unreachable; - continue; - }, - Token.Id.Semicolon => continue, - else => return self.parseError(token, "expected ';' or '{{', found {}", @tagName(token.id)), - } - }, - - State.Block => |block| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.RBrace => { - block.end_token = token; - continue; - }, - else => { - self.putBackToken(token); - stack.append(State { .Block = block }) %% unreachable; - %return stack.append(State { .Statement = block }); - continue; - }, - } - }, - - State.Statement => |block| { - { - // Look for comptime var, comptime const - const comptime_token = self.getNextToken(); - if (comptime_token.id == Token.Id.Keyword_comptime) { - const mut_token = self.getNextToken(); - if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) { - // TODO shouldn't need these casts - const var_decl = %return self.createAttachVarDecl(&block.statements, (?Token)(null), - mut_token, (?Token)(comptime_token), (?Token)(null)); - %return stack.append(State { .VarDecl = var_decl }); - continue; - } - self.putBackToken(mut_token); - } - self.putBackToken(comptime_token); - } - { - // Look for const, var - const mut_token = self.getNextToken(); - if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) { - // TODO shouldn't need these casts - const var_decl = %return self.createAttachVarDecl(&block.statements, (?Token)(null), - mut_token, (?Token)(null), (?Token)(null)); - %return stack.append(State { .VarDecl = var_decl }); - continue; - } - self.putBackToken(mut_token); - } - - stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; - %return stack.append(State { .Expression = DestPtr{.List = &block.statements} }); - continue; - }, - - State.GroupedExpression => @panic("TODO"), - } - unreachable; - } - } - - fn createRoot(self: &Parser) -> %&AstNodeRoot { - const node = %return self.allocator.create(AstNodeRoot); - %defer self.allocator.destroy(node); - - *node = AstNodeRoot { - .base = AstNode {.id = AstNode.Id.Root}, - .decls = ArrayList(&AstNode).init(self.allocator), - }; - return node; - } - - fn createVarDecl(self: &Parser, visib_token: &const ?Token, mut_token: &const Token, comptime_token: &const ?Token, - extern_token: &const ?Token) -> %&AstNodeVarDecl - { - const node = %return self.allocator.create(AstNodeVarDecl); - %defer self.allocator.destroy(node); - - *node = AstNodeVarDecl { - .base = AstNode {.id = AstNode.Id.VarDecl}, - .visib_token = *visib_token, - .mut_token = *mut_token, - .comptime_token = *comptime_token, - .extern_token = *extern_token, - .type_node = null, - .align_node = null, - .init_node = null, - .lib_name = null, - // initialized later - .name_token = undefined, - .eq_token = undefined, - }; - return node; - } - - fn createIdentifier(self: &Parser, name_token: &const Token) -> %&AstNodeIdentifier { - const node = %return self.allocator.create(AstNodeIdentifier); - %defer self.allocator.destroy(node); - - *node = AstNodeIdentifier { - .base = AstNode {.id = AstNode.Id.Identifier}, - .name_token = *name_token, - }; - return node; - } - - fn createFnProto(self: &Parser, fn_token: &const Token, extern_token: &const ?Token, - cc_token: &const ?Token, visib_token: &const ?Token, inline_token: &const ?Token) -> %&AstNodeFnProto - { - const node = %return self.allocator.create(AstNodeFnProto); - %defer self.allocator.destroy(node); - - *node = AstNodeFnProto { - .base = AstNode {.id = AstNode.Id.FnProto}, - .visib_token = *visib_token, - .name_token = null, - .fn_token = *fn_token, - .params = ArrayList(&AstNode).init(self.allocator), - .return_type = null, - .var_args_token = null, - .extern_token = *extern_token, - .inline_token = *inline_token, - .cc_token = *cc_token, - .body_node = null, - .lib_name = null, - .align_expr = null, - }; - return node; - } - - fn createParamDecl(self: &Parser) -> %&AstNodeParamDecl { - const node = %return self.allocator.create(AstNodeParamDecl); - %defer self.allocator.destroy(node); - - *node = AstNodeParamDecl { - .base = AstNode {.id = AstNode.Id.ParamDecl}, - .comptime_token = null, - .noalias_token = null, - .name_token = null, - .type_node = undefined, - .var_args_token = null, - }; - return node; - } - - fn createAddrOfExpr(self: &Parser, op_token: &const Token) -> %&AstNodeAddrOfExpr { - const node = %return self.allocator.create(AstNodeAddrOfExpr); - %defer self.allocator.destroy(node); - - *node = AstNodeAddrOfExpr { - .base = AstNode {.id = AstNode.Id.AddrOfExpr}, - .align_expr = null, - .op_token = *op_token, - .bit_offset_start_token = null, - .bit_offset_end_token = null, - .const_token = null, - .volatile_token = null, - .op_expr = undefined, - }; - return node; - } - - fn createBlock(self: &Parser, begin_token: &const Token) -> %&AstNodeBlock { - const node = %return self.allocator.create(AstNodeBlock); - %defer self.allocator.destroy(node); - - *node = AstNodeBlock { - .base = AstNode {.id = AstNode.Id.Block}, - .begin_token = *begin_token, - .end_token = undefined, - .statements = ArrayList(&AstNode).init(self.allocator), - }; - return node; - } - - fn createAttachAddrOfExpr(self: &Parser, dest_ptr: &const DestPtr, op_token: &const Token) -> %&AstNodeAddrOfExpr { - const node = %return self.createAddrOfExpr(op_token); - %defer self.allocator.destroy(node); - %return dest_ptr.store(&node.base); - return node; - } - - fn createAttachParamDecl(self: &Parser, list: &ArrayList(&AstNode)) -> %&AstNodeParamDecl { - const node = %return self.createParamDecl(); - %defer self.allocator.destroy(node); - %return list.append(&node.base); - return node; - } - - fn createAttachFnProto(self: &Parser, list: &ArrayList(&AstNode), fn_token: &const Token, - extern_token: &const ?Token, cc_token: &const ?Token, visib_token: &const ?Token, - inline_token: &const ?Token) -> %&AstNodeFnProto - { - const node = %return self.createFnProto(fn_token, extern_token, cc_token, visib_token, inline_token); - %defer self.allocator.destroy(node); - %return list.append(&node.base); - return node; - } - - fn createAttachVarDecl(self: &Parser, list: &ArrayList(&AstNode), visib_token: &const ?Token, - mut_token: &const Token, comptime_token: &const ?Token, extern_token: &const ?Token) -> %&AstNodeVarDecl - { - const node = %return self.createVarDecl(visib_token, mut_token, comptime_token, extern_token); - %defer self.allocator.destroy(node); - %return list.append(&node.base); - return node; - } - - fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) -> error { - const loc = self.tokenizer.getTokenLocation(token); - warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args); - warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]); - { - var i: usize = 0; - while (i < loc.column) : (i += 1) { - warn(" "); - } - } - { - const caret_count = token.end - token.start; - var i: usize = 0; - while (i < caret_count) : (i += 1) { - warn("~"); - } - } - warn("\n"); - return error.ParseError; - } - - fn expectToken(self: &Parser, token: &const Token, id: @TagType(Token.Id)) -> %void { - if (token.id != id) { - return self.parseError(token, "expected {}, found {}", @tagName(id), @tagName(token.id)); - } - } - - fn eatToken(self: &Parser, id: @TagType(Token.Id)) -> %Token { - const token = self.getNextToken(); - %return self.expectToken(token, id); - return token; - } - - fn putBackToken(self: &Parser, token: &const Token) { - self.put_back_tokens[self.put_back_count] = *token; - self.put_back_count += 1; - } - - fn getNextToken(self: &Parser) -> Token { - return if (self.put_back_count != 0) { - const put_back_index = self.put_back_count - 1; - const put_back_token = self.put_back_tokens[put_back_index]; - self.put_back_count = put_back_index; - put_back_token - } else { - self.tokenizer.next() - }; - } - - const RenderAstFrame = struct { - node: &AstNode, - indent: usize, - }; - - pub fn renderAst(self: &Parser, stream: &std.io.OutStream, root_node: &AstNodeRoot) -> %void { - var stack = self.initUtilityArrayList(RenderAstFrame); - defer self.deinitUtilityArrayList(stack); - - %return stack.append(RenderAstFrame { - .node = &root_node.base, - .indent = 0, - }); - - while (stack.popOrNull()) |frame| { - { - var i: usize = 0; - while (i < frame.indent) : (i += 1) { - %return stream.print(" "); - } - } - %return stream.print("{}\n", @tagName(frame.node.id)); - var child_i: usize = 0; - while (frame.node.iterate(child_i)) |child| : (child_i += 1) { - %return stack.append(RenderAstFrame { - .node = child, - .indent = frame.indent + 2, - }); - } - } - } - - - pub const RenderState = union(enum) { - TopLevelDecl: &AstNode, - FnProtoRParen: &AstNodeFnProto, - ParamDecl: &AstNode, - Text: []const u8, - Expression: &AstNode, - AddrOfExprBit: &AstNodeAddrOfExpr, - VarDecl: &AstNodeVarDecl, - VarDeclAlign: &AstNodeVarDecl, - Statement: &AstNode, - PrintIndent, - Indent: usize, - }; - - pub fn renderSource(self: &Parser, stream: &std.io.OutStream, root_node: &AstNodeRoot) -> %void { - var stack = self.initUtilityArrayList(RenderState); - defer self.deinitUtilityArrayList(stack); - - { - var i = root_node.decls.len; - while (i != 0) { - i -= 1; - const decl = root_node.decls.items[i]; - %return stack.append(RenderState {.TopLevelDecl = decl}); - } - } - - const indent_delta = 4; - var indent: usize = 0; - while (stack.popOrNull()) |state| { - switch (state) { - RenderState.TopLevelDecl => |decl| { - switch (decl.id) { - AstNode.Id.FnProto => { - const fn_proto = @fieldParentPtr(AstNodeFnProto, "base", decl); - if (fn_proto.visib_token) |visib_token| { - switch (visib_token.id) { - Token.Id.Keyword_pub => %return stream.print("pub "), - Token.Id.Keyword_export => %return stream.print("export "), - else => unreachable, - }; - } - if (fn_proto.extern_token) |extern_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); - } - %return stream.print("fn"); - - if (fn_proto.name_token) |name_token| { - %return stream.print(" {}", self.tokenizer.getTokenSlice(name_token)); - } - - %return stream.print("("); - - %return stack.append(RenderState { .Text = "\n" }); - if (fn_proto.body_node == null) { - %return stack.append(RenderState { .Text = ";" }); - } - - %return stack.append(RenderState { .FnProtoRParen = fn_proto}); - var i = fn_proto.params.len; - while (i != 0) { - i -= 1; - const param_decl_node = fn_proto.params.items[i]; - %return stack.append(RenderState { .ParamDecl = param_decl_node}); - if (i != 0) { - %return stack.append(RenderState { .Text = ", " }); - } - } - }, - AstNode.Id.VarDecl => { - const var_decl = @fieldParentPtr(AstNodeVarDecl, "base", decl); - %return stack.append(RenderState { .Text = "\n"}); - %return stack.append(RenderState { .VarDecl = var_decl}); - - }, - else => unreachable, - } - }, - - RenderState.VarDecl => |var_decl| { - if (var_decl.visib_token) |visib_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); - } - if (var_decl.extern_token) |extern_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); - if (var_decl.lib_name != null) { - @panic("TODO"); - } - } - if (var_decl.comptime_token) |comptime_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); - } - %return stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token)); - %return stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token)); - - %return stack.append(RenderState { .VarDeclAlign = var_decl }); - if (var_decl.type_node) |type_node| { - %return stream.print(": "); - %return stack.append(RenderState { .Expression = type_node }); - } - }, - - RenderState.VarDeclAlign => |var_decl| { - if (var_decl.align_node != null) { - @panic("TODO"); - } - %return stack.append(RenderState { .Text = ";" }); - if (var_decl.init_node) |init_node| { - %return stream.print(" = "); - %return stack.append(RenderState { .Expression = init_node }); - } - }, - - RenderState.ParamDecl => |base| { - const param_decl = @fieldParentPtr(AstNodeParamDecl, "base", base); - if (param_decl.comptime_token) |comptime_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); - } - if (param_decl.noalias_token) |noalias_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(noalias_token)); - } - if (param_decl.name_token) |name_token| { - %return stream.print("{}: ", self.tokenizer.getTokenSlice(name_token)); - } - if (param_decl.var_args_token) |var_args_token| { - %return stream.print("{}", self.tokenizer.getTokenSlice(var_args_token)); - } else { - %return stack.append(RenderState { .Expression = param_decl.type_node}); - } - }, - RenderState.Text => |bytes| { - %return stream.write(bytes); - }, - RenderState.Expression => |base| switch (base.id) { - AstNode.Id.Identifier => { - const identifier = @fieldParentPtr(AstNodeIdentifier, "base", base); - %return stream.print("{}", self.tokenizer.getTokenSlice(identifier.name_token)); - }, - AstNode.Id.AddrOfExpr => { - const addr_of_expr = @fieldParentPtr(AstNodeAddrOfExpr, "base", base); - %return stream.print("{}", self.tokenizer.getTokenSlice(addr_of_expr.op_token)); - %return stack.append(RenderState { .AddrOfExprBit = addr_of_expr}); - - if (addr_of_expr.align_expr) |align_expr| { - %return stream.print("align("); - %return stack.append(RenderState { .Text = ")"}); - %return stack.append(RenderState { .Expression = align_expr}); - } - }, - AstNode.Id.Block => { - const block = @fieldParentPtr(AstNodeBlock, "base", base); - %return stream.write("{"); - %return stack.append(RenderState { .Text = "}"}); - %return stack.append(RenderState.PrintIndent); - %return stack.append(RenderState { .Indent = indent}); - %return stack.append(RenderState { .Text = "\n"}); - var i = block.statements.len; - while (i != 0) { - i -= 1; - const statement_node = block.statements.items[i]; - %return stack.append(RenderState { .Statement = statement_node}); - %return stack.append(RenderState.PrintIndent); - %return stack.append(RenderState { .Indent = indent + indent_delta}); - %return stack.append(RenderState { .Text = "\n" }); - } - }, - else => unreachable, - }, - RenderState.AddrOfExprBit => |addr_of_expr| { - if (addr_of_expr.bit_offset_start_token) |bit_offset_start_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_start_token)); - } - if (addr_of_expr.bit_offset_end_token) |bit_offset_end_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_end_token)); - } - if (addr_of_expr.const_token) |const_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(const_token)); - } - if (addr_of_expr.volatile_token) |volatile_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(volatile_token)); - } - %return stack.append(RenderState { .Expression = addr_of_expr.op_expr}); - }, - RenderState.FnProtoRParen => |fn_proto| { - %return stream.print(")"); - if (fn_proto.align_expr != null) { - @panic("TODO"); - } - if (fn_proto.return_type) |return_type| { - %return stream.print(" -> "); - if (fn_proto.body_node) |body_node| { - %return stack.append(RenderState { .Expression = body_node}); - %return stack.append(RenderState { .Text = " "}); - } - %return stack.append(RenderState { .Expression = return_type}); - } - }, - RenderState.Statement => |base| { - switch (base.id) { - AstNode.Id.VarDecl => { - const var_decl = @fieldParentPtr(AstNodeVarDecl, "base", base); - %return stack.append(RenderState { .VarDecl = var_decl}); - }, - else => unreachable, - } - }, - RenderState.Indent => |new_indent| indent = new_indent, - RenderState.PrintIndent => %return stream.writeByteNTimes(' ', indent), - } - } - } -}; +const Tokenizer = @import("tokenizer.zig").Tokenizer; +const Token = @import("tokenizer.zig").Token; +const Parser = @import("parser.zig").Parser; pub fn main() -> %void { main2() %% |err| { @@ -1795,4 +126,11 @@ test "zig fmt" { \\} \\ ); + + testCanonical( + \\fn foo(argc: c_int, argv: &&u8) -> c_int { + \\ return 0; + \\} + \\ + ); } diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig new file mode 100644 index 0000000000..53f11c0416 --- /dev/null +++ b/src-self-hosted/parser.zig @@ -0,0 +1,1074 @@ +const std = @import("std"); +const assert = std.debug.assert; +const ArrayList = std.ArrayList; +const mem = std.mem; +const ast = @import("ast.zig"); +const Tokenizer = @import("tokenizer.zig").Tokenizer; +const Token = @import("tokenizer.zig").Token; + +// TODO when we make parse errors into error types instead of printing directly, +// get rid of this +const warn = std.debug.warn; + +error ParseError; + +pub const Parser = struct { + allocator: &mem.Allocator, + tokenizer: &Tokenizer, + put_back_tokens: [2]Token, + put_back_count: usize, + source_file_name: []const u8, + + // This memory contents are used only during a function call. It's used to repurpose memory; + // specifically so that freeAst can be guaranteed to succeed. + const utility_bytes_align = @alignOf( union { a: RenderAstFrame, b: State, c: RenderState } ); + utility_bytes: []align(utility_bytes_align) u8, + + pub fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator, source_file_name: []const u8) -> Parser { + return Parser { + .allocator = allocator, + .tokenizer = tokenizer, + .put_back_tokens = undefined, + .put_back_count = 0, + .source_file_name = source_file_name, + .utility_bytes = []align(utility_bytes_align) u8{}, + }; + } + + pub fn deinit(self: &Parser) { + self.allocator.free(self.utility_bytes); + } + + const TopLevelDeclCtx = struct { + visib_token: ?Token, + extern_token: ?Token, + }; + + const DestPtr = union(enum) { + Field: &&ast.Node, + NullableField: &?&ast.Node, + List: &ArrayList(&ast.Node), + + pub fn store(self: &const DestPtr, value: &ast.Node) -> %void { + switch (*self) { + DestPtr.Field => |ptr| *ptr = value, + DestPtr.NullableField => |ptr| *ptr = value, + DestPtr.List => |list| %return list.append(value), + } + } + }; + + const State = union(enum) { + TopLevel, + TopLevelExtern: ?Token, + TopLevelDecl: TopLevelDeclCtx, + Expression: DestPtr, + GroupedExpression: DestPtr, + UnwrapExpression: DestPtr, + BoolOrExpression: DestPtr, + BoolAndExpression: DestPtr, + ComparisonExpression: DestPtr, + BinaryOrExpression: DestPtr, + BinaryXorExpression: DestPtr, + BinaryAndExpression: DestPtr, + BitShiftExpression: DestPtr, + AdditionExpression: DestPtr, + MultiplyExpression: DestPtr, + BraceSuffixExpression: DestPtr, + PrefixOpExpression: DestPtr, + SuffixOpExpression: DestPtr, + PrimaryExpression: DestPtr, + TypeExpr: DestPtr, + VarDecl: &ast.NodeVarDecl, + VarDeclAlign: &ast.NodeVarDecl, + VarDeclEq: &ast.NodeVarDecl, + ExpectToken: @TagType(Token.Id), + FnProto: &ast.NodeFnProto, + FnProtoAlign: &ast.NodeFnProto, + ParamDecl: &ast.NodeFnProto, + ParamDeclComma, + FnDef: &ast.NodeFnProto, + Block: &ast.NodeBlock, + Statement: &ast.NodeBlock, + }; + + pub fn freeAst(self: &Parser, root_node: &ast.NodeRoot) { + // utility_bytes is big enough to do this iteration since we were able to do + // the parsing in the first place + comptime assert(@sizeOf(State) >= @sizeOf(&ast.Node)); + + var stack = self.initUtilityArrayList(&ast.Node); + defer self.deinitUtilityArrayList(stack); + + stack.append(&root_node.base) %% unreachable; + while (stack.popOrNull()) |node| { + var i: usize = 0; + while (node.iterate(i)) |child| : (i += 1) { + if (child.iterate(0) != null) { + stack.append(child) %% unreachable; + } else { + child.destroy(self.allocator); + } + } + node.destroy(self.allocator); + } + } + + pub fn parse(self: &Parser) -> %&ast.NodeRoot { + var stack = self.initUtilityArrayList(State); + defer self.deinitUtilityArrayList(stack); + + const root_node = %return self.createRoot(); + %defer self.allocator.destroy(root_node); + %return stack.append(State.TopLevel); + %defer self.freeAst(root_node); + + while (true) { + //{ + // const token = self.getNextToken(); + // warn("{} ", @tagName(token.id)); + // self.putBackToken(token); + // var i: usize = stack.len; + // while (i != 0) { + // i -= 1; + // warn("{} ", @tagName(stack.items[i])); + // } + // warn("\n"); + //} + + // This gives us 1 free append that can't fail + const state = stack.pop(); + + switch (state) { + State.TopLevel => { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_pub, Token.Id.Keyword_export => { + stack.append(State { .TopLevelExtern = token }) %% unreachable; + continue; + }, + Token.Id.Eof => return root_node, + else => { + self.putBackToken(token); + // TODO shouldn't need this cast + stack.append(State { .TopLevelExtern = null }) %% unreachable; + continue; + }, + } + }, + State.TopLevelExtern => |visib_token| { + const token = self.getNextToken(); + if (token.id == Token.Id.Keyword_extern) { + stack.append(State { + .TopLevelDecl = TopLevelDeclCtx { + .visib_token = visib_token, + .extern_token = token, + }, + }) %% unreachable; + continue; + } + self.putBackToken(token); + stack.append(State { + .TopLevelDecl = TopLevelDeclCtx { + .visib_token = visib_token, + .extern_token = null, + }, + }) %% unreachable; + continue; + }, + State.TopLevelDecl => |ctx| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_var, Token.Id.Keyword_const => { + stack.append(State.TopLevel) %% unreachable; + // TODO shouldn't need these casts + const var_decl_node = %return self.createAttachVarDecl(&root_node.decls, ctx.visib_token, + token, (?Token)(null), ctx.extern_token); + %return stack.append(State { .VarDecl = var_decl_node }); + continue; + }, + Token.Id.Keyword_fn => { + stack.append(State.TopLevel) %% unreachable; + // TODO shouldn't need these casts + const fn_proto = %return self.createAttachFnProto(&root_node.decls, token, + ctx.extern_token, (?Token)(null), (?Token)(null), (?Token)(null)); + %return stack.append(State { .FnDef = fn_proto }); + %return stack.append(State { .FnProto = fn_proto }); + continue; + }, + Token.Id.StringLiteral => { + @panic("TODO extern with string literal"); + }, + Token.Id.Keyword_coldcc, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { + stack.append(State.TopLevel) %% unreachable; + const fn_token = %return self.eatToken(Token.Id.Keyword_fn); + // TODO shouldn't need this cast + const fn_proto = %return self.createAttachFnProto(&root_node.decls, fn_token, + ctx.extern_token, (?Token)(token), (?Token)(null), (?Token)(null)); + %return stack.append(State { .FnDef = fn_proto }); + %return stack.append(State { .FnProto = fn_proto }); + continue; + }, + else => return self.parseError(token, "expected variable declaration or function, found {}", @tagName(token.id)), + } + }, + State.VarDecl => |var_decl| { + var_decl.name_token = %return self.eatToken(Token.Id.Identifier); + stack.append(State { .VarDeclAlign = var_decl }) %% unreachable; + + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Colon) { + %return stack.append(State { .TypeExpr = DestPtr {.NullableField = &var_decl.type_node} }); + continue; + } + + self.putBackToken(next_token); + continue; + }, + State.VarDeclAlign => |var_decl| { + stack.append(State { .VarDeclEq = var_decl }) %% unreachable; + + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Keyword_align) { + %return stack.append(State { + .GroupedExpression = DestPtr { + .NullableField = &var_decl.align_node + } + }); + continue; + } + + self.putBackToken(next_token); + continue; + }, + State.VarDeclEq => |var_decl| { + const token = self.getNextToken(); + if (token.id == Token.Id.Equal) { + var_decl.eq_token = token; + stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; + %return stack.append(State { + .Expression = DestPtr {.NullableField = &var_decl.init_node}, + }); + continue; + } + if (token.id == Token.Id.Semicolon) { + continue; + } + return self.parseError(token, "expected '=' or ';', found {}", @tagName(token.id)); + }, + State.ExpectToken => |token_id| { + _ = %return self.eatToken(token_id); + continue; + }, + State.Expression => |dest_ptr| { + const token = self.getNextToken(); + if (token.id == Token.Id.Keyword_return) { + const return_node = %return self.createAttachReturn(dest_ptr, token); + stack.append(State {.UnwrapExpression = DestPtr {.Field = &return_node.expr} }) %% unreachable; + continue; + } + self.putBackToken(token); + stack.append(State {.UnwrapExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.UnwrapExpression => |dest_ptr| { + stack.append(State {.BoolOrExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.BoolOrExpression => |dest_ptr| { + stack.append(State {.BoolAndExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.BoolAndExpression => |dest_ptr| { + stack.append(State {.ComparisonExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.ComparisonExpression => |dest_ptr| { + stack.append(State {.BinaryOrExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.BinaryOrExpression => |dest_ptr| { + stack.append(State {.BinaryXorExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.BinaryXorExpression => |dest_ptr| { + stack.append(State {.BinaryAndExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.BinaryAndExpression => |dest_ptr| { + stack.append(State {.BitShiftExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.BitShiftExpression => |dest_ptr| { + stack.append(State {.AdditionExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.AdditionExpression => |dest_ptr| { + stack.append(State {.MultiplyExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.MultiplyExpression => |dest_ptr| { + stack.append(State {.BraceSuffixExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.BraceSuffixExpression => |dest_ptr| { + stack.append(State {.PrefixOpExpression = dest_ptr}) %% unreachable; + continue; + }, + + State.PrefixOpExpression => |dest_ptr| { + const first_token = self.getNextToken(); + if (first_token.id == Token.Id.Ampersand) { + const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, first_token); + var token = self.getNextToken(); + if (token.id == Token.Id.Keyword_align) { + @panic("TODO align"); + } + if (token.id == Token.Id.Keyword_const) { + addr_of_expr.const_token = token; + token = self.getNextToken(); + } + if (token.id == Token.Id.Keyword_volatile) { + addr_of_expr.volatile_token = token; + token = self.getNextToken(); + } + self.putBackToken(token); + stack.append(State { + .PrefixOpExpression = DestPtr { .Field = &addr_of_expr.op_expr}, + }) %% unreachable; + continue; + } + + self.putBackToken(first_token); + stack.append(State { .SuffixOpExpression = dest_ptr }) %% unreachable; + continue; + }, + + State.SuffixOpExpression => |dest_ptr| { + stack.append(State { .PrimaryExpression = dest_ptr }) %% unreachable; + continue; + }, + + State.PrimaryExpression => |dest_ptr| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.Identifier => { + _ = %return self.createAttachIdentifier(dest_ptr, token); + continue; + }, + Token.Id.IntegerLiteral => { + _ = %return self.createAttachIntegerLiteral(dest_ptr, token); + continue; + }, + Token.Id.FloatLiteral => { + _ = %return self.createAttachFloatLiteral(dest_ptr, token); + continue; + }, + else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), + } + }, + + State.TypeExpr => |dest_ptr| { + const token = self.getNextToken(); + if (token.id == Token.Id.Keyword_var) { + @panic("TODO param with type var"); + } + self.putBackToken(token); + + stack.append(State { .PrefixOpExpression = dest_ptr }) %% unreachable; + continue; + }, + + State.FnProto => |fn_proto| { + stack.append(State { .FnProtoAlign = fn_proto }) %% unreachable; + %return stack.append(State { .ParamDecl = fn_proto }); + %return stack.append(State { .ExpectToken = Token.Id.LParen }); + + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Identifier) { + fn_proto.name_token = next_token; + continue; + } + self.putBackToken(next_token); + continue; + }, + + State.FnProtoAlign => |fn_proto| { + const token = self.getNextToken(); + if (token.id == Token.Id.Keyword_align) { + @panic("TODO fn proto align"); + } + if (token.id == Token.Id.Arrow) { + stack.append(State { + .TypeExpr = DestPtr {.NullableField = &fn_proto.return_type}, + }) %% unreachable; + continue; + } else { + self.putBackToken(token); + continue; + } + }, + + State.ParamDecl => |fn_proto| { + var token = self.getNextToken(); + if (token.id == Token.Id.RParen) { + continue; + } + const param_decl = %return self.createAttachParamDecl(&fn_proto.params); + if (token.id == Token.Id.Keyword_comptime) { + param_decl.comptime_token = token; + token = self.getNextToken(); + } else if (token.id == Token.Id.Keyword_noalias) { + param_decl.noalias_token = token; + token = self.getNextToken(); + }; + if (token.id == Token.Id.Identifier) { + const next_token = self.getNextToken(); + if (next_token.id == Token.Id.Colon) { + param_decl.name_token = token; + token = self.getNextToken(); + } else { + self.putBackToken(next_token); + } + } + if (token.id == Token.Id.Ellipsis3) { + param_decl.var_args_token = token; + stack.append(State { .ExpectToken = Token.Id.RParen }) %% unreachable; + continue; + } else { + self.putBackToken(token); + } + + stack.append(State { .ParamDecl = fn_proto }) %% unreachable; + %return stack.append(State.ParamDeclComma); + %return stack.append(State { + .TypeExpr = DestPtr {.Field = ¶m_decl.type_node} + }); + continue; + }, + + State.ParamDeclComma => { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.RParen => { + _ = stack.pop(); // pop off the ParamDecl + continue; + }, + Token.Id.Comma => continue, + else => return self.parseError(token, "expected ',' or ')', found {}", @tagName(token.id)), + } + }, + + State.FnDef => |fn_proto| { + const token = self.getNextToken(); + switch(token.id) { + Token.Id.LBrace => { + const block = %return self.createBlock(token); + fn_proto.body_node = &block.base; + stack.append(State { .Block = block }) %% unreachable; + continue; + }, + Token.Id.Semicolon => continue, + else => return self.parseError(token, "expected ';' or '{{', found {}", @tagName(token.id)), + } + }, + + State.Block => |block| { + const token = self.getNextToken(); + switch (token.id) { + Token.Id.RBrace => { + block.end_token = token; + continue; + }, + else => { + self.putBackToken(token); + stack.append(State { .Block = block }) %% unreachable; + %return stack.append(State { .Statement = block }); + continue; + }, + } + }, + + State.Statement => |block| { + { + // Look for comptime var, comptime const + const comptime_token = self.getNextToken(); + if (comptime_token.id == Token.Id.Keyword_comptime) { + const mut_token = self.getNextToken(); + if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) { + // TODO shouldn't need these casts + const var_decl = %return self.createAttachVarDecl(&block.statements, (?Token)(null), + mut_token, (?Token)(comptime_token), (?Token)(null)); + %return stack.append(State { .VarDecl = var_decl }); + continue; + } + self.putBackToken(mut_token); + } + self.putBackToken(comptime_token); + } + { + // Look for const, var + const mut_token = self.getNextToken(); + if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) { + // TODO shouldn't need these casts + const var_decl = %return self.createAttachVarDecl(&block.statements, (?Token)(null), + mut_token, (?Token)(null), (?Token)(null)); + %return stack.append(State { .VarDecl = var_decl }); + continue; + } + self.putBackToken(mut_token); + } + + stack.append(State { .ExpectToken = Token.Id.Semicolon }) %% unreachable; + %return stack.append(State { .Expression = DestPtr{.List = &block.statements} }); + continue; + }, + + State.GroupedExpression => @panic("TODO"), + } + unreachable; + } + } + + fn createRoot(self: &Parser) -> %&ast.NodeRoot { + const node = %return self.allocator.create(ast.NodeRoot); + %defer self.allocator.destroy(node); + + *node = ast.NodeRoot { + .base = ast.Node {.id = ast.Node.Id.Root}, + .decls = ArrayList(&ast.Node).init(self.allocator), + }; + return node; + } + + fn createVarDecl(self: &Parser, visib_token: &const ?Token, mut_token: &const Token, comptime_token: &const ?Token, + extern_token: &const ?Token) -> %&ast.NodeVarDecl + { + const node = %return self.allocator.create(ast.NodeVarDecl); + %defer self.allocator.destroy(node); + + *node = ast.NodeVarDecl { + .base = ast.Node {.id = ast.Node.Id.VarDecl}, + .visib_token = *visib_token, + .mut_token = *mut_token, + .comptime_token = *comptime_token, + .extern_token = *extern_token, + .type_node = null, + .align_node = null, + .init_node = null, + .lib_name = null, + // initialized later + .name_token = undefined, + .eq_token = undefined, + }; + return node; + } + + fn createFnProto(self: &Parser, fn_token: &const Token, extern_token: &const ?Token, + cc_token: &const ?Token, visib_token: &const ?Token, inline_token: &const ?Token) -> %&ast.NodeFnProto + { + const node = %return self.allocator.create(ast.NodeFnProto); + %defer self.allocator.destroy(node); + + *node = ast.NodeFnProto { + .base = ast.Node {.id = ast.Node.Id.FnProto}, + .visib_token = *visib_token, + .name_token = null, + .fn_token = *fn_token, + .params = ArrayList(&ast.Node).init(self.allocator), + .return_type = null, + .var_args_token = null, + .extern_token = *extern_token, + .inline_token = *inline_token, + .cc_token = *cc_token, + .body_node = null, + .lib_name = null, + .align_expr = null, + }; + return node; + } + + fn createParamDecl(self: &Parser) -> %&ast.NodeParamDecl { + const node = %return self.allocator.create(ast.NodeParamDecl); + %defer self.allocator.destroy(node); + + *node = ast.NodeParamDecl { + .base = ast.Node {.id = ast.Node.Id.ParamDecl}, + .comptime_token = null, + .noalias_token = null, + .name_token = null, + .type_node = undefined, + .var_args_token = null, + }; + return node; + } + + fn createAddrOfExpr(self: &Parser, op_token: &const Token) -> %&ast.NodeAddrOfExpr { + const node = %return self.allocator.create(ast.NodeAddrOfExpr); + %defer self.allocator.destroy(node); + + *node = ast.NodeAddrOfExpr { + .base = ast.Node {.id = ast.Node.Id.AddrOfExpr}, + .align_expr = null, + .op_token = *op_token, + .bit_offset_start_token = null, + .bit_offset_end_token = null, + .const_token = null, + .volatile_token = null, + .op_expr = undefined, + }; + return node; + } + + fn createBlock(self: &Parser, begin_token: &const Token) -> %&ast.NodeBlock { + const node = %return self.allocator.create(ast.NodeBlock); + %defer self.allocator.destroy(node); + + *node = ast.NodeBlock { + .base = ast.Node {.id = ast.Node.Id.Block}, + .begin_token = *begin_token, + .end_token = undefined, + .statements = ArrayList(&ast.Node).init(self.allocator), + }; + return node; + } + + fn createReturn(self: &Parser, return_token: &const Token) -> %&ast.NodeReturn { + const node = %return self.allocator.create(ast.NodeReturn); + %defer self.allocator.destroy(node); + + *node = ast.NodeReturn { + .base = ast.Node {.id = ast.Node.Id.Return}, + .return_token = *return_token, + .expr = undefined, + }; + return node; + } + + fn createIdentifier(self: &Parser, name_token: &const Token) -> %&ast.NodeIdentifier { + const node = %return self.allocator.create(ast.NodeIdentifier); + %defer self.allocator.destroy(node); + + *node = ast.NodeIdentifier { + .base = ast.Node {.id = ast.Node.Id.Identifier}, + .name_token = *name_token, + }; + return node; + } + + fn createIntegerLiteral(self: &Parser, token: &const Token) -> %&ast.NodeIntegerLiteral { + const node = %return self.allocator.create(ast.NodeIntegerLiteral); + %defer self.allocator.destroy(node); + + *node = ast.NodeIntegerLiteral { + .base = ast.Node {.id = ast.Node.Id.IntegerLiteral}, + .token = *token, + }; + return node; + } + + fn createFloatLiteral(self: &Parser, token: &const Token) -> %&ast.NodeFloatLiteral { + const node = %return self.allocator.create(ast.NodeFloatLiteral); + %defer self.allocator.destroy(node); + + *node = ast.NodeFloatLiteral { + .base = ast.Node {.id = ast.Node.Id.FloatLiteral}, + .token = *token, + }; + return node; + } + + fn createAttachFloatLiteral(self: &Parser, dest_ptr: &const DestPtr, token: &const Token) -> %&ast.NodeFloatLiteral { + const node = %return self.createFloatLiteral(token); + %defer self.allocator.destroy(node); + %return dest_ptr.store(&node.base); + return node; + } + + fn createAttachIntegerLiteral(self: &Parser, dest_ptr: &const DestPtr, token: &const Token) -> %&ast.NodeIntegerLiteral { + const node = %return self.createIntegerLiteral(token); + %defer self.allocator.destroy(node); + %return dest_ptr.store(&node.base); + return node; + } + + fn createAttachIdentifier(self: &Parser, dest_ptr: &const DestPtr, name_token: &const Token) -> %&ast.NodeIdentifier { + const node = %return self.createIdentifier(name_token); + %defer self.allocator.destroy(node); + %return dest_ptr.store(&node.base); + return node; + } + + fn createAttachReturn(self: &Parser, dest_ptr: &const DestPtr, return_token: &const Token) -> %&ast.NodeReturn { + const node = %return self.createReturn(return_token); + %defer self.allocator.destroy(node); + %return dest_ptr.store(&node.base); + return node; + } + + fn createAttachAddrOfExpr(self: &Parser, dest_ptr: &const DestPtr, op_token: &const Token) -> %&ast.NodeAddrOfExpr { + const node = %return self.createAddrOfExpr(op_token); + %defer self.allocator.destroy(node); + %return dest_ptr.store(&node.base); + return node; + } + + fn createAttachParamDecl(self: &Parser, list: &ArrayList(&ast.Node)) -> %&ast.NodeParamDecl { + const node = %return self.createParamDecl(); + %defer self.allocator.destroy(node); + %return list.append(&node.base); + return node; + } + + fn createAttachFnProto(self: &Parser, list: &ArrayList(&ast.Node), fn_token: &const Token, + extern_token: &const ?Token, cc_token: &const ?Token, visib_token: &const ?Token, + inline_token: &const ?Token) -> %&ast.NodeFnProto + { + const node = %return self.createFnProto(fn_token, extern_token, cc_token, visib_token, inline_token); + %defer self.allocator.destroy(node); + %return list.append(&node.base); + return node; + } + + fn createAttachVarDecl(self: &Parser, list: &ArrayList(&ast.Node), visib_token: &const ?Token, + mut_token: &const Token, comptime_token: &const ?Token, extern_token: &const ?Token) -> %&ast.NodeVarDecl + { + const node = %return self.createVarDecl(visib_token, mut_token, comptime_token, extern_token); + %defer self.allocator.destroy(node); + %return list.append(&node.base); + return node; + } + + fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) -> error { + const loc = self.tokenizer.getTokenLocation(token); + warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args); + warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]); + { + var i: usize = 0; + while (i < loc.column) : (i += 1) { + warn(" "); + } + } + { + const caret_count = token.end - token.start; + var i: usize = 0; + while (i < caret_count) : (i += 1) { + warn("~"); + } + } + warn("\n"); + return error.ParseError; + } + + fn expectToken(self: &Parser, token: &const Token, id: @TagType(Token.Id)) -> %void { + if (token.id != id) { + return self.parseError(token, "expected {}, found {}", @tagName(id), @tagName(token.id)); + } + } + + fn eatToken(self: &Parser, id: @TagType(Token.Id)) -> %Token { + const token = self.getNextToken(); + %return self.expectToken(token, id); + return token; + } + + fn putBackToken(self: &Parser, token: &const Token) { + self.put_back_tokens[self.put_back_count] = *token; + self.put_back_count += 1; + } + + fn getNextToken(self: &Parser) -> Token { + return if (self.put_back_count != 0) { + const put_back_index = self.put_back_count - 1; + const put_back_token = self.put_back_tokens[put_back_index]; + self.put_back_count = put_back_index; + put_back_token + } else { + self.tokenizer.next() + }; + } + + const RenderAstFrame = struct { + node: &ast.Node, + indent: usize, + }; + + pub fn renderAst(self: &Parser, stream: &std.io.OutStream, root_node: &ast.NodeRoot) -> %void { + var stack = self.initUtilityArrayList(RenderAstFrame); + defer self.deinitUtilityArrayList(stack); + + %return stack.append(RenderAstFrame { + .node = &root_node.base, + .indent = 0, + }); + + while (stack.popOrNull()) |frame| { + { + var i: usize = 0; + while (i < frame.indent) : (i += 1) { + %return stream.print(" "); + } + } + %return stream.print("{}\n", @tagName(frame.node.id)); + var child_i: usize = 0; + while (frame.node.iterate(child_i)) |child| : (child_i += 1) { + %return stack.append(RenderAstFrame { + .node = child, + .indent = frame.indent + 2, + }); + } + } + } + + const RenderState = union(enum) { + TopLevelDecl: &ast.Node, + FnProtoRParen: &ast.NodeFnProto, + ParamDecl: &ast.Node, + Text: []const u8, + Expression: &ast.Node, + AddrOfExprBit: &ast.NodeAddrOfExpr, + VarDecl: &ast.NodeVarDecl, + VarDeclAlign: &ast.NodeVarDecl, + Statement: &ast.Node, + PrintIndent, + Indent: usize, + }; + + pub fn renderSource(self: &Parser, stream: &std.io.OutStream, root_node: &ast.NodeRoot) -> %void { + var stack = self.initUtilityArrayList(RenderState); + defer self.deinitUtilityArrayList(stack); + + { + var i = root_node.decls.len; + while (i != 0) { + i -= 1; + const decl = root_node.decls.items[i]; + %return stack.append(RenderState {.TopLevelDecl = decl}); + } + } + + const indent_delta = 4; + var indent: usize = 0; + while (stack.popOrNull()) |state| { + switch (state) { + RenderState.TopLevelDecl => |decl| { + switch (decl.id) { + ast.Node.Id.FnProto => { + const fn_proto = @fieldParentPtr(ast.NodeFnProto, "base", decl); + if (fn_proto.visib_token) |visib_token| { + switch (visib_token.id) { + Token.Id.Keyword_pub => %return stream.print("pub "), + Token.Id.Keyword_export => %return stream.print("export "), + else => unreachable, + }; + } + if (fn_proto.extern_token) |extern_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); + } + %return stream.print("fn"); + + if (fn_proto.name_token) |name_token| { + %return stream.print(" {}", self.tokenizer.getTokenSlice(name_token)); + } + + %return stream.print("("); + + %return stack.append(RenderState { .Text = "\n" }); + if (fn_proto.body_node == null) { + %return stack.append(RenderState { .Text = ";" }); + } + + %return stack.append(RenderState { .FnProtoRParen = fn_proto}); + var i = fn_proto.params.len; + while (i != 0) { + i -= 1; + const param_decl_node = fn_proto.params.items[i]; + %return stack.append(RenderState { .ParamDecl = param_decl_node}); + if (i != 0) { + %return stack.append(RenderState { .Text = ", " }); + } + } + }, + ast.Node.Id.VarDecl => { + const var_decl = @fieldParentPtr(ast.NodeVarDecl, "base", decl); + %return stack.append(RenderState { .Text = "\n"}); + %return stack.append(RenderState { .VarDecl = var_decl}); + + }, + else => unreachable, + } + }, + + RenderState.VarDecl => |var_decl| { + if (var_decl.visib_token) |visib_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); + } + if (var_decl.extern_token) |extern_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token)); + if (var_decl.lib_name != null) { + @panic("TODO"); + } + } + if (var_decl.comptime_token) |comptime_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); + } + %return stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token)); + %return stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token)); + + %return stack.append(RenderState { .VarDeclAlign = var_decl }); + if (var_decl.type_node) |type_node| { + %return stream.print(": "); + %return stack.append(RenderState { .Expression = type_node }); + } + }, + + RenderState.VarDeclAlign => |var_decl| { + if (var_decl.align_node != null) { + @panic("TODO"); + } + %return stack.append(RenderState { .Text = ";" }); + if (var_decl.init_node) |init_node| { + %return stream.print(" = "); + %return stack.append(RenderState { .Expression = init_node }); + } + }, + + RenderState.ParamDecl => |base| { + const param_decl = @fieldParentPtr(ast.NodeParamDecl, "base", base); + if (param_decl.comptime_token) |comptime_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); + } + if (param_decl.noalias_token) |noalias_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(noalias_token)); + } + if (param_decl.name_token) |name_token| { + %return stream.print("{}: ", self.tokenizer.getTokenSlice(name_token)); + } + if (param_decl.var_args_token) |var_args_token| { + %return stream.print("{}", self.tokenizer.getTokenSlice(var_args_token)); + } else { + %return stack.append(RenderState { .Expression = param_decl.type_node}); + } + }, + RenderState.Text => |bytes| { + %return stream.write(bytes); + }, + RenderState.Expression => |base| switch (base.id) { + ast.Node.Id.Identifier => { + const identifier = @fieldParentPtr(ast.NodeIdentifier, "base", base); + %return stream.print("{}", self.tokenizer.getTokenSlice(identifier.name_token)); + }, + ast.Node.Id.AddrOfExpr => { + const addr_of_expr = @fieldParentPtr(ast.NodeAddrOfExpr, "base", base); + %return stream.print("{}", self.tokenizer.getTokenSlice(addr_of_expr.op_token)); + %return stack.append(RenderState { .AddrOfExprBit = addr_of_expr}); + + if (addr_of_expr.align_expr) |align_expr| { + %return stream.print("align("); + %return stack.append(RenderState { .Text = ")"}); + %return stack.append(RenderState { .Expression = align_expr}); + } + }, + ast.Node.Id.Block => { + const block = @fieldParentPtr(ast.NodeBlock, "base", base); + %return stream.write("{"); + %return stack.append(RenderState { .Text = "}"}); + %return stack.append(RenderState.PrintIndent); + %return stack.append(RenderState { .Indent = indent}); + %return stack.append(RenderState { .Text = "\n"}); + var i = block.statements.len; + while (i != 0) { + i -= 1; + const statement_node = block.statements.items[i]; + %return stack.append(RenderState { .Statement = statement_node}); + %return stack.append(RenderState.PrintIndent); + %return stack.append(RenderState { .Indent = indent + indent_delta}); + %return stack.append(RenderState { .Text = "\n" }); + } + }, + ast.Node.Id.Return => { + const return_node = @fieldParentPtr(ast.NodeReturn, "base", base); + %return stream.write("return "); + %return stack.append(RenderState { .Expression = return_node.expr }); + }, + ast.Node.Id.IntegerLiteral => { + const integer_literal = @fieldParentPtr(ast.NodeIntegerLiteral, "base", base); + %return stream.print("{}", self.tokenizer.getTokenSlice(integer_literal.token)); + }, + ast.Node.Id.FloatLiteral => { + const float_literal = @fieldParentPtr(ast.NodeFloatLiteral, "base", base); + %return stream.print("{}", self.tokenizer.getTokenSlice(float_literal.token)); + }, + else => unreachable, + }, + RenderState.AddrOfExprBit => |addr_of_expr| { + if (addr_of_expr.bit_offset_start_token) |bit_offset_start_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_start_token)); + } + if (addr_of_expr.bit_offset_end_token) |bit_offset_end_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_end_token)); + } + if (addr_of_expr.const_token) |const_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(const_token)); + } + if (addr_of_expr.volatile_token) |volatile_token| { + %return stream.print("{} ", self.tokenizer.getTokenSlice(volatile_token)); + } + %return stack.append(RenderState { .Expression = addr_of_expr.op_expr}); + }, + RenderState.FnProtoRParen => |fn_proto| { + %return stream.print(")"); + if (fn_proto.align_expr != null) { + @panic("TODO"); + } + if (fn_proto.return_type) |return_type| { + %return stream.print(" -> "); + if (fn_proto.body_node) |body_node| { + %return stack.append(RenderState { .Expression = body_node}); + %return stack.append(RenderState { .Text = " "}); + } + %return stack.append(RenderState { .Expression = return_type}); + } + }, + RenderState.Statement => |base| { + switch (base.id) { + ast.Node.Id.VarDecl => { + const var_decl = @fieldParentPtr(ast.NodeVarDecl, "base", base); + %return stack.append(RenderState { .VarDecl = var_decl}); + }, + else => { + %return stack.append(RenderState { .Text = ";"}); + %return stack.append(RenderState { .Expression = base}); + }, + } + }, + RenderState.Indent => |new_indent| indent = new_indent, + RenderState.PrintIndent => %return stream.writeByteNTimes(' ', indent), + } + } + } + + fn initUtilityArrayList(self: &Parser, comptime T: type) -> ArrayList(T) { + const new_byte_count = self.utility_bytes.len - self.utility_bytes.len % @sizeOf(T); + self.utility_bytes = self.allocator.alignedShrink(u8, utility_bytes_align, self.utility_bytes, new_byte_count); + const typed_slice = ([]T)(self.utility_bytes); + return ArrayList(T).fromOwnedSlice(self.allocator, typed_slice); + } + + fn deinitUtilityArrayList(self: &Parser, list: var) { + self.utility_bytes = ([]align(utility_bytes_align) u8)(list.toOwnedSlice()); + } + +}; + diff --git a/src-self-hosted/tokenizer.zig b/src-self-hosted/tokenizer.zig new file mode 100644 index 0000000000..5d32a83af7 --- /dev/null +++ b/src-self-hosted/tokenizer.zig @@ -0,0 +1,479 @@ +const std = @import("std"); +const mem = std.mem; + +pub const Token = struct { + id: Id, + start: usize, + end: usize, + + const KeywordId = struct { + bytes: []const u8, + id: Id, + }; + + const keywords = []KeywordId { + KeywordId{.bytes="align", .id = Id.Keyword_align}, + KeywordId{.bytes="and", .id = Id.Keyword_and}, + KeywordId{.bytes="asm", .id = Id.Keyword_asm}, + KeywordId{.bytes="break", .id = Id.Keyword_break}, + KeywordId{.bytes="coldcc", .id = Id.Keyword_coldcc}, + KeywordId{.bytes="comptime", .id = Id.Keyword_comptime}, + KeywordId{.bytes="const", .id = Id.Keyword_const}, + KeywordId{.bytes="continue", .id = Id.Keyword_continue}, + KeywordId{.bytes="defer", .id = Id.Keyword_defer}, + KeywordId{.bytes="else", .id = Id.Keyword_else}, + KeywordId{.bytes="enum", .id = Id.Keyword_enum}, + KeywordId{.bytes="error", .id = Id.Keyword_error}, + KeywordId{.bytes="export", .id = Id.Keyword_export}, + KeywordId{.bytes="extern", .id = Id.Keyword_extern}, + KeywordId{.bytes="false", .id = Id.Keyword_false}, + KeywordId{.bytes="fn", .id = Id.Keyword_fn}, + KeywordId{.bytes="for", .id = Id.Keyword_for}, + KeywordId{.bytes="goto", .id = Id.Keyword_goto}, + KeywordId{.bytes="if", .id = Id.Keyword_if}, + KeywordId{.bytes="inline", .id = Id.Keyword_inline}, + KeywordId{.bytes="nakedcc", .id = Id.Keyword_nakedcc}, + KeywordId{.bytes="noalias", .id = Id.Keyword_noalias}, + KeywordId{.bytes="null", .id = Id.Keyword_null}, + KeywordId{.bytes="or", .id = Id.Keyword_or}, + KeywordId{.bytes="packed", .id = Id.Keyword_packed}, + KeywordId{.bytes="pub", .id = Id.Keyword_pub}, + KeywordId{.bytes="return", .id = Id.Keyword_return}, + KeywordId{.bytes="stdcallcc", .id = Id.Keyword_stdcallcc}, + KeywordId{.bytes="struct", .id = Id.Keyword_struct}, + KeywordId{.bytes="switch", .id = Id.Keyword_switch}, + KeywordId{.bytes="test", .id = Id.Keyword_test}, + KeywordId{.bytes="this", .id = Id.Keyword_this}, + KeywordId{.bytes="true", .id = Id.Keyword_true}, + KeywordId{.bytes="undefined", .id = Id.Keyword_undefined}, + KeywordId{.bytes="union", .id = Id.Keyword_union}, + KeywordId{.bytes="unreachable", .id = Id.Keyword_unreachable}, + KeywordId{.bytes="use", .id = Id.Keyword_use}, + KeywordId{.bytes="var", .id = Id.Keyword_var}, + KeywordId{.bytes="volatile", .id = Id.Keyword_volatile}, + KeywordId{.bytes="while", .id = Id.Keyword_while}, + }; + + fn getKeyword(bytes: []const u8) -> ?Id { + for (keywords) |kw| { + if (mem.eql(u8, kw.bytes, bytes)) { + return kw.id; + } + } + return null; + } + + const StrLitKind = enum {Normal, C}; + + pub const Id = union(enum) { + Invalid, + Identifier, + StringLiteral: StrLitKind, + Eof, + Builtin, + Equal, + LParen, + RParen, + Semicolon, + Percent, + LBrace, + RBrace, + Period, + Ellipsis2, + Ellipsis3, + Minus, + Arrow, + Colon, + Slash, + Comma, + Ampersand, + AmpersandEqual, + IntegerLiteral, + FloatLiteral, + Keyword_align, + Keyword_and, + Keyword_asm, + Keyword_break, + Keyword_coldcc, + Keyword_comptime, + Keyword_const, + Keyword_continue, + Keyword_defer, + Keyword_else, + Keyword_enum, + Keyword_error, + Keyword_export, + Keyword_extern, + Keyword_false, + Keyword_fn, + Keyword_for, + Keyword_goto, + Keyword_if, + Keyword_inline, + Keyword_nakedcc, + Keyword_noalias, + Keyword_null, + Keyword_or, + Keyword_packed, + Keyword_pub, + Keyword_return, + Keyword_stdcallcc, + Keyword_struct, + Keyword_switch, + Keyword_test, + Keyword_this, + Keyword_true, + Keyword_undefined, + Keyword_union, + Keyword_unreachable, + Keyword_use, + Keyword_var, + Keyword_volatile, + Keyword_while, + }; +}; + +pub const Tokenizer = struct { + buffer: []const u8, + index: usize, + + pub const Location = struct { + line: usize, + column: usize, + line_start: usize, + line_end: usize, + }; + + pub fn getTokenLocation(self: &Tokenizer, token: &const Token) -> Location { + var loc = Location { + .line = 0, + .column = 0, + .line_start = 0, + .line_end = 0, + }; + for (self.buffer) |c, i| { + if (i == token.start) { + loc.line_end = i; + while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {} + return loc; + } + if (c == '\n') { + loc.line += 1; + loc.column = 0; + loc.line_start = i + 1; + } else { + loc.column += 1; + } + } + return loc; + } + + /// For debugging purposes + pub fn dump(self: &Tokenizer, token: &const Token) { + std.debug.warn("{} \"{}\"\n", @tagName(token.id), self.buffer[token.start..token.end]); + } + + pub fn init(buffer: []const u8) -> Tokenizer { + return Tokenizer { + .buffer = buffer, + .index = 0, + }; + } + + const State = enum { + Start, + Identifier, + Builtin, + C, + StringLiteral, + StringLiteralBackslash, + Minus, + Slash, + LineComment, + Zero, + IntegerLiteral, + NumberDot, + FloatFraction, + FloatExponentUnsigned, + FloatExponentNumber, + Ampersand, + Period, + Period2, + }; + + pub fn next(self: &Tokenizer) -> Token { + var state = State.Start; + var result = Token { + .id = Token.Id.Eof, + .start = self.index, + .end = undefined, + }; + while (self.index < self.buffer.len) : (self.index += 1) { + const c = self.buffer[self.index]; + switch (state) { + State.Start => switch (c) { + ' ', '\n' => { + result.start = self.index + 1; + }, + 'c' => { + state = State.C; + result.id = Token.Id.Identifier; + }, + '"' => { + state = State.StringLiteral; + result.id = Token.Id { .StringLiteral = Token.StrLitKind.Normal }; + }, + 'a'...'b', 'd'...'z', 'A'...'Z', '_' => { + state = State.Identifier; + result.id = Token.Id.Identifier; + }, + '@' => { + state = State.Builtin; + result.id = Token.Id.Builtin; + }, + '=' => { + result.id = Token.Id.Equal; + self.index += 1; + break; + }, + '(' => { + result.id = Token.Id.LParen; + self.index += 1; + break; + }, + ')' => { + result.id = Token.Id.RParen; + self.index += 1; + break; + }, + ';' => { + result.id = Token.Id.Semicolon; + self.index += 1; + break; + }, + ',' => { + result.id = Token.Id.Comma; + self.index += 1; + break; + }, + ':' => { + result.id = Token.Id.Colon; + self.index += 1; + break; + }, + '%' => { + result.id = Token.Id.Percent; + self.index += 1; + break; + }, + '{' => { + result.id = Token.Id.LBrace; + self.index += 1; + break; + }, + '}' => { + result.id = Token.Id.RBrace; + self.index += 1; + break; + }, + '.' => { + state = State.Period; + }, + '-' => { + state = State.Minus; + }, + '/' => { + state = State.Slash; + }, + '&' => { + state = State.Ampersand; + }, + '0' => { + state = State.Zero; + result.id = Token.Id.IntegerLiteral; + }, + '1'...'9' => { + state = State.IntegerLiteral; + result.id = Token.Id.IntegerLiteral; + }, + else => { + result.id = Token.Id.Invalid; + self.index += 1; + break; + }, + }, + State.Ampersand => switch (c) { + '=' => { + result.id = Token.Id.AmpersandEqual; + self.index += 1; + break; + }, + else => { + result.id = Token.Id.Ampersand; + break; + }, + }, + State.Identifier => switch (c) { + 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + else => { + if (Token.getKeyword(self.buffer[result.start..self.index])) |id| { + result.id = id; + } + break; + }, + }, + State.Builtin => switch (c) { + 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + else => break, + }, + State.C => switch (c) { + '\\' => @panic("TODO"), + '"' => { + state = State.StringLiteral; + result.id = Token.Id { .StringLiteral = Token.StrLitKind.C }; + }, + 'a'...'z', 'A'...'Z', '_', '0'...'9' => { + state = State.Identifier; + }, + else => break, + }, + State.StringLiteral => switch (c) { + '\\' => { + state = State.StringLiteralBackslash; + }, + '"' => { + self.index += 1; + break; + }, + '\n' => break, // Look for this error later. + else => {}, + }, + + State.StringLiteralBackslash => switch (c) { + '\n' => break, // Look for this error later. + else => { + state = State.StringLiteral; + }, + }, + + State.Minus => switch (c) { + '>' => { + result.id = Token.Id.Arrow; + self.index += 1; + break; + }, + else => { + result.id = Token.Id.Minus; + break; + }, + }, + + State.Period => switch (c) { + '.' => { + state = State.Period2; + }, + else => { + result.id = Token.Id.Period; + break; + }, + }, + + State.Period2 => switch (c) { + '.' => { + result.id = Token.Id.Ellipsis3; + self.index += 1; + break; + }, + else => { + result.id = Token.Id.Ellipsis2; + break; + }, + }, + + State.Slash => switch (c) { + '/' => { + result.id = undefined; + state = State.LineComment; + }, + else => { + result.id = Token.Id.Slash; + break; + }, + }, + State.LineComment => switch (c) { + '\n' => { + state = State.Start; + result = Token { + .id = Token.Id.Eof, + .start = self.index + 1, + .end = undefined, + }; + }, + else => {}, + }, + State.Zero => switch (c) { + 'b', 'o', 'x' => { + state = State.IntegerLiteral; + }, + else => { + // reinterpret as a normal number + self.index -= 1; + state = State.IntegerLiteral; + }, + }, + State.IntegerLiteral => switch (c) { + '.' => { + state = State.NumberDot; + }, + 'p', 'P', 'e', 'E' => { + state = State.FloatExponentUnsigned; + }, + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => break, + }, + State.NumberDot => switch (c) { + '.' => { + self.index -= 1; + state = State.Start; + break; + }, + else => { + self.index -= 1; + result.id = Token.Id.FloatLiteral; + state = State.FloatFraction; + }, + }, + State.FloatFraction => switch (c) { + 'p', 'P', 'e', 'E' => { + state = State.FloatExponentUnsigned; + }, + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => break, + }, + State.FloatExponentUnsigned => switch (c) { + '+', '-' => { + state = State.FloatExponentNumber; + }, + else => { + // reinterpret as a normal exponent number + self.index -= 1; + state = State.FloatExponentNumber; + } + }, + State.FloatExponentNumber => switch (c) { + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => break, + }, + } + } + result.end = self.index; + // TODO check state when returning EOF + return result; + } + + pub fn getTokenSlice(self: &const Tokenizer, token: &const Token) -> []const u8 { + return self.buffer[token.start..token.end]; + } +}; + + From c4e7d05ce37d60141afab997f23df33f5d4a218b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Dec 2017 17:27:31 -0500 Subject: [PATCH 37/69] refactor debug.global_allocator into mem.FixedBufferAllocator --- src-self-hosted/main.zig | 6 ++++-- std/debug.zig | 41 +++--------------------------------- std/mem.zig | 45 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index bba16dfb3f..0912e7d5f1 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -66,9 +66,11 @@ pub fn main2() -> %void { } +var fixed_buffer_mem: [100 * 1024]u8 = undefined; + fn testCanonical(source: []const u8) { - const allocator = std.debug.global_allocator; - std.debug.global_allocator_index = 0; + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + const allocator = &fixed_allocator.allocator; var tokenizer = Tokenizer.init(source); var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); diff --git a/std/debug.zig b/std/debug.zig index 3e7a38d043..25d6c84e0b 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -965,41 +965,6 @@ fn readILeb128(in_stream: &io.InStream) -> %i64 { } } -pub const global_allocator = &global_allocator_state; - -var global_allocator_state = mem.Allocator { - .allocFn = globalAlloc, - .reallocFn = globalRealloc, - .freeFn = globalFree, -}; - -pub var global_allocator_mem: [100 * 1024]u8 = undefined; -pub var global_allocator_index: usize = 0; - -error OutOfMemory; - -fn globalAlloc(self: &mem.Allocator, n: usize, alignment: u29) -> %[]u8 { - const addr = @ptrToInt(&global_allocator_mem[global_allocator_index]); - const rem = @rem(addr, alignment); - const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); - const adjusted_index = global_allocator_index + march_forward_bytes; - const end_index = adjusted_index + n; - if (end_index > global_allocator_mem.len) { - return error.OutOfMemory; - } - const result = global_allocator_mem[adjusted_index .. end_index]; - global_allocator_index = end_index; - return result; -} - -fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { - if (new_size <= old_mem.len) { - return old_mem[0..new_size]; - } else { - const result = %return globalAlloc(self, new_size, alignment); - @memcpy(result.ptr, old_mem.ptr, old_mem.len); - return result; - } -} - -fn globalFree(self: &mem.Allocator, memory: []u8) { } +pub const global_allocator = &global_fixed_allocator.allocator; +var global_fixed_allocator = mem.FixedBufferAllocator.init(global_allocator_mem[0..]); +var global_allocator_mem: [100 * 1024]u8 = undefined; diff --git a/std/mem.zig b/std/mem.zig index eec29e9caf..4f7609dd94 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -105,6 +105,51 @@ pub const Allocator = struct { } }; +pub const FixedBufferAllocator = struct { + allocator: Allocator, + end_index: usize, + buffer: []u8, + + pub fn init(buffer: []u8) -> FixedBufferAllocator { + return FixedBufferAllocator { + .allocator = Allocator { + .allocFn = alloc, + .reallocFn = realloc, + .freeFn = free, + }, + .buffer = buffer, + .end_index = 0, + }; + } + + fn alloc(allocator: &Allocator, n: usize, alignment: u29) -> %[]u8 { + const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); + const addr = @ptrToInt(&self.buffer[self.end_index]); + const rem = @rem(addr, alignment); + const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); + const adjusted_index = self.end_index + march_forward_bytes; + const new_end_index = adjusted_index + n; + if (new_end_index > self.buffer.len) { + return error.OutOfMemory; + } + const result = self.buffer[adjusted_index .. new_end_index]; + self.end_index = new_end_index; + return result; + } + + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { + if (new_size <= old_mem.len) { + return old_mem[0..new_size]; + } else { + const result = %return alloc(allocator, new_size, alignment); + copy(u8, result, old_mem); + return result; + } + } + + fn free(allocator: &Allocator, bytes: []u8) { } +}; + /// Copy all of source into dest at position 0. /// dest.len must be >= source.len. From ed4d94a5d54bc49b3661d602301a5ec926abef61 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Dec 2017 21:12:47 -0500 Subject: [PATCH 38/69] self-hosted: test all out of memory conditions --- src-self-hosted/main.zig | 49 +++++++++++++++++++++++---------- src-self-hosted/parser.zig | 35 ++++++++++++++++++++---- std/debug.zig | 56 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 20 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 0912e7d5f1..8a7ce3786a 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -8,6 +8,7 @@ const warn = std.debug.warn; const Tokenizer = @import("tokenizer.zig").Tokenizer; const Token = @import("tokenizer.zig").Token; const Parser = @import("parser.zig").Parser; +const assert = std.debug.assert; pub fn main() -> %void { main2() %% |err| { @@ -68,28 +69,48 @@ pub fn main2() -> %void { var fixed_buffer_mem: [100 * 1024]u8 = undefined; -fn testCanonical(source: []const u8) { - var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - const allocator = &fixed_allocator.allocator; - +fn testParse(source: []const u8, allocator: &mem.Allocator) -> %[]u8 { var tokenizer = Tokenizer.init(source); var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); defer parser.deinit(); - const root_node = parser.parse() %% unreachable; + const root_node = %return parser.parse(); defer parser.freeAst(root_node); - var buffer = std.Buffer.initSize(allocator, 0) %% unreachable; + var buffer = %return std.Buffer.initSize(allocator, 0); var buffer_out_stream = io.BufferOutStream.init(&buffer); - parser.renderSource(&buffer_out_stream.stream, root_node) %% unreachable; + %return parser.renderSource(&buffer_out_stream.stream, root_node); + return buffer.toOwnedSlice(); +} - if (!mem.eql(u8, buffer.toSliceConst(), source)) { - warn("\n====== expected this output: =========\n"); - warn("{}", source); - warn("\n======== instead found this: =========\n"); - warn("{}", buffer.toSliceConst()); - warn("\n======================================\n"); - @panic("test failed"); +fn testCanonical(source: []const u8) { + const needed_alloc_count = { + // Try it once with unlimited memory, make sure it works + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, @maxValue(usize)); + const result_source = testParse(source, &failing_allocator.allocator) %% @panic("test failed"); + if (!mem.eql(u8, result_source, source)) { + warn("\n====== expected this output: =========\n"); + warn("{}", source); + warn("\n======== instead found this: =========\n"); + warn("{}", result_source); + warn("\n======================================\n"); + @panic("test failed"); + } + failing_allocator.allocator.free(result_source); + failing_allocator.index + }; + + var fail_index = needed_alloc_count; + while (fail_index != 0) { + fail_index -= 1; + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index); + if (testParse(source, &failing_allocator.allocator)) |_| { + @panic("non-deterministic memory usage"); + } else |err| { + assert(err == error.OutOfMemory); + } } } diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig index 53f11c0416..6f1f00646a 100644 --- a/src-self-hosted/parser.zig +++ b/src-self-hosted/parser.zig @@ -18,6 +18,7 @@ pub const Parser = struct { put_back_tokens: [2]Token, put_back_count: usize, source_file_name: []const u8, + cleanup_root_node: ?&ast.NodeRoot, // This memory contents are used only during a function call. It's used to repurpose memory; // specifically so that freeAst can be guaranteed to succeed. @@ -32,10 +33,12 @@ pub const Parser = struct { .put_back_count = 0, .source_file_name = source_file_name, .utility_bytes = []align(utility_bytes_align) u8{}, + .cleanup_root_node = null, }; } pub fn deinit(self: &Parser) { + assert(self.cleanup_root_node == null); self.allocator.free(self.utility_bytes); } @@ -115,13 +118,29 @@ pub const Parser = struct { } pub fn parse(self: &Parser) -> %&ast.NodeRoot { + const result = self.parseInner() %% |err| { + if (self.cleanup_root_node) |root_node| { + self.freeAst(root_node); + } + err + }; + self.cleanup_root_node = null; + return result; + } + + pub fn parseInner(self: &Parser) -> %&ast.NodeRoot { var stack = self.initUtilityArrayList(State); defer self.deinitUtilityArrayList(stack); - const root_node = %return self.createRoot(); - %defer self.allocator.destroy(root_node); - %return stack.append(State.TopLevel); - %defer self.freeAst(root_node); + const root_node = { + const root_node = %return self.createRoot(); + %defer self.allocator.destroy(root_node); + // This stack append has to succeed for freeAst to work + %return stack.append(State.TopLevel); + root_node + }; + assert(self.cleanup_root_node == null); + self.cleanup_root_node = root_node; while (true) { //{ @@ -1063,11 +1082,15 @@ pub const Parser = struct { const new_byte_count = self.utility_bytes.len - self.utility_bytes.len % @sizeOf(T); self.utility_bytes = self.allocator.alignedShrink(u8, utility_bytes_align, self.utility_bytes, new_byte_count); const typed_slice = ([]T)(self.utility_bytes); - return ArrayList(T).fromOwnedSlice(self.allocator, typed_slice); + return ArrayList(T) { + .allocator = self.allocator, + .items = typed_slice, + .len = 0, + }; } fn deinitUtilityArrayList(self: &Parser, list: var) { - self.utility_bytes = ([]align(utility_bytes_align) u8)(list.toOwnedSlice()); + self.utility_bytes = ([]align(utility_bytes_align) u8)(list.items); } }; diff --git a/std/debug.zig b/std/debug.zig index 25d6c84e0b..f5768ef199 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -968,3 +968,59 @@ fn readILeb128(in_stream: &io.InStream) -> %i64 { pub const global_allocator = &global_fixed_allocator.allocator; var global_fixed_allocator = mem.FixedBufferAllocator.init(global_allocator_mem[0..]); var global_allocator_mem: [100 * 1024]u8 = undefined; + +/// Allocator that fails after N allocations, useful for making sure out of +/// memory conditions are handled correctly. +pub const FailingAllocator = struct { + allocator: mem.Allocator, + index: usize, + fail_index: usize, + internal_allocator: &mem.Allocator, + allocated_bytes: usize, + + pub fn init(allocator: &mem.Allocator, fail_index: usize) -> FailingAllocator { + return FailingAllocator { + .internal_allocator = allocator, + .fail_index = fail_index, + .index = 0, + .allocated_bytes = 0, + .allocator = mem.Allocator { + .allocFn = alloc, + .reallocFn = realloc, + .freeFn = free, + }, + }; + } + + fn alloc(allocator: &mem.Allocator, n: usize, alignment: u29) -> %[]u8 { + const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); + if (self.index == self.fail_index) { + return error.OutOfMemory; + } + self.index += 1; + const result = %return self.internal_allocator.allocFn(self.internal_allocator, n, alignment); + self.allocated_bytes += result.len; + return result; + } + + fn realloc(allocator: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { + const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); + if (new_size <= old_mem.len) { + self.allocated_bytes -= old_mem.len - new_size; + return self.internal_allocator.reallocFn(self.internal_allocator, old_mem, new_size, alignment); + } + if (self.index == self.fail_index) { + return error.OutOfMemory; + } + self.index += 1; + const result = %return self.internal_allocator.reallocFn(self.internal_allocator, old_mem, new_size, alignment); + self.allocated_bytes += new_size - old_mem.len; + return result; + } + + fn free(allocator: &mem.Allocator, bytes: []u8) { + const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); + self.allocated_bytes -= bytes.len; + return self.internal_allocator.freeFn(self.internal_allocator, bytes); + } +}; From 23058d8b435266852d4ca69ee89f8c3d27f52e24 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Dec 2017 23:34:59 -0500 Subject: [PATCH 39/69] self-hosted: link with LLVM --- build.zig | 181 ++++++++++++++++++++++++++++++++++++- src-self-hosted/c.zig | 7 ++ src-self-hosted/main.zig | 97 ++------------------ src-self-hosted/parser.zig | 95 +++++++++++++++++++ src-self-hosted/target.zig | 9 ++ std/build.zig | 18 +++- 6 files changed, 313 insertions(+), 94 deletions(-) create mode 100644 src-self-hosted/c.zig create mode 100644 src-self-hosted/target.zig diff --git a/build.zig b/build.zig index 84c3b05eda..a97142b090 100644 --- a/build.zig +++ b/build.zig @@ -1,6 +1,13 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); +const Builder = std.build.Builder; const tests = @import("test/tests.zig"); -const os = @import("std").os; +const os = std.os; +const BufMap = std.BufMap; +const warn = std.debug.warn; +const mem = std.mem; +const ArrayList = std.ArrayList; +const Buffer = std.Buffer; +const io = std.io; pub fn build(b: &Builder) { const mode = b.standardReleaseOptions(); @@ -28,6 +35,7 @@ pub fn build(b: &Builder) { var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); exe.setBuildMode(mode); exe.linkSystemLibrary("c"); + dependOnLib(exe, findLLVM(b)); b.default_step.dependOn(&exe.step); b.default_step.dependOn(docs_step); @@ -64,3 +72,172 @@ pub fn build(b: &Builder) { test_step.dependOn(tests.addDebugSafetyTests(b, test_filter)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); } + +fn dependOnLib(lib_exe_obj: &std.build.LibExeObjStep, dep: &const LibraryDep) { + for (dep.libdirs.toSliceConst()) |lib_dir| { + lib_exe_obj.addLibPath(lib_dir); + } + for (dep.libs.toSliceConst()) |lib| { + lib_exe_obj.linkSystemLibrary(lib); + } + for (dep.includes.toSliceConst()) |include_path| { + lib_exe_obj.addIncludeDir(include_path); + } +} + +const LibraryDep = struct { + libdirs: ArrayList([]const u8), + libs: ArrayList([]const u8), + includes: ArrayList([]const u8), +}; + +fn findLLVM(b: &Builder) -> LibraryDep { + const libs_output = { + const args1 = [][]const u8{"llvm-config-5.0", "--libs", "--system-libs"}; + const args2 = [][]const u8{"llvm-config", "--libs", "--system-libs"}; + const max_output_size = 10 * 1024; + const good_result = exec(b.allocator, args1, null, null, max_output_size) %% |err| { + if (err == error.FileNotFound) { + exec(b.allocator, args2, null, null, max_output_size) %% |err2| { + std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); + } + } else { + std.debug.panic("unable to spawn {}: {}\n", args1[0], err); + } + }; + switch (good_result.term) { + os.ChildProcess.Term.Exited => |code| { + if (code != 0) { + std.debug.panic("llvm-config exited with {}:\n{}\n", code, good_result.stderr); + } + }, + else => { + std.debug.panic("llvm-config failed:\n{}\n", good_result.stderr); + }, + } + good_result.stdout + }; + const includes_output = { + const args1 = [][]const u8{"llvm-config-5.0", "--includedir"}; + const args2 = [][]const u8{"llvm-config", "--includedir"}; + const max_output_size = 10 * 1024; + const good_result = exec(b.allocator, args1, null, null, max_output_size) %% |err| { + if (err == error.FileNotFound) { + exec(b.allocator, args2, null, null, max_output_size) %% |err2| { + std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); + } + } else { + std.debug.panic("unable to spawn {}: {}\n", args1[0], err); + } + }; + switch (good_result.term) { + os.ChildProcess.Term.Exited => |code| { + if (code != 0) { + std.debug.panic("llvm-config --includedir exited with {}:\n{}\n", code, good_result.stderr); + } + }, + else => { + std.debug.panic("llvm-config failed:\n{}\n", good_result.stderr); + }, + } + good_result.stdout + }; + const libdir_output = { + const args1 = [][]const u8{"llvm-config-5.0", "--libdir"}; + const args2 = [][]const u8{"llvm-config", "--libdir"}; + const max_output_size = 10 * 1024; + const good_result = exec(b.allocator, args1, null, null, max_output_size) %% |err| { + if (err == error.FileNotFound) { + exec(b.allocator, args2, null, null, max_output_size) %% |err2| { + std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); + } + } else { + std.debug.panic("unable to spawn {}: {}\n", args1[0], err); + } + }; + switch (good_result.term) { + os.ChildProcess.Term.Exited => |code| { + if (code != 0) { + std.debug.panic("llvm-config --libdir exited with {}:\n{}\n", code, good_result.stderr); + } + }, + else => { + std.debug.panic("llvm-config failed:\n{}\n", good_result.stderr); + }, + } + good_result.stdout + }; + + var result = LibraryDep { + .libs = ArrayList([]const u8).init(b.allocator), + .includes = ArrayList([]const u8).init(b.allocator), + .libdirs = ArrayList([]const u8).init(b.allocator), + }; + { + var it = mem.split(libs_output, " \n"); + while (it.next()) |lib_arg| { + if (mem.startsWith(u8, lib_arg, "-l")) { + %%result.libs.append(lib_arg[2..]); + } + } + } + { + var it = mem.split(includes_output, " \n"); + while (it.next()) |include_arg| { + if (mem.startsWith(u8, include_arg, "-I")) { + %%result.includes.append(include_arg[2..]); + } else { + %%result.includes.append(include_arg); + } + } + } + { + var it = mem.split(libdir_output, " \n"); + while (it.next()) |libdir| { + if (mem.startsWith(u8, libdir, "-L")) { + %%result.libdirs.append(libdir[2..]); + } else { + %%result.libdirs.append(libdir); + } + } + } + return result; +} + + +// TODO move to std lib +const ExecResult = struct { + term: os.ChildProcess.Term, + stdout: []u8, + stderr: []u8, +}; + +fn exec(allocator: &std.mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?&const BufMap, max_output_size: usize) -> %ExecResult { + const child = %%os.ChildProcess.init(argv, allocator); + defer child.deinit(); + + child.stdin_behavior = os.ChildProcess.StdIo.Ignore; + child.stdout_behavior = os.ChildProcess.StdIo.Pipe; + child.stderr_behavior = os.ChildProcess.StdIo.Pipe; + child.cwd = cwd; + child.env_map = env_map; + + %return child.spawn(); + + var stdout = Buffer.initNull(allocator); + var stderr = Buffer.initNull(allocator); + defer Buffer.deinit(&stdout); + defer Buffer.deinit(&stderr); + + var stdout_file_in_stream = io.FileInStream.init(&??child.stdout); + var stderr_file_in_stream = io.FileInStream.init(&??child.stderr); + + %return stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); + %return stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size); + + return ExecResult { + .term = %return child.wait(), + .stdout = stdout.toOwnedSlice(), + .stderr = stderr.toOwnedSlice(), + }; +} diff --git a/src-self-hosted/c.zig b/src-self-hosted/c.zig new file mode 100644 index 0000000000..b7e057b941 --- /dev/null +++ b/src-self-hosted/c.zig @@ -0,0 +1,7 @@ +pub use @cImport({ + @cInclude("llvm-c/Core.h"); + @cInclude("llvm-c/Analysis.h"); + @cInclude("llvm-c/Target.h"); + @cInclude("llvm-c/Initialization.h"); + @cInclude("llvm-c/TargetMachine.h"); +}); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 8a7ce3786a..6c40d2216f 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1,6 +1,5 @@ const std = @import("std"); const mem = std.mem; -const builtin = @import("builtin"); const io = std.io; const os = std.os; const heap = std.heap; @@ -9,6 +8,7 @@ const Tokenizer = @import("tokenizer.zig").Tokenizer; const Token = @import("tokenizer.zig").Token; const Parser = @import("parser.zig").Parser; const assert = std.debug.assert; +const target = @import("target.zig"); pub fn main() -> %void { main2() %% |err| { @@ -26,6 +26,8 @@ pub fn main2() -> %void { const args = %return os.argsAlloc(allocator); defer os.argsFree(allocator, args); + target.initializeAll(); + const target_file = args[1]; const target_file_buf = %return io.readFileAlloc(target_file, allocator); @@ -66,94 +68,7 @@ pub fn main2() -> %void { %return parser.renderSource(out_stream, root_node); } - -var fixed_buffer_mem: [100 * 1024]u8 = undefined; - -fn testParse(source: []const u8, allocator: &mem.Allocator) -> %[]u8 { - var tokenizer = Tokenizer.init(source); - var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); - defer parser.deinit(); - - const root_node = %return parser.parse(); - defer parser.freeAst(root_node); - - var buffer = %return std.Buffer.initSize(allocator, 0); - var buffer_out_stream = io.BufferOutStream.init(&buffer); - %return parser.renderSource(&buffer_out_stream.stream, root_node); - return buffer.toOwnedSlice(); -} - -fn testCanonical(source: []const u8) { - const needed_alloc_count = { - // Try it once with unlimited memory, make sure it works - var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, @maxValue(usize)); - const result_source = testParse(source, &failing_allocator.allocator) %% @panic("test failed"); - if (!mem.eql(u8, result_source, source)) { - warn("\n====== expected this output: =========\n"); - warn("{}", source); - warn("\n======== instead found this: =========\n"); - warn("{}", result_source); - warn("\n======================================\n"); - @panic("test failed"); - } - failing_allocator.allocator.free(result_source); - failing_allocator.index - }; - - var fail_index = needed_alloc_count; - while (fail_index != 0) { - fail_index -= 1; - var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index); - if (testParse(source, &failing_allocator.allocator)) |_| { - @panic("non-deterministic memory usage"); - } else |err| { - assert(err == error.OutOfMemory); - } - } -} - -test "zig fmt" { - if (builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386) { - // TODO get this test passing - // https://github.com/zig-lang/zig/issues/537 - return; - } - - testCanonical( - \\extern fn puts(s: &const u8) -> c_int; - \\ - ); - - testCanonical( - \\const a = b; - \\pub const a = b; - \\var a = b; - \\pub var a = b; - \\const a: i32 = b; - \\pub const a: i32 = b; - \\var a: i32 = b; - \\pub var a: i32 = b; - \\ - ); - - testCanonical( - \\extern var foo: c_int; - \\ - ); - - testCanonical( - \\fn main(argc: c_int, argv: &&u8) -> c_int { - \\ const a = b; - \\} - \\ - ); - - testCanonical( - \\fn foo(argc: c_int, argv: &&u8) -> c_int { - \\ return 0; - \\} - \\ - ); +test "import other tests" { + _ = @import("parser.zig"); + _ = @import("tokenizer.zig"); } diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig index 6f1f00646a..4492d8259a 100644 --- a/src-self-hosted/parser.zig +++ b/src-self-hosted/parser.zig @@ -5,6 +5,8 @@ const mem = std.mem; const ast = @import("ast.zig"); const Tokenizer = @import("tokenizer.zig").Tokenizer; const Token = @import("tokenizer.zig").Token; +const builtin = @import("builtin"); +const io = std.io; // TODO when we make parse errors into error types instead of printing directly, // get rid of this @@ -1095,3 +1097,96 @@ pub const Parser = struct { }; +var fixed_buffer_mem: [100 * 1024]u8 = undefined; + +fn testParse(source: []const u8, allocator: &mem.Allocator) -> %[]u8 { + var tokenizer = Tokenizer.init(source); + var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); + defer parser.deinit(); + + const root_node = %return parser.parse(); + defer parser.freeAst(root_node); + + var buffer = %return std.Buffer.initSize(allocator, 0); + var buffer_out_stream = io.BufferOutStream.init(&buffer); + %return parser.renderSource(&buffer_out_stream.stream, root_node); + return buffer.toOwnedSlice(); +} + +// TODO test for memory leaks +// TODO test for valid frees +fn testCanonical(source: []const u8) { + const needed_alloc_count = { + // Try it once with unlimited memory, make sure it works + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, @maxValue(usize)); + const result_source = testParse(source, &failing_allocator.allocator) %% @panic("test failed"); + if (!mem.eql(u8, result_source, source)) { + warn("\n====== expected this output: =========\n"); + warn("{}", source); + warn("\n======== instead found this: =========\n"); + warn("{}", result_source); + warn("\n======================================\n"); + @panic("test failed"); + } + failing_allocator.allocator.free(result_source); + failing_allocator.index + }; + + // TODO make this pass + //var fail_index = needed_alloc_count; + //while (fail_index != 0) { + // fail_index -= 1; + // var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + // var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index); + // if (testParse(source, &failing_allocator.allocator)) |_| { + // @panic("non-deterministic memory usage"); + // } else |err| { + // assert(err == error.OutOfMemory); + // } + //} +} + +test "zig fmt" { + if (builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386) { + // TODO get this test passing + // https://github.com/zig-lang/zig/issues/537 + return; + } + + testCanonical( + \\extern fn puts(s: &const u8) -> c_int; + \\ + ); + + testCanonical( + \\const a = b; + \\pub const a = b; + \\var a = b; + \\pub var a = b; + \\const a: i32 = b; + \\pub const a: i32 = b; + \\var a: i32 = b; + \\pub var a: i32 = b; + \\ + ); + + testCanonical( + \\extern var foo: c_int; + \\ + ); + + testCanonical( + \\fn main(argc: c_int, argv: &&u8) -> c_int { + \\ const a = b; + \\} + \\ + ); + + testCanonical( + \\fn foo(argc: c_int, argv: &&u8) -> c_int { + \\ return 0; + \\} + \\ + ); +} diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig new file mode 100644 index 0000000000..90e2132ac5 --- /dev/null +++ b/src-self-hosted/target.zig @@ -0,0 +1,9 @@ +const c = @import("c.zig"); + +pub fn initializeAll() { + c.LLVMInitializeAllTargets(); + c.LLVMInitializeAllTargetInfos(); + c.LLVMInitializeAllTargetMCs(); + c.LLVMInitializeAllAsmPrinters(); + c.LLVMInitializeAllAsmParsers(); +} diff --git a/std/build.zig b/std/build.zig index 9bdc4b3076..532eca1cc9 100644 --- a/std/build.zig +++ b/std/build.zig @@ -755,6 +755,7 @@ pub const LibExeObjStep = struct { is_zig: bool, cflags: ArrayList([]const u8), include_dirs: ArrayList([]const u8), + lib_paths: ArrayList([]const u8), disable_libc: bool, frameworks: BufSet, @@ -865,6 +866,7 @@ pub const LibExeObjStep = struct { .cflags = ArrayList([]const u8).init(builder.allocator), .source_files = undefined, .include_dirs = ArrayList([]const u8).init(builder.allocator), + .lib_paths = ArrayList([]const u8).init(builder.allocator), .object_src = undefined, .disable_libc = true, }; @@ -888,6 +890,7 @@ pub const LibExeObjStep = struct { .frameworks = BufSet.init(builder.allocator), .full_path_libs = ArrayList([]const u8).init(builder.allocator), .include_dirs = ArrayList([]const u8).init(builder.allocator), + .lib_paths = ArrayList([]const u8).init(builder.allocator), .output_path = null, .out_filename = undefined, .major_only_filename = undefined, @@ -1069,11 +1072,14 @@ pub const LibExeObjStep = struct { %%self.include_dirs.append(self.builder.cache_root); } - // TODO put include_dirs in zig command line pub fn addIncludeDir(self: &LibExeObjStep, path: []const u8) { %%self.include_dirs.append(path); } + pub fn addLibPath(self: &LibExeObjStep, path: []const u8) { + %%self.lib_paths.append(path); + } + pub fn addPackagePath(self: &LibExeObjStep, name: []const u8, pkg_index_path: []const u8) { assert(self.is_zig); @@ -1222,6 +1228,11 @@ pub const LibExeObjStep = struct { %%zig_args.append("--pkg-end"); } + for (self.include_dirs.toSliceConst()) |include_path| { + %%zig_args.append("-isystem"); + %%zig_args.append(self.builder.pathFromRoot(include_path)); + } + for (builder.include_paths.toSliceConst()) |include_path| { %%zig_args.append("-isystem"); %%zig_args.append(builder.pathFromRoot(include_path)); @@ -1232,6 +1243,11 @@ pub const LibExeObjStep = struct { %%zig_args.append(rpath); } + for (self.lib_paths.toSliceConst()) |lib_path| { + %%zig_args.append("--library-path"); + %%zig_args.append(lib_path); + } + for (builder.lib_paths.toSliceConst()) |lib_path| { %%zig_args.append("--library-path"); %%zig_args.append(lib_path); From caa6433b5636af968aa7018a3bea8e658bca05a3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Dec 2017 11:33:14 -0500 Subject: [PATCH 40/69] stack traces: support DW_AT_ranges This makes some cases print stack traces where it previously failed. --- std/debug.zig | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/std/debug.zig b/std/debug.zig index f5768ef199..a6229f88da 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -113,6 +113,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty .debug_abbrev = undefined, .debug_str = undefined, .debug_line = undefined, + .debug_ranges = null, .abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator), .compile_unit_list = ArrayList(CompileUnit).init(allocator), }; @@ -127,6 +128,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty st.debug_abbrev = (%return st.elf.findSection(".debug_abbrev")) ?? return error.MissingDebugInfo; st.debug_str = (%return st.elf.findSection(".debug_str")) ?? return error.MissingDebugInfo; st.debug_line = (%return st.elf.findSection(".debug_line")) ?? return error.MissingDebugInfo; + st.debug_ranges = (%return st.elf.findSection(".debug_ranges")); %return scanAllCompileUnits(st); var ignored_count: usize = 0; @@ -144,7 +146,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty // at compile time. I'll call it issue #313 const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}"; - const compile_unit = findCompileUnit(st, return_address) ?? { + const compile_unit = findCompileUnit(st, return_address) %% { %return out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", return_address); continue; @@ -233,6 +235,7 @@ const ElfStackTrace = struct { debug_abbrev: &elf.SectionHeader, debug_str: &elf.SectionHeader, debug_line: &elf.SectionHeader, + debug_ranges: ?&elf.SectionHeader, abbrev_table_list: ArrayList(AbbrevTableHeader), compile_unit_list: ArrayList(CompileUnit), @@ -333,6 +336,15 @@ const Die = struct { }; } + fn getAttrSecOffset(self: &const Die, id: u64) -> %u64 { + const form_value = self.getAttr(id) ?? return error.MissingDebugInfo; + return switch (*form_value) { + FormValue.Const => |value| value.asUnsignedLe(), + FormValue.SecOffset => |value| value, + else => error.InvalidDebugInfo, + }; + } + fn getAttrUnsignedLe(self: &const Die, id: u64) -> %u64 { const form_value = self.getAttr(id) ?? return error.MissingDebugInfo; return switch (*form_value) { @@ -900,14 +912,40 @@ fn scanAllCompileUnits(st: &ElfStackTrace) -> %void { } } -fn findCompileUnit(st: &ElfStackTrace, target_address: u64) -> ?&const CompileUnit { +fn findCompileUnit(st: &ElfStackTrace, target_address: u64) -> %&const CompileUnit { + var in_file_stream = io.FileInStream.init(&st.self_exe_file); + const in_stream = &in_file_stream.stream; for (st.compile_unit_list.toSlice()) |*compile_unit| { if (compile_unit.pc_range) |range| { if (target_address >= range.start and target_address < range.end) return compile_unit; } + if (compile_unit.die.getAttrSecOffset(DW.AT_ranges)) |ranges_offset| { + var base_address: usize = 0; + if (st.debug_ranges) |debug_ranges| { + %return st.self_exe_file.seekTo(debug_ranges.offset + ranges_offset); + while (true) { + const begin_addr = %return in_stream.readIntLe(usize); + const end_addr = %return in_stream.readIntLe(usize); + if (begin_addr == 0 and end_addr == 0) { + break; + } + if (begin_addr == @maxValue(usize)) { + base_address = begin_addr; + continue; + } + if (target_address >= begin_addr and target_address < end_addr) { + return compile_unit; + } + } + } + } else |err| { + if (err != error.MissingDebugInfo) + return err; + continue; + } } - return null; + return error.MissingDebugInfo; } fn readInitialLength(in_stream: &io.InStream, is_64: &bool) -> %u64 { From cd5fd653d7dd738d4c67b304e9cb17fb211f0163 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Dec 2017 14:35:53 -0500 Subject: [PATCH 41/69] self-hosted: move code to std.os.ChildProcess.exec --- build.zig | 50 +++++----------------------------------- std/os/child_process.zig | 46 +++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/build.zig b/build.zig index a97142b090..223c225383 100644 --- a/build.zig +++ b/build.zig @@ -96,9 +96,9 @@ fn findLLVM(b: &Builder) -> LibraryDep { const args1 = [][]const u8{"llvm-config-5.0", "--libs", "--system-libs"}; const args2 = [][]const u8{"llvm-config", "--libs", "--system-libs"}; const max_output_size = 10 * 1024; - const good_result = exec(b.allocator, args1, null, null, max_output_size) %% |err| { + const good_result = os.ChildProcess.exec(b.allocator, args1, null, null, max_output_size) %% |err| { if (err == error.FileNotFound) { - exec(b.allocator, args2, null, null, max_output_size) %% |err2| { + os.ChildProcess.exec(b.allocator, args2, null, null, max_output_size) %% |err2| { std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); } } else { @@ -121,9 +121,9 @@ fn findLLVM(b: &Builder) -> LibraryDep { const args1 = [][]const u8{"llvm-config-5.0", "--includedir"}; const args2 = [][]const u8{"llvm-config", "--includedir"}; const max_output_size = 10 * 1024; - const good_result = exec(b.allocator, args1, null, null, max_output_size) %% |err| { + const good_result = os.ChildProcess.exec(b.allocator, args1, null, null, max_output_size) %% |err| { if (err == error.FileNotFound) { - exec(b.allocator, args2, null, null, max_output_size) %% |err2| { + os.ChildProcess.exec(b.allocator, args2, null, null, max_output_size) %% |err2| { std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); } } else { @@ -146,9 +146,9 @@ fn findLLVM(b: &Builder) -> LibraryDep { const args1 = [][]const u8{"llvm-config-5.0", "--libdir"}; const args2 = [][]const u8{"llvm-config", "--libdir"}; const max_output_size = 10 * 1024; - const good_result = exec(b.allocator, args1, null, null, max_output_size) %% |err| { + const good_result = os.ChildProcess.exec(b.allocator, args1, null, null, max_output_size) %% |err| { if (err == error.FileNotFound) { - exec(b.allocator, args2, null, null, max_output_size) %% |err2| { + os.ChildProcess.exec(b.allocator, args2, null, null, max_output_size) %% |err2| { std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); } } else { @@ -203,41 +203,3 @@ fn findLLVM(b: &Builder) -> LibraryDep { } return result; } - - -// TODO move to std lib -const ExecResult = struct { - term: os.ChildProcess.Term, - stdout: []u8, - stderr: []u8, -}; - -fn exec(allocator: &std.mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?&const BufMap, max_output_size: usize) -> %ExecResult { - const child = %%os.ChildProcess.init(argv, allocator); - defer child.deinit(); - - child.stdin_behavior = os.ChildProcess.StdIo.Ignore; - child.stdout_behavior = os.ChildProcess.StdIo.Pipe; - child.stderr_behavior = os.ChildProcess.StdIo.Pipe; - child.cwd = cwd; - child.env_map = env_map; - - %return child.spawn(); - - var stdout = Buffer.initNull(allocator); - var stderr = Buffer.initNull(allocator); - defer Buffer.deinit(&stdout); - defer Buffer.deinit(&stderr); - - var stdout_file_in_stream = io.FileInStream.init(&??child.stdout); - var stderr_file_in_stream = io.FileInStream.init(&??child.stderr); - - %return stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); - %return stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size); - - return ExecResult { - .term = %return child.wait(), - .stdout = stdout.toOwnedSlice(), - .stderr = stderr.toOwnedSlice(), - }; -} diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 75a2dcf24d..5aa1578583 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -5,7 +5,6 @@ const os = std.os; const posix = os.posix; const windows = os.windows; const mem = std.mem; -const Allocator = mem.Allocator; const debug = std.debug; const assert = debug.assert; const BufMap = std.BufMap; @@ -74,7 +73,7 @@ pub const ChildProcess = struct { /// First argument in argv is the executable. /// On success must call deinit. - pub fn init(argv: []const []const u8, allocator: &Allocator) -> %&ChildProcess { + pub fn init(argv: []const []const u8, allocator: &mem.Allocator) -> %&ChildProcess { const child = %return allocator.create(ChildProcess); %defer allocator.destroy(child); @@ -180,6 +179,46 @@ pub const ChildProcess = struct { } } + pub const ExecResult = struct { + term: os.ChildProcess.Term, + stdout: []u8, + stderr: []u8, + }; + + /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. + /// If it succeeds, the caller owns result.stdout and result.stderr memory. + pub fn exec(allocator: &mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, + env_map: ?&const BufMap, max_output_size: usize) -> %ExecResult + { + const child = %%ChildProcess.init(argv, allocator); + defer child.deinit(); + + child.stdin_behavior = ChildProcess.StdIo.Ignore; + child.stdout_behavior = ChildProcess.StdIo.Pipe; + child.stderr_behavior = ChildProcess.StdIo.Pipe; + child.cwd = cwd; + child.env_map = env_map; + + %return child.spawn(); + + var stdout = Buffer.initNull(allocator); + var stderr = Buffer.initNull(allocator); + defer Buffer.deinit(&stdout); + defer Buffer.deinit(&stderr); + + var stdout_file_in_stream = io.FileInStream.init(&??child.stdout); + var stderr_file_in_stream = io.FileInStream.init(&??child.stderr); + + %return stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); + %return stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size); + + return ExecResult { + .term = %return child.wait(), + .stdout = stdout.toOwnedSlice(), + .stderr = stderr.toOwnedSlice(), + }; + } + fn waitWindows(self: &ChildProcess) -> %Term { if (self.term) |term| { self.cleanupStreams(); @@ -589,6 +628,7 @@ pub const ChildProcess = struct { StdIo.Ignore => %return os.posixDup2(dev_null_fd, std_fileno), } } + }; fn windowsCreateProcess(app_name: &u8, cmd_line: &u8, envp_ptr: ?&u8, cwd_ptr: ?&u8, @@ -611,7 +651,7 @@ fn windowsCreateProcess(app_name: &u8, cmd_line: &u8, envp_ptr: ?&u8, cwd_ptr: ? /// Caller must dealloc. /// Guarantees a null byte at result[result.len]. -fn windowsCreateCommandLine(allocator: &Allocator, argv: []const []const u8) -> %[]u8 { +fn windowsCreateCommandLine(allocator: &mem.Allocator, argv: []const []const u8) -> %[]u8 { var buf = %return Buffer.initSize(allocator, 0); defer buf.deinit(); From 2b9302107fdd4adbbcfc2735afd7f2e4cd4c6769 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Dec 2017 16:03:20 -0500 Subject: [PATCH 42/69] self-hosted: cleanup build looking for llvm-config --- build.zig | 88 ++++++++----------------------------------------- std/buf_map.zig | 5 +++ std/build.zig | 57 ++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 74 deletions(-) diff --git a/build.zig b/build.zig index 223c225383..2ac98634ae 100644 --- a/build.zig +++ b/build.zig @@ -92,81 +92,21 @@ const LibraryDep = struct { }; fn findLLVM(b: &Builder) -> LibraryDep { - const libs_output = { - const args1 = [][]const u8{"llvm-config-5.0", "--libs", "--system-libs"}; - const args2 = [][]const u8{"llvm-config", "--libs", "--system-libs"}; - const max_output_size = 10 * 1024; - const good_result = os.ChildProcess.exec(b.allocator, args1, null, null, max_output_size) %% |err| { - if (err == error.FileNotFound) { - os.ChildProcess.exec(b.allocator, args2, null, null, max_output_size) %% |err2| { - std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); - } - } else { - std.debug.panic("unable to spawn {}: {}\n", args1[0], err); - } - }; - switch (good_result.term) { - os.ChildProcess.Term.Exited => |code| { - if (code != 0) { - std.debug.panic("llvm-config exited with {}:\n{}\n", code, good_result.stderr); - } - }, - else => { - std.debug.panic("llvm-config failed:\n{}\n", good_result.stderr); - }, - } - good_result.stdout - }; - const includes_output = { - const args1 = [][]const u8{"llvm-config-5.0", "--includedir"}; - const args2 = [][]const u8{"llvm-config", "--includedir"}; - const max_output_size = 10 * 1024; - const good_result = os.ChildProcess.exec(b.allocator, args1, null, null, max_output_size) %% |err| { - if (err == error.FileNotFound) { - os.ChildProcess.exec(b.allocator, args2, null, null, max_output_size) %% |err2| { - std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); - } - } else { - std.debug.panic("unable to spawn {}: {}\n", args1[0], err); - } - }; - switch (good_result.term) { - os.ChildProcess.Term.Exited => |code| { - if (code != 0) { - std.debug.panic("llvm-config --includedir exited with {}:\n{}\n", code, good_result.stderr); - } - }, - else => { - std.debug.panic("llvm-config failed:\n{}\n", good_result.stderr); - }, - } - good_result.stdout - }; - const libdir_output = { - const args1 = [][]const u8{"llvm-config-5.0", "--libdir"}; - const args2 = [][]const u8{"llvm-config", "--libdir"}; - const max_output_size = 10 * 1024; - const good_result = os.ChildProcess.exec(b.allocator, args1, null, null, max_output_size) %% |err| { - if (err == error.FileNotFound) { - os.ChildProcess.exec(b.allocator, args2, null, null, max_output_size) %% |err2| { - std.debug.panic("unable to spawn {}: {}\n", args2[0], err2); - } - } else { - std.debug.panic("unable to spawn {}: {}\n", args1[0], err); - } - }; - switch (good_result.term) { - os.ChildProcess.Term.Exited => |code| { - if (code != 0) { - std.debug.panic("llvm-config --libdir exited with {}:\n{}\n", code, good_result.stderr); - } - }, - else => { - std.debug.panic("llvm-config failed:\n{}\n", good_result.stderr); - }, - } - good_result.stdout + const llvm_config_exe = b.findProgram( + [][]const u8{"llvm-config-5.0", "llvm-config"}, + [][]const u8{ + "/usr/local/opt/llvm@5/", + "/mingw64/bin", + "/c/msys64/mingw64/bin", + "c:/msys64/mingw64/bin", + "C:/Libraries/llvm-5.0.0/bin", + }) %% |err| + { + std.debug.panic("unable to find llvm-config: {}\n", err); }; + const libs_output = b.exec([][]const u8{llvm_config_exe, "--libs", "--system-libs"}); + const includes_output = b.exec([][]const u8{llvm_config_exe, "--includedir"}); + const libdir_output = b.exec([][]const u8{llvm_config_exe, "--libdir"}); var result = LibraryDep { .libs = ArrayList([]const u8).init(b.allocator), diff --git a/std/buf_map.zig b/std/buf_map.zig index e913b8765d..2a9bd77660 100644 --- a/std/buf_map.zig +++ b/std/buf_map.zig @@ -42,6 +42,11 @@ pub const BufMap = struct { } } + pub fn get(self: &BufMap, key: []const u8) -> ?[]const u8 { + const entry = self.hash_map.get(key) ?? return null; + return entry.value; + } + pub fn delete(self: &BufMap, key: []const u8) { const entry = self.hash_map.remove(key) ?? return; self.free(entry.key); diff --git a/std/build.zig b/std/build.zig index 532eca1cc9..63d635de70 100644 --- a/std/build.zig +++ b/std/build.zig @@ -671,6 +671,63 @@ pub const Builder = struct { }; } } + + pub fn findProgram(self: &Builder, names: []const []const u8, paths: []const []const u8) -> %[]const u8 { + if (self.env_map.get("PATH")) |PATH| { + for (names) |name| { + if (os.path.isAbsolute(name)) { + return name; + } + var it = mem.split(PATH, []u8{os.path.delimiter}); + while (it.next()) |path| { + const full_path = %return os.path.join(self.allocator, path, name); + if (os.path.real(self.allocator, full_path)) |real_path| { + return real_path; + } else |_| { + continue; + } + } + } + } + for (names) |name| { + if (os.path.isAbsolute(name)) { + return name; + } + for (paths) |path| { + const full_path = %return os.path.join(self.allocator, path, name); + if (os.path.real(self.allocator, full_path)) |real_path| { + return real_path; + } else |_| { + continue; + } + } + } + return error.FileNotFound; + } + + pub fn exec(self: &Builder, argv: []const []const u8) -> []u8 { + const max_output_size = 100 * 1024; + const result = os.ChildProcess.exec(self.allocator, argv, null, null, max_output_size) %% |err| { + std.debug.panic("Unable to spawn {}: {}", argv[0], @errorName(err)); + }; + switch (result.term) { + os.ChildProcess.Term.Exited => |code| { + if (code != 0) { + warn("The following command exited with error code {}:\n", code); + printCmd(null, argv); + warn("stderr:{}\n", result.stderr); + std.debug.panic("command failed"); + } + return result.stdout; + }, + else => { + warn("The following command terminated unexpectedly:\n"); + printCmd(null, argv); + warn("stderr:{}\n", result.stderr); + std.debug.panic("command failed"); + }, + } + } }; const Version = struct { From cdaa735b2bfe969874691302e4c5fe015e08cf8c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Dec 2017 16:40:04 -0500 Subject: [PATCH 43/69] self-hosted: build tries to find llvm-config.exe --- std/build.zig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/std/build.zig b/std/build.zig index 63d635de70..c1c42a4bc1 100644 --- a/std/build.zig +++ b/std/build.zig @@ -673,6 +673,7 @@ pub const Builder = struct { } pub fn findProgram(self: &Builder, names: []const []const u8, paths: []const []const u8) -> %[]const u8 { + const exe_extension = (Target { .Native = {}}).exeFileExt(); if (self.env_map.get("PATH")) |PATH| { for (names) |name| { if (os.path.isAbsolute(name)) { @@ -680,7 +681,7 @@ pub const Builder = struct { } var it = mem.split(PATH, []u8{os.path.delimiter}); while (it.next()) |path| { - const full_path = %return os.path.join(self.allocator, path, name); + const full_path = %return os.path.join(self.allocator, path, self.fmt("{}{}", name, exe_extension)); if (os.path.real(self.allocator, full_path)) |real_path| { return real_path; } else |_| { @@ -694,7 +695,7 @@ pub const Builder = struct { return name; } for (paths) |path| { - const full_path = %return os.path.join(self.allocator, path, name); + const full_path = %return os.path.join(self.allocator, path, self.fmt("{}{}", name, exe_extension)); if (os.path.real(self.allocator, full_path)) |real_path| { return real_path; } else |_| { @@ -902,7 +903,7 @@ pub const LibExeObjStep = struct { .kind = kind, .root_src = root_src, .name = name, - .target = Target { .Native = {} }, + .target = Target.Native, .linker_script = null, .link_libs = BufSet.init(builder.allocator), .frameworks = BufSet.init(builder.allocator), @@ -938,7 +939,7 @@ pub const LibExeObjStep = struct { .kind = kind, .version = *version, .static = static, - .target = Target { .Native = {} }, + .target = Target.Native, .cflags = ArrayList([]const u8).init(builder.allocator), .source_files = ArrayList([]const u8).init(builder.allocator), .object_files = ArrayList([]const u8).init(builder.allocator), From 24c2703dfa64d4a76684c68f6bb029eb50f96aca Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Dec 2017 17:25:57 -0500 Subject: [PATCH 44/69] self-hosted: look for llvm-config in homebrew --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 2ac98634ae..45953cc4af 100644 --- a/build.zig +++ b/build.zig @@ -95,7 +95,7 @@ fn findLLVM(b: &Builder) -> LibraryDep { const llvm_config_exe = b.findProgram( [][]const u8{"llvm-config-5.0", "llvm-config"}, [][]const u8{ - "/usr/local/opt/llvm@5/", + "/usr/local/opt/llvm@5/bin", "/mingw64/bin", "/c/msys64/mingw64/bin", "c:/msys64/mingw64/bin", From 0003cc81056b5be394b857b717cf64f08f44c0f5 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Tue, 12 Dec 2017 19:26:33 -0700 Subject: [PATCH 45/69] self-hosted: implement addr of align parsing --- src-self-hosted/parser.zig | 80 +++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig index 4492d8259a..4eb86f9152 100644 --- a/src-self-hosted/parser.zig +++ b/src-self-hosted/parser.zig @@ -81,6 +81,7 @@ pub const Parser = struct { MultiplyExpression: DestPtr, BraceSuffixExpression: DestPtr, PrefixOpExpression: DestPtr, + AddrOfModifiers: &ast.NodeAddrOfExpr, SuffixOpExpression: DestPtr, PrimaryExpression: DestPtr, TypeExpr: DestPtr, @@ -350,30 +351,51 @@ pub const Parser = struct { State.PrefixOpExpression => |dest_ptr| { const first_token = self.getNextToken(); - if (first_token.id == Token.Id.Ampersand) { - const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, first_token); - var token = self.getNextToken(); - if (token.id == Token.Id.Keyword_align) { - @panic("TODO align"); - } - if (token.id == Token.Id.Keyword_const) { - addr_of_expr.const_token = token; - token = self.getNextToken(); - } - if (token.id == Token.Id.Keyword_volatile) { - addr_of_expr.volatile_token = token; - token = self.getNextToken(); - } - self.putBackToken(token); - stack.append(State { - .PrefixOpExpression = DestPtr { .Field = &addr_of_expr.op_expr}, - }) %% unreachable; - continue; + switch (first_token.id) { + Token.Id.Ampersand => { + const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, first_token); + stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; + continue; + }, + else => { + self.putBackToken(first_token); + stack.append(State { .SuffixOpExpression = dest_ptr }) %% unreachable; + continue; + }, } + }, - self.putBackToken(first_token); - stack.append(State { .SuffixOpExpression = dest_ptr }) %% unreachable; - continue; + State.AddrOfModifiers => |addr_of_expr| { + var token = self.getNextToken(); + switch (token.id) { + Token.Id.Keyword_align => { + stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; + if (addr_of_expr.align_expr != null) return self.parseError(token, "multiple align qualifiers"); + _ = %return self.eatToken(Token.Id.LParen); + %return stack.append(State { .ExpectToken = Token.Id.RParen }); + %return stack.append(State { .Expression = DestPtr{.NullableField = &addr_of_expr.align_expr} }); + continue; + }, + Token.Id.Keyword_const => { + if (addr_of_expr.const_token != null) return self.parseError(token, "duplicate qualifier: const"); + addr_of_expr.const_token = token; + stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; + continue; + }, + Token.Id.Keyword_volatile => { + if (addr_of_expr.volatile_token != null) return self.parseError(token, "duplicate qualifier: volatile"); + addr_of_expr.volatile_token = token; + stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; + continue; + }, + else => { + self.putBackToken(token); + stack.append(State { + .PrefixOpExpression = DestPtr { .Field = &addr_of_expr.op_expr}, + }) %% unreachable; + continue; + }, + } }, State.SuffixOpExpression => |dest_ptr| { @@ -997,7 +1019,7 @@ pub const Parser = struct { if (addr_of_expr.align_expr) |align_expr| { %return stream.print("align("); - %return stack.append(RenderState { .Text = ")"}); + %return stack.append(RenderState { .Text = ") "}); %return stack.append(RenderState { .Expression = align_expr}); } }, @@ -1189,4 +1211,16 @@ test "zig fmt" { \\} \\ ); + + testCanonical( + \\extern fn f1(s: &align(&u8) u8) -> c_int; + \\ + ); + + testCanonical( + \\extern fn f1(s: &&align(1) &const &volatile u8) -> c_int; + \\extern fn f2(s: &align(1) const &align(1) volatile &const volatile u8) -> c_int; + \\extern fn f3(s: &align(1) const volatile u8) -> c_int; + \\ + ); } From d295279b16dd21ec5263c4db2f08e182ccb4fad0 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Tue, 12 Dec 2017 19:50:43 -0700 Subject: [PATCH 46/69] self-hosted: implement var decl align --- src-self-hosted/parser.zig | 175 +++++++++---------------------------- 1 file changed, 40 insertions(+), 135 deletions(-) diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig index 4eb86f9152..09e6ab8180 100644 --- a/src-self-hosted/parser.zig +++ b/src-self-hosted/parser.zig @@ -68,22 +68,7 @@ pub const Parser = struct { TopLevelExtern: ?Token, TopLevelDecl: TopLevelDeclCtx, Expression: DestPtr, - GroupedExpression: DestPtr, - UnwrapExpression: DestPtr, - BoolOrExpression: DestPtr, - BoolAndExpression: DestPtr, - ComparisonExpression: DestPtr, - BinaryOrExpression: DestPtr, - BinaryXorExpression: DestPtr, - BinaryAndExpression: DestPtr, - BitShiftExpression: DestPtr, - AdditionExpression: DestPtr, - MultiplyExpression: DestPtr, - BraceSuffixExpression: DestPtr, - PrefixOpExpression: DestPtr, AddrOfModifiers: &ast.NodeAddrOfExpr, - SuffixOpExpression: DestPtr, - PrimaryExpression: DestPtr, TypeExpr: DestPtr, VarDecl: &ast.NodeVarDecl, VarDeclAlign: &ast.NodeVarDecl, @@ -252,11 +237,9 @@ pub const Parser = struct { const next_token = self.getNextToken(); if (next_token.id == Token.Id.Keyword_align) { - %return stack.append(State { - .GroupedExpression = DestPtr { - .NullableField = &var_decl.align_node - } - }); + _ = %return self.eatToken(Token.Id.LParen); + %return stack.append(State { .ExpectToken = Token.Id.RParen }); + %return stack.append(State { .Expression = DestPtr{.NullableField = &var_decl.align_node} }); continue; } @@ -284,84 +267,30 @@ pub const Parser = struct { }, State.Expression => |dest_ptr| { const token = self.getNextToken(); - if (token.id == Token.Id.Keyword_return) { - const return_node = %return self.createAttachReturn(dest_ptr, token); - stack.append(State {.UnwrapExpression = DestPtr {.Field = &return_node.expr} }) %% unreachable; - continue; - } - self.putBackToken(token); - stack.append(State {.UnwrapExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.UnwrapExpression => |dest_ptr| { - stack.append(State {.BoolOrExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BoolOrExpression => |dest_ptr| { - stack.append(State {.BoolAndExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BoolAndExpression => |dest_ptr| { - stack.append(State {.ComparisonExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.ComparisonExpression => |dest_ptr| { - stack.append(State {.BinaryOrExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BinaryOrExpression => |dest_ptr| { - stack.append(State {.BinaryXorExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BinaryXorExpression => |dest_ptr| { - stack.append(State {.BinaryAndExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BinaryAndExpression => |dest_ptr| { - stack.append(State {.BitShiftExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BitShiftExpression => |dest_ptr| { - stack.append(State {.AdditionExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.AdditionExpression => |dest_ptr| { - stack.append(State {.MultiplyExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.MultiplyExpression => |dest_ptr| { - stack.append(State {.BraceSuffixExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.BraceSuffixExpression => |dest_ptr| { - stack.append(State {.PrefixOpExpression = dest_ptr}) %% unreachable; - continue; - }, - - State.PrefixOpExpression => |dest_ptr| { - const first_token = self.getNextToken(); - switch (first_token.id) { + switch (token.id) { + Token.Id.Keyword_return => { + const return_node = %return self.createAttachReturn(dest_ptr, token); + stack.append(State {.Expression = DestPtr {.Field = &return_node.expr} }) %% unreachable; + continue; + }, + Token.Id.Identifier => { + _ = %return self.createAttachIdentifier(dest_ptr, token); + continue; + }, + Token.Id.IntegerLiteral => { + _ = %return self.createAttachIntegerLiteral(dest_ptr, token); + continue; + }, + Token.Id.FloatLiteral => { + _ = %return self.createAttachFloatLiteral(dest_ptr, token); + continue; + }, Token.Id.Ampersand => { - const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, first_token); + const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, token); stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; continue; }, - else => { - self.putBackToken(first_token); - stack.append(State { .SuffixOpExpression = dest_ptr }) %% unreachable; - continue; - }, + else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), } }, @@ -391,37 +320,13 @@ pub const Parser = struct { else => { self.putBackToken(token); stack.append(State { - .PrefixOpExpression = DestPtr { .Field = &addr_of_expr.op_expr}, + .Expression = DestPtr { .Field = &addr_of_expr.op_expr}, }) %% unreachable; continue; }, } }, - State.SuffixOpExpression => |dest_ptr| { - stack.append(State { .PrimaryExpression = dest_ptr }) %% unreachable; - continue; - }, - - State.PrimaryExpression => |dest_ptr| { - const token = self.getNextToken(); - switch (token.id) { - Token.Id.Identifier => { - _ = %return self.createAttachIdentifier(dest_ptr, token); - continue; - }, - Token.Id.IntegerLiteral => { - _ = %return self.createAttachIntegerLiteral(dest_ptr, token); - continue; - }, - Token.Id.FloatLiteral => { - _ = %return self.createAttachFloatLiteral(dest_ptr, token); - continue; - }, - else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), - } - }, - State.TypeExpr => |dest_ptr| { const token = self.getNextToken(); if (token.id == Token.Id.Keyword_var) { @@ -429,7 +334,7 @@ pub const Parser = struct { } self.putBackToken(token); - stack.append(State { .PrefixOpExpression = dest_ptr }) %% unreachable; + stack.append(State { .Expression = dest_ptr }) %% unreachable; continue; }, @@ -577,8 +482,6 @@ pub const Parser = struct { %return stack.append(State { .Expression = DestPtr{.List = &block.statements} }); continue; }, - - State.GroupedExpression => @panic("TODO"), } unreachable; } @@ -882,7 +785,6 @@ pub const Parser = struct { Expression: &ast.Node, AddrOfExprBit: &ast.NodeAddrOfExpr, VarDecl: &ast.NodeVarDecl, - VarDeclAlign: &ast.NodeVarDecl, Statement: &ast.Node, PrintIndent, Indent: usize, @@ -969,24 +871,22 @@ pub const Parser = struct { %return stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token)); %return stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token)); - %return stack.append(RenderState { .VarDeclAlign = var_decl }); + %return stack.append(RenderState { .Text = ";" }); + if (var_decl.init_node) |init_node| { + %return stack.append(RenderState { .Expression = init_node }); + %return stack.append(RenderState { .Text = " = " }); + } + if (var_decl.align_node) |align_node| { + %return stack.append(RenderState { .Text = ")" }); + %return stack.append(RenderState { .Expression = align_node }); + %return stack.append(RenderState { .Text = " align(" }); + } if (var_decl.type_node) |type_node| { %return stream.print(": "); %return stack.append(RenderState { .Expression = type_node }); } }, - RenderState.VarDeclAlign => |var_decl| { - if (var_decl.align_node != null) { - @panic("TODO"); - } - %return stack.append(RenderState { .Text = ";" }); - if (var_decl.init_node) |init_node| { - %return stream.print(" = "); - %return stack.append(RenderState { .Expression = init_node }); - } - }, - RenderState.ParamDecl => |base| { const param_decl = @fieldParentPtr(ast.NodeParamDecl, "base", base); if (param_decl.comptime_token) |comptime_token| { @@ -1198,6 +1098,11 @@ test "zig fmt" { \\ ); + testCanonical( + \\var foo: c_int align(1); + \\ + ); + testCanonical( \\fn main(argc: c_int, argv: &&u8) -> c_int { \\ const a = b; From 84619abe9f16bc030c5bdb888f47a9b49b87c2ff Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Dec 2017 21:50:37 -0500 Subject: [PATCH 47/69] add test for allowing slice[slice.len..slice.len] --- test/cases/slice.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/cases/slice.zig b/test/cases/slice.zig index 1498f948ea..44df8aa612 100644 --- a/test/cases/slice.zig +++ b/test/cases/slice.zig @@ -1,4 +1,5 @@ const assert = @import("std").debug.assert; +const mem = @import("std").mem; const x = @intToPtr(&i32, 0x1000)[0..0x500]; const y = x[0x100..]; @@ -15,3 +16,12 @@ test "slice child property" { var slice = array[0..]; assert(@typeOf(slice).Child == i32); } + +test "debug safety lets us slice from len..len" { + var an_array = []u8{1, 2, 3}; + assert(mem.eql(u8, sliceFromLenToLen(an_array[0..], 3, 3), "")); +} + +fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) -> []u8 { + return a_slice[start..end]; +} From f55fdc00fcde3cd17ed0ddb94c0997c882dbe6b9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 13 Dec 2017 21:53:52 -0500 Subject: [PATCH 48/69] fix const and volatile qualifiers being dropped sometimes in the expression `&const a.b`, the const (and/or volatile) qualifiers would be incorrectly dropped. closes #655 --- doc/docgen.zig | 2 +- src/ir.cpp | 23 ++++++++++++++++------- src/ir_print.cpp | 8 ++++++-- std/base64.zig | 2 +- test/behavior.zig | 1 + test/cases/bugs/655.zig | 12 ++++++++++++ test/cases/bugs/655_other_file.zig | 1 + test/cases/misc.zig | 2 +- 8 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 test/cases/bugs/655.zig create mode 100644 test/cases/bugs/655_other_file.zig diff --git a/doc/docgen.zig b/doc/docgen.zig index 81febb074e..bec12d98b7 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -42,7 +42,7 @@ const State = enum { // TODO look for code segments -fn gen(in: &io.InStream, out: &const io.OutStream) { +fn gen(in: &io.InStream, out: &io.OutStream) { var state = State.Start; while (true) { const byte = in.readByte() %% |err| { diff --git a/src/ir.cpp b/src/ir.cpp index 40f853531f..62b9545348 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -1025,7 +1025,7 @@ static IrInstruction *ir_build_ptr_type_of(IrBuilder *irb, Scope *scope, AstNode ptr_type_of_instruction->bit_offset_start = bit_offset_start; ptr_type_of_instruction->bit_offset_end = bit_offset_end; - ir_ref_instruction(align_value, irb->current_basic_block); + if (align_value) ir_ref_instruction(align_value, irb->current_basic_block); ir_ref_instruction(child_type, irb->current_basic_block); return &ptr_type_of_instruction->base; @@ -4897,13 +4897,18 @@ static IrInstruction *ir_gen_address_of(IrBuilder *irb, Scope *scope, AstNode *n AstNode *expr_node = node->data.addr_of_expr.op_expr; AstNode *align_expr = node->data.addr_of_expr.align_expr; - if (align_expr == nullptr) { + if (align_expr == nullptr && !is_const && !is_volatile) { return ir_gen_node_extra(irb, expr_node, scope, make_lval_addr(is_const, is_volatile)); } - IrInstruction *align_value = ir_gen_node(irb, align_expr, scope); - if (align_value == irb->codegen->invalid_instruction) - return align_value; + IrInstruction *align_value; + if (align_expr != nullptr) { + align_value = ir_gen_node(irb, align_expr, scope); + if (align_value == irb->codegen->invalid_instruction) + return align_value; + } else { + align_value = nullptr; + } IrInstruction *child_type = ir_gen_node(irb, expr_node, scope); if (child_type == irb->codegen->invalid_instruction) @@ -15959,8 +15964,12 @@ static TypeTableEntry *ir_analyze_instruction_ptr_type_of(IrAnalyze *ira, IrInst return ira->codegen->builtin_types.entry_invalid; uint32_t align_bytes; - if (!ir_resolve_align(ira, instruction->align_value->other, &align_bytes)) - return ira->codegen->builtin_types.entry_invalid; + if (instruction->align_value != nullptr) { + if (!ir_resolve_align(ira, instruction->align_value->other, &align_bytes)) + return ira->codegen->builtin_types.entry_invalid; + } else { + align_bytes = get_abi_alignment(ira->codegen, child_type); + } ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); out_val->data.x_type = get_pointer_to_type_extra(ira->codegen, child_type, diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 223a012456..b6a7c32dfc 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -886,8 +886,12 @@ static void ir_print_can_implicit_cast(IrPrint *irp, IrInstructionCanImplicitCas } static void ir_print_ptr_type_of(IrPrint *irp, IrInstructionPtrTypeOf *instruction) { - fprintf(irp->f, "&align "); - ir_print_other_instruction(irp, instruction->align_value); + fprintf(irp->f, "&"); + if (instruction->align_value != nullptr) { + fprintf(irp->f, "align("); + ir_print_other_instruction(irp, instruction->align_value); + fprintf(irp->f, ")"); + } const char *const_str = instruction->is_const ? "const " : ""; const char *volatile_str = instruction->is_volatile ? "volatile " : ""; fprintf(irp->f, ":%" PRIu32 ":%" PRIu32 " %s%s", instruction->bit_offset_start, instruction->bit_offset_end, diff --git a/std/base64.zig b/std/base64.zig index 25e438c4fb..840643b565 100644 --- a/std/base64.zig +++ b/std/base64.zig @@ -193,7 +193,7 @@ pub const Base64DecoderWithIgnore = struct { /// Decoding more data than can fit in dest results in error.OutputTooSmall. See also ::calcSizeUpperBound. /// Returns the number of bytes writen to dest. pub fn decode(decoder_with_ignore: &const Base64DecoderWithIgnore, dest: []u8, source: []const u8) -> %usize { - const decoder = &const decoder_with_ignore.decoder; + const decoder = &decoder_with_ignore.decoder; var src_cursor: usize = 0; var dest_cursor: usize = 0; diff --git a/test/behavior.zig b/test/behavior.zig index bdd428074b..da5b173e17 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -7,6 +7,7 @@ comptime { _ = @import("cases/bitcast.zig"); _ = @import("cases/bool.zig"); _ = @import("cases/bugs/394.zig"); + _ = @import("cases/bugs/655.zig"); _ = @import("cases/cast.zig"); _ = @import("cases/const_slice_child.zig"); _ = @import("cases/defer.zig"); diff --git a/test/cases/bugs/655.zig b/test/cases/bugs/655.zig new file mode 100644 index 0000000000..a0da9d53a2 --- /dev/null +++ b/test/cases/bugs/655.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +const other_file = @import("655_other_file.zig"); + +test "function with &const parameter with type dereferenced by namespace" { + const x: other_file.Integer = 1234; + comptime std.debug.assert(@typeOf(&x) == &const other_file.Integer); + foo(x); +} + +fn foo(x: &const other_file.Integer) { + std.debug.assert(*x == 1234); +} diff --git a/test/cases/bugs/655_other_file.zig b/test/cases/bugs/655_other_file.zig new file mode 100644 index 0000000000..df1df44955 --- /dev/null +++ b/test/cases/bugs/655_other_file.zig @@ -0,0 +1 @@ +pub const Integer = u32; diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 9f4f064f6b..e5e6575fab 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -504,7 +504,7 @@ test "@typeName" { test "volatile load and store" { var number: i32 = 1234; - const ptr = &volatile number; + const ptr = (&volatile i32)(&number); *ptr += 1; assert(*ptr == 1235); } From c9e01412a451419491786bf3db1713875928092c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 14 Dec 2017 01:07:23 -0500 Subject: [PATCH 49/69] fix compiler crash in a nullable if after an if in... ...a switch prong of a switch with 2 prongs in an else closes #656 --- src/ir.cpp | 6 +++--- test/behavior.zig | 1 + test/cases/bugs/656.zig | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 test/cases/bugs/656.zig diff --git a/src/ir.cpp b/src/ir.cpp index 62b9545348..ce7d8dcedd 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -1191,6 +1191,8 @@ static IrInstruction *ir_build_var_decl(IrBuilder *irb, Scope *scope, AstNode *s if (align_value) ir_ref_instruction(align_value, irb->current_basic_block); ir_ref_instruction(init_value, irb->current_basic_block); + var->decl_instruction = &decl_var_instruction->base; + return &decl_var_instruction->base; } @@ -5108,9 +5110,7 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod if (init_value == irb->codegen->invalid_instruction) return init_value; - IrInstruction *result = ir_build_var_decl(irb, scope, node, var, type_instruction, align_value, init_value); - var->decl_instruction = result; - return result; + return ir_build_var_decl(irb, scope, node, var, type_instruction, align_value, init_value); } static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *node) { diff --git a/test/behavior.zig b/test/behavior.zig index da5b173e17..ecb8cf74c9 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -8,6 +8,7 @@ comptime { _ = @import("cases/bool.zig"); _ = @import("cases/bugs/394.zig"); _ = @import("cases/bugs/655.zig"); + _ = @import("cases/bugs/656.zig"); _ = @import("cases/cast.zig"); _ = @import("cases/const_slice_child.zig"); _ = @import("cases/defer.zig"); diff --git a/test/cases/bugs/656.zig b/test/cases/bugs/656.zig new file mode 100644 index 0000000000..70000c9efd --- /dev/null +++ b/test/cases/bugs/656.zig @@ -0,0 +1,30 @@ +const assert = @import("std").debug.assert; + +const PrefixOp = union(enum) { + Return, + AddrOf: Value, +}; + +const Value = struct { + align_expr: ?u32, +}; + +test "nullable if after an if in a switch prong of a switch with 2 prongs in an else" { + foo(false, true); +} + +fn foo(a: bool, b: bool) { + var prefix_op = PrefixOp { .AddrOf = Value { .align_expr = 1234 } }; + if (a) { + } else { + switch (prefix_op) { + PrefixOp.AddrOf => |addr_of_info| { + if (b) { } + if (addr_of_info.align_expr) |align_expr| { + assert(align_expr == 1234); + } + }, + PrefixOp.Return => {}, + } + } +} From 75ecfdf66db22942da349d4279b9ddaa8167788f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 14 Dec 2017 19:41:35 -0500 Subject: [PATCH 50/69] replace quicksort with blocksort closes #657 --- std/math/index.zig | 31 +- std/math/sqrt.zig | 62 ++- std/mem.zig | 37 ++ std/sort.zig | 1035 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 1102 insertions(+), 63 deletions(-) diff --git a/std/math/index.zig b/std/math/index.zig index 8a583fb8e9..029f2836b7 100644 --- a/std/math/index.zig +++ b/std/math/index.zig @@ -174,12 +174,6 @@ test "math" { } -pub const Cmp = enum { - Less, - Equal, - Greater, -}; - pub fn min(x: var, y: var) -> @typeOf(x + y) { if (x < y) x else y } @@ -522,3 +516,28 @@ pub fn cast(comptime T: type, x: var) -> %T { return T(x); } } + +pub fn floorPowerOfTwo(comptime T: type, value: T) -> T { + var x = value; + + comptime var i = 1; + inline while(T.bit_count > i) : (i *= 2) { + x |= (x >> i); + } + + return x - (x >> 1); +} + +test "math.floorPowerOfTwo" { + testFloorPowerOfTwo(); + comptime testFloorPowerOfTwo(); +} + +fn testFloorPowerOfTwo() { + assert(floorPowerOfTwo(u32, 63) == 32); + assert(floorPowerOfTwo(u32, 64) == 64); + assert(floorPowerOfTwo(u32, 65) == 64); + assert(floorPowerOfTwo(u4, 7) == 4); + assert(floorPowerOfTwo(u4, 8) == 8); + assert(floorPowerOfTwo(u4, 9) == 8); +} diff --git a/std/math/sqrt.zig b/std/math/sqrt.zig index bd1bb1fca4..86426af3aa 100644 --- a/std/math/sqrt.zig +++ b/std/math/sqrt.zig @@ -7,12 +7,34 @@ const math = @import("index.zig"); const assert = @import("../debug.zig").assert; +const builtin = @import("builtin"); +const TypeId = builtin.TypeId; -pub fn sqrt(x: var) -> @typeOf(x) { +pub fn sqrt(x: var) -> (if (@typeId(@typeOf(x)) == TypeId.Int) @IntType(false, @typeOf(x).bit_count / 2) else @typeOf(x)) { const T = @typeOf(x); - switch (T) { - f32 => @inlineCall(sqrt32, x), - f64 => @inlineCall(sqrt64, x), + switch (@typeId(T)) { + TypeId.FloatLiteral => { + return T(sqrt64(x)) + }, + TypeId.Float => { + return switch (T) { + f32 => sqrt32(x), + f64 => sqrt64(x), + else => @compileError("sqrt not implemented for " ++ @typeName(T)), + }; + }, + TypeId.IntLiteral => comptime { + if (x > @maxValue(u128)) { + @compileError("sqrt not implemented for comptime_int greater than 128 bits"); + } + if (x < 0) { + @compileError("sqrt on negative number"); + } + return T(sqrt_int(u128, x)); + }, + TypeId.Int => { + return sqrt_int(T, x); + }, else => @compileError("sqrt not implemented for " ++ @typeName(T)), } } @@ -274,3 +296,35 @@ test "math.sqrt64.special" { assert(math.isNan(sqrt64(-1.0))); assert(math.isNan(sqrt64(math.nan(f64)))); } + +fn sqrt_int(comptime T: type, value: T) -> @IntType(false, T.bit_count / 2) { + var op = value; + var res: T = 0; + var one: T = 1 << (T.bit_count - 2); + + // "one" starts at the highest power of four <= than the argument. + while (one > op) { + one >>= 2; + } + + while (one != 0) { + if (op >= res + one) { + op -= res + one; + res += 2 * one; + } + res >>= 1; + one >>= 2; + } + + const ResultType = @IntType(false, T.bit_count / 2); + return ResultType(res); +} + +test "math.sqrt_int" { + assert(sqrt_int(u32, 3) == 1); + assert(sqrt_int(u32, 4) == 2); + assert(sqrt_int(u32, 5) == 2); + assert(sqrt_int(u32, 8) == 2); + assert(sqrt_int(u32, 9) == 3); + assert(sqrt_int(u32, 10) == 3); +} diff --git a/std/mem.zig b/std/mem.zig index 4f7609dd94..10d2221f9a 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -527,3 +527,40 @@ pub fn max(comptime T: type, slice: []const T) -> T { test "mem.max" { assert(max(u8, "abcdefg") == 'g'); } + +pub fn swap(comptime T: type, a: &T, b: &T) { + const tmp = *a; + *a = *b; + *b = tmp; +} + +/// In-place order reversal of a slice +pub fn reverse(comptime T: type, items: []T) { + var i: usize = 0; + const end = items.len / 2; + while (i < end) : (i += 1) { + swap(T, &items[i], &items[items.len - i - 1]); + } +} + +test "std.mem.reverse" { + var arr = []i32{ 5, 3, 1, 2, 4 }; + reverse(i32, arr[0..]); + + assert(eql(i32, arr, []i32{ 4, 2, 1, 3, 5 })) +} + +/// In-place rotation of the values in an array ([0 1 2 3] becomes [1 2 3 0] if we rotate by 1) +/// Assumes 0 <= amount <= items.len +pub fn rotate(comptime T: type, items: []T, amount: usize) { + reverse(T, items[0..amount]); + reverse(T, items[amount..]); + reverse(T, items); +} + +test "std.mem.rotate" { + var arr = []i32{ 5, 3, 1, 2, 4 }; + rotate(i32, arr[0..], 2); + + assert(eql(i32, arr, []i32{ 1, 2, 4, 5, 3 })) +} diff --git a/std/sort.zig b/std/sort.zig index d02d685e07..a3538d3487 100644 --- a/std/sort.zig +++ b/std/sort.zig @@ -1,75 +1,965 @@ -const assert = @import("debug.zig").assert; -const mem = @import("mem.zig"); -const math = @import("math/index.zig"); +const std = @import("index.zig"); +const assert = std.debug.assert; +const mem = std.mem; +const math = std.math; -pub const Cmp = math.Cmp; - -/// Stable sort using O(1) space. Currently implemented as insertion sort. -pub fn sort_stable(comptime T: type, array: []T, comptime cmp: fn(a: &const T, b: &const T)->Cmp) { - {var i: usize = 1; while (i < array.len) : (i += 1) { - const x = array[i]; +/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case. O(1) memory (no allocator required). +pub fn insertionSort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool) { + {var i: usize = 1; while (i < items.len) : (i += 1) { + const x = items[i]; var j: usize = i; - while (j > 0 and cmp(array[j - 1], x) == Cmp.Greater) : (j -= 1) { - array[j] = array[j - 1]; + while (j > 0 and lessThan(x, items[j - 1])) : (j -= 1) { + items[j] = items[j - 1]; } - array[j] = x; + items[j] = x; }} } -/// Unstable sort using O(n) stack space. Currently implemented as quicksort. -pub fn sort(comptime T: type, array: []T, comptime cmp: fn(a: &const T, b: &const T)->Cmp) { - if (array.len > 0) { - quicksort(T, array, 0, array.len - 1, cmp); +const Range = struct { + start: usize, + end: usize, + + fn init(start: usize, end: usize) -> Range { + return Range { .start = start, .end = end }; + } + + fn length(self: &const Range) -> usize { + return self.end - self.start; + } +}; + + +const Iterator = struct { + size: usize, + power_of_two: usize, + numerator: usize, + decimal: usize, + denominator: usize, + decimal_step: usize, + numerator_step: usize, + + fn init(size2: usize, min_level: usize) -> Iterator { + const power_of_two = math.floorPowerOfTwo(usize, size2); + const denominator = power_of_two / min_level; + return Iterator { + .numerator = 0, + .decimal = 0, + .size = size2, + .power_of_two = power_of_two, + .denominator = denominator, + .decimal_step = size2 / denominator, + .numerator_step = size2 % denominator, + }; + } + + fn begin(self: &Iterator) { + self.numerator = 0; + self.decimal = 0; + } + + fn nextRange(self: &Iterator) -> Range { + const start = self.decimal; + + self.decimal += self.decimal_step; + self.numerator += self.numerator_step; + if (self.numerator >= self.denominator) { + self.numerator -= self.denominator; + self.decimal += 1; + } + + return Range {.start = start, .end = self.decimal}; + } + + fn finished(self: &Iterator) -> bool { + return self.decimal >= self.size; + } + + fn nextLevel(self: &Iterator) -> bool { + self.decimal_step += self.decimal_step; + self.numerator_step += self.numerator_step; + if (self.numerator_step >= self.denominator) { + self.numerator_step -= self.denominator; + self.decimal_step += 1; + } + + return (self.decimal_step < self.size); + } + + fn length(self: &Iterator) -> usize { + return self.decimal_step; + } +}; + +const Pull = struct { + from: usize, + to: usize, + count: usize, + range: Range, +}; + +/// Stable in-place sort. O(n) best case, O(n*log(n)) worst case and average case. O(1) memory (no allocator required). +/// Currently implemented as block sort. +pub fn sort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool) { + // Implementation ported from https://github.com/BonzaiThePenguin/WikiSort/blob/master/WikiSort.c + var cache: [512]T = undefined; + + if (items.len < 4) { + if (items.len == 3) { + // hard coded insertion sort + if (lessThan(items[1], items[0])) mem.swap(T, &items[0], &items[1]); + if (lessThan(items[2], items[1])) { + mem.swap(T, &items[1], &items[2]); + if (lessThan(items[1], items[0])) mem.swap(T, &items[0], &items[1]); + } + } else if (items.len == 2) { + if (lessThan(items[1], items[0])) mem.swap(T, &items[0], &items[1]); + } + return; + } + + // sort groups of 4-8 items at a time using an unstable sorting network, + // but keep track of the original item orders to force it to be stable + // http://pages.ripco.net/~jgamble/nw.html + var iterator = Iterator.init(items.len, 4); + while (!iterator.finished()) { + var order = []u8{0, 1, 2, 3, 4, 5, 6, 7}; + const range = iterator.nextRange(); + + const sliced_items = items[range.start..]; + switch (range.length()) { + 8 => { + swap(T, sliced_items, lessThan, &order, 0, 1); + swap(T, sliced_items, lessThan, &order, 2, 3); + swap(T, sliced_items, lessThan, &order, 4, 5); + swap(T, sliced_items, lessThan, &order, 6, 7); + swap(T, sliced_items, lessThan, &order, 0, 2); + swap(T, sliced_items, lessThan, &order, 1, 3); + swap(T, sliced_items, lessThan, &order, 4, 6); + swap(T, sliced_items, lessThan, &order, 5, 7); + swap(T, sliced_items, lessThan, &order, 1, 2); + swap(T, sliced_items, lessThan, &order, 5, 6); + swap(T, sliced_items, lessThan, &order, 0, 4); + swap(T, sliced_items, lessThan, &order, 3, 7); + swap(T, sliced_items, lessThan, &order, 1, 5); + swap(T, sliced_items, lessThan, &order, 2, 6); + swap(T, sliced_items, lessThan, &order, 1, 4); + swap(T, sliced_items, lessThan, &order, 3, 6); + swap(T, sliced_items, lessThan, &order, 2, 4); + swap(T, sliced_items, lessThan, &order, 3, 5); + swap(T, sliced_items, lessThan, &order, 3, 4); + }, + 7 => { + swap(T, sliced_items, lessThan, &order, 1, 2); + swap(T, sliced_items, lessThan, &order, 3, 4); + swap(T, sliced_items, lessThan, &order, 5, 6); + swap(T, sliced_items, lessThan, &order, 0, 2); + swap(T, sliced_items, lessThan, &order, 3, 5); + swap(T, sliced_items, lessThan, &order, 4, 6); + swap(T, sliced_items, lessThan, &order, 0, 1); + swap(T, sliced_items, lessThan, &order, 4, 5); + swap(T, sliced_items, lessThan, &order, 2, 6); + swap(T, sliced_items, lessThan, &order, 0, 4); + swap(T, sliced_items, lessThan, &order, 1, 5); + swap(T, sliced_items, lessThan, &order, 0, 3); + swap(T, sliced_items, lessThan, &order, 2, 5); + swap(T, sliced_items, lessThan, &order, 1, 3); + swap(T, sliced_items, lessThan, &order, 2, 4); + swap(T, sliced_items, lessThan, &order, 2, 3); + }, + 6 => { + swap(T, sliced_items, lessThan, &order, 1, 2); + swap(T, sliced_items, lessThan, &order, 4, 5); + swap(T, sliced_items, lessThan, &order, 0, 2); + swap(T, sliced_items, lessThan, &order, 3, 5); + swap(T, sliced_items, lessThan, &order, 0, 1); + swap(T, sliced_items, lessThan, &order, 3, 4); + swap(T, sliced_items, lessThan, &order, 2, 5); + swap(T, sliced_items, lessThan, &order, 0, 3); + swap(T, sliced_items, lessThan, &order, 1, 4); + swap(T, sliced_items, lessThan, &order, 2, 4); + swap(T, sliced_items, lessThan, &order, 1, 3); + swap(T, sliced_items, lessThan, &order, 2, 3); + }, + 5 => { + swap(T, sliced_items, lessThan, &order, 0, 1); + swap(T, sliced_items, lessThan, &order, 3, 4); + swap(T, sliced_items, lessThan, &order, 2, 4); + swap(T, sliced_items, lessThan, &order, 2, 3); + swap(T, sliced_items, lessThan, &order, 1, 4); + swap(T, sliced_items, lessThan, &order, 0, 3); + swap(T, sliced_items, lessThan, &order, 0, 2); + swap(T, sliced_items, lessThan, &order, 1, 3); + swap(T, sliced_items, lessThan, &order, 1, 2); + }, + 4 => { + swap(T, sliced_items, lessThan, &order, 0, 1); + swap(T, sliced_items, lessThan, &order, 2, 3); + swap(T, sliced_items, lessThan, &order, 0, 2); + swap(T, sliced_items, lessThan, &order, 1, 3); + swap(T, sliced_items, lessThan, &order, 1, 2); + }, + else => {}, + } + } + if (items.len < 8) return; + + // then merge sort the higher levels, which can be 8-15, 16-31, 32-63, 64-127, etc. + while (true) { + // if every A and B block will fit into the cache, use a special branch specifically for merging with the cache + // (we use < rather than <= since the block size might be one more than iterator.length()) + if (iterator.length() < cache.len) { + // if four subarrays fit into the cache, it's faster to merge both pairs of subarrays into the cache, + // then merge the two merged subarrays from the cache back into the original array + if ((iterator.length() + 1) * 4 <= cache.len and iterator.length() * 4 <= items.len) { + iterator.begin(); + while (!iterator.finished()) { + // merge A1 and B1 into the cache + var A1 = iterator.nextRange(); + var B1 = iterator.nextRange(); + var A2 = iterator.nextRange(); + var B2 = iterator.nextRange(); + + if (lessThan(items[B1.end - 1], items[A1.start])) { + // the two ranges are in reverse order, so copy them in reverse order into the cache + mem.copy(T, cache[B1.length()..], items[A1.start..A1.end]); + mem.copy(T, cache[0..], items[B1.start..B1.end]); + } else if (lessThan(items[B1.start], items[A1.end - 1])) { + // these two ranges weren't already in order, so merge them into the cache + mergeInto(T, items, A1, B1, lessThan, cache[0..]); + } else { + // if A1, B1, A2, and B2 are all in order, skip doing anything else + if (!lessThan(items[B2.start], items[A2.end - 1]) and !lessThan(items[A2.start], items[B1.end - 1])) continue; + + // copy A1 and B1 into the cache in the same order + mem.copy(T, cache[0..], items[A1.start..A1.end]); + mem.copy(T, cache[A1.length()..], items[B1.start..B1.end]); + } + A1 = Range.init(A1.start, B1.end); + + // merge A2 and B2 into the cache + if (lessThan(items[B2.end - 1], items[A2.start])) { + // the two ranges are in reverse order, so copy them in reverse order into the cache + mem.copy(T, cache[A1.length() + B2.length()..], items[A2.start..A2.end]); + mem.copy(T, cache[A1.length()..], items[B2.start..B2.end]); + } else if (lessThan(items[B2.start], items[A2.end - 1])) { + // these two ranges weren't already in order, so merge them into the cache + mergeInto(T, items, A2, B2, lessThan, cache[A1.length()..]); + } else { + // copy A2 and B2 into the cache in the same order + mem.copy(T, cache[A1.length()..], items[A2.start..A2.end]); + mem.copy(T, cache[A1.length() + A2.length()..], items[B2.start..B2.end]); + } + A2 = Range.init(A2.start, B2.end); + + // merge A1 and A2 from the cache into the items + const A3 = Range.init(0, A1.length()); + const B3 = Range.init(A1.length(), A1.length() + A2.length()); + + if (lessThan(cache[B3.end - 1], cache[A3.start])) { + // the two ranges are in reverse order, so copy them in reverse order into the items + mem.copy(T, items[A1.start + A2.length()..], cache[A3.start..A3.end]); + mem.copy(T, items[A1.start..], cache[B3.start..B3.end]); + } else if (lessThan(cache[B3.start], cache[A3.end - 1])) { + // these two ranges weren't already in order, so merge them back into the items + mergeInto(T, cache[0..], A3, B3, lessThan, items[A1.start..]); + } else { + // copy A3 and B3 into the items in the same order + mem.copy(T, items[A1.start..], cache[A3.start..A3.end]); + mem.copy(T, items[A1.start + A1.length()..], cache[B3.start..B3.end]); + } + } + + // we merged two levels at the same time, so we're done with this level already + // (iterator.nextLevel() is called again at the bottom of this outer merge loop) + _ = iterator.nextLevel(); + + } else { + iterator.begin(); + while (!iterator.finished()) { + var A = iterator.nextRange(); + var B = iterator.nextRange(); + + if (lessThan(items[B.end - 1], items[A.start])) { + // the two ranges are in reverse order, so a simple rotation should fix it + mem.rotate(T, items[A.start..B.end], A.length()); + } else if (lessThan(items[B.start], items[A.end - 1])) { + // these two ranges weren't already in order, so we'll need to merge them! + mem.copy(T, cache[0..], items[A.start..A.end]); + mergeExternal(T, items, A, B, lessThan, cache[0..]); + } + } + } + } else { + // this is where the in-place merge logic starts! + // 1. pull out two internal buffers each containing √A unique values + // 1a. adjust block_size and buffer_size if we couldn't find enough unique values + // 2. loop over the A and B subarrays within this level of the merge sort + // 3. break A and B into blocks of size 'block_size' + // 4. "tag" each of the A blocks with values from the first internal buffer + // 5. roll the A blocks through the B blocks and drop/rotate them where they belong + // 6. merge each A block with any B values that follow, using the cache or the second internal buffer + // 7. sort the second internal buffer if it exists + // 8. redistribute the two internal buffers back into the items + + var block_size: usize = math.sqrt(iterator.length()); + var buffer_size = iterator.length()/block_size + 1; + + // as an optimization, we really only need to pull out the internal buffers once for each level of merges + // after that we can reuse the same buffers over and over, then redistribute it when we're finished with this level + var A: Range = undefined; + var B: Range = undefined; + var index: usize = 0; + var last: usize = 0; + var count: usize = 0; + var find: usize = 0; + var start: usize = 0; + var pull_index: usize = 0; + var pull = []Pull{ + Pull {.from = 0, .to = 0, .count = 0, .range = Range.init(0, 0),}, + Pull {.from = 0, .to = 0, .count = 0, .range = Range.init(0, 0),}, + }; + + var buffer1 = Range.init(0, 0); + var buffer2 = Range.init(0, 0); + + // find two internal buffers of size 'buffer_size' each + find = buffer_size + buffer_size; + var find_separately = false; + + if (block_size <= cache.len) { + // if every A block fits into the cache then we won't need the second internal buffer, + // so we really only need to find 'buffer_size' unique values + find = buffer_size; + } else if (find > iterator.length()) { + // we can't fit both buffers into the same A or B subarray, so find two buffers separately + find = buffer_size; + find_separately = true; + } + + // we need to find either a single contiguous space containing 2√A unique values (which will be split up into two buffers of size √A each), + // or we need to find one buffer of < 2√A unique values, and a second buffer of √A unique values, + // OR if we couldn't find that many unique values, we need the largest possible buffer we can get + + // in the case where it couldn't find a single buffer of at least √A unique values, + // all of the Merge steps must be replaced by a different merge algorithm (MergeInPlace) + iterator.begin(); + while (!iterator.finished()) { + A = iterator.nextRange(); + B = iterator.nextRange(); + + // just store information about where the values will be pulled from and to, + // as well as how many values there are, to create the two internal buffers + + // check A for the number of unique values we need to fill an internal buffer + // these values will be pulled out to the start of A + last = A.start; + count = 1; + while (count < find) : ({last = index; count += 1}) { + index = findLastForward(T, items, items[last], Range.init(last + 1, A.end), lessThan, find - count); + if (index == A.end) break; + } + index = last; + + if (count >= buffer_size) { + // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffer + pull[pull_index] = Pull { + .range = Range.init(A.start, B.end), + .count = count, + .from = index, + .to = A.start, + }; + pull_index = 1; + + if (count == buffer_size + buffer_size) { + // we were able to find a single contiguous section containing 2√A unique values, + // so this section can be used to contain both of the internal buffers we'll need + buffer1 = Range.init(A.start, A.start + buffer_size); + buffer2 = Range.init(A.start + buffer_size, A.start + count); + break; + } else if (find == buffer_size + buffer_size) { + // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values, + // so we still need to find a second separate buffer of at least √A unique values + buffer1 = Range.init(A.start, A.start + count); + find = buffer_size; + } else if (block_size <= cache.len) { + // we found the first and only internal buffer that we need, so we're done! + buffer1 = Range.init(A.start, A.start + count); + break; + } else if (find_separately) { + // found one buffer, but now find the other one + buffer1 = Range.init(A.start, A.start + count); + find_separately = false; + } else { + // we found a second buffer in an 'A' subarray containing √A unique values, so we're done! + buffer2 = Range.init(A.start, A.start + count); + break; + } + } else if (pull_index == 0 and count > buffer1.length()) { + // keep track of the largest buffer we were able to find + buffer1 = Range.init(A.start, A.start + count); + pull[pull_index] = Pull { + .range = Range.init(A.start, B.end), + .count = count, + .from = index, + .to = A.start, + }; + } + + // check B for the number of unique values we need to fill an internal buffer + // these values will be pulled out to the end of B + last = B.end - 1; + count = 1; + while (count < find) : ({last = index - 1; count += 1}) { + index = findFirstBackward(T, items, items[last], Range.init(B.start, last), lessThan, find - count); + if (index == B.start) break; + } + index = last; + + if (count >= buffer_size) { + // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffe + pull[pull_index] = Pull { + .range = Range.init(A.start, B.end), + .count = count, + .from = index, + .to = B.end, + }; + pull_index = 1; + + if (count == buffer_size + buffer_size) { + // we were able to find a single contiguous section containing 2√A unique values, + // so this section can be used to contain both of the internal buffers we'll need + buffer1 = Range.init(B.end - count, B.end - buffer_size); + buffer2 = Range.init(B.end - buffer_size, B.end); + break; + } else if (find == buffer_size + buffer_size) { + // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values, + // so we still need to find a second separate buffer of at least √A unique values + buffer1 = Range.init(B.end - count, B.end); + find = buffer_size; + } else if (block_size <= cache.len) { + // we found the first and only internal buffer that we need, so we're done! + buffer1 = Range.init(B.end - count, B.end); + break; + } else if (find_separately) { + // found one buffer, but now find the other one + buffer1 = Range.init(B.end - count, B.end); + find_separately = false; + } else { + // buffer2 will be pulled out from a 'B' subarray, so if the first buffer was pulled out from the corresponding 'A' subarray, + // we need to adjust the end point for that A subarray so it knows to stop redistributing its values before reaching buffer2 + if (pull[0].range.start == A.start) pull[0].range.end -= pull[1].count; + + // we found a second buffer in an 'B' subarray containing √A unique values, so we're done! + buffer2 = Range.init(B.end - count, B.end); + break; + } + } else if (pull_index == 0 and count > buffer1.length()) { + // keep track of the largest buffer we were able to find + buffer1 = Range.init(B.end - count, B.end); + pull[pull_index] = Pull { + .range = Range.init(A.start, B.end), + .count = count, + .from = index, + .to = B.end, + }; + } + } + + // pull out the two ranges so we can use them as internal buffers + pull_index = 0; + while (pull_index < 2) : (pull_index += 1) { + const length = pull[pull_index].count; + + if (pull[pull_index].to < pull[pull_index].from) { + // we're pulling the values out to the left, which means the start of an A subarray + index = pull[pull_index].from; + count = 1; + while (count < length) : (count += 1) { + index = findFirstBackward(T, items, items[index - 1], Range.init(pull[pull_index].to, pull[pull_index].from - (count - 1)), lessThan, length - count); + const range = Range.init(index + 1, pull[pull_index].from + 1); + mem.rotate(T, items[range.start..range.end], range.length() - count); + pull[pull_index].from = index + count; + } + } else if (pull[pull_index].to > pull[pull_index].from) { + // we're pulling values out to the right, which means the end of a B subarray + index = pull[pull_index].from + 1; + count = 1; + while (count < length) : (count += 1) { + index = findLastForward(T, items, items[index], Range.init(index, pull[pull_index].to), lessThan, length - count); + const range = Range.init(pull[pull_index].from, index - 1); + mem.rotate(T, items[range.start..range.end], count); + pull[pull_index].from = index - 1 - count; + } + } + } + + // adjust block_size and buffer_size based on the values we were able to pull out + buffer_size = buffer1.length(); + block_size = iterator.length()/buffer_size + 1; + + // the first buffer NEEDS to be large enough to tag each of the evenly sized A blocks, + // so this was originally here to test the math for adjusting block_size above + // assert((iterator.length() + 1)/block_size <= buffer_size); + + // now that the two internal buffers have been created, it's time to merge each A+B combination at this level of the merge sort! + iterator.begin(); + while (!iterator.finished()) { + A = iterator.nextRange(); + B = iterator.nextRange(); + + // remove any parts of A or B that are being used by the internal buffers + start = A.start; + if (start == pull[0].range.start) { + if (pull[0].from > pull[0].to) { + A.start += pull[0].count; + + // if the internal buffer takes up the entire A or B subarray, then there's nothing to merge + // this only happens for very small subarrays, like √4 = 2, 2 * (2 internal buffers) = 4, + // which also only happens when cache.len is small or 0 since it'd otherwise use MergeExternal + if (A.length() == 0) continue; + } else if (pull[0].from < pull[0].to) { + B.end -= pull[0].count; + if (B.length() == 0) continue; + } + } + if (start == pull[1].range.start) { + if (pull[1].from > pull[1].to) { + A.start += pull[1].count; + if (A.length() == 0) continue; + } else if (pull[1].from < pull[1].to) { + B.end -= pull[1].count; + if (B.length() == 0) continue; + } + } + + if (lessThan(items[B.end - 1], items[A.start])) { + // the two ranges are in reverse order, so a simple rotation should fix it + mem.rotate(T, items[A.start..B.end], A.length()); + } else if (lessThan(items[A.end], items[A.end - 1])) { + // these two ranges weren't already in order, so we'll need to merge them! + var findA: usize = undefined; + + // break the remainder of A into blocks. firstA is the uneven-sized first A block + var blockA = Range.init(A.start, A.end); + var firstA = Range.init(A.start, A.start + blockA.length() % block_size); + + // swap the first value of each A block with the value in buffer1 + var indexA = buffer1.start; + index = firstA.end; + while (index < blockA.end) : ({indexA += 1; index += block_size}) { + mem.swap(T, &items[indexA], &items[index]); + } + + // start rolling the A blocks through the B blocks! + // whenever we leave an A block behind, we'll need to merge the previous A block with any B blocks that follow it, so track that information as well + var lastA = firstA; + var lastB = Range.init(0, 0); + var blockB = Range.init(B.start, B.start + math.min(block_size, B.length())); + blockA.start += firstA.length(); + indexA = buffer1.start; + + // if the first unevenly sized A block fits into the cache, copy it there for when we go to Merge it + // otherwise, if the second buffer is available, block swap the contents into that + if (lastA.length() <= cache.len) { + mem.copy(T, cache[0..], items[lastA.start..lastA.end]); + } else if (buffer2.length() > 0) { + blockSwap(T, items, lastA.start, buffer2.start, lastA.length()); + } + + if (blockA.length() > 0) { + while (true) { + // if there's a previous B block and the first value of the minimum A block is <= the last value of the previous B block, + // then drop that minimum A block behind. or if there are no B blocks left then keep dropping the remaining A blocks. + if ((lastB.length() > 0 and !lessThan(items[lastB.end - 1], items[indexA])) or blockB.length() == 0) { + // figure out where to split the previous B block, and rotate it at the split + const B_split = binaryFirst(T, items, items[indexA], lastB, lessThan); + const B_remaining = lastB.end - B_split; + + // swap the minimum A block to the beginning of the rolling A blocks + var minA = blockA.start; + findA = minA + block_size; + while (findA < blockA.end) : (findA += block_size) { + if (lessThan(items[findA], items[minA])) { + minA = findA; + } + } + blockSwap(T, items, blockA.start, minA, block_size); + + // swap the first item of the previous A block back with its original value, which is stored in buffer1 + mem.swap(T, &items[blockA.start], &items[indexA]); + indexA += 1; + + // locally merge the previous A block with the B values that follow it + // if lastA fits into the external cache we'll use that (with MergeExternal), + // or if the second internal buffer exists we'll use that (with MergeInternal), + // or failing that we'll use a strictly in-place merge algorithm (MergeInPlace) + + if (lastA.length() <= cache.len) { + mergeExternal(T, items, lastA, Range.init(lastA.end, B_split), lessThan, cache[0..]); + } else if (buffer2.length() > 0) { + mergeInternal(T, items, lastA, Range.init(lastA.end, B_split), lessThan, buffer2); + } else { + mergeInPlace(T, items, lastA, Range.init(lastA.end, B_split), lessThan); + } + + if (buffer2.length() > 0 or block_size <= cache.len) { + // copy the previous A block into the cache or buffer2, since that's where we need it to be when we go to merge it anyway + if (block_size <= cache.len) { + mem.copy(T, cache[0..], items[blockA.start..blockA.start + block_size]); + } else { + blockSwap(T, items, blockA.start, buffer2.start, block_size); + } + + // this is equivalent to rotating, but faster + // the area normally taken up by the A block is either the contents of buffer2, or data we don't need anymore since we memcopied it + // either way, we don't need to retain the order of those items, so instead of rotating we can just block swap B to where it belongs + blockSwap(T, items, B_split, blockA.start + block_size - B_remaining, B_remaining); + } else { + // we are unable to use the 'buffer2' trick to speed up the rotation operation since buffer2 doesn't exist, so perform a normal rotation + mem.rotate(T, items[B_split..blockA.start + block_size], blockA.start - B_split); + } + + // update the range for the remaining A blocks, and the range remaining from the B block after it was split + lastA = Range.init(blockA.start - B_remaining, blockA.start - B_remaining + block_size); + lastB = Range.init(lastA.end, lastA.end + B_remaining); + + // if there are no more A blocks remaining, this step is finished! + blockA.start += block_size; + if (blockA.length() == 0) + break; + + } else if (blockB.length() < block_size) { + // move the last B block, which is unevenly sized, to before the remaining A blocks, by using a rotation + // the cache is disabled here since it might contain the contents of the previous A block + mem.rotate(T, items[blockA.start..blockB.end], blockB.start - blockA.start); + + lastB = Range.init(blockA.start, blockA.start + blockB.length()); + blockA.start += blockB.length(); + blockA.end += blockB.length(); + blockB.end = blockB.start; + } else { + // roll the leftmost A block to the end by swapping it with the next B block + blockSwap(T, items, blockA.start, blockB.start, block_size); + lastB = Range.init(blockA.start, blockA.start + block_size); + + blockA.start += block_size; + blockA.end += block_size; + blockB.start += block_size; + + if (blockB.end > B.end - block_size) { + blockB.end = B.end; + } else { + blockB.end += block_size; + } + } + } + } + + // merge the last A block with the remaining B values + if (lastA.length() <= cache.len) { + mergeExternal(T, items, lastA, Range.init(lastA.end, B.end), lessThan, cache[0..]); + } else if (buffer2.length() > 0) { + mergeInternal(T, items, lastA, Range.init(lastA.end, B.end), lessThan, buffer2); + } else { + mergeInPlace(T, items, lastA, Range.init(lastA.end, B.end), lessThan); + } + } + } + + // when we're finished with this merge step we should have the one or two internal buffers left over, where the second buffer is all jumbled up + // insertion sort the second buffer, then redistribute the buffers back into the items using the opposite process used for creating the buffer + + // while an unstable sort like quicksort could be applied here, in benchmarks it was consistently slightly slower than a simple insertion sort, + // even for tens of millions of items. this may be because insertion sort is quite fast when the data is already somewhat sorted, like it is here + insertionSort(T, items[buffer2.start..buffer2.end], lessThan); + + pull_index = 0; + while (pull_index < 2) : (pull_index += 1) { + var unique = pull[pull_index].count * 2; + if (pull[pull_index].from > pull[pull_index].to) { + // the values were pulled out to the left, so redistribute them back to the right + var buffer = Range.init(pull[pull_index].range.start, pull[pull_index].range.start + pull[pull_index].count); + while (buffer.length() > 0) { + index = findFirstForward(T, items, items[buffer.start], Range.init(buffer.end, pull[pull_index].range.end), lessThan, unique); + const amount = index - buffer.end; + mem.rotate(T, items[buffer.start..index], buffer.length()); + buffer.start += (amount + 1); + buffer.end += amount; + unique -= 2; + } + } else if (pull[pull_index].from < pull[pull_index].to) { + // the values were pulled out to the right, so redistribute them back to the left + var buffer = Range.init(pull[pull_index].range.end - pull[pull_index].count, pull[pull_index].range.end); + while (buffer.length() > 0) { + index = findLastBackward(T, items, items[buffer.end - 1], Range.init(pull[pull_index].range.start, buffer.start), lessThan, unique); + const amount = buffer.start - index; + mem.rotate(T, items[index..buffer.end], amount); + buffer.start -= amount; + buffer.end -= (amount + 1); + unique -= 2; + } + } + } + } + + // double the size of each A and B subarray that will be merged in the next level + if (!iterator.nextLevel()) break; } } -fn quicksort(comptime T: type, array: []T, left: usize, right: usize, comptime cmp: fn(a: &const T, b: &const T)->Cmp) { - var i = left; - var j = right; - const p = (i + j) / 2; +// merge operation without a buffer +fn mergeInPlace(comptime T: type, items: []T, A_arg: &const Range, B_arg: &const Range, lessThan: fn(&const T,&const T)->bool) { + if (A_arg.length() == 0 or B_arg.length() == 0) return; + + // this just repeatedly binary searches into B and rotates A into position. + // the paper suggests using the 'rotation-based Hwang and Lin algorithm' here, + // but I decided to stick with this because it had better situational performance + // + // (Hwang and Lin is designed for merging subarrays of very different sizes, + // but WikiSort almost always uses subarrays that are roughly the same size) + // + // normally this is incredibly suboptimal, but this function is only called + // when none of the A or B blocks in any subarray contained 2√A unique values, + // which places a hard limit on the number of times this will ACTUALLY need + // to binary search and rotate. + // + // according to my analysis the worst case is √A rotations performed on √A items + // once the constant factors are removed, which ends up being O(n) + // + // again, this is NOT a general-purpose solution – it only works well in this case! + // kind of like how the O(n^2) insertion sort is used in some places - while (i <= j) { - while (cmp(array[i], array[p]) == Cmp.Less) { - i += 1; - } - while (cmp(array[j], array[p]) == Cmp.Greater) { - j -= 1; - } - if (i <= j) { - const tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - i += 1; - if (j > 0) j -= 1; + var A = *A_arg; + var B = *B_arg; + + while (true) { + // find the first place in B where the first item in A needs to be inserted + const mid = binaryFirst(T, items, items[A.start], B, lessThan); + + // rotate A into place + const amount = mid - A.end; + mem.rotate(T, items[A.start..mid], A.length()); + if (B.end == mid) break; + + // calculate the new A and B ranges + B.start = mid; + A = Range.init(A.start + amount, B.start); + A.start = binaryLast(T, items, items[A.start], A, lessThan); + if (A.length() == 0) break; + } +} + +// merge operation using an internal buffer +fn mergeInternal(comptime T: type, items: []T, A: &const Range, B: &const Range, lessThan: fn(&const T,&const T)->bool, buffer: &const Range) { + // whenever we find a value to add to the final array, swap it with the value that's already in that spot + // when this algorithm is finished, 'buffer' will contain its original contents, but in a different order + var A_count: usize = 0; + var B_count: usize = 0; + var insert: usize = 0; + + if (B.length() > 0 and A.length() > 0) { + while (true) { + if (!lessThan(items[B.start + B_count], items[buffer.start + A_count])) { + mem.swap(T, &items[A.start + insert], &items[buffer.start + A_count]); + A_count += 1; + insert += 1; + if (A_count >= A.length()) break; + } else { + mem.swap(T, &items[A.start + insert], &items[B.start + B_count]); + B_count += 1; + insert += 1; + if (B_count >= B.length()) break; + } } } - - if (left < j) quicksort(T, array, left, j, cmp); - if (i < right) quicksort(T, array, i, right, cmp); + + // swap the remainder of A into the final array + blockSwap(T, items, buffer.start + A_count, A.start + insert, A.length() - A_count); } -pub fn i32asc(a: &const i32, b: &const i32) -> Cmp { - return if (*a > *b) Cmp.Greater else if (*a < *b) Cmp.Less else Cmp.Equal +fn blockSwap(comptime T: type, items: []T, start1: usize, start2: usize, block_size: usize) { + var index: usize = 0; + while (index < block_size) : (index += 1) { + mem.swap(T, &items[start1 + index], &items[start2 + index]); + } } -pub fn i32desc(a: &const i32, b: &const i32) -> Cmp { - reverse(i32asc(a, b)) +// combine a linear search with a binary search to reduce the number of comparisons in situations +// where have some idea as to how many unique values there are and where the next value might be +fn findFirstForward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize { + if (range.length() == 0) return range.start; + const skip = math.max(range.length()/unique, usize(1)); + + var index = range.start + skip; + while (lessThan(items[index - 1], value)) : (index += skip) { + if (index >= range.end - skip) { + return binaryFirst(T, items, value, Range.init(index, range.end), lessThan); + } + } + + return binaryFirst(T, items, value, Range.init(index - skip, index), lessThan); } -pub fn u8asc(a: &const u8, b: &const u8) -> Cmp { - if (*a > *b) Cmp.Greater else if (*a < *b) Cmp.Less else Cmp.Equal +fn findFirstBackward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize { + if (range.length() == 0) return range.start; + const skip = math.max(range.length()/unique, usize(1)); + + var index = range.end - skip; + while (index > range.start and !lessThan(items[index - 1], value)) : (index -= skip) { + if (index < range.start + skip) { + return binaryFirst(T, items, value, Range.init(range.start, index), lessThan); + } + } + + return binaryFirst(T, items, value, Range.init(index, index + skip), lessThan); } -pub fn u8desc(a: &const u8, b: &const u8) -> Cmp { - reverse(u8asc(a, b)) +fn findLastForward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize { + if (range.length() == 0) return range.start; + const skip = math.max(range.length()/unique, usize(1)); + + var index = range.start + skip; + while (!lessThan(value, items[index - 1])) : (index += skip) { + if (index >= range.end - skip) { + return binaryLast(T, items, value, Range.init(index, range.end), lessThan); + } + } + + return binaryLast(T, items, value, Range.init(index - skip, index), lessThan); } -fn reverse(was: Cmp) -> Cmp { - if (was == Cmp.Greater) Cmp.Less else if (was == Cmp.Less) Cmp.Greater else Cmp.Equal +fn findLastBackward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize { + if (range.length() == 0) return range.start; + const skip = math.max(range.length()/unique, usize(1)); + + var index = range.end - skip; + while (index > range.start and lessThan(value, items[index - 1])) : (index -= skip) { + if (index < range.start + skip) { + return binaryLast(T, items, value, Range.init(range.start, index), lessThan); + } + } + + return binaryLast(T, items, value, Range.init(index, index + skip), lessThan); } -// --------------------------------------- -// tests +fn binaryFirst(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool) -> usize { + var start = range.start; + var end = range.end - 1; + if (range.start >= range.end) return range.end; + while (start < end) { + const mid = start + (end - start)/2; + if (lessThan(items[mid], value)) { + start = mid + 1; + } else { + end = mid; + } + } + if (start == range.end - 1 and lessThan(items[start], value)) { + start += 1; + } + return start; +} + +fn binaryLast(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool) -> usize { + var start = range.start; + var end = range.end - 1; + if (range.start >= range.end) return range.end; + while (start < end) { + const mid = start + (end - start)/2; + if (!lessThan(value, items[mid])) { + start = mid + 1; + } else { + end = mid; + } + } + if (start == range.end - 1 and !lessThan(value, items[start])) { + start += 1; + } + return start; +} + +fn mergeInto(comptime T: type, from: []T, A: &const Range, B: &const Range, lessThan: fn(&const T,&const T)->bool, into: []T) { + var A_index: usize = A.start; + var B_index: usize = B.start; + const A_last = A.end; + const B_last = B.end; + var insert_index: usize = 0; + + while (true) { + if (!lessThan(from[B_index], from[A_index])) { + into[insert_index] = from[A_index]; + A_index += 1; + insert_index += 1; + if (A_index == A_last) { + // copy the remainder of B into the final array + mem.copy(T, into[insert_index..], from[B_index..B_last]); + break; + } + } else { + into[insert_index] = from[B_index]; + B_index += 1; + insert_index += 1; + if (B_index == B_last) { + // copy the remainder of A into the final array + mem.copy(T, into[insert_index..], from[A_index..A_last]); + break; + } + } + } +} + +fn mergeExternal(comptime T: type, items: []T, A: &const Range, B: &const Range, lessThan: fn(&const T,&const T)->bool, cache: []T) { + // A fits into the cache, so use that instead of the internal buffer + var A_index: usize = 0; + var B_index: usize = B.start; + var insert_index: usize = A.start; + const A_last = A.length(); + const B_last = B.end; + + if (B.length() > 0 and A.length() > 0) { + while (true) { + if (!lessThan(items[B_index], cache[A_index])) { + items[insert_index] = cache[A_index]; + A_index += 1; + insert_index += 1; + if (A_index == A_last) break; + } else { + items[insert_index] = items[B_index]; + B_index += 1; + insert_index += 1; + if (B_index == B_last) break; + } + } + } + + // copy the remainder of A into the final array + mem.copy(T, items[insert_index..], cache[A_index..A_last]); +} + +fn swap(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool, order: &[8]u8, x: usize, y: usize) { + if (lessThan(items[y], items[x]) or + ((*order)[x] > (*order)[y] and !lessThan(items[x], items[y]))) + { + mem.swap(T, &items[x], &items[y]); + mem.swap(u8, &(*order)[x], &(*order)[y]); + } +} + +fn i32asc(lhs: &const i32, rhs: &const i32) -> bool { + return *lhs < *rhs; +} + +fn i32desc(lhs: &const i32, rhs: &const i32) -> bool { + return *rhs < *lhs; +} + +fn u8asc(lhs: &const u8, rhs: &const u8) -> bool { + return *lhs < *rhs; +} + +fn u8desc(lhs: &const u8, rhs: &const u8) -> bool { + return *rhs < *lhs; +} test "stable sort" { testStableSort(); @@ -113,7 +1003,7 @@ fn testStableSort() { }, }; for (cases) |*case| { - sort_stable(IdAndValue, (*case)[0..], cmpByValue); + insertionSort(IdAndValue, (*case)[0..], cmpByValue); for (*case) |item, i| { assert(item.id == expected[i].id); assert(item.value == expected[i].value); @@ -121,14 +1011,14 @@ fn testStableSort() { } } const IdAndValue = struct { - id: i32, + id: usize, value: i32, }; -fn cmpByValue(a: &const IdAndValue, b: &const IdAndValue) -> Cmp { +fn cmpByValue(a: &const IdAndValue, b: &const IdAndValue) -> bool { return i32asc(a.value, b.value); } -test "testSort" { +test "std.sort" { const u8cases = [][]const []const u8 { [][]const u8{"", ""}, [][]const u8{"a", "a"}, @@ -164,7 +1054,7 @@ test "testSort" { } } -test "testSortDesc" { +test "std.sort descending" { const rev_cases = [][]const []const i32 { [][]const i32{[]i32{}, []i32{}}, [][]const i32{[]i32{1}, []i32{1}}, @@ -182,3 +1072,42 @@ test "testSortDesc" { assert(mem.eql(i32, slice, case[1])); } } + +test "another sort case" { + var arr = []i32{ 5, 3, 1, 2, 4 }; + sort(i32, arr[0..], i32asc); + + assert(mem.eql(i32, arr, []i32{ 1, 2, 3, 4, 5 })) +} + +test "sort fuzz testing" { + var rng = std.rand.Rand.init(0x12345678); + const test_case_count = 10; + var i: usize = 0; + while (i < test_case_count) : (i += 1) { + fuzzTest(&rng); + } +} + +var fixed_buffer_mem: [100 * 1024]u8 = undefined; + +fn fuzzTest(rng: &std.rand.Rand) { + const array_size = rng.range(usize, 0, 1000); + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var array = %%fixed_allocator.allocator.alloc(IdAndValue, array_size); + // populate with random data + for (array) |*item, index| { + item.id = index; + item.value = rng.range(i32, 0, 100); + } + sort(IdAndValue, array, cmpByValue); + + var index: usize = 1; + while (index < array.len) : (index += 1) { + if (array[index].value == array[index - 1].value) { + assert(array[index].id > array[index - 1].id); + } else { + assert(array[index].value > array[index - 1].value); + } + } +} From 6bc0561d13556c50737ea0fa24af5c9468bba17a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 14 Dec 2017 19:55:34 -0500 Subject: [PATCH 51/69] disable sort tests for 32-bit windows because of issue #537 --- std/sort.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/std/sort.zig b/std/sort.zig index a3538d3487..4327cdd042 100644 --- a/std/sort.zig +++ b/std/sort.zig @@ -1019,6 +1019,11 @@ fn cmpByValue(a: &const IdAndValue, b: &const IdAndValue) -> bool { } test "std.sort" { + if (builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386) { + // TODO get this test passing + // https://github.com/zig-lang/zig/issues/537 + return; + } const u8cases = [][]const []const u8 { [][]const u8{"", ""}, [][]const u8{"a", "a"}, @@ -1055,6 +1060,11 @@ test "std.sort" { } test "std.sort descending" { + if (builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386) { + // TODO get this test passing + // https://github.com/zig-lang/zig/issues/537 + return; + } const rev_cases = [][]const []const i32 { [][]const i32{[]i32{}, []i32{}}, [][]const i32{[]i32{1}, []i32{1}}, @@ -1074,6 +1084,11 @@ test "std.sort descending" { } test "another sort case" { + if (builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386) { + // TODO get this test passing + // https://github.com/zig-lang/zig/issues/537 + return; + } var arr = []i32{ 5, 3, 1, 2, 4 }; sort(i32, arr[0..], i32asc); @@ -1081,6 +1096,11 @@ test "another sort case" { } test "sort fuzz testing" { + if (builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386) { + // TODO get this test passing + // https://github.com/zig-lang/zig/issues/537 + return; + } var rng = std.rand.Rand.init(0x12345678); const test_case_count = 10; var i: usize = 0; From 68f63323437e1b974be7a9982f5d70b95624878b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 14 Dec 2017 21:24:00 -0500 Subject: [PATCH 52/69] fix missing import from previous commit --- std/sort.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/std/sort.zig b/std/sort.zig index 4327cdd042..ec6f94bd8e 100644 --- a/std/sort.zig +++ b/std/sort.zig @@ -2,6 +2,7 @@ const std = @import("index.zig"); const assert = std.debug.assert; const mem = std.mem; const math = std.math; +const builtin = @import("builtin"); /// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case. O(1) memory (no allocator required). pub fn insertionSort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool) { From 39e96d933ec3611017edccfdd443fece826a7207 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 15 Dec 2017 17:26:22 -0500 Subject: [PATCH 53/69] change mem.cmp to mem.lessThan and add test --- std/mem.zig | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/std/mem.zig b/std/mem.zig index 10d2221f9a..7edca350a0 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -3,8 +3,6 @@ const assert = debug.assert; const math = @import("math/index.zig"); const builtin = @import("builtin"); -pub const Cmp = math.Cmp; - pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the /// slice's pointer aligned at least to alignment bytes. @@ -166,17 +164,24 @@ pub fn set(comptime T: type, dest: []T, value: T) { for (dest) |*d| *d = value; } -/// Return < 0, == 0, or > 0 if memory a is less than, equal to, or greater than, -/// memory b, respectively. -pub fn cmp(comptime T: type, a: []const T, b: []const T) -> Cmp { - const n = math.min(a.len, b.len); +/// Returns true if lhs < rhs, false otherwise +pub fn lessThan(comptime T: type, lhs: []const T, rhs: []const T) -> bool { + const n = math.min(lhs.len, rhs.len); var i: usize = 0; while (i < n) : (i += 1) { - if (a[i] == b[i]) continue; - return if (a[i] > b[i]) Cmp.Greater else if (a[i] < b[i]) Cmp.Less else Cmp.Equal; + if (lhs[i] == rhs[i]) continue; + return lhs[i] < rhs[i]; } - return if (a.len > b.len) Cmp.Greater else if (a.len < b.len) Cmp.Less else Cmp.Equal; + return lhs.len < rhs.len; +} + +test "mem.lessThan" { + assert(lessThan(u8, "abcd", "bee")); + assert(!lessThan(u8, "abc", "abc")); + assert(lessThan(u8, "abc", "abc0")); + assert(!lessThan(u8, "", "")); + assert(lessThan(u8, "", "a")); } /// Compares two slices and returns whether they are equal. From ab44939941215a753ca51f7b2d84a90d52bfebcf Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Tue, 12 Dec 2017 22:41:21 -0700 Subject: [PATCH 54/69] roughly parsing infix operators --- src-self-hosted/ast.zig | 100 ++++++++---- src-self-hosted/parser.zig | 298 +++++++++++++++++++++------------- src-self-hosted/tokenizer.zig | 36 +++- 3 files changed, 282 insertions(+), 152 deletions(-) diff --git a/src-self-hosted/ast.zig b/src-self-hosted/ast.zig index 40da01481c..5fd836b950 100644 --- a/src-self-hosted/ast.zig +++ b/src-self-hosted/ast.zig @@ -13,9 +13,9 @@ pub const Node = struct { Identifier, FnProto, ParamDecl, - AddrOfExpr, Block, - Return, + InfixOp, + PrefixOp, IntegerLiteral, FloatLiteral, }; @@ -27,9 +27,9 @@ pub const Node = struct { Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).iterate(index), Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).iterate(index), Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).iterate(index), - Id.AddrOfExpr => @fieldParentPtr(NodeAddrOfExpr, "base", base).iterate(index), Id.Block => @fieldParentPtr(NodeBlock, "base", base).iterate(index), - Id.Return => @fieldParentPtr(NodeReturn, "base", base).iterate(index), + Id.InfixOp => @fieldParentPtr(NodeInfixOp, "base", base).iterate(index), + Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).iterate(index), Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).iterate(index), Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).iterate(index), }; @@ -42,9 +42,9 @@ pub const Node = struct { Id.Identifier => allocator.destroy(@fieldParentPtr(NodeIdentifier, "base", base)), Id.FnProto => allocator.destroy(@fieldParentPtr(NodeFnProto, "base", base)), Id.ParamDecl => allocator.destroy(@fieldParentPtr(NodeParamDecl, "base", base)), - Id.AddrOfExpr => allocator.destroy(@fieldParentPtr(NodeAddrOfExpr, "base", base)), Id.Block => allocator.destroy(@fieldParentPtr(NodeBlock, "base", base)), - Id.Return => allocator.destroy(@fieldParentPtr(NodeReturn, "base", base)), + Id.InfixOp => allocator.destroy(@fieldParentPtr(NodeInfixOp, "base", base)), + Id.PrefixOp => allocator.destroy(@fieldParentPtr(NodePrefixOp, "base", base)), Id.IntegerLiteral => allocator.destroy(@fieldParentPtr(NodeIntegerLiteral, "base", base)), Id.FloatLiteral => allocator.destroy(@fieldParentPtr(NodeFloatLiteral, "base", base)), }; @@ -170,31 +170,6 @@ pub const NodeParamDecl = struct { } }; -pub const NodeAddrOfExpr = struct { - base: Node, - op_token: Token, - align_expr: ?&Node, - bit_offset_start_token: ?Token, - bit_offset_end_token: ?Token, - const_token: ?Token, - volatile_token: ?Token, - op_expr: &Node, - - pub fn iterate(self: &NodeAddrOfExpr, index: usize) -> ?&Node { - var i = index; - - if (self.align_expr) |align_expr| { - if (i < 1) return align_expr; - i -= 1; - } - - if (i < 1) return self.op_expr; - i -= 1; - - return null; - } -}; - pub const NodeBlock = struct { base: Node, begin_token: Token, @@ -211,15 +186,68 @@ pub const NodeBlock = struct { } }; -pub const NodeReturn = struct { +pub const NodeInfixOp = struct { base: Node, - return_token: Token, - expr: &Node, + op_token: Token, + lhs: &Node, + op: InfixOp, + rhs: &Node, - pub fn iterate(self: &NodeReturn, index: usize) -> ?&Node { + const InfixOp = enum { + EqualEqual, + BangEqual, + }; + + pub fn iterate(self: &NodeInfixOp, index: usize) -> ?&Node { var i = index; - if (i < 1) return self.expr; + if (i < 1) return self.lhs; + i -= 1; + + switch (self.op) { + InfixOp.EqualEqual => {}, + InfixOp.BangEqual => {}, + } + + if (i < 1) return self.rhs; + i -= 1; + + return null; + } +}; + +pub const NodePrefixOp = struct { + base: Node, + op_token: Token, + op: PrefixOp, + rhs: &Node, + + const PrefixOp = union(enum) { + Return, + AddrOf: AddrOfInfo, + }; + const AddrOfInfo = struct { + align_expr: ?&Node, + bit_offset_start_token: ?Token, + bit_offset_end_token: ?Token, + const_token: ?Token, + volatile_token: ?Token, + }; + + pub fn iterate(self: &NodePrefixOp, index: usize) -> ?&Node { + var i = index; + + switch (self.op) { + PrefixOp.Return => {}, + PrefixOp.AddrOf => |addr_of_info| { + if (addr_of_info.align_expr) |align_expr| { + if (i < 1) return align_expr; + i -= 1; + } + }, + } + + if (i < 1) return self.rhs; i -= 1; return null; diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig index 09e6ab8180..b34603f131 100644 --- a/src-self-hosted/parser.zig +++ b/src-self-hosted/parser.zig @@ -68,7 +68,12 @@ pub const Parser = struct { TopLevelExtern: ?Token, TopLevelDecl: TopLevelDeclCtx, Expression: DestPtr, - AddrOfModifiers: &ast.NodeAddrOfExpr, + ExpectOperand, + Operand: &ast.Node, + AfterOperand, + InfixOp: &ast.NodeInfixOp, + PrefixOp: &ast.NodePrefixOp, + AddrOfModifiers: &ast.NodePrefixOp.AddrOfInfo, TypeExpr: DestPtr, VarDecl: &ast.NodeVarDecl, VarDeclAlign: &ast.NodeVarDecl, @@ -265,63 +270,140 @@ pub const Parser = struct { _ = %return self.eatToken(token_id); continue; }, + State.Expression => |dest_ptr| { + // save the dest_ptr for later + stack.append(state) %% unreachable; + %return stack.append(State.ExpectOperand); + continue; + }, + State.ExpectOperand => { + // we'll either get an operand (like 1 or x), + // or a prefix operator (like ~ or return). const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_return => { - const return_node = %return self.createAttachReturn(dest_ptr, token); - stack.append(State {.Expression = DestPtr {.Field = &return_node.expr} }) %% unreachable; - continue; - }, - Token.Id.Identifier => { - _ = %return self.createAttachIdentifier(dest_ptr, token); - continue; - }, - Token.Id.IntegerLiteral => { - _ = %return self.createAttachIntegerLiteral(dest_ptr, token); - continue; - }, - Token.Id.FloatLiteral => { - _ = %return self.createAttachFloatLiteral(dest_ptr, token); + %return stack.append(State { .PrefixOp = %return self.createPrefixOp(token, + ast.NodePrefixOp.PrefixOp.Return) }); + %return stack.append(State.ExpectOperand); continue; }, Token.Id.Ampersand => { - const addr_of_expr = %return self.createAttachAddrOfExpr(dest_ptr, token); - stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; + const prefix_op = %return self.createPrefixOp(token, ast.NodePrefixOp.PrefixOp{ + .AddrOf = ast.NodePrefixOp.AddrOfInfo { + .align_expr = null, + .bit_offset_start_token = null, + .bit_offset_end_token = null, + .const_token = null, + .volatile_token = null, + } + }); + %return stack.append(State { .PrefixOp = prefix_op }); + %return stack.append(State.ExpectOperand); + %return stack.append(State { .AddrOfModifiers = &prefix_op.op.AddrOf }); + continue; + }, + Token.Id.Identifier => { + %return stack.append(State { + .Operand = &(%return self.createIdentifier(token)).base + }); + %return stack.append(State.AfterOperand); + continue; + }, + Token.Id.IntegerLiteral => { + %return stack.append(State { + .Operand = &(%return self.createIntegerLiteral(token)).base + }); + %return stack.append(State.AfterOperand); + continue; + }, + Token.Id.FloatLiteral => { + %return stack.append(State { + .Operand = &(%return self.createFloatLiteral(token)).base + }); + %return stack.append(State.AfterOperand); continue; }, else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)), } }, - State.AddrOfModifiers => |addr_of_expr| { + State.AfterOperand => { + // we'll either get an infix operator (like != or ^), + // or a postfix operator (like () or {}), + // otherwise this expression is done (like on a ; or else). + var token = self.getNextToken(); + switch (token.id) { + Token.Id.EqualEqual => { + %return stack.append(State { + .InfixOp = %return self.createInfixOp(token, ast.NodeInfixOp.InfixOp.EqualEqual) + }); + %return stack.append(State.ExpectOperand); + continue; + }, + Token.Id.BangEqual => { + %return stack.append(State { + .InfixOp = %return self.createInfixOp(token, ast.NodeInfixOp.InfixOp.BangEqual) + }); + %return stack.append(State.ExpectOperand); + continue; + }, + else => { + // no postfix/infix operator after this operand. + self.putBackToken(token); + // reduce the stack + var expression: &ast.Node = stack.pop().Operand; + while (true) { + switch (stack.pop()) { + State.Expression => |dest_ptr| { + // we're done + %return dest_ptr.store(expression); + break; + }, + State.InfixOp => |infix_op| { + infix_op.rhs = expression; + infix_op.lhs = stack.pop().Operand; + expression = &infix_op.base; + continue; + }, + State.PrefixOp => |prefix_op| { + prefix_op.rhs = expression; + expression = &prefix_op.base; + continue; + }, + else => unreachable, + } + } + continue; + }, + } + }, + + State.AddrOfModifiers => |addr_of_info| { var token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_align => { - stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; - if (addr_of_expr.align_expr != null) return self.parseError(token, "multiple align qualifiers"); + stack.append(state) %% unreachable; + if (addr_of_info.align_expr != null) return self.parseError(token, "multiple align qualifiers"); _ = %return self.eatToken(Token.Id.LParen); %return stack.append(State { .ExpectToken = Token.Id.RParen }); - %return stack.append(State { .Expression = DestPtr{.NullableField = &addr_of_expr.align_expr} }); + %return stack.append(State { .Expression = DestPtr{.NullableField = &addr_of_info.align_expr} }); continue; }, Token.Id.Keyword_const => { - if (addr_of_expr.const_token != null) return self.parseError(token, "duplicate qualifier: const"); - addr_of_expr.const_token = token; - stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; + stack.append(state) %% unreachable; + if (addr_of_info.const_token != null) return self.parseError(token, "duplicate qualifier: const"); + addr_of_info.const_token = token; continue; }, Token.Id.Keyword_volatile => { - if (addr_of_expr.volatile_token != null) return self.parseError(token, "duplicate qualifier: volatile"); - addr_of_expr.volatile_token = token; - stack.append(State { .AddrOfModifiers = addr_of_expr }) %% unreachable; + stack.append(state) %% unreachable; + if (addr_of_info.volatile_token != null) return self.parseError(token, "duplicate qualifier: volatile"); + addr_of_info.volatile_token = token; continue; }, else => { self.putBackToken(token); - stack.append(State { - .Expression = DestPtr { .Field = &addr_of_expr.op_expr}, - }) %% unreachable; continue; }, } @@ -482,8 +564,14 @@ pub const Parser = struct { %return stack.append(State { .Expression = DestPtr{.List = &block.statements} }); continue; }, + + // These are data, not control flow. + State.InfixOp => unreachable, + State.PrefixOp => unreachable, + State.Operand => unreachable, } - unreachable; + @import("std").debug.panic("{}", @tagName(state)); + //unreachable; } } @@ -560,23 +648,6 @@ pub const Parser = struct { return node; } - fn createAddrOfExpr(self: &Parser, op_token: &const Token) -> %&ast.NodeAddrOfExpr { - const node = %return self.allocator.create(ast.NodeAddrOfExpr); - %defer self.allocator.destroy(node); - - *node = ast.NodeAddrOfExpr { - .base = ast.Node {.id = ast.Node.Id.AddrOfExpr}, - .align_expr = null, - .op_token = *op_token, - .bit_offset_start_token = null, - .bit_offset_end_token = null, - .const_token = null, - .volatile_token = null, - .op_expr = undefined, - }; - return node; - } - fn createBlock(self: &Parser, begin_token: &const Token) -> %&ast.NodeBlock { const node = %return self.allocator.create(ast.NodeBlock); %defer self.allocator.destroy(node); @@ -590,14 +661,29 @@ pub const Parser = struct { return node; } - fn createReturn(self: &Parser, return_token: &const Token) -> %&ast.NodeReturn { - const node = %return self.allocator.create(ast.NodeReturn); + fn createInfixOp(self: &Parser, op_token: &const Token, op: &const ast.NodeInfixOp.InfixOp) -> %&ast.NodeInfixOp { + const node = %return self.allocator.create(ast.NodeInfixOp); %defer self.allocator.destroy(node); - *node = ast.NodeReturn { - .base = ast.Node {.id = ast.Node.Id.Return}, - .return_token = *return_token, - .expr = undefined, + *node = ast.NodeInfixOp { + .base = ast.Node {.id = ast.Node.Id.InfixOp}, + .op_token = *op_token, + .lhs = undefined, + .op = *op, + .rhs = undefined, + }; + return node; + } + + fn createPrefixOp(self: &Parser, op_token: &const Token, op: &const ast.NodePrefixOp.PrefixOp) -> %&ast.NodePrefixOp { + const node = %return self.allocator.create(ast.NodePrefixOp); + %defer self.allocator.destroy(node); + + *node = ast.NodePrefixOp { + .base = ast.Node {.id = ast.Node.Id.PrefixOp}, + .op_token = *op_token, + .op = *op, + .rhs = undefined, }; return node; } @@ -635,20 +721,6 @@ pub const Parser = struct { return node; } - fn createAttachFloatLiteral(self: &Parser, dest_ptr: &const DestPtr, token: &const Token) -> %&ast.NodeFloatLiteral { - const node = %return self.createFloatLiteral(token); - %defer self.allocator.destroy(node); - %return dest_ptr.store(&node.base); - return node; - } - - fn createAttachIntegerLiteral(self: &Parser, dest_ptr: &const DestPtr, token: &const Token) -> %&ast.NodeIntegerLiteral { - const node = %return self.createIntegerLiteral(token); - %defer self.allocator.destroy(node); - %return dest_ptr.store(&node.base); - return node; - } - fn createAttachIdentifier(self: &Parser, dest_ptr: &const DestPtr, name_token: &const Token) -> %&ast.NodeIdentifier { const node = %return self.createIdentifier(name_token); %defer self.allocator.destroy(node); @@ -656,20 +728,6 @@ pub const Parser = struct { return node; } - fn createAttachReturn(self: &Parser, dest_ptr: &const DestPtr, return_token: &const Token) -> %&ast.NodeReturn { - const node = %return self.createReturn(return_token); - %defer self.allocator.destroy(node); - %return dest_ptr.store(&node.base); - return node; - } - - fn createAttachAddrOfExpr(self: &Parser, dest_ptr: &const DestPtr, op_token: &const Token) -> %&ast.NodeAddrOfExpr { - const node = %return self.createAddrOfExpr(op_token); - %defer self.allocator.destroy(node); - %return dest_ptr.store(&node.base); - return node; - } - fn createAttachParamDecl(self: &Parser, list: &ArrayList(&ast.Node)) -> %&ast.NodeParamDecl { const node = %return self.createParamDecl(); %defer self.allocator.destroy(node); @@ -783,7 +841,6 @@ pub const Parser = struct { ParamDecl: &ast.Node, Text: []const u8, Expression: &ast.Node, - AddrOfExprBit: &ast.NodeAddrOfExpr, VarDecl: &ast.NodeVarDecl, Statement: &ast.Node, PrintIndent, @@ -912,17 +969,6 @@ pub const Parser = struct { const identifier = @fieldParentPtr(ast.NodeIdentifier, "base", base); %return stream.print("{}", self.tokenizer.getTokenSlice(identifier.name_token)); }, - ast.Node.Id.AddrOfExpr => { - const addr_of_expr = @fieldParentPtr(ast.NodeAddrOfExpr, "base", base); - %return stream.print("{}", self.tokenizer.getTokenSlice(addr_of_expr.op_token)); - %return stack.append(RenderState { .AddrOfExprBit = addr_of_expr}); - - if (addr_of_expr.align_expr) |align_expr| { - %return stream.print("align("); - %return stack.append(RenderState { .Text = ") "}); - %return stack.append(RenderState { .Expression = align_expr}); - } - }, ast.Node.Id.Block => { const block = @fieldParentPtr(ast.NodeBlock, "base", base); %return stream.write("{"); @@ -940,10 +986,43 @@ pub const Parser = struct { %return stack.append(RenderState { .Text = "\n" }); } }, - ast.Node.Id.Return => { - const return_node = @fieldParentPtr(ast.NodeReturn, "base", base); - %return stream.write("return "); - %return stack.append(RenderState { .Expression = return_node.expr }); + ast.Node.Id.InfixOp => { + const prefix_op_node = @fieldParentPtr(ast.NodeInfixOp, "base", base); + %return stack.append(RenderState { .Expression = prefix_op_node.rhs }); + switch (prefix_op_node.op) { + ast.NodeInfixOp.InfixOp.EqualEqual => { + %return stack.append(RenderState { .Text = " == "}); + }, + ast.NodeInfixOp.InfixOp.BangEqual => { + %return stack.append(RenderState { .Text = " != "}); + }, + else => unreachable, + } + %return stack.append(RenderState { .Expression = prefix_op_node.lhs }); + }, + ast.Node.Id.PrefixOp => { + const prefix_op_node = @fieldParentPtr(ast.NodePrefixOp, "base", base); + %return stack.append(RenderState { .Expression = prefix_op_node.rhs }); + switch (prefix_op_node.op) { + ast.NodePrefixOp.PrefixOp.Return => { + %return stream.write("return "); + }, + ast.NodePrefixOp.PrefixOp.AddrOf => |addr_of_info| { + %return stream.write("&"); + if (addr_of_info.volatile_token != null) { + %return stack.append(RenderState { .Text = "volatile "}); + } + if (addr_of_info.const_token != null) { + %return stack.append(RenderState { .Text = "const "}); + } + if (addr_of_info.align_expr) |align_expr| { + %return stream.print("align("); + %return stack.append(RenderState { .Text = ") "}); + %return stack.append(RenderState { .Expression = align_expr}); + } + }, + else => unreachable, + } }, ast.Node.Id.IntegerLiteral => { const integer_literal = @fieldParentPtr(ast.NodeIntegerLiteral, "base", base); @@ -955,21 +1034,6 @@ pub const Parser = struct { }, else => unreachable, }, - RenderState.AddrOfExprBit => |addr_of_expr| { - if (addr_of_expr.bit_offset_start_token) |bit_offset_start_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_start_token)); - } - if (addr_of_expr.bit_offset_end_token) |bit_offset_end_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(bit_offset_end_token)); - } - if (addr_of_expr.const_token) |const_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(const_token)); - } - if (addr_of_expr.volatile_token) |volatile_token| { - %return stream.print("{} ", self.tokenizer.getTokenSlice(volatile_token)); - } - %return stack.append(RenderState { .Expression = addr_of_expr.op_expr}); - }, RenderState.FnProtoRParen => |fn_proto| { %return stream.print(")"); if (fn_proto.align_expr != null) { @@ -1128,4 +1192,12 @@ test "zig fmt" { \\extern fn f3(s: &align(1) const volatile u8) -> c_int; \\ ); + + testCanonical( + \\fn f1(a: bool, b: bool) -> bool { + \\ a != b; + \\ return a == b; + \\} + \\ + ); } diff --git a/src-self-hosted/tokenizer.zig b/src-self-hosted/tokenizer.zig index 5d32a83af7..570d41fa7e 100644 --- a/src-self-hosted/tokenizer.zig +++ b/src-self-hosted/tokenizer.zig @@ -71,7 +71,10 @@ pub const Token = struct { StringLiteral: StrLitKind, Eof, Builtin, + Bang, Equal, + EqualEqual, + BangEqual, LParen, RParen, Semicolon, @@ -187,6 +190,8 @@ pub const Tokenizer = struct { C, StringLiteral, StringLiteralBackslash, + Equal, + Bang, Minus, Slash, LineComment, @@ -232,9 +237,10 @@ pub const Tokenizer = struct { result.id = Token.Id.Builtin; }, '=' => { - result.id = Token.Id.Equal; - self.index += 1; - break; + state = State.Equal; + }, + '!' => { + state = State.Bang; }, '(' => { result.id = Token.Id.LParen; @@ -356,6 +362,30 @@ pub const Tokenizer = struct { }, }, + State.Bang => switch (c) { + '=' => { + result.id = Token.Id.BangEqual; + self.index += 1; + break; + }, + else => { + result.id = Token.Id.Bang; + break; + }, + }, + + State.Equal => switch (c) { + '=' => { + result.id = Token.Id.EqualEqual; + self.index += 1; + break; + }, + else => { + result.id = Token.Id.Equal; + break; + }, + }, + State.Minus => switch (c) { '>' => { result.id = Token.Id.Arrow; From 3f658879740e3f7aee05be33532b5ba9aaa316bf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 17 Dec 2017 20:52:29 -0500 Subject: [PATCH 55/69] fix std.mem missing error.OutOfMemory decl this will be fixed in a better way later by #632 --- std/mem.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/std/mem.zig b/std/mem.zig index 7edca350a0..59b01fab17 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -3,6 +3,8 @@ const assert = debug.assert; const math = @import("math/index.zig"); const builtin = @import("builtin"); +error OutOfMemory; + pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the /// slice's pointer aligned at least to alignment bytes. From 1fdebc1dc4881a00766f7c2b4b2d8ee6ad6e79b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 18 Dec 2017 09:59:57 -0500 Subject: [PATCH 56/69] wip export rewrite --- doc/langref.html.in | 8 +- example/hello_world/hello_libc.zig | 8 +- example/mix_o_files/base64.zig | 5 +- example/shared_library/mathtest.zig | 5 +- src/all_types.hpp | 61 +- src/analyze.cpp | 129 ++-- src/analyze.hpp | 2 + src/ast_render.cpp | 21 +- src/codegen.cpp | 106 ++-- src/ir.cpp | 459 ++++++++------ src/ir_print.cpp | 40 +- src/parser.cpp | 28 +- src/tokenizer.cpp | 4 +- src/tokenizer.hpp | 2 +- src/translate_c.cpp | 6 +- std/debug.zig | 2 - std/elf.zig | 62 +- std/mem.zig | 1 + std/os/linux.zig | 22 - std/os/linux_i386.zig | 10 - std/special/bootstrap.zig | 35 +- std/special/bootstrap_lib.zig | 6 +- std/special/builtin.zig | 36 +- std/special/compiler_rt/aulldiv.zig | 119 ++-- std/special/compiler_rt/aullrem.zig | 121 ++-- std/special/compiler_rt/comparetf2.zig | 45 +- std/special/compiler_rt/fixunsdfdi.zig | 4 +- std/special/compiler_rt/fixunsdfsi.zig | 4 +- std/special/compiler_rt/fixunsdfti.zig | 4 +- std/special/compiler_rt/fixunssfdi.zig | 4 +- std/special/compiler_rt/fixunssfsi.zig | 4 +- std/special/compiler_rt/fixunssfti.zig | 4 +- std/special/compiler_rt/fixunstfdi.zig | 4 +- std/special/compiler_rt/fixunstfsi.zig | 4 +- std/special/compiler_rt/fixunstfti.zig | 4 +- std/special/compiler_rt/index.zig | 343 +++++------ std/special/compiler_rt/udivmoddi4.zig | 4 +- std/special/compiler_rt/udivmodti4.zig | 4 +- std/special/compiler_rt/udivti3.zig | 4 +- std/special/compiler_rt/umodti3.zig | 4 +- test/cases/asm.zig | 13 +- test/cases/misc.zig | 23 +- test/compare_output.zig | 16 +- test/compile_errors.zig | 822 ++++++++++++++++--------- test/standalone/issue_339/test.zig | 5 +- test/translate_c.zig | 64 +- 46 files changed, 1487 insertions(+), 1194 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index e5b896410d..f302cbec5d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5815,13 +5815,13 @@ TopLevelItem = ErrorValueDecl | CompTimeExpression(Block) | TopLevelDecl | TestD TestDecl = "test" String Block -TopLevelDecl = option(VisibleMod) (FnDef | ExternDecl | GlobalVarDecl | UseDecl) +TopLevelDecl = option("pub") (FnDef | ExternDecl | GlobalVarDecl | UseDecl) ErrorValueDecl = "error" Symbol ";" GlobalVarDecl = VariableDeclaration ";" -VariableDeclaration = option("comptime") ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") "=" Expression +VariableDeclaration = option("comptime") ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") option("section" "(" Expression ")") "=" Expression ContainerMember = (ContainerField | FnDef | GlobalVarDecl) @@ -5831,9 +5831,7 @@ UseDecl = "use" Expression ";" ExternDecl = "extern" option(String) (FnProto | VariableDeclaration) ";" -FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("->" TypeExpr) - -VisibleMod = "pub" | "export" +FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) FnDef = option("inline" | "extern") FnProto Block diff --git a/example/hello_world/hello_libc.zig b/example/hello_world/hello_libc.zig index ea8aec1e4f..16d0f303cc 100644 --- a/example/hello_world/hello_libc.zig +++ b/example/hello_world/hello_libc.zig @@ -5,9 +5,13 @@ const c = @cImport({ @cInclude("string.h"); }); -const msg = c"Hello, world!\n"; +comptime { + @export("main", main); +} + +extern fn main(argc: c_int, argv: &&u8) -> c_int { + const msg = c"Hello, world!\n"; -export fn main(argc: c_int, argv: &&u8) -> c_int { if (c.printf(msg) != c_int(c.strlen(msg))) return -1; diff --git a/example/mix_o_files/base64.zig b/example/mix_o_files/base64.zig index 49c9bc6012..a8358e9685 100644 --- a/example/mix_o_files/base64.zig +++ b/example/mix_o_files/base64.zig @@ -1,6 +1,9 @@ const base64 = @import("std").base64; -export fn decode_base_64(dest_ptr: &u8, dest_len: usize, source_ptr: &const u8, source_len: usize) -> usize { +comptime { + @export("decode_base_64", decode_base_64); +} +extern fn decode_base_64(dest_ptr: &u8, dest_len: usize, source_ptr: &const u8, source_len: usize) -> usize { const src = source_ptr[0..source_len]; const dest = dest_ptr[0..dest_len]; const base64_decoder = base64.standard_decoder_unsafe; diff --git a/example/shared_library/mathtest.zig b/example/shared_library/mathtest.zig index a11642554f..f6d0a61c90 100644 --- a/example/shared_library/mathtest.zig +++ b/example/shared_library/mathtest.zig @@ -1,3 +1,6 @@ -export fn add(a: i32, b: i32) -> i32 { +comptime { + @export("add", add); +} +extern fn add(a: i32, b: i32) -> i32 { a + b } diff --git a/src/all_types.hpp b/src/all_types.hpp index c86b99d35c..9256777428 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -37,6 +37,7 @@ struct IrBasicBlock; struct ScopeDecls; struct ZigWindowsSDK; struct Tld; +struct TldExport; struct IrGotoItem { AstNode *source_node; @@ -272,7 +273,6 @@ enum ReturnKnowledge { enum VisibMod { VisibModPrivate, VisibModPub, - VisibModExport, }; enum GlobalLinkageId { @@ -313,11 +313,14 @@ struct TldVar { Tld base; VariableTableEntry *var; - AstNode *set_global_section_node; - Buf *section_name; - AstNode *set_global_linkage_node; - GlobalLinkageId linkage; Buf *extern_lib_name; + Buf *section_name; + + size_t export_count; + union { + TldExport *tld; // if export_count == 1 + TldExport **tld_list; // if export_count > 1 + } export_data; }; struct TldFn { @@ -432,6 +435,8 @@ struct AstNodeFnProto { Buf *lib_name; // populated if the "align A" is present AstNode *align_expr; + // populated if the "section(S)" is present + AstNode *section_expr; }; struct AstNodeFnDef { @@ -487,8 +492,10 @@ struct AstNodeVariableDeclaration { AstNode *expr; // populated if this is an extern declaration Buf *lib_name; - // populated if the "align A" is present + // populated if the "align(A)" is present AstNode *align_expr; + // populated if the "section(S)" is present + AstNode *section_expr; }; struct AstNodeErrorValueDecl { @@ -1177,6 +1184,12 @@ enum FnInline { FnInlineNever, }; +struct FnExport { + Buf name; + GlobalLinkageId linkage; + AstNode *source_node; +}; + struct FnTableEntry { LLVMValueRef llvm_value; const char *llvm_name; @@ -1204,12 +1217,11 @@ struct FnTableEntry { ZigList alloca_list; ZigList variable_list; - AstNode *set_global_section_node; Buf *section_name; - AstNode *set_global_linkage_node; - GlobalLinkageId linkage; AstNode *set_alignstack_node; uint32_t alignstack_value; + + ZigList export_list; }; uint32_t fn_table_entry_hash(FnTableEntry*); @@ -1258,8 +1270,6 @@ enum BuiltinFnId { BuiltinFnIdSetFloatMode, BuiltinFnIdTypeName, BuiltinFnIdCanImplicitCast, - BuiltinFnIdSetGlobalSection, - BuiltinFnIdSetGlobalLinkage, BuiltinFnIdPanic, BuiltinFnIdPtrCast, BuiltinFnIdBitCast, @@ -1279,6 +1289,8 @@ enum BuiltinFnId { BuiltinFnIdOpaqueType, BuiltinFnIdSetAlignStack, BuiltinFnIdArgType, + BuiltinFnIdExport, + BuiltinFnIdExportWithLinkage, }; struct BuiltinFnEntry { @@ -1425,7 +1437,7 @@ struct CodeGen { HashMap generic_table; HashMap memoized_fn_eval_table; HashMap llvm_fn_table; - HashMap exported_symbol_names; + HashMap exported_symbol_names; HashMap external_prototypes; @@ -1886,8 +1898,6 @@ enum IrInstructionId { IrInstructionIdCheckStatementIsVoid, IrInstructionIdTypeName, IrInstructionIdCanImplicitCast, - IrInstructionIdSetGlobalSection, - IrInstructionIdSetGlobalLinkage, IrInstructionIdDeclRef, IrInstructionIdPanic, IrInstructionIdTagName, @@ -1901,6 +1911,7 @@ enum IrInstructionId { IrInstructionIdOpaqueType, IrInstructionIdSetAlignStack, IrInstructionIdArgType, + IrInstructionIdExport, }; struct IrInstruction { @@ -2626,20 +2637,6 @@ struct IrInstructionCanImplicitCast { IrInstruction *target_value; }; -struct IrInstructionSetGlobalSection { - IrInstruction base; - - Tld *tld; - IrInstruction *value; -}; - -struct IrInstructionSetGlobalLinkage { - IrInstruction base; - - Tld *tld; - IrInstruction *value; -}; - struct IrInstructionDeclRef { IrInstruction base; @@ -2728,6 +2725,14 @@ struct IrInstructionArgType { IrInstruction *arg_index; }; +struct IrInstructionExport { + IrInstruction base; + + IrInstruction *name; + IrInstruction *linkage; + IrInstruction *target; +}; + static const size_t slice_ptr_index = 0; static const size_t slice_len_index = 1; diff --git a/src/analyze.cpp b/src/analyze.cpp index 1d5d5e4790..84d9b9feaf 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1062,7 +1062,7 @@ void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_cou AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; if (fn_proto->cc == CallingConventionUnspecified) { - bool extern_abi = fn_proto->is_extern || (fn_proto->visib_mod == VisibModExport); + bool extern_abi = fn_proto->is_extern; fn_type_id->cc = extern_abi ? CallingConventionC : CallingConventionUnspecified; } else { fn_type_id->cc = fn_proto->cc; @@ -1093,6 +1093,38 @@ static bool analyze_const_align(CodeGen *g, Scope *scope, AstNode *node, uint32_ return true; } +static bool analyze_const_string(CodeGen *g, Scope *scope, AstNode *node, Buf **out_buffer) { + TypeTableEntry *ptr_type = get_pointer_to_type(g, g->builtin_types.entry_u8, true); + TypeTableEntry *str_type = get_slice_type(g, ptr_type); + IrInstruction *instr = analyze_const_value(g, scope, node, str_type, nullptr); + if (type_is_invalid(instr->value.type)) + return false; + + ConstExprValue *ptr_field = &instr->value.data.x_struct.fields[slice_ptr_index]; + ConstExprValue *len_field = &instr->value.data.x_struct.fields[slice_len_index]; + + assert(ptr_field->data.x_ptr.special == ConstPtrSpecialBaseArray); + ConstExprValue *array_val = ptr_field->data.x_ptr.data.base_array.array_val; + expand_undef_array(g, array_val); + size_t len = bigint_as_unsigned(&len_field->data.x_bigint); + Buf *result = buf_alloc(); + buf_resize(result, len); + for (size_t i = 0; i < len; i += 1) { + size_t new_index = ptr_field->data.x_ptr.data.base_array.elem_index + i; + ConstExprValue *char_val = &array_val->data.x_array.s_none.elements[new_index]; + if (char_val->special == ConstValSpecialUndef) { + add_node_error(g, node, buf_sprintf("use of undefined value")); + return false; + } + uint64_t big_c = bigint_as_unsigned(&char_val->data.x_bigint); + assert(big_c <= UINT8_MAX); + uint8_t c = (uint8_t)big_c; + buf_ptr(result)[i] = c; + } + *out_buffer = result; + return true; +} + static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_scope) { assert(proto_node->type == NodeTypeFnProto); AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; @@ -2472,7 +2504,7 @@ static void get_fully_qualified_decl_name(Buf *buf, Tld *tld, uint8_t sep) { buf_append_buf(buf, tld->name); } -FnTableEntry *create_fn_raw(FnInline inline_value, GlobalLinkageId linkage) { +FnTableEntry *create_fn_raw(FnInline inline_value) { FnTableEntry *fn_entry = allocate(1); fn_entry->analyzed_executable.backward_branch_count = &fn_entry->prealloc_bbc; @@ -2480,7 +2512,6 @@ FnTableEntry *create_fn_raw(FnInline inline_value, GlobalLinkageId linkage) { fn_entry->analyzed_executable.fn_entry = fn_entry; fn_entry->ir_executable.fn_entry = fn_entry; fn_entry->fn_inline = inline_value; - fn_entry->linkage = linkage; return fn_entry; } @@ -2490,9 +2521,7 @@ FnTableEntry *create_fn(AstNode *proto_node) { AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; FnInline inline_value = fn_proto->is_inline ? FnInlineAlways : FnInlineAuto; - GlobalLinkageId linkage = (fn_proto->visib_mod == VisibModExport || proto_node->data.fn_proto.is_extern) ? - GlobalLinkageIdStrong : GlobalLinkageIdInternal; - FnTableEntry *fn_entry = create_fn_raw(inline_value, linkage); + FnTableEntry *fn_entry = create_fn_raw(inline_value); fn_entry->proto_node = proto_node; fn_entry->body_node = (proto_node->data.fn_proto.fn_def_node == nullptr) ? nullptr : @@ -2572,7 +2601,7 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { add_node_error(g, param_node, buf_sprintf("missing parameter name")); } } - } else if (fn_table_entry->linkage != GlobalLinkageIdInternal) { + } else { g->external_prototypes.put_unique(tld_fn->base.name, &tld_fn->base); } @@ -2580,6 +2609,15 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { fn_table_entry->type_entry = analyze_fn_type(g, source_node, child_scope); + if (fn_proto->section_expr != nullptr) { + if (fn_table_entry->body_node == nullptr) { + add_node_error(g, fn_proto->section_expr, + buf_sprintf("cannot set section of external function '%s'", buf_ptr(&fn_table_entry->symbol_name))); + } else { + analyze_const_string(g, child_scope, fn_proto->section_expr, &fn_table_entry->section_name); + } + } + if (fn_table_entry->type_entry->id == TypeTableEntryIdInvalid) { tld_fn->base.resolution = TldResolutionInvalid; return; @@ -2594,15 +2632,12 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { { if (g->have_pub_main && buf_eql_str(&fn_table_entry->symbol_name, "main")) { g->main_fn = fn_table_entry; - - if (tld_fn->base.visib_mod != VisibModExport) { - TypeTableEntry *err_void = get_error_type(g, g->builtin_types.entry_void); - TypeTableEntry *actual_return_type = fn_table_entry->type_entry->data.fn.fn_type_id.return_type; - if (actual_return_type != err_void) { - add_node_error(g, fn_proto->return_type, - buf_sprintf("expected return type of main to be '%%void', instead is '%s'", - buf_ptr(&actual_return_type->name))); - } + TypeTableEntry *err_void = get_error_type(g, g->builtin_types.entry_void); + TypeTableEntry *actual_return_type = fn_table_entry->type_entry->data.fn.fn_type_id.return_type; + if (actual_return_type != err_void) { + add_node_error(g, fn_proto->return_type, + buf_sprintf("expected return type of main to be '%%void', instead is '%s'", + buf_ptr(&actual_return_type->name))); } } else if ((import->package == g->panic_package || g->have_pub_panic) && buf_eql_str(&fn_table_entry->symbol_name, "panic")) @@ -2613,7 +2648,7 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { } } } else if (source_node->type == NodeTypeTestDecl) { - FnTableEntry *fn_table_entry = create_fn_raw(FnInlineAuto, GlobalLinkageIdStrong); + FnTableEntry *fn_table_entry = create_fn_raw(FnInlineAuto); get_fully_qualified_decl_name(&fn_table_entry->symbol_name, &tld_fn->base, '_'); @@ -2640,20 +2675,6 @@ static void resolve_decl_comptime(CodeGen *g, TldCompTime *tld_comptime) { } static void add_top_level_decl(CodeGen *g, ScopeDecls *decls_scope, Tld *tld) { - if (tld->visib_mod == VisibModExport) { - g->resolve_queue.append(tld); - } - - if (tld->visib_mod == VisibModExport) { - auto entry = g->exported_symbol_names.put_unique(tld->name, tld); - if (entry) { - Tld *other_tld = entry->value; - ErrorMsg *msg = add_node_error(g, tld->source_node, - buf_sprintf("exported symbol collision: '%s'", buf_ptr(tld->name))); - add_error_note(g, msg, other_tld->source_node, buf_sprintf("other symbol is here")); - } - } - { auto entry = decls_scope->decl_table.put_unique(tld->name, tld); if (entry) { @@ -2729,7 +2750,6 @@ static void preview_comptime_decl(CodeGen *g, AstNode *node, ScopeDecls *decls_s g->resolve_queue.append(&tld_comptime->base); } - void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node, Scope *parent_scope) { @@ -2985,7 +3005,6 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { AstNodeVariableDeclaration *var_decl = &source_node->data.variable_declaration; bool is_const = var_decl->is_const; - bool is_export = (tld_var->base.visib_mod == VisibModExport); bool is_extern = var_decl->is_extern; TypeTableEntry *explicit_type = nullptr; @@ -2994,20 +3013,13 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { explicit_type = validate_var_type(g, var_decl->type, proposed_type); } - if (is_export && is_extern) { - add_node_error(g, source_node, buf_sprintf("variable is both export and extern")); - } - VarLinkage linkage; - if (is_export) { - linkage = VarLinkageExport; - } else if (is_extern) { + if (is_extern) { linkage = VarLinkageExternal; } else { linkage = VarLinkageInternal; } - IrInstruction *init_value = nullptr; // TODO more validation for types that can't be used for export/extern variables @@ -3056,6 +3068,15 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { } } + if (var_decl->section_expr != nullptr) { + if (var_decl->is_extern) { + add_node_error(g, var_decl->section_expr, + buf_sprintf("cannot set section of external variable '%s'", buf_ptr(var_decl->symbol))); + } else if (!analyze_const_string(g, tld_var->base.parent_scope, var_decl->section_expr, &tld_var->section_name)) { + tld_var->section_name = nullptr; + } + } + g->global_vars.append(tld_var); } @@ -3724,8 +3745,10 @@ ImportTableEntry *add_source_file(CodeGen *g, PackageTableEntry *package, Buf *a Buf *proto_name = proto_node->data.fn_proto.name; bool is_pub = (proto_node->data.fn_proto.visib_mod == VisibModPub); + bool ok_cc = (proto_node->data.fn_proto.cc == CallingConventionUnspecified || + proto_node->data.fn_proto.cc == CallingConventionCold); - if (is_pub) { + if (is_pub && ok_cc) { if (buf_eql_str(proto_name, "main")) { g->have_pub_main = true; g->windows_subsystem_windows = false; @@ -3733,28 +3756,7 @@ ImportTableEntry *add_source_file(CodeGen *g, PackageTableEntry *package, Buf *a } else if (buf_eql_str(proto_name, "panic")) { g->have_pub_panic = true; } - } else if (proto_node->data.fn_proto.visib_mod == VisibModExport && buf_eql_str(proto_name, "main") && - g->libc_link_lib != nullptr) - { - g->have_c_main = true; - g->windows_subsystem_windows = false; - g->windows_subsystem_console = true; - } else if (proto_node->data.fn_proto.visib_mod == VisibModExport && buf_eql_str(proto_name, "WinMain") && - g->zig_target.os == ZigLLVM_Win32) - { - g->have_winmain = true; - g->windows_subsystem_windows = true; - g->windows_subsystem_console = false; - } else if (proto_node->data.fn_proto.visib_mod == VisibModExport && - buf_eql_str(proto_name, "WinMainCRTStartup") && g->zig_target.os == ZigLLVM_Win32) - { - g->have_winmain_crt_startup = true; - } else if (proto_node->data.fn_proto.visib_mod == VisibModExport && - buf_eql_str(proto_name, "DllMainCRTStartup") && g->zig_target.os == ZigLLVM_Win32) - { - g->have_dllmain_crt_startup = true; } - } } @@ -5445,3 +5447,4 @@ uint32_t type_ptr_hash(const TypeTableEntry *ptr) { bool type_ptr_eql(const TypeTableEntry *a, const TypeTableEntry *b) { return a == b; } + diff --git a/src/analyze.hpp b/src/analyze.hpp index e12c4cda44..0e727568c6 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -182,4 +182,6 @@ uint32_t get_abi_alignment(CodeGen *g, TypeTableEntry *type_entry); TypeTableEntry *get_align_amt_type(CodeGen *g); PackageTableEntry *new_anonymous_package(void); +Buf *const_value_to_buffer(ConstExprValue *const_val); + #endif diff --git a/src/ast_render.cpp b/src/ast_render.cpp index d6a23e5f85..fc01c79ca6 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -78,7 +78,6 @@ static const char *visib_mod_string(VisibMod mod) { switch (mod) { case VisibModPub: return "pub "; case VisibModPrivate: return ""; - case VisibModExport: return "export "; } zig_unreachable(); } @@ -440,6 +439,16 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { } } fprintf(ar->f, ")"); + if (node->data.fn_proto.align_expr) { + fprintf(ar->f, " align("); + render_node_grouped(ar, node->data.fn_proto.align_expr); + fprintf(ar->f, ")"); + } + if (node->data.fn_proto.section_expr) { + fprintf(ar->f, " section("); + render_node_grouped(ar, node->data.fn_proto.section_expr); + fprintf(ar->f, ")"); + } AstNode *return_type_node = node->data.fn_proto.return_type; if (return_type_node != nullptr) { @@ -526,6 +535,16 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { fprintf(ar->f, ": "); render_node_grouped(ar, node->data.variable_declaration.type); } + if (node->data.variable_declaration.align_expr) { + fprintf(ar->f, "align("); + render_node_grouped(ar, node->data.variable_declaration.align_expr); + fprintf(ar->f, ") "); + } + if (node->data.variable_declaration.section_expr) { + fprintf(ar->f, "section("); + render_node_grouped(ar, node->data.variable_declaration.section_expr); + fprintf(ar->f, ") "); + } if (node->data.variable_declaration.expr) { fprintf(ar->f, " = "); render_node_grouped(ar, node->data.variable_declaration.expr); diff --git a/src/codegen.cpp b/src/codegen.cpp index 44b3df3526..5be26eb445 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -391,24 +391,51 @@ static void add_uwtable_attr(CodeGen *g, LLVMValueRef fn_val) { } } +static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) { + switch (id) { + case GlobalLinkageIdInternal: + return LLVMInternalLinkage; + case GlobalLinkageIdStrong: + return LLVMExternalLinkage; + case GlobalLinkageIdWeak: + return LLVMWeakODRLinkage; + case GlobalLinkageIdLinkOnce: + return LLVMLinkOnceODRLinkage; + } + zig_unreachable(); +} + static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { if (fn_table_entry->llvm_value) return fn_table_entry->llvm_value; - bool external_linkage = (fn_table_entry->linkage != GlobalLinkageIdInternal); - Buf *symbol_name = get_mangled_name(g, &fn_table_entry->symbol_name, external_linkage); + Buf *unmangled_name = &fn_table_entry->symbol_name; + Buf *symbol_name; + GlobalLinkageId linkage; + if (fn_table_entry->body_node == nullptr) { + symbol_name = unmangled_name; + linkage = GlobalLinkageIdStrong; + } else if (fn_table_entry->export_list.length == 0) { + symbol_name = get_mangled_name(g, unmangled_name, false); + linkage = GlobalLinkageIdInternal; + } else { + FnExport *fn_export = &fn_table_entry->export_list.items[0]; + symbol_name = &fn_export->name; + linkage = fn_export->linkage; + } + bool external_linkage = linkage != GlobalLinkageIdInternal; if (fn_table_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionStdcall && external_linkage && g->zig_target.arch.arch == ZigLLVM_x86) { - // prevent name mangling + // prevent llvm name mangling symbol_name = buf_sprintf("\x01_%s", buf_ptr(symbol_name)); } TypeTableEntry *fn_type = fn_table_entry->type_entry; LLVMTypeRef fn_llvm_type = fn_type->data.fn.raw_type_ref; - if (external_linkage && fn_table_entry->body_node == nullptr) { + if (fn_table_entry->body_node == nullptr) { LLVMValueRef existing_llvm_fn = LLVMGetNamedFunction(g->module, buf_ptr(symbol_name)); if (existing_llvm_fn) { fn_table_entry->llvm_value = LLVMConstBitCast(existing_llvm_fn, LLVMPointerType(fn_llvm_type, 0)); @@ -418,6 +445,12 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { } } else { fn_table_entry->llvm_value = LLVMAddFunction(g->module, buf_ptr(symbol_name), fn_llvm_type); + + for (size_t i = 1; i < fn_table_entry->export_list.length; i += 1) { + FnExport *fn_export = &fn_table_entry->export_list.items[i]; + LLVMAddAlias(g->module, LLVMTypeOf(fn_table_entry->llvm_value), + fn_table_entry->llvm_value, buf_ptr(&fn_export->name)); + } } fn_table_entry->llvm_name = LLVMGetValueName(fn_table_entry->llvm_value); @@ -445,20 +478,10 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { } } - switch (fn_table_entry->linkage) { - case GlobalLinkageIdInternal: - LLVMSetLinkage(fn_table_entry->llvm_value, LLVMInternalLinkage); - LLVMSetUnnamedAddr(fn_table_entry->llvm_value, true); - break; - case GlobalLinkageIdStrong: - LLVMSetLinkage(fn_table_entry->llvm_value, LLVMExternalLinkage); - break; - case GlobalLinkageIdWeak: - LLVMSetLinkage(fn_table_entry->llvm_value, LLVMWeakODRLinkage); - break; - case GlobalLinkageIdLinkOnce: - LLVMSetLinkage(fn_table_entry->llvm_value, LLVMLinkOnceODRLinkage); - break; + LLVMSetLinkage(fn_table_entry->llvm_value, to_llvm_linkage(linkage)); + + if (linkage == GlobalLinkageIdInternal) { + LLVMSetUnnamedAddr(fn_table_entry->llvm_value, true); } if (fn_type->data.fn.fn_type_id.return_type->id == TypeTableEntryIdUnreachable) { @@ -565,7 +588,8 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) { bool is_definition = fn_table_entry->body_node != nullptr; unsigned flags = 0; bool is_optimized = g->build_mode != BuildModeDebug; - bool is_internal_linkage = (fn_table_entry->linkage == GlobalLinkageIdInternal); + bool is_internal_linkage = (fn_table_entry->body_node != nullptr && + fn_table_entry->export_list.length == 0); ZigLLVMDISubprogram *subprogram = ZigLLVMCreateFunction(g->dbuilder, get_di_scope(g, scope->parent), buf_ptr(&fn_table_entry->symbol_name), "", import->di_file, line_number, @@ -3487,8 +3511,6 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdCheckStatementIsVoid: case IrInstructionIdTypeName: case IrInstructionIdCanImplicitCast: - case IrInstructionIdSetGlobalSection: - case IrInstructionIdSetGlobalLinkage: case IrInstructionIdDeclRef: case IrInstructionIdSwitchVar: case IrInstructionIdOffsetOf: @@ -3499,6 +3521,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdSetAlignStack: case IrInstructionIdArgType: case IrInstructionIdTagType: + case IrInstructionIdExport: zig_unreachable(); case IrInstructionIdReturn: return ir_render_return(g, executable, (IrInstructionReturn *)instruction); @@ -4969,8 +4992,6 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); // TODO rename to Int create_builtin_fn(g, BuiltinFnIdSetDebugSafety, "setDebugSafety", 2); create_builtin_fn(g, BuiltinFnIdSetFloatMode, "setFloatMode", 2); - create_builtin_fn(g, BuiltinFnIdSetGlobalSection, "setGlobalSection", 2); - create_builtin_fn(g, BuiltinFnIdSetGlobalLinkage, "setGlobalLinkage", 2); create_builtin_fn(g, BuiltinFnIdPanic, "panic", 1); create_builtin_fn(g, BuiltinFnIdPtrCast, "ptrCast", 2); create_builtin_fn(g, BuiltinFnIdBitCast, "bitCast", 2); @@ -4995,6 +5016,8 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdOpaqueType, "OpaqueType", 0); create_builtin_fn(g, BuiltinFnIdSetAlignStack, "setAlignStack", 1); create_builtin_fn(g, BuiltinFnIdArgType, "ArgType", 2); + create_builtin_fn(g, BuiltinFnIdExport, "export", 2); + create_builtin_fn(g, BuiltinFnIdExportWithLinkage, "exportWithLinkage", 3); } static const char *bool_to_str(bool b) { @@ -5433,6 +5456,27 @@ static void gen_root_source(CodeGen *g) { assert(g->root_out_name); assert(g->out_type != OutTypeUnknown); + { + // Zig has lazy top level definitions. Here we semantically analyze the panic function. + ImportTableEntry *import_with_panic; + if (g->have_pub_panic) { + import_with_panic = g->root_import; + } else { + g->panic_package = create_panic_pkg(g); + import_with_panic = add_special_code(g, g->panic_package, "panic.zig"); + } + scan_import(g, import_with_panic); + Tld *panic_tld = find_decl(g, &import_with_panic->decls_scope->base, buf_create_from_str("panic")); + assert(panic_tld != nullptr); + resolve_top_level_decl(g, panic_tld, false, nullptr); + } + + + if (!g->error_during_imports) { + semantic_analyze(g); + } + report_errors_and_maybe_exit(g); + if (!g->is_test_build && g->zig_target.os != ZigLLVM_UnknownOS && !g->have_c_main && !g->have_winmain && !g->have_winmain_crt_startup && ((g->have_pub_main && g->out_type == OutTypeObj) || g->out_type == OutTypeExe)) @@ -5442,20 +5486,6 @@ static void gen_root_source(CodeGen *g) { if (g->zig_target.os == ZigLLVM_Win32 && !g->have_dllmain_crt_startup && g->out_type == OutTypeLib) { g->bootstrap_import = add_special_code(g, create_bootstrap_pkg(g, g->root_package), "bootstrap_lib.zig"); } - ImportTableEntry *import_with_panic; - if (g->have_pub_panic) { - import_with_panic = g->root_import; - } else { - g->panic_package = create_panic_pkg(g); - import_with_panic = add_special_code(g, g->panic_package, "panic.zig"); - } - // Zig has lazy top level definitions. Here we semantically analyze the panic function. - { - scan_import(g, import_with_panic); - Tld *panic_tld = find_decl(g, &import_with_panic->decls_scope->base, buf_create_from_str("panic")); - assert(panic_tld != nullptr); - resolve_top_level_decl(g, panic_tld, false, nullptr); - } if (!g->error_during_imports) { semantic_analyze(g); @@ -5682,7 +5712,7 @@ static void gen_h_file(CodeGen *g) { for (size_t fn_def_i = 0; fn_def_i < g->fn_defs.length; fn_def_i += 1) { FnTableEntry *fn_table_entry = g->fn_defs.at(fn_def_i); - if (fn_table_entry->linkage == GlobalLinkageIdInternal) + if (fn_table_entry->export_list.length == 0) continue; FnTypeId *fn_type_id = &fn_table_entry->type_entry->data.fn.fn_type_id; diff --git a/src/ir.cpp b/src/ir.cpp index ce7d8dcedd..81b64a318d 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -207,6 +207,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionDeclVar *) { return IrInstructionIdDeclVar; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionExport *) { + return IrInstructionIdExport; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionLoadPtr *) { return IrInstructionIdLoadPtr; } @@ -523,14 +527,6 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionCanImplicitCast return IrInstructionIdCanImplicitCast; } -static constexpr IrInstructionId ir_instruction_id(IrInstructionSetGlobalSection *) { - return IrInstructionIdSetGlobalSection; -} - -static constexpr IrInstructionId ir_instruction_id(IrInstructionSetGlobalLinkage *) { - return IrInstructionIdSetGlobalLinkage; -} - static constexpr IrInstructionId ir_instruction_id(IrInstructionDeclRef *) { return IrInstructionIdDeclRef; } @@ -1205,6 +1201,24 @@ static IrInstruction *ir_build_var_decl_from(IrBuilder *irb, IrInstruction *old_ return new_instruction; } +static IrInstruction *ir_build_export(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *name, IrInstruction *target, IrInstruction *linkage) +{ + IrInstructionExport *export_instruction = ir_build_instruction( + irb, scope, source_node); + export_instruction->base.value.special = ConstValSpecialStatic; + export_instruction->base.value.type = irb->codegen->builtin_types.entry_void; + export_instruction->name = name; + export_instruction->target = target; + export_instruction->linkage = linkage; + + ir_ref_instruction(name, irb->current_basic_block); + ir_ref_instruction(target, irb->current_basic_block); + if (linkage) ir_ref_instruction(linkage, irb->current_basic_block); + + return &export_instruction->base; +} + static IrInstruction *ir_build_load_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *ptr) { IrInstructionLoadPtr *instruction = ir_build_instruction(irb, scope, source_node); instruction->ptr = ptr; @@ -2159,32 +2173,6 @@ static IrInstruction *ir_build_can_implicit_cast(IrBuilder *irb, Scope *scope, A return &instruction->base; } -static IrInstruction *ir_build_set_global_section(IrBuilder *irb, Scope *scope, AstNode *source_node, - Tld *tld, IrInstruction *value) -{ - IrInstructionSetGlobalSection *instruction = ir_build_instruction( - irb, scope, source_node); - instruction->tld = tld; - instruction->value = value; - - ir_ref_instruction(value, irb->current_basic_block); - - return &instruction->base; -} - -static IrInstruction *ir_build_set_global_linkage(IrBuilder *irb, Scope *scope, AstNode *source_node, - Tld *tld, IrInstruction *value) -{ - IrInstructionSetGlobalLinkage *instruction = ir_build_instruction( - irb, scope, source_node); - instruction->tld = tld; - instruction->value = value; - - ir_ref_instruction(value, irb->current_basic_block); - - return &instruction->base; -} - static IrInstruction *ir_build_decl_ref(IrBuilder *irb, Scope *scope, AstNode *source_node, Tld *tld, LVal lval) { @@ -2396,6 +2384,21 @@ static IrInstruction *ir_instruction_declvar_get_dep(IrInstructionDeclVar *instr return nullptr; } +static IrInstruction *ir_instruction_export_get_dep(IrInstructionExport *instruction, size_t index) { + if (index < 1) return instruction->name; + index -= 1; + + if (index < 1) return instruction->target; + index -= 1; + + if (instruction->linkage != nullptr) { + if (index < 1) return instruction->linkage; + index -= 1; + } + + return nullptr; +} + static IrInstruction *ir_instruction_loadptr_get_dep(IrInstructionLoadPtr *instruction, size_t index) { switch (index) { case 0: return instruction->ptr; @@ -2979,20 +2982,6 @@ static IrInstruction *ir_instruction_canimplicitcast_get_dep(IrInstructionCanImp } } -static IrInstruction *ir_instruction_setglobalsection_get_dep(IrInstructionSetGlobalSection *instruction, size_t index) { - switch (index) { - case 0: return instruction->value; - default: return nullptr; - } -} - -static IrInstruction *ir_instruction_setgloballinkage_get_dep(IrInstructionSetGlobalLinkage *instruction, size_t index) { - switch (index) { - case 0: return instruction->value; - default: return nullptr; - } -} - static IrInstruction *ir_instruction_declref_get_dep(IrInstructionDeclRef *instruction, size_t index) { return nullptr; } @@ -3106,6 +3095,8 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_binop_get_dep((IrInstructionBinOp *) instruction, index); case IrInstructionIdDeclVar: return ir_instruction_declvar_get_dep((IrInstructionDeclVar *) instruction, index); + case IrInstructionIdExport: + return ir_instruction_export_get_dep((IrInstructionExport *) instruction, index); case IrInstructionIdLoadPtr: return ir_instruction_loadptr_get_dep((IrInstructionLoadPtr *) instruction, index); case IrInstructionIdStorePtr: @@ -3264,10 +3255,6 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_typename_get_dep((IrInstructionTypeName *) instruction, index); case IrInstructionIdCanImplicitCast: return ir_instruction_canimplicitcast_get_dep((IrInstructionCanImplicitCast *) instruction, index); - case IrInstructionIdSetGlobalSection: - return ir_instruction_setglobalsection_get_dep((IrInstructionSetGlobalSection *) instruction, index); - case IrInstructionIdSetGlobalLinkage: - return ir_instruction_setgloballinkage_get_dep((IrInstructionSetGlobalLinkage *) instruction, index); case IrInstructionIdDeclRef: return ir_instruction_declref_get_dep((IrInstructionDeclRef *) instruction, index); case IrInstructionIdPanic: @@ -4528,39 +4515,6 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_can_implicit_cast(irb, scope, node, arg0_value, arg1_value); } - case BuiltinFnIdSetGlobalSection: - case BuiltinFnIdSetGlobalLinkage: - { - AstNode *arg0_node = node->data.fn_call_expr.params.at(0); - if (arg0_node->type != NodeTypeSymbol) { - add_node_error(irb->codegen, arg0_node, buf_sprintf("expected identifier")); - return irb->codegen->invalid_instruction; - } - Buf *variable_name = arg0_node->data.symbol_expr.symbol; - Tld *tld = find_decl(irb->codegen, scope, variable_name); - if (!tld) { - add_node_error(irb->codegen, node, buf_sprintf("use of undeclared identifier '%s'", - buf_ptr(variable_name))); - return irb->codegen->invalid_instruction; - } - if (tld->id != TldIdVar && tld->id != TldIdFn) { - add_node_error(irb->codegen, node, buf_sprintf("'%s' must be global variable or function", - buf_ptr(variable_name))); - return irb->codegen->invalid_instruction; - } - AstNode *arg1_node = node->data.fn_call_expr.params.at(1); - IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); - if (arg1_value == irb->codegen->invalid_instruction) - return arg1_value; - - if (builtin_fn->id == BuiltinFnIdSetGlobalSection) { - return ir_build_set_global_section(irb, scope, node, tld, arg1_value); - } else if (builtin_fn->id == BuiltinFnIdSetGlobalLinkage) { - return ir_build_set_global_linkage(irb, scope, node, tld, arg1_value); - } else { - zig_unreachable(); - } - } case BuiltinFnIdPanic: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -4784,6 +4738,31 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_arg_type(irb, scope, node, arg0_value, arg1_value); } + case BuiltinFnIdExport: + case BuiltinFnIdExportWithLinkage: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + AstNode *arg1_node = node->data.fn_call_expr.params.at(1); + IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); + if (arg1_value == irb->codegen->invalid_instruction) + return arg1_value; + + IrInstruction *arg2_value; + if (builtin_fn->id == BuiltinFnIdExportWithLinkage) { + AstNode *arg2_node = node->data.fn_call_expr.params.at(2); + arg2_value = ir_gen_node(irb, arg2_node, scope); + if (arg2_value == irb->codegen->invalid_instruction) + return arg2_value; + } else { + arg2_value = nullptr; + } + + return ir_build_export(irb, scope, node, arg0_value, arg1_value, arg2_value); + } } zig_unreachable(); } @@ -5106,6 +5085,11 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod return align_value; } + if (variable_declaration->section_expr != nullptr) { + add_node_error(irb->codegen, variable_declaration->section_expr, + buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol))); + } + IrInstruction *init_value = ir_gen_node(irb, variable_declaration->expr, scope); if (init_value == irb->codegen->invalid_instruction) return init_value; @@ -6355,6 +6339,10 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop case NodeTypeSwitchRange: case NodeTypeStructField: case NodeTypeLabel: + case NodeTypeFnDef: + case NodeTypeFnDecl: + case NodeTypeErrorValueDecl: + case NodeTypeTestDecl: zig_unreachable(); case NodeTypeBlock: return ir_lval_wrap(irb, scope, ir_gen_block(irb, scope, node), lval); @@ -6436,14 +6424,6 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop return ir_lval_wrap(irb, scope, ir_gen_container_decl(irb, scope, node), lval); case NodeTypeFnProto: return ir_lval_wrap(irb, scope, ir_gen_fn_proto(irb, scope, node), lval); - case NodeTypeFnDef: - zig_panic("TODO IR gen NodeTypeFnDef"); - case NodeTypeFnDecl: - zig_panic("TODO IR gen NodeTypeFnDecl"); - case NodeTypeErrorValueDecl: - zig_panic("TODO IR gen NodeTypeErrorValueDecl"); - case NodeTypeTestDecl: - zig_panic("TODO IR gen NodeTypeTestDecl"); } zig_unreachable(); } @@ -10481,6 +10461,194 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc return ira->codegen->builtin_types.entry_void; } +static TypeTableEntry *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructionExport *instruction) { + IrInstruction *name = instruction->name->other; + Buf *symbol_name = ir_resolve_str(ira, name); + if (symbol_name == nullptr) { + return ira->codegen->builtin_types.entry_invalid; + } + + IrInstruction *target = instruction->target->other; + if (type_is_invalid(target->value.type)) { + return ira->codegen->builtin_types.entry_invalid; + } + + GlobalLinkageId global_linkage_id = GlobalLinkageIdStrong; + if (instruction->linkage != nullptr) { + IrInstruction *linkage_value = instruction->linkage->other; + if (!ir_resolve_global_linkage(ira, linkage_value, &global_linkage_id)) { + return ira->codegen->builtin_types.entry_invalid; + } + } + + auto entry = ira->codegen->exported_symbol_names.put_unique(symbol_name, instruction->base.source_node); + if (entry) { + AstNode *other_export_node = entry->value; + ErrorMsg *msg = ir_add_error(ira, &instruction->base, + buf_sprintf("exported symbol collision: '%s'", buf_ptr(symbol_name))); + add_error_note(ira->codegen, msg, other_export_node, buf_sprintf("other symbol is here")); + } + + switch (target->value.type->id) { + case TypeTableEntryIdInvalid: + case TypeTableEntryIdVar: + case TypeTableEntryIdUnreachable: + zig_unreachable(); + case TypeTableEntryIdFn: { + FnTableEntry *fn_entry = target->value.data.x_fn.fn_entry; + CallingConvention cc = fn_entry->type_entry->data.fn.fn_type_id.cc; + switch (cc) { + case CallingConventionUnspecified: { + ErrorMsg *msg = ir_add_error(ira, target, + buf_sprintf("exported function must specify calling convention")); + add_error_note(ira->codegen, msg, fn_entry->proto_node, buf_sprintf("declared here")); + } break; + case CallingConventionC: + if (buf_eql_str(symbol_name, "main") && ira->codegen->libc_link_lib != nullptr) { + ira->codegen->have_c_main = true; + ira->codegen->windows_subsystem_windows = false; + ira->codegen->windows_subsystem_console = true; + } else if (buf_eql_str(symbol_name, "WinMain") && + ira->codegen->zig_target.os == ZigLLVM_Win32) + { + ira->codegen->have_winmain = true; + ira->codegen->windows_subsystem_windows = true; + ira->codegen->windows_subsystem_console = false; + } else if (buf_eql_str(symbol_name, "WinMainCRTStartup") && + ira->codegen->zig_target.os == ZigLLVM_Win32) + { + ira->codegen->have_winmain_crt_startup = true; + } else if (buf_eql_str(symbol_name, "DllMainCRTStartup") && + ira->codegen->zig_target.os == ZigLLVM_Win32) + { + ira->codegen->have_dllmain_crt_startup = true; + } + // fallthrough + case CallingConventionNaked: + case CallingConventionCold: + case CallingConventionStdcall: { + FnExport *fn_export = fn_entry->export_list.add_one(); + memset(fn_export, 0, sizeof(FnExport)); + buf_init_from_buf(&fn_export->name, symbol_name); + fn_export->linkage = global_linkage_id; + fn_export->source_node = instruction->base.source_node; + } break; + } + } break; + case TypeTableEntryIdStruct: + if (is_slice(target->value.type)) { + ir_add_error(ira, target, + buf_sprintf("unable to export value of type '%s'", buf_ptr(&target->value.type->name))); + } else if (target->value.type->data.structure.layout != ContainerLayoutExtern) { + ErrorMsg *msg = ir_add_error(ira, target, + buf_sprintf("exported struct value must be declared extern")); + add_error_note(ira->codegen, msg, target->value.type->data.structure.decl_node, buf_sprintf("declared here")); + } + break; + case TypeTableEntryIdUnion: + if (target->value.type->data.unionation.layout != ContainerLayoutExtern) { + ErrorMsg *msg = ir_add_error(ira, target, + buf_sprintf("exported union value must be declared extern")); + add_error_note(ira->codegen, msg, target->value.type->data.unionation.decl_node, buf_sprintf("declared here")); + } + break; + case TypeTableEntryIdEnum: + if (target->value.type->data.enumeration.layout != ContainerLayoutExtern) { + ErrorMsg *msg = ir_add_error(ira, target, + buf_sprintf("exported enum value must be declared extern")); + add_error_note(ira->codegen, msg, target->value.type->data.enumeration.decl_node, buf_sprintf("declared here")); + } + break; + case TypeTableEntryIdMetaType: { + TypeTableEntry *type_value = target->value.data.x_type; + switch (type_value->id) { + case TypeTableEntryIdInvalid: + case TypeTableEntryIdVar: + zig_unreachable(); + case TypeTableEntryIdStruct: + if (is_slice(type_value)) { + ir_add_error(ira, target, + buf_sprintf("unable to export type '%s'", buf_ptr(&type_value->name))); + } else if (type_value->data.structure.layout != ContainerLayoutExtern) { + ErrorMsg *msg = ir_add_error(ira, target, + buf_sprintf("exported struct must be declared extern")); + add_error_note(ira->codegen, msg, type_value->data.structure.decl_node, buf_sprintf("declared here")); + } + break; + case TypeTableEntryIdUnion: + if (type_value->data.unionation.layout != ContainerLayoutExtern) { + ErrorMsg *msg = ir_add_error(ira, target, + buf_sprintf("exported union must be declared extern")); + add_error_note(ira->codegen, msg, type_value->data.unionation.decl_node, buf_sprintf("declared here")); + } + break; + case TypeTableEntryIdEnum: + if (type_value->data.enumeration.layout != ContainerLayoutExtern) { + ErrorMsg *msg = ir_add_error(ira, target, + buf_sprintf("exported enum must be declared extern")); + add_error_note(ira->codegen, msg, type_value->data.enumeration.decl_node, buf_sprintf("declared here")); + } + break; + case TypeTableEntryIdFn: { + if (type_value->data.fn.fn_type_id.cc == CallingConventionUnspecified) { + ir_add_error(ira, target, + buf_sprintf("exported function type must specify calling convention")); + } + } break; + case TypeTableEntryIdInt: + case TypeTableEntryIdFloat: + case TypeTableEntryIdPointer: + case TypeTableEntryIdArray: + case TypeTableEntryIdBool: + break; + case TypeTableEntryIdMetaType: + case TypeTableEntryIdVoid: + case TypeTableEntryIdUnreachable: + case TypeTableEntryIdNumLitFloat: + case TypeTableEntryIdNumLitInt: + case TypeTableEntryIdUndefLit: + case TypeTableEntryIdNullLit: + case TypeTableEntryIdMaybe: + case TypeTableEntryIdErrorUnion: + case TypeTableEntryIdPureError: + case TypeTableEntryIdNamespace: + case TypeTableEntryIdBlock: + case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: + case TypeTableEntryIdOpaque: + ir_add_error(ira, target, + buf_sprintf("invalid export target '%s'", buf_ptr(&type_value->name))); + break; + } + } break; + case TypeTableEntryIdVoid: + case TypeTableEntryIdBool: + case TypeTableEntryIdInt: + case TypeTableEntryIdFloat: + case TypeTableEntryIdPointer: + case TypeTableEntryIdArray: + case TypeTableEntryIdNumLitFloat: + case TypeTableEntryIdNumLitInt: + case TypeTableEntryIdUndefLit: + case TypeTableEntryIdNullLit: + case TypeTableEntryIdMaybe: + case TypeTableEntryIdErrorUnion: + case TypeTableEntryIdPureError: + zig_panic("TODO export const value of type %s", buf_ptr(&target->value.type->name)); + case TypeTableEntryIdNamespace: + case TypeTableEntryIdBlock: + case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: + case TypeTableEntryIdOpaque: + ir_add_error(ira, target, + buf_sprintf("invalid export target type '%s'", buf_ptr(&target->value.type->name))); + break; + } + + ir_build_const_from(ira, &instruction->base); + return ira->codegen->builtin_types.entry_void; +} + static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node, IrInstruction *arg, Scope **exec_scope, size_t *next_proto_i) { @@ -12399,102 +12567,6 @@ static TypeTableEntry *ir_analyze_instruction_ptr_type_child(IrAnalyze *ira, return ira->codegen->builtin_types.entry_type; } -static TypeTableEntry *ir_analyze_instruction_set_global_section(IrAnalyze *ira, - IrInstructionSetGlobalSection *instruction) -{ - Tld *tld = instruction->tld; - IrInstruction *section_value = instruction->value->other; - - resolve_top_level_decl(ira->codegen, tld, true, instruction->base.source_node); - if (tld->resolution == TldResolutionInvalid) - return ira->codegen->builtin_types.entry_invalid; - - Buf *section_name = ir_resolve_str(ira, section_value); - if (!section_name) - return ira->codegen->builtin_types.entry_invalid; - - AstNode **set_global_section_node; - Buf **section_name_ptr; - if (tld->id == TldIdVar) { - TldVar *tld_var = (TldVar *)tld; - set_global_section_node = &tld_var->set_global_section_node; - section_name_ptr = &tld_var->section_name; - - if (tld_var->var->linkage == VarLinkageExternal) { - ErrorMsg *msg = ir_add_error(ira, &instruction->base, - buf_sprintf("cannot set section of external variable '%s'", buf_ptr(&tld_var->var->name))); - add_error_note(ira->codegen, msg, tld->source_node, buf_sprintf("declared here")); - return ira->codegen->builtin_types.entry_invalid; - } - } else if (tld->id == TldIdFn) { - TldFn *tld_fn = (TldFn *)tld; - FnTableEntry *fn_entry = tld_fn->fn_entry; - set_global_section_node = &fn_entry->set_global_section_node; - section_name_ptr = &fn_entry->section_name; - - if (fn_entry->def_scope == nullptr) { - ErrorMsg *msg = ir_add_error(ira, &instruction->base, - buf_sprintf("cannot set section of external function '%s'", buf_ptr(&fn_entry->symbol_name))); - add_error_note(ira->codegen, msg, tld->source_node, buf_sprintf("declared here")); - return ira->codegen->builtin_types.entry_invalid; - } - } else { - // error is caught in pass1 IR gen - zig_unreachable(); - } - - AstNode *source_node = instruction->base.source_node; - if (*set_global_section_node) { - ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("section set twice")); - add_error_note(ira->codegen, msg, *set_global_section_node, buf_sprintf("first set here")); - return ira->codegen->builtin_types.entry_invalid; - } - *set_global_section_node = source_node; - *section_name_ptr = section_name; - - ir_build_const_from(ira, &instruction->base); - return ira->codegen->builtin_types.entry_void; -} - -static TypeTableEntry *ir_analyze_instruction_set_global_linkage(IrAnalyze *ira, - IrInstructionSetGlobalLinkage *instruction) -{ - Tld *tld = instruction->tld; - IrInstruction *linkage_value = instruction->value->other; - - GlobalLinkageId linkage_scalar; - if (!ir_resolve_global_linkage(ira, linkage_value, &linkage_scalar)) - return ira->codegen->builtin_types.entry_invalid; - - AstNode **set_global_linkage_node; - GlobalLinkageId *dest_linkage_ptr; - if (tld->id == TldIdVar) { - TldVar *tld_var = (TldVar *)tld; - set_global_linkage_node = &tld_var->set_global_linkage_node; - dest_linkage_ptr = &tld_var->linkage; - } else if (tld->id == TldIdFn) { - TldFn *tld_fn = (TldFn *)tld; - FnTableEntry *fn_entry = tld_fn->fn_entry; - set_global_linkage_node = &fn_entry->set_global_linkage_node; - dest_linkage_ptr = &fn_entry->linkage; - } else { - // error is caught in pass1 IR gen - zig_unreachable(); - } - - AstNode *source_node = instruction->base.source_node; - if (*set_global_linkage_node) { - ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("linkage set twice")); - add_error_note(ira->codegen, msg, *set_global_linkage_node, buf_sprintf("first set here")); - return ira->codegen->builtin_types.entry_invalid; - } - *set_global_linkage_node = source_node; - *dest_linkage_ptr = linkage_scalar; - - ir_build_const_from(ira, &instruction->base); - return ira->codegen->builtin_types.entry_void; -} - static TypeTableEntry *ir_analyze_instruction_set_debug_safety(IrAnalyze *ira, IrInstructionSetDebugSafety *set_debug_safety_instruction) { @@ -16165,10 +16237,6 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_to_ptr_type(ira, (IrInstructionToPtrType *)instruction); case IrInstructionIdPtrTypeChild: return ir_analyze_instruction_ptr_type_child(ira, (IrInstructionPtrTypeChild *)instruction); - case IrInstructionIdSetGlobalSection: - return ir_analyze_instruction_set_global_section(ira, (IrInstructionSetGlobalSection *)instruction); - case IrInstructionIdSetGlobalLinkage: - return ir_analyze_instruction_set_global_linkage(ira, (IrInstructionSetGlobalLinkage *)instruction); case IrInstructionIdSetDebugSafety: return ir_analyze_instruction_set_debug_safety(ira, (IrInstructionSetDebugSafety *)instruction); case IrInstructionIdSetFloatMode: @@ -16311,6 +16379,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_arg_type(ira, (IrInstructionArgType *)instruction); case IrInstructionIdTagType: return ir_analyze_instruction_tag_type(ira, (IrInstructionTagType *)instruction); + case IrInstructionIdExport: + return ir_analyze_instruction_export(ira, (IrInstructionExport *)instruction); } zig_unreachable(); } @@ -16418,12 +16488,11 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdOverflowOp: // TODO when we support multiple returns this can be side effect free case IrInstructionIdCheckSwitchProngs: case IrInstructionIdCheckStatementIsVoid: - case IrInstructionIdSetGlobalSection: - case IrInstructionIdSetGlobalLinkage: case IrInstructionIdPanic: case IrInstructionIdSetEvalBranchQuota: case IrInstructionIdPtrTypeOf: case IrInstructionIdSetAlignStack: + case IrInstructionIdExport: return true; case IrInstructionIdPhi: case IrInstructionIdUnOp: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index b6a7c32dfc..f5aba2a45d 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -899,19 +899,6 @@ static void ir_print_ptr_type_of(IrPrint *irp, IrInstructionPtrTypeOf *instructi ir_print_other_instruction(irp, instruction->child_type); } -static void ir_print_set_global_section(IrPrint *irp, IrInstructionSetGlobalSection *instruction) { - fprintf(irp->f, "@setGlobalSection(%s,", buf_ptr(instruction->tld->name)); - ir_print_other_instruction(irp, instruction->value); - fprintf(irp->f, ")"); -} - -static void ir_print_set_global_linkage(IrPrint *irp, IrInstructionSetGlobalLinkage *instruction) { - fprintf(irp->f, "@setGlobalLinkage(%s,", buf_ptr(instruction->tld->name)); - ir_print_other_instruction(irp, instruction->value); - fprintf(irp->f, ")"); -} - - static void ir_print_decl_ref(IrPrint *irp, IrInstructionDeclRef *instruction) { const char *ptr_str = instruction->lval.is_ptr ? "ptr " : ""; const char *const_str = instruction->lval.is_const ? "const " : ""; @@ -991,6 +978,24 @@ static void ir_print_enum_tag_type(IrPrint *irp, IrInstructionTagType *instructi fprintf(irp->f, ")"); } +static void ir_print_export(IrPrint *irp, IrInstructionExport *instruction) { + if (instruction->linkage == nullptr) { + fprintf(irp->f, "@export("); + ir_print_other_instruction(irp, instruction->name); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->target); + fprintf(irp->f, ")"); + } else { + fprintf(irp->f, "@exportWithLinkage("); + ir_print_other_instruction(irp, instruction->name); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->target); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->linkage); + fprintf(irp->f, ")"); + } +} + static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); @@ -1267,12 +1272,6 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdPtrTypeOf: ir_print_ptr_type_of(irp, (IrInstructionPtrTypeOf *)instruction); break; - case IrInstructionIdSetGlobalSection: - ir_print_set_global_section(irp, (IrInstructionSetGlobalSection *)instruction); - break; - case IrInstructionIdSetGlobalLinkage: - ir_print_set_global_linkage(irp, (IrInstructionSetGlobalLinkage *)instruction); - break; case IrInstructionIdDeclRef: ir_print_decl_ref(irp, (IrInstructionDeclRef *)instruction); break; @@ -1306,6 +1305,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdTagType: ir_print_enum_tag_type(irp, (IrInstructionTagType *)instruction); break; + case IrInstructionIdExport: + ir_print_export(irp, (IrInstructionExport *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/src/parser.cpp b/src/parser.cpp index 26ca7da31a..07c221e287 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1600,6 +1600,14 @@ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, size_t *to next_token = &pc->tokens->at(*token_index); } + if (next_token->id == TokenIdKeywordSection) { + *token_index += 1; + ast_eat_token(pc, token_index, TokenIdLParen); + node->data.variable_declaration.section_expr = ast_parse_expression(pc, token_index, true); + ast_eat_token(pc, token_index, TokenIdRParen); + next_token = &pc->tokens->at(*token_index); + } + if (next_token->id == TokenIdEq) { *token_index += 1; node->data.variable_declaration.expr = ast_parse_expression(pc, token_index, true); @@ -2144,7 +2152,7 @@ static bool statement_terminates_without_semicolon(AstNode *node) { /* Block = "{" many(Statement) option(Expression) "}" -Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" +Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" | ExportDecl */ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mandatory) { Token *last_token = &pc->tokens->at(*token_index); @@ -2205,7 +2213,7 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand } /* -FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("->" TypeExpr) +FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) */ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) { Token *first_token = &pc->tokens->at(*token_index); @@ -2259,6 +2267,14 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m ast_eat_token(pc, token_index, TokenIdRParen); next_token = &pc->tokens->at(*token_index); } + if (next_token->id == TokenIdKeywordSection) { + *token_index += 1; + ast_eat_token(pc, token_index, TokenIdLParen); + + node->data.fn_proto.section_expr = ast_parse_expression(pc, token_index, true); + ast_eat_token(pc, token_index, TokenIdRParen); + next_token = &pc->tokens->at(*token_index); + } if (next_token->id == TokenIdArrow) { *token_index += 1; node->data.fn_proto.return_type = ast_parse_type_expr(pc, token_index, false); @@ -2447,9 +2463,6 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index, if (visib_tok->id == TokenIdKeywordPub) { *token_index += 1; visib_mod = VisibModPub; - } else if (visib_tok->id == TokenIdKeywordExport) { - *token_index += 1; - visib_mod = VisibModExport; } else { visib_mod = VisibModPrivate; } @@ -2580,9 +2593,6 @@ static void ast_parse_top_level_decls(ParseContext *pc, size_t *token_index, Zig if (visib_tok->id == TokenIdKeywordPub) { *token_index += 1; visib_mod = VisibModPub; - } else if (visib_tok->id == TokenIdKeywordExport) { - *token_index += 1; - visib_mod = VisibModExport; } else { visib_mod = VisibModPrivate; } @@ -2669,6 +2679,7 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont visit_field(&node->data.fn_proto.return_type, visit, context); visit_node_list(&node->data.fn_proto.params, visit, context); visit_field(&node->data.fn_proto.align_expr, visit, context); + visit_field(&node->data.fn_proto.section_expr, visit, context); break; case NodeTypeFnDef: visit_field(&node->data.fn_def.fn_proto, visit, context); @@ -2696,6 +2707,7 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont visit_field(&node->data.variable_declaration.type, visit, context); visit_field(&node->data.variable_declaration.expr, visit, context); visit_field(&node->data.variable_declaration.align_expr, visit, context); + visit_field(&node->data.variable_declaration.section_expr, visit, context); break; case NodeTypeErrorValueDecl: // none diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 77d74c52ee..a14f709744 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -119,7 +119,6 @@ static const struct ZigKeyword zig_keywords[] = { {"else", TokenIdKeywordElse}, {"enum", TokenIdKeywordEnum}, {"error", TokenIdKeywordError}, - {"export", TokenIdKeywordExport}, {"extern", TokenIdKeywordExtern}, {"false", TokenIdKeywordFalse}, {"fn", TokenIdKeywordFn}, @@ -134,6 +133,7 @@ static const struct ZigKeyword zig_keywords[] = { {"packed", TokenIdKeywordPacked}, {"pub", TokenIdKeywordPub}, {"return", TokenIdKeywordReturn}, + {"section", TokenIdKeywordSection}, {"stdcallcc", TokenIdKeywordStdcallCC}, {"struct", TokenIdKeywordStruct}, {"switch", TokenIdKeywordSwitch}, @@ -1518,7 +1518,6 @@ const char * token_name(TokenId id) { case TokenIdKeywordElse: return "else"; case TokenIdKeywordEnum: return "enum"; case TokenIdKeywordError: return "error"; - case TokenIdKeywordExport: return "export"; case TokenIdKeywordExtern: return "extern"; case TokenIdKeywordFalse: return "false"; case TokenIdKeywordFn: return "fn"; @@ -1533,6 +1532,7 @@ const char * token_name(TokenId id) { case TokenIdKeywordPacked: return "packed"; case TokenIdKeywordPub: return "pub"; case TokenIdKeywordReturn: return "return"; + case TokenIdKeywordSection: return "section"; case TokenIdKeywordStdcallCC: return "stdcallcc"; case TokenIdKeywordStruct: return "struct"; case TokenIdKeywordSwitch: return "switch"; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index bcad977864..b0444b9c3b 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -47,6 +47,7 @@ enum TokenId { TokenIdFloatLiteral, TokenIdIntLiteral, TokenIdKeywordAlign, + TokenIdKeywordSection, TokenIdKeywordAnd, TokenIdKeywordAsm, TokenIdKeywordBreak, @@ -58,7 +59,6 @@ enum TokenId { TokenIdKeywordElse, TokenIdKeywordEnum, TokenIdKeywordError, - TokenIdKeywordExport, TokenIdKeywordExtern, TokenIdKeywordFalse, TokenIdKeywordFn, diff --git a/src/translate_c.cpp b/src/translate_c.cpp index 4575d4ee56..77afc38a51 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -73,7 +73,6 @@ struct Context { ImportTableEntry *import; ZigList *errors; VisibMod visib_mod; - VisibMod export_visib_mod; AstNode *root; HashMap decl_table; HashMap macro_table; @@ -3251,7 +3250,8 @@ static void visit_fn_decl(Context *c, const FunctionDecl *fn_decl) { StorageClass sc = fn_decl->getStorageClass(); if (sc == SC_None) { - proto_node->data.fn_proto.visib_mod = fn_decl->hasBody() ? c->export_visib_mod : c->visib_mod; + // TODO add export decl + proto_node->data.fn_proto.visib_mod = c->visib_mod; } else if (sc == SC_Extern || sc == SC_Static) { proto_node->data.fn_proto.visib_mod = c->visib_mod; } else if (sc == SC_PrivateExtern) { @@ -4274,10 +4274,8 @@ int parse_h_file(ImportTableEntry *import, ZigList *errors, const ch c->errors = errors; if (buf_ends_with_str(buf_create_from_str(target_file), ".h")) { c->visib_mod = VisibModPub; - c->export_visib_mod = VisibModPub; } else { c->visib_mod = VisibModPub; - c->export_visib_mod = VisibModExport; } c->decl_table.init(8); c->macro_table.init(8); diff --git a/std/debug.zig b/std/debug.zig index a6229f88da..1035948e3e 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -96,8 +96,6 @@ const WHITE = "\x1b[37;1m"; const DIM = "\x1b[2m"; const RESET = "\x1b[0m"; -pub var user_main_fn: ?fn() -> %void = null; - error PathNotFound; error InvalidDebugInfo; diff --git a/std/elf.zig b/std/elf.zig index f7be236d15..2c5b10b3f2 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -188,39 +188,39 @@ pub const Elf = struct { if (elf.is_64) { if (sh_entry_size != 64) return error.InvalidFormat; - for (elf.section_headers) |*section| { - section.name = %return in.readInt(elf.endian, u32); - section.sh_type = %return in.readInt(elf.endian, u32); - section.flags = %return in.readInt(elf.endian, u64); - section.addr = %return in.readInt(elf.endian, u64); - section.offset = %return in.readInt(elf.endian, u64); - section.size = %return in.readInt(elf.endian, u64); - section.link = %return in.readInt(elf.endian, u32); - section.info = %return in.readInt(elf.endian, u32); - section.addr_align = %return in.readInt(elf.endian, u64); - section.ent_size = %return in.readInt(elf.endian, u64); + for (elf.section_headers) |*elf_section| { + elf_section.name = %return in.readInt(elf.endian, u32); + elf_section.sh_type = %return in.readInt(elf.endian, u32); + elf_section.flags = %return in.readInt(elf.endian, u64); + elf_section.addr = %return in.readInt(elf.endian, u64); + elf_section.offset = %return in.readInt(elf.endian, u64); + elf_section.size = %return in.readInt(elf.endian, u64); + elf_section.link = %return in.readInt(elf.endian, u32); + elf_section.info = %return in.readInt(elf.endian, u32); + elf_section.addr_align = %return in.readInt(elf.endian, u64); + elf_section.ent_size = %return in.readInt(elf.endian, u64); } } else { if (sh_entry_size != 40) return error.InvalidFormat; - for (elf.section_headers) |*section| { + for (elf.section_headers) |*elf_section| { // TODO (multiple occurences) allow implicit cast from %u32 -> %u64 ? - section.name = %return in.readInt(elf.endian, u32); - section.sh_type = %return in.readInt(elf.endian, u32); - section.flags = u64(%return in.readInt(elf.endian, u32)); - section.addr = u64(%return in.readInt(elf.endian, u32)); - section.offset = u64(%return in.readInt(elf.endian, u32)); - section.size = u64(%return in.readInt(elf.endian, u32)); - section.link = %return in.readInt(elf.endian, u32); - section.info = %return in.readInt(elf.endian, u32); - section.addr_align = u64(%return in.readInt(elf.endian, u32)); - section.ent_size = u64(%return in.readInt(elf.endian, u32)); + elf_section.name = %return in.readInt(elf.endian, u32); + elf_section.sh_type = %return in.readInt(elf.endian, u32); + elf_section.flags = u64(%return in.readInt(elf.endian, u32)); + elf_section.addr = u64(%return in.readInt(elf.endian, u32)); + elf_section.offset = u64(%return in.readInt(elf.endian, u32)); + elf_section.size = u64(%return in.readInt(elf.endian, u32)); + elf_section.link = %return in.readInt(elf.endian, u32); + elf_section.info = %return in.readInt(elf.endian, u32); + elf_section.addr_align = u64(%return in.readInt(elf.endian, u32)); + elf_section.ent_size = u64(%return in.readInt(elf.endian, u32)); } } - for (elf.section_headers) |*section| { - if (section.sh_type != SHT_NOBITS) { - const file_end_offset = %return math.add(u64, section.offset, section.size); + for (elf.section_headers) |*elf_section| { + if (elf_section.sh_type != SHT_NOBITS) { + const file_end_offset = %return math.add(u64, elf_section.offset, elf_section.size); if (stream_end < file_end_offset) return error.InvalidFormat; } } @@ -243,10 +243,10 @@ pub const Elf = struct { var file_stream = io.FileInStream.init(elf.in_file); const in = &file_stream.stream; - for (elf.section_headers) |*section| { - if (section.sh_type == SHT_NULL) continue; + for (elf.section_headers) |*elf_section| { + if (elf_section.sh_type == SHT_NULL) continue; - const name_offset = elf.string_section.offset + section.name; + const name_offset = elf.string_section.offset + elf_section.name; %return elf.in_file.seekTo(name_offset); for (name) |expected_c| { @@ -256,7 +256,7 @@ pub const Elf = struct { { const null_byte = %return in.readByte(); - if (null_byte == 0) return section; + if (null_byte == 0) return elf_section; } next_section: @@ -265,7 +265,7 @@ pub const Elf = struct { return null; } - pub fn seekToSection(elf: &Elf, section: &SectionHeader) -> %void { - %return elf.in_file.seekTo(section.offset); + pub fn seekToSection(elf: &Elf, elf_section: &SectionHeader) -> %void { + %return elf.in_file.seekTo(elf_section.offset); } }; diff --git a/std/mem.zig b/std/mem.zig index 10d2221f9a..e9883afd39 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -4,6 +4,7 @@ const math = @import("math/index.zig"); const builtin = @import("builtin"); pub const Cmp = math.Cmp; +error OutOfMemory; pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the diff --git a/std/os/linux.zig b/std/os/linux.zig index 5951f9d7bc..4ba4db603f 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -651,28 +651,6 @@ pub const iovec = extern struct { iov_len: usize, }; -// -//const IF_NAMESIZE = 16; -// -//export struct ifreq { -// ifrn_name: [IF_NAMESIZE]u8, -// union { -// ifru_addr: sockaddr, -// ifru_dstaddr: sockaddr, -// ifru_broadaddr: sockaddr, -// ifru_netmask: sockaddr, -// ifru_hwaddr: sockaddr, -// ifru_flags: i16, -// ifru_ivalue: i32, -// ifru_mtu: i32, -// ifru_map: ifmap, -// ifru_slave: [IF_NAMESIZE]u8, -// ifru_newname: [IF_NAMESIZE]u8, -// ifru_data: &u8, -// } ifr_ifru; -//} -// - pub fn getsockname(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t) -> usize { arch.syscall3(arch.SYS_getsockname, usize(fd), @ptrToInt(addr), @ptrToInt(len)) } diff --git a/std/os/linux_i386.zig b/std/os/linux_i386.zig index 215670e3a9..ed49e33c2b 100644 --- a/std/os/linux_i386.zig +++ b/std/os/linux_i386.zig @@ -502,13 +502,3 @@ pub nakedcc fn restore_rt() { : [number] "{eax}" (usize(SYS_rt_sigreturn)) : "rcx", "r11") } - -export struct msghdr { - msg_name: &u8, - msg_namelen: socklen_t, - msg_iov: &iovec, - msg_iovlen: i32, - msg_control: &u8, - msg_controllen: socklen_t, - msg_flags: i32, -} diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index 99bea09726..ee63467305 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -5,20 +5,19 @@ const root = @import("@root"); const std = @import("std"); const builtin = @import("builtin"); -const is_windows = builtin.os == builtin.Os.windows; -const want_main_symbol = builtin.link_libc; -const want_start_symbol = !want_main_symbol and !is_windows; -const want_WinMainCRTStartup = is_windows and !builtin.link_libc; - var argc_ptr: &usize = undefined; - -export nakedcc fn _start() -> noreturn { - if (!want_start_symbol) { - @setGlobalLinkage(_start, builtin.GlobalLinkage.Internal); - unreachable; +comptime { + if (builtin.link_libc) { + @export("main", main); + } else if (builtin.os == builtin.Os.windows) { + @export("WinMainCRTStartup", WinMainCRTStartup); + } else { + @export("_start", _start); } +} +nakedcc fn _start() -> noreturn { switch (builtin.arch) { builtin.Arch.x86_64 => { argc_ptr = asm("lea (%%rsp), %[argc]": [argc] "=r" (-> &usize)); @@ -33,14 +32,9 @@ export nakedcc fn _start() -> noreturn { @noInlineCall(posixCallMainAndExit); } -export fn WinMainCRTStartup() -> noreturn { - if (!want_WinMainCRTStartup) { - @setGlobalLinkage(WinMainCRTStartup, builtin.GlobalLinkage.Internal); - unreachable; - } +extern fn WinMainCRTStartup() -> noreturn { @setAlignStack(16); - std.debug.user_main_fn = root.main; root.main() %% std.os.windows.ExitProcess(1); std.os.windows.ExitProcess(0); } @@ -60,17 +54,10 @@ fn callMain(argc: usize, argv: &&u8, envp: &?&u8) -> %void { while (envp[env_count] != null) : (env_count += 1) {} std.os.posix_environ_raw = @ptrCast(&&u8, envp)[0..env_count]; - std.debug.user_main_fn = root.main; - return root.main(); } -export fn main(c_argc: i32, c_argv: &&u8, c_envp: &?&u8) -> i32 { - if (!want_main_symbol) { - @setGlobalLinkage(main, builtin.GlobalLinkage.Internal); - unreachable; - } - +extern fn main(c_argc: i32, c_argv: &&u8, c_envp: &?&u8) -> i32 { callMain(usize(c_argc), c_argv, c_envp) %% return 1; return 0; } diff --git a/std/special/bootstrap_lib.zig b/std/special/bootstrap_lib.zig index 7412d64fa6..3c7789f30a 100644 --- a/std/special/bootstrap_lib.zig +++ b/std/special/bootstrap_lib.zig @@ -2,7 +2,11 @@ const std = @import("std"); -export stdcallcc fn _DllMainCRTStartup(hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, +comptime { + @export("_DllMainCRTStartup", _DllMainCRTStartup); +} + +stdcallcc fn _DllMainCRTStartup(hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, lpReserved: std.os.windows.LPVOID) -> std.os.windows.BOOL { return std.os.windows.TRUE; diff --git a/std/special/builtin.zig b/std/special/builtin.zig index 51e6646574..9ace9b46ca 100644 --- a/std/special/builtin.zig +++ b/std/special/builtin.zig @@ -13,10 +13,24 @@ pub coldcc fn panic(msg: []const u8) -> noreturn { } } +comptime { + @export("memset", memset); + @export("memcpy", memcpy); + @export("fmodf", fmodf); + @export("fmod", fmod); + @export("floorf", floorf); + @export("ceilf", ceilf); + @export("floor", floor); + @export("ceil", ceil); + if (builtin.mode != builtin.Mode.ReleaseFast and builtin.os != builtin.Os.windows) { + @export("__stack_chk_fail", __stack_chk_fail); + } +} + // Note that memset does not return `dest`, like the libc API. // The semantics of memset is dictated by the corresponding // LLVM intrinsics, not by the libc API. -export fn memset(dest: ?&u8, c: u8, n: usize) { +extern fn memset(dest: ?&u8, c: u8, n: usize) { @setDebugSafety(this, false); var index: usize = 0; @@ -27,7 +41,7 @@ export fn memset(dest: ?&u8, c: u8, n: usize) { // Note that memcpy does not return `dest`, like the libc API. // The semantics of memcpy is dictated by the corresponding // LLVM intrinsics, not by the libc API. -export fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) { +extern fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) { @setDebugSafety(this, false); var index: usize = 0; @@ -35,25 +49,21 @@ export fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) { (??dest)[index] = (??src)[index]; } -export fn __stack_chk_fail() -> noreturn { - if (builtin.mode == builtin.Mode.ReleaseFast or builtin.os == builtin.Os.windows) { - @setGlobalLinkage(__stack_chk_fail, builtin.GlobalLinkage.Internal); - unreachable; - } +extern fn __stack_chk_fail() -> noreturn { @panic("stack smashing detected"); } const math = @import("../math/index.zig"); -export fn fmodf(x: f32, y: f32) -> f32 { generic_fmod(f32, x, y) } -export fn fmod(x: f64, y: f64) -> f64 { generic_fmod(f64, x, y) } +extern fn fmodf(x: f32, y: f32) -> f32 { generic_fmod(f32, x, y) } +extern fn fmod(x: f64, y: f64) -> f64 { generic_fmod(f64, x, y) } // TODO add intrinsics for these (and probably the double version too) // and have the math stuff use the intrinsic. same as @mod and @rem -export fn floorf(x: f32) -> f32 { math.floor(x) } -export fn ceilf(x: f32) -> f32 { math.ceil(x) } -export fn floor(x: f64) -> f64 { math.floor(x) } -export fn ceil(x: f64) -> f64 { math.ceil(x) } +extern fn floorf(x: f32) -> f32 { math.floor(x) } +extern fn ceilf(x: f32) -> f32 { math.ceil(x) } +extern fn floor(x: f64) -> f64 { math.floor(x) } +extern fn ceil(x: f64) -> f64 { math.ceil(x) } fn generic_fmod(comptime T: type, x: T, y: T) -> T { @setDebugSafety(this, false); diff --git a/std/special/compiler_rt/aulldiv.zig b/std/special/compiler_rt/aulldiv.zig index 511aa91f80..9d4faf95b9 100644 --- a/std/special/compiler_rt/aulldiv.zig +++ b/std/special/compiler_rt/aulldiv.zig @@ -1,66 +1,55 @@ -const builtin = @import("builtin"); -const linkage = if (builtin.is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong; -const is_win32 = builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386; - -export nakedcc fn _aulldiv() { - if (is_win32) { - @setDebugSafety(this, false); - @setGlobalLinkage(_aulldiv, linkage); - asm volatile ( - \\.intel_syntax noprefix - \\ - \\ push ebx - \\ push esi - \\ mov eax,dword ptr [esp+18h] - \\ or eax,eax - \\ jne L1 - \\ mov ecx,dword ptr [esp+14h] - \\ mov eax,dword ptr [esp+10h] - \\ xor edx,edx - \\ div ecx - \\ mov ebx,eax - \\ mov eax,dword ptr [esp+0Ch] - \\ div ecx - \\ mov edx,ebx - \\ jmp L2 - \\ L1: - \\ mov ecx,eax - \\ mov ebx,dword ptr [esp+14h] - \\ mov edx,dword ptr [esp+10h] - \\ mov eax,dword ptr [esp+0Ch] - \\ L3: - \\ shr ecx,1 - \\ rcr ebx,1 - \\ shr edx,1 - \\ rcr eax,1 - \\ or ecx,ecx - \\ jne L3 - \\ div ebx - \\ mov esi,eax - \\ mul dword ptr [esp+18h] - \\ mov ecx,eax - \\ mov eax,dword ptr [esp+14h] - \\ mul esi - \\ add edx,ecx - \\ jb L4 - \\ cmp edx,dword ptr [esp+10h] - \\ ja L4 - \\ jb L5 - \\ cmp eax,dword ptr [esp+0Ch] - \\ jbe L5 - \\ L4: - \\ dec esi - \\ L5: - \\ xor edx,edx - \\ mov eax,esi - \\ L2: - \\ pop esi - \\ pop ebx - \\ ret 10h - ); - unreachable; - } - - @setGlobalLinkage(_aulldiv, builtin.GlobalLinkage.Internal); - unreachable; +pub nakedcc fn _aulldiv() { + @setDebugSafety(this, false); + asm volatile ( + \\.intel_syntax noprefix + \\ + \\ push ebx + \\ push esi + \\ mov eax,dword ptr [esp+18h] + \\ or eax,eax + \\ jne L1 + \\ mov ecx,dword ptr [esp+14h] + \\ mov eax,dword ptr [esp+10h] + \\ xor edx,edx + \\ div ecx + \\ mov ebx,eax + \\ mov eax,dword ptr [esp+0Ch] + \\ div ecx + \\ mov edx,ebx + \\ jmp L2 + \\ L1: + \\ mov ecx,eax + \\ mov ebx,dword ptr [esp+14h] + \\ mov edx,dword ptr [esp+10h] + \\ mov eax,dword ptr [esp+0Ch] + \\ L3: + \\ shr ecx,1 + \\ rcr ebx,1 + \\ shr edx,1 + \\ rcr eax,1 + \\ or ecx,ecx + \\ jne L3 + \\ div ebx + \\ mov esi,eax + \\ mul dword ptr [esp+18h] + \\ mov ecx,eax + \\ mov eax,dword ptr [esp+14h] + \\ mul esi + \\ add edx,ecx + \\ jb L4 + \\ cmp edx,dword ptr [esp+10h] + \\ ja L4 + \\ jb L5 + \\ cmp eax,dword ptr [esp+0Ch] + \\ jbe L5 + \\ L4: + \\ dec esi + \\ L5: + \\ xor edx,edx + \\ mov eax,esi + \\ L2: + \\ pop esi + \\ pop ebx + \\ ret 10h + ); } diff --git a/std/special/compiler_rt/aullrem.zig b/std/special/compiler_rt/aullrem.zig index e218890959..b6c54d33ae 100644 --- a/std/special/compiler_rt/aullrem.zig +++ b/std/special/compiler_rt/aullrem.zig @@ -1,67 +1,56 @@ -const builtin = @import("builtin"); -const linkage = if (builtin.is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong; -const is_win32 = builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386; - -export nakedcc fn _aullrem() { - if (is_win32) { - @setDebugSafety(this, false); - @setGlobalLinkage(_aullrem, linkage); - asm volatile ( - \\.intel_syntax noprefix - \\ - \\ push ebx - \\ mov eax,dword ptr [esp+14h] - \\ or eax,eax - \\ jne L1a - \\ mov ecx,dword ptr [esp+10h] - \\ mov eax,dword ptr [esp+0Ch] - \\ xor edx,edx - \\ div ecx - \\ mov eax,dword ptr [esp+8] - \\ div ecx - \\ mov eax,edx - \\ xor edx,edx - \\ jmp L2a - \\ L1a: - \\ mov ecx,eax - \\ mov ebx,dword ptr [esp+10h] - \\ mov edx,dword ptr [esp+0Ch] - \\ mov eax,dword ptr [esp+8] - \\ L3a: - \\ shr ecx,1 - \\ rcr ebx,1 - \\ shr edx,1 - \\ rcr eax,1 - \\ or ecx,ecx - \\ jne L3a - \\ div ebx - \\ mov ecx,eax - \\ mul dword ptr [esp+14h] - \\ xchg eax,ecx - \\ mul dword ptr [esp+10h] - \\ add edx,ecx - \\ jb L4a - \\ cmp edx,dword ptr [esp+0Ch] - \\ ja L4a - \\ jb L5a - \\ cmp eax,dword ptr [esp+8] - \\ jbe L5a - \\ L4a: - \\ sub eax,dword ptr [esp+10h] - \\ sbb edx,dword ptr [esp+14h] - \\ L5a: - \\ sub eax,dword ptr [esp+8] - \\ sbb edx,dword ptr [esp+0Ch] - \\ neg edx - \\ neg eax - \\ sbb edx,0 - \\ L2a: - \\ pop ebx - \\ ret 10h - ); - unreachable; - } - - @setGlobalLinkage(_aullrem, builtin.GlobalLinkage.Internal); - unreachable; +pub nakedcc fn _aullrem() { + @setDebugSafety(this, false); + asm volatile ( + \\.intel_syntax noprefix + \\ + \\ push ebx + \\ mov eax,dword ptr [esp+14h] + \\ or eax,eax + \\ jne L1a + \\ mov ecx,dword ptr [esp+10h] + \\ mov eax,dword ptr [esp+0Ch] + \\ xor edx,edx + \\ div ecx + \\ mov eax,dword ptr [esp+8] + \\ div ecx + \\ mov eax,edx + \\ xor edx,edx + \\ jmp L2a + \\ L1a: + \\ mov ecx,eax + \\ mov ebx,dword ptr [esp+10h] + \\ mov edx,dword ptr [esp+0Ch] + \\ mov eax,dword ptr [esp+8] + \\ L3a: + \\ shr ecx,1 + \\ rcr ebx,1 + \\ shr edx,1 + \\ rcr eax,1 + \\ or ecx,ecx + \\ jne L3a + \\ div ebx + \\ mov ecx,eax + \\ mul dword ptr [esp+14h] + \\ xchg eax,ecx + \\ mul dword ptr [esp+10h] + \\ add edx,ecx + \\ jb L4a + \\ cmp edx,dword ptr [esp+0Ch] + \\ ja L4a + \\ jb L5a + \\ cmp eax,dword ptr [esp+8] + \\ jbe L5a + \\ L4a: + \\ sub eax,dword ptr [esp+10h] + \\ sbb edx,dword ptr [esp+14h] + \\ L5a: + \\ sub eax,dword ptr [esp+8] + \\ sbb edx,dword ptr [esp+0Ch] + \\ neg edx + \\ neg eax + \\ sbb edx,0 + \\ L2a: + \\ pop ebx + \\ ret 10h + ); } diff --git a/std/special/compiler_rt/comparetf2.zig b/std/special/compiler_rt/comparetf2.zig index f1552ca61f..0834072672 100644 --- a/std/special/compiler_rt/comparetf2.zig +++ b/std/special/compiler_rt/comparetf2.zig @@ -20,11 +20,9 @@ const infRep = exponentMask; const builtin = @import("builtin"); const is_test = builtin.is_test; -const linkage = @import("index.zig").linkage; -export fn __letf2(a: f128, b: f128) -> c_int { +pub extern fn __letf2(a: f128, b: f128) -> c_int { @setDebugSafety(this, is_test); - @setGlobalLinkage(__letf2, linkage); const aInt = @bitCast(rep_t, a); const bInt = @bitCast(rep_t, b); @@ -63,14 +61,6 @@ export fn __letf2(a: f128, b: f128) -> c_int { }; } -// Alias for libgcc compatibility -// TODO https://github.com/zig-lang/zig/issues/420 -export fn __cmptf2(a: f128, b: f128) -> c_int { - @setGlobalLinkage(__cmptf2, linkage); - @setDebugSafety(this, is_test); - return __letf2(a, b); -} - // TODO https://github.com/zig-lang/zig/issues/305 // and then make the return types of some of these functions the enum instead of c_int const GE_LESS = c_int(-1); @@ -78,8 +68,7 @@ const GE_EQUAL = c_int(0); const GE_GREATER = c_int(1); const GE_UNORDERED = c_int(-1); // Note: different from LE_UNORDERED -export fn __getf2(a: f128, b: f128) -> c_int { - @setGlobalLinkage(__getf2, linkage); +pub extern fn __getf2(a: f128, b: f128) -> c_int { @setDebugSafety(this, is_test); const aInt = @bitCast(srep_t, a); @@ -108,38 +97,10 @@ export fn __getf2(a: f128, b: f128) -> c_int { }; } -export fn __unordtf2(a: f128, b: f128) -> c_int { - @setGlobalLinkage(__unordtf2, linkage); +pub extern fn __unordtf2(a: f128, b: f128) -> c_int { @setDebugSafety(this, is_test); const aAbs = @bitCast(rep_t, a) & absMask; const bAbs = @bitCast(rep_t, b) & absMask; return c_int(aAbs > infRep or bAbs > infRep); } - -// The following are alternative names for the preceding routines. -// TODO use aliases https://github.com/zig-lang/zig/issues/462 - -export fn __eqtf2(a: f128, b: f128) -> c_int { - @setGlobalLinkage(__eqtf2, linkage); - @setDebugSafety(this, is_test); - return __letf2(a, b); -} - -export fn __lttf2(a: f128, b: f128) -> c_int { - @setGlobalLinkage(__lttf2, linkage); - @setDebugSafety(this, is_test); - return __letf2(a, b); -} - -export fn __netf2(a: f128, b: f128) -> c_int { - @setGlobalLinkage(__netf2, linkage); - @setDebugSafety(this, is_test); - return __letf2(a, b); -} - -export fn __gttf2(a: f128, b: f128) -> c_int { - @setGlobalLinkage(__gttf2, linkage); - @setDebugSafety(this, is_test); - return __getf2(a, b); -} diff --git a/std/special/compiler_rt/fixunsdfdi.zig b/std/special/compiler_rt/fixunsdfdi.zig index 5f730bbc85..7e33987997 100644 --- a/std/special/compiler_rt/fixunsdfdi.zig +++ b/std/special/compiler_rt/fixunsdfdi.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunsdfdi(a: f64) -> u64 { +pub extern fn __fixunsdfdi(a: f64) -> u64 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunsdfdi, linkage); return fixuint(f64, u64, a); } diff --git a/std/special/compiler_rt/fixunsdfsi.zig b/std/special/compiler_rt/fixunsdfsi.zig index 784d5fde4f..e710e1852b 100644 --- a/std/special/compiler_rt/fixunsdfsi.zig +++ b/std/special/compiler_rt/fixunsdfsi.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunsdfsi(a: f64) -> u32 { +pub extern fn __fixunsdfsi(a: f64) -> u32 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunsdfsi, linkage); return fixuint(f64, u32, a); } diff --git a/std/special/compiler_rt/fixunsdfti.zig b/std/special/compiler_rt/fixunsdfti.zig index 579455c2f9..79d924f0a8 100644 --- a/std/special/compiler_rt/fixunsdfti.zig +++ b/std/special/compiler_rt/fixunsdfti.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunsdfti(a: f64) -> u128 { +pub extern fn __fixunsdfti(a: f64) -> u128 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunsdfti, linkage); return fixuint(f64, u128, a); } diff --git a/std/special/compiler_rt/fixunssfdi.zig b/std/special/compiler_rt/fixunssfdi.zig index eab553d8c9..f72f62d68c 100644 --- a/std/special/compiler_rt/fixunssfdi.zig +++ b/std/special/compiler_rt/fixunssfdi.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunssfdi(a: f32) -> u64 { +pub extern fn __fixunssfdi(a: f32) -> u64 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunssfdi, linkage); return fixuint(f32, u64, a); } diff --git a/std/special/compiler_rt/fixunssfsi.zig b/std/special/compiler_rt/fixunssfsi.zig index 18c0e66677..4c9a5001ab 100644 --- a/std/special/compiler_rt/fixunssfsi.zig +++ b/std/special/compiler_rt/fixunssfsi.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunssfsi(a: f32) -> u32 { +pub extern fn __fixunssfsi(a: f32) -> u32 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunssfsi, linkage); return fixuint(f32, u32, a); } diff --git a/std/special/compiler_rt/fixunssfti.zig b/std/special/compiler_rt/fixunssfti.zig index f513604247..59b94cfc51 100644 --- a/std/special/compiler_rt/fixunssfti.zig +++ b/std/special/compiler_rt/fixunssfti.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunssfti(a: f32) -> u128 { +pub extern fn __fixunssfti(a: f32) -> u128 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunssfti, linkage); return fixuint(f32, u128, a); } diff --git a/std/special/compiler_rt/fixunstfdi.zig b/std/special/compiler_rt/fixunstfdi.zig index 85212e2176..06b117a414 100644 --- a/std/special/compiler_rt/fixunstfdi.zig +++ b/std/special/compiler_rt/fixunstfdi.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunstfdi(a: f128) -> u64 { +pub extern fn __fixunstfdi(a: f128) -> u64 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunstfdi, linkage); return fixuint(f128, u64, a); } diff --git a/std/special/compiler_rt/fixunstfsi.zig b/std/special/compiler_rt/fixunstfsi.zig index 33c85c9224..8a5efe711d 100644 --- a/std/special/compiler_rt/fixunstfsi.zig +++ b/std/special/compiler_rt/fixunstfsi.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunstfsi(a: f128) -> u32 { +pub extern fn __fixunstfsi(a: f128) -> u32 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunstfsi, linkage); return fixuint(f128, u32, a); } diff --git a/std/special/compiler_rt/fixunstfti.zig b/std/special/compiler_rt/fixunstfti.zig index 1bf7fbab4b..d8b654d3a3 100644 --- a/std/special/compiler_rt/fixunstfti.zig +++ b/std/special/compiler_rt/fixunstfti.zig @@ -1,10 +1,8 @@ const fixuint = @import("fixuint.zig").fixuint; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __fixunstfti(a: f128) -> u128 { +pub extern fn __fixunstfti(a: f128) -> u128 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__fixunstfti, linkage); return fixuint(f128, u128, a); } diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index bea06a0b41..717c6934f5 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -1,34 +1,75 @@ -comptime { - _ = @import("comparetf2.zig"); - _ = @import("fixunsdfdi.zig"); - _ = @import("fixunsdfsi.zig"); - _ = @import("fixunsdfti.zig"); - _ = @import("fixunssfdi.zig"); - _ = @import("fixunssfsi.zig"); - _ = @import("fixunssfti.zig"); - _ = @import("fixunstfdi.zig"); - _ = @import("fixunstfsi.zig"); - _ = @import("fixunstfti.zig"); - _ = @import("udivmoddi4.zig"); - _ = @import("udivmodti4.zig"); - _ = @import("udivti3.zig"); - _ = @import("umodti3.zig"); - _ = @import("aulldiv.zig"); - _ = @import("aullrem.zig"); -} - const builtin = @import("builtin"); const is_test = builtin.is_test; + +comptime { + const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak; + const strong_linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong; + + @exportWithLinkage("__letf2", @import("comparetf2.zig").__letf2, linkage); + @exportWithLinkage("__getf2", @import("comparetf2.zig").__getf2, linkage); + + if (!is_test) { + // only create these aliases when not testing + @exportWithLinkage("__cmptf2", @import("comparetf2.zig").__letf2, linkage); + @exportWithLinkage("__eqtf2", @import("comparetf2.zig").__letf2, linkage); + @exportWithLinkage("__lttf2", @import("comparetf2.zig").__letf2, linkage); + @exportWithLinkage("__netf2", @import("comparetf2.zig").__letf2, linkage); + @exportWithLinkage("__gttf2", @import("comparetf2.zig").__getf2, linkage); + } + + @exportWithLinkage("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); + + @exportWithLinkage("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); + @exportWithLinkage("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); + @exportWithLinkage("__fixunssfti", @import("fixunssfti.zig").__fixunssfti, linkage); + + @exportWithLinkage("__fixunsdfsi", @import("fixunsdfsi.zig").__fixunsdfsi, linkage); + @exportWithLinkage("__fixunsdfdi", @import("fixunsdfdi.zig").__fixunsdfdi, linkage); + @exportWithLinkage("__fixunsdfti", @import("fixunsdfti.zig").__fixunsdfti, linkage); + + @exportWithLinkage("__fixunstfsi", @import("fixunstfsi.zig").__fixunstfsi, linkage); + @exportWithLinkage("__fixunstfdi", @import("fixunstfdi.zig").__fixunstfdi, linkage); + @exportWithLinkage("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage); + + @exportWithLinkage("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage); + @exportWithLinkage("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage); + + @exportWithLinkage("__udivti3", @import("udivti3.zig").__udivti3, linkage); + @exportWithLinkage("__umodti3", @import("umodti3.zig").__umodti3, linkage); + + @exportWithLinkage("__udivsi3", __udivsi3, linkage); + @exportWithLinkage("__udivdi3", __udivdi3, linkage); + @exportWithLinkage("__umoddi3", __umoddi3, linkage); + @exportWithLinkage("__udivmodsi4", __udivmodsi4, linkage); + + if (isArmArch()) { + @exportWithLinkage("__aeabi_uldivmod", __aeabi_uldivmod, linkage); + @exportWithLinkage("__aeabi_uidivmod", __aeabi_uidivmod, linkage); + @exportWithLinkage("__aeabi_uidiv", __udivsi3, linkage); + } + if (builtin.os == builtin.Os.windows) { + switch (builtin.arch) { + builtin.Arch.i386 => { + if (!builtin.link_libc) { + @exportWithLinkage("_chkstk", _chkstk, strong_linkage); + @exportWithLinkage("__chkstk_ms", __chkstk_ms, linkage); + } + @exportWithLinkage("_aulldiv", @import("aulldiv.zig")._aulldiv, strong_linkage); + @exportWithLinkage("_aullrem", @import("aullrem.zig")._aullrem, strong_linkage); + }, + builtin.Arch.x86_64 => { + if (!builtin.link_libc) { + @exportWithLinkage("__chkstk", __chkstk, strong_linkage); + @exportWithLinkage("___chkstk_ms", ___chkstk_ms, linkage); + } + }, + else => {}, + } + } +} + const assert = @import("../../debug.zig").assert; - -const win32 = builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.i386; -const win64 = builtin.os == builtin.Os.windows and builtin.arch == builtin.Arch.x86_64; -const win32_nocrt = win32 and !builtin.link_libc; -const win64_nocrt = win64 and !builtin.link_libc; -pub const linkage = if (builtin.is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak; -const strong_linkage = if (builtin.is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong; - const __udivmoddi4 = @import("udivmoddi4.zig").__udivmoddi4; // Avoid dragging in the debug safety mechanisms into this .o file, @@ -41,15 +82,13 @@ pub coldcc fn panic(msg: []const u8) -> noreturn { } } -export fn __udivdi3(a: u64, b: u64) -> u64 { +extern fn __udivdi3(a: u64, b: u64) -> u64 { @setDebugSafety(this, is_test); - @setGlobalLinkage(__udivdi3, linkage); return __udivmoddi4(a, b, null); } -export fn __umoddi3(a: u64, b: u64) -> u64 { +extern fn __umoddi3(a: u64, b: u64) -> u64 { @setDebugSafety(this, is_test); - @setGlobalLinkage(__umoddi3, linkage); var r: u64 = undefined; _ = __udivmoddi4(a, b, &r); @@ -60,17 +99,11 @@ const AeabiUlDivModResult = extern struct { quot: u64, rem: u64, }; -export fn __aeabi_uldivmod(numerator: u64, denominator: u64) -> AeabiUlDivModResult { +extern fn __aeabi_uldivmod(numerator: u64, denominator: u64) -> AeabiUlDivModResult { @setDebugSafety(this, is_test); - if (comptime isArmArch()) { - @setGlobalLinkage(__aeabi_uldivmod, linkage); - var result: AeabiUlDivModResult = undefined; - result.quot = __udivmoddi4(numerator, denominator, &result.rem); - return result; - } - - @setGlobalLinkage(__aeabi_uldivmod, builtin.GlobalLinkage.Internal); - unreachable; + var result: AeabiUlDivModResult = undefined; + result.quot = __udivmoddi4(numerator, denominator, &result.rem); + return result; } fn isArmArch() -> bool { @@ -98,156 +131,124 @@ fn isArmArch() -> bool { }; } -export nakedcc fn __aeabi_uidivmod() { +nakedcc fn __aeabi_uidivmod() { @setDebugSafety(this, false); - - if (comptime isArmArch()) { - @setGlobalLinkage(__aeabi_uidivmod, linkage); - asm volatile ( - \\ push { lr } - \\ sub sp, sp, #4 - \\ mov r2, sp - \\ bl __udivmodsi4 - \\ ldr r1, [sp] - \\ add sp, sp, #4 - \\ pop { pc } - ::: "r2", "r1"); - unreachable; - } - - @setGlobalLinkage(__aeabi_uidivmod, builtin.GlobalLinkage.Internal); + asm volatile ( + \\ push { lr } + \\ sub sp, sp, #4 + \\ mov r2, sp + \\ bl __udivmodsi4 + \\ ldr r1, [sp] + \\ add sp, sp, #4 + \\ pop { pc } + ::: "r2", "r1"); } // _chkstk (_alloca) routine - probe stack between %esp and (%esp-%eax) in 4k increments, // then decrement %esp by %eax. Preserves all registers except %esp and flags. // This routine is windows specific // http://msdn.microsoft.com/en-us/library/ms648426.aspx -export nakedcc fn _chkstk() align(4) { +nakedcc fn _chkstk() align(4) { @setDebugSafety(this, false); - if (win32_nocrt) { - @setGlobalLinkage(_chkstk, strong_linkage); - asm volatile ( - \\ push %%ecx - \\ push %%eax - \\ cmp $0x1000,%%eax - \\ lea 12(%%esp),%%ecx - \\ jb 1f - \\ 2: - \\ sub $0x1000,%%ecx - \\ test %%ecx,(%%ecx) - \\ sub $0x1000,%%eax - \\ cmp $0x1000,%%eax - \\ ja 2b - \\ 1: - \\ sub %%eax,%%ecx - \\ test %%ecx,(%%ecx) - \\ pop %%eax - \\ pop %%ecx - \\ ret - ); - unreachable; - } - - @setGlobalLinkage(_chkstk, builtin.GlobalLinkage.Internal); + asm volatile ( + \\ push %%ecx + \\ push %%eax + \\ cmp $0x1000,%%eax + \\ lea 12(%%esp),%%ecx + \\ jb 1f + \\ 2: + \\ sub $0x1000,%%ecx + \\ test %%ecx,(%%ecx) + \\ sub $0x1000,%%eax + \\ cmp $0x1000,%%eax + \\ ja 2b + \\ 1: + \\ sub %%eax,%%ecx + \\ test %%ecx,(%%ecx) + \\ pop %%eax + \\ pop %%ecx + \\ ret + ); } -export nakedcc fn __chkstk() align(4) { +nakedcc fn __chkstk() align(4) { @setDebugSafety(this, false); - if (win64_nocrt) { - @setGlobalLinkage(__chkstk, strong_linkage); - asm volatile ( - \\ push %%rcx - \\ push %%rax - \\ cmp $0x1000,%%rax - \\ lea 24(%%rsp),%%rcx - \\ jb 1f - \\2: - \\ sub $0x1000,%%rcx - \\ test %%rcx,(%%rcx) - \\ sub $0x1000,%%rax - \\ cmp $0x1000,%%rax - \\ ja 2b - \\1: - \\ sub %%rax,%%rcx - \\ test %%rcx,(%%rcx) - \\ pop %%rax - \\ pop %%rcx - \\ ret - ); - unreachable; - } - - @setGlobalLinkage(__chkstk, builtin.GlobalLinkage.Internal); + asm volatile ( + \\ push %%rcx + \\ push %%rax + \\ cmp $0x1000,%%rax + \\ lea 24(%%rsp),%%rcx + \\ jb 1f + \\2: + \\ sub $0x1000,%%rcx + \\ test %%rcx,(%%rcx) + \\ sub $0x1000,%%rax + \\ cmp $0x1000,%%rax + \\ ja 2b + \\1: + \\ sub %%rax,%%rcx + \\ test %%rcx,(%%rcx) + \\ pop %%rax + \\ pop %%rcx + \\ ret + ); } // _chkstk routine // This routine is windows specific // http://msdn.microsoft.com/en-us/library/ms648426.aspx -export nakedcc fn __chkstk_ms() align(4) { +nakedcc fn __chkstk_ms() align(4) { @setDebugSafety(this, false); - if (win32_nocrt) { - @setGlobalLinkage(__chkstk_ms, linkage); - asm volatile ( - \\ push %%ecx - \\ push %%eax - \\ cmp $0x1000,%%eax - \\ lea 12(%%esp),%%ecx - \\ jb 1f - \\ 2: - \\ sub $0x1000,%%ecx - \\ test %%ecx,(%%ecx) - \\ sub $0x1000,%%eax - \\ cmp $0x1000,%%eax - \\ ja 2b - \\ 1: - \\ sub %%eax,%%ecx - \\ test %%ecx,(%%ecx) - \\ pop %%eax - \\ pop %%ecx - \\ ret - ); - unreachable; - } - - @setGlobalLinkage(__chkstk_ms, builtin.GlobalLinkage.Internal); + asm volatile ( + \\ push %%ecx + \\ push %%eax + \\ cmp $0x1000,%%eax + \\ lea 12(%%esp),%%ecx + \\ jb 1f + \\ 2: + \\ sub $0x1000,%%ecx + \\ test %%ecx,(%%ecx) + \\ sub $0x1000,%%eax + \\ cmp $0x1000,%%eax + \\ ja 2b + \\ 1: + \\ sub %%eax,%%ecx + \\ test %%ecx,(%%ecx) + \\ pop %%eax + \\ pop %%ecx + \\ ret + ); } -export nakedcc fn ___chkstk_ms() align(4) { +nakedcc fn ___chkstk_ms() align(4) { @setDebugSafety(this, false); - if (win64_nocrt) { - @setGlobalLinkage(___chkstk_ms, linkage); - asm volatile ( - \\ push %%rcx - \\ push %%rax - \\ cmp $0x1000,%%rax - \\ lea 24(%%rsp),%%rcx - \\ jb 1f - \\2: - \\ sub $0x1000,%%rcx - \\ test %%rcx,(%%rcx) - \\ sub $0x1000,%%rax - \\ cmp $0x1000,%%rax - \\ ja 2b - \\1: - \\ sub %%rax,%%rcx - \\ test %%rcx,(%%rcx) - \\ pop %%rax - \\ pop %%rcx - \\ ret - ); - unreachable; - } - - @setGlobalLinkage(___chkstk_ms, builtin.GlobalLinkage.Internal); + asm volatile ( + \\ push %%rcx + \\ push %%rax + \\ cmp $0x1000,%%rax + \\ lea 24(%%rsp),%%rcx + \\ jb 1f + \\2: + \\ sub $0x1000,%%rcx + \\ test %%rcx,(%%rcx) + \\ sub $0x1000,%%rax + \\ cmp $0x1000,%%rax + \\ ja 2b + \\1: + \\ sub %%rax,%%rcx + \\ test %%rcx,(%%rcx) + \\ pop %%rax + \\ pop %%rcx + \\ ret + ); } -export fn __udivmodsi4(a: u32, b: u32, rem: &u32) -> u32 { +extern fn __udivmodsi4(a: u32, b: u32, rem: &u32) -> u32 { @setDebugSafety(this, is_test); - @setGlobalLinkage(__udivmodsi4, linkage); const d = __udivsi3(a, b); *rem = u32(i32(a) -% (i32(d) * i32(b))); @@ -255,19 +256,8 @@ export fn __udivmodsi4(a: u32, b: u32, rem: &u32) -> u32 { } -// TODO make this an alias instead of an extra function call -// https://github.com/andrewrk/zig/issues/256 - -export fn __aeabi_uidiv(n: u32, d: u32) -> u32 { +extern fn __udivsi3(n: u32, d: u32) -> u32 { @setDebugSafety(this, is_test); - @setGlobalLinkage(__aeabi_uidiv, linkage); - - return __udivsi3(n, d); -} - -export fn __udivsi3(n: u32, d: u32) -> u32 { - @setDebugSafety(this, is_test); - @setGlobalLinkage(__udivsi3, linkage); const n_uword_bits: c_uint = u32.bit_count; // special cases @@ -463,4 +453,3 @@ fn test_one_udivsi3(a: u32, b: u32, expected_q: u32) { const q: u32 = __udivsi3(a, b); assert(q == expected_q); } - diff --git a/std/special/compiler_rt/udivmoddi4.zig b/std/special/compiler_rt/udivmoddi4.zig index 8005538d9a..4e2117cfa5 100644 --- a/std/special/compiler_rt/udivmoddi4.zig +++ b/std/special/compiler_rt/udivmoddi4.zig @@ -1,10 +1,8 @@ const udivmod = @import("udivmod.zig").udivmod; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __udivmoddi4(a: u64, b: u64, maybe_rem: ?&u64) -> u64 { +pub extern fn __udivmoddi4(a: u64, b: u64, maybe_rem: ?&u64) -> u64 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__udivmoddi4, linkage); return udivmod(u64, a, b, maybe_rem); } diff --git a/std/special/compiler_rt/udivmodti4.zig b/std/special/compiler_rt/udivmodti4.zig index 2ee2fdb57f..c56a958f27 100644 --- a/std/special/compiler_rt/udivmodti4.zig +++ b/std/special/compiler_rt/udivmodti4.zig @@ -1,10 +1,8 @@ const udivmod = @import("udivmod.zig").udivmod; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __udivmodti4(a: u128, b: u128, maybe_rem: ?&u128) -> u128 { +pub extern fn __udivmodti4(a: u128, b: u128, maybe_rem: ?&u128) -> u128 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__udivmodti4, linkage); return udivmod(u128, a, b, maybe_rem); } diff --git a/std/special/compiler_rt/udivti3.zig b/std/special/compiler_rt/udivti3.zig index 3764449758..115c748cfb 100644 --- a/std/special/compiler_rt/udivti3.zig +++ b/std/special/compiler_rt/udivti3.zig @@ -1,9 +1,7 @@ const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __udivti3(a: u128, b: u128) -> u128 { +pub extern fn __udivti3(a: u128, b: u128) -> u128 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__udivti3, linkage); return __udivmodti4(a, b, null); } diff --git a/std/special/compiler_rt/umodti3.zig b/std/special/compiler_rt/umodti3.zig index 0ad9e127b3..9f680369eb 100644 --- a/std/special/compiler_rt/umodti3.zig +++ b/std/special/compiler_rt/umodti3.zig @@ -1,10 +1,8 @@ const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4; const builtin = @import("builtin"); -const linkage = @import("index.zig").linkage; -export fn __umodti3(a: u128, b: u128) -> u128 { +pub extern fn __umodti3(a: u128, b: u128) -> u128 { @setDebugSafety(this, builtin.is_test); - @setGlobalLinkage(__umodti3, linkage); var r: u128 = undefined; _ = __udivmodti4(a, b, &r); return r; diff --git a/test/cases/asm.zig b/test/cases/asm.zig index 8a3020fe23..d7b88bd69e 100644 --- a/test/cases/asm.zig +++ b/test/cases/asm.zig @@ -2,23 +2,24 @@ const config = @import("builtin"); const assert = @import("std").debug.assert; comptime { + @export("derp", derp); if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { asm volatile ( - \\.globl aoeu; - \\.type aoeu, @function; - \\.set aoeu, derp; + \\.globl my_aoeu_symbol_asdf; + \\.type my_aoeu_symbol_asdf, @function; + \\.set my_aoeu_symbol_asdf, derp; ); } } test "module level assembly" { if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { - assert(aoeu() == 1234); + assert(my_aoeu_symbol_asdf() == 1234); } } -extern fn aoeu() -> i32; +extern fn my_aoeu_symbol_asdf() -> i32; -export fn derp() -> i32 { +extern fn derp() -> i32 { return 1234; } diff --git a/test/cases/misc.zig b/test/cases/misc.zig index e5e6575fab..1511c84b0c 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -12,8 +12,10 @@ test "empty function with comments" { emptyFunctionWithComments(); } -export fn disabledExternFn() { - @setGlobalLinkage(disabledExternFn, builtin.GlobalLinkage.Internal); +comptime { + @exportWithLinkage("disabledExternFn", disabledExternFn, builtin.GlobalLinkage.Internal) +} +extern fn disabledExternFn() { } test "call disabled extern fn" { @@ -533,7 +535,10 @@ var global_ptr = &gdt[0]; // can't really run this test but we can make sure it has no compile error // and generates code const vram = @intToPtr(&volatile u8, 0x20000000)[0..0x8000]; -export fn writeToVRam() { +comptime { + @export("writeToVRam", writeToVRam); +} +extern fn writeToVRam() { vram[0] = 'X'; } @@ -557,3 +562,15 @@ fn hereIsAnOpaqueType(ptr: &OpaqueA) -> &OpaqueA { var a = ptr; return a; } + +test "function and variable in weird section" { + if (builtin.os == builtin.Os.linux or builtin.os == builtin.Os.windows) { + // macos can't handle this + assert(fnInWeirdSection() == 1234); + } +} + +var varInWeirdSection: i32 section(".data2") = 1234; +fn fnInWeirdSection() section(".text2") -> i32 { + return varInWeirdSection; +} diff --git a/test/compare_output.zig b/test/compare_output.zig index ad9c91ff20..4829a39fb6 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -4,7 +4,8 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompareOutputContext) { cases.addC("hello world with libc", \\const c = @cImport(@cInclude("stdio.h")); - \\export fn main(argc: c_int, argv: &&u8) -> c_int { + \\comptime { @export("main", main); } + \\extern fn main(argc: c_int, argv: &&u8) -> c_int { \\ _ = c.puts(c"Hello, world!"); \\ return 0; \\} @@ -137,7 +138,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ @cInclude("stdio.h"); \\}); \\ - \\export fn main(argc: c_int, argv: &&u8) -> c_int { + \\comptime { @export("main", main); } + \\extern fn main(argc: c_int, argv: &&u8) -> c_int { \\ if (is_windows) { \\ // we want actual \n, not \r\n \\ _ = c._setmode(1, c._O_BINARY); @@ -282,7 +284,10 @@ pub fn addCases(cases: &tests.CompareOutputContext) { cases.addC("expose function pointer to C land", \\const c = @cImport(@cInclude("stdlib.h")); \\ - \\export fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int { + \\comptime { + \\ @export("main", main); + \\} + \\extern fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int { \\ const a_int = @ptrCast(&align(1) i32, a ?? unreachable); \\ const b_int = @ptrCast(&align(1) i32, b ?? unreachable); \\ if (*a_int < *b_int) { @@ -294,7 +299,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ } \\} \\ - \\export fn main() -> c_int { + \\extern fn main() -> c_int { \\ var array = []u32 { 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 }; \\ \\ c.qsort(@ptrCast(&c_void, &array[0]), c_ulong(array.len), @sizeOf(i32), compare_fn); @@ -322,7 +327,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ @cInclude("stdio.h"); \\}); \\ - \\export fn main(argc: c_int, argv: &&u8) -> c_int { + \\comptime { @export("main", main); } + \\extern fn main(argc: c_int, argv: &&u8) -> c_int { \\ if (is_windows) { \\ // we want actual \n, not \r\n \\ _ = c._setmode(1, c._O_BINARY); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 8dbb8171c2..8aa57c4468 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,253 +1,328 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) { + cases.add("wrong return type for main", + \\pub fn main() { } + , ".tmp_source.zig:1:15: error: expected return type of main to be '%void', instead is 'void'"); + + cases.add("double ?? on main return value", + \\pub fn main() -> ??void { + \\} + , ".tmp_source.zig:1:18: error: expected return type of main to be '%void', instead is '??void'"); + + cases.add("setting a section on an extern variable", + \\extern var foo: i32 section(".text2"); + \\extern fn entry() -> i32 { + \\ return foo; + \\} + \\comptime { @export("entry", entry); } + , + ".tmp_source.zig:1:29: error: cannot set section of external variable 'foo'"); + + cases.add("setting a section on a local variable", + \\extern fn entry() -> i32 { + \\ var foo: i32 section(".text2") = 1234; + \\ return foo; + \\} + \\comptime { @export("entry", entry); } + , + ".tmp_source.zig:2:26: error: cannot set section of local variable 'foo'"); + + cases.add("setting a section on an extern fn", + \\extern fn foo() section(".text2"); + \\extern fn entry() { + \\ foo(); + \\} + \\comptime { @export("entry", entry); } + , + ".tmp_source.zig:1:25: error: cannot set section of external function 'foo'"); + + cases.add("wrong types given to exportWithLinkage", + \\extern fn entry() { } + \\comptime { + \\ @exportWithLinkage("entry", entry, u32(1234)); + \\} + , + ".tmp_source.zig:3:43: error: expected type 'GlobalLinkage', found 'u32'"); + cases.add("implicit semicolon - block statement", - \\export fn entry() { + \\extern fn entry() { \\ {} \\ var good = {}; \\ ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - block expr", - \\export fn entry() { + \\extern fn entry() { \\ _ = {}; \\ var good = {}; \\ _ = {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - comptime statement", - \\export fn entry() { + \\extern fn entry() { \\ comptime {} \\ var good = {}; \\ comptime ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - comptime expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = comptime {}; \\ var good = {}; \\ _ = comptime {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - defer", - \\export fn entry() { + \\extern fn entry() { \\ defer {} \\ var good = {}; \\ defer ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: expected token ';', found 'var'"); cases.add("implicit semicolon - if statement", - \\export fn entry() { + \\extern fn entry() { \\ if(true) {} \\ var good = {}; \\ if(true) ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = if(true) {}; \\ var good = {}; \\ _ = if(true) {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else statement", - \\export fn entry() { + \\extern fn entry() { \\ if(true) {} else {} \\ var good = {}; \\ if(true) ({}) else ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = if(true) {} else {}; \\ var good = {}; \\ _ = if(true) {} else {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if statement", - \\export fn entry() { + \\extern fn entry() { \\ if(true) {} else if(true) {} \\ var good = {}; \\ if(true) ({}) else if(true) ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = if(true) {} else if(true) {}; \\ var good = {}; \\ _ = if(true) {} else if(true) {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if-else statement", - \\export fn entry() { + \\extern fn entry() { \\ if(true) {} else if(true) {} else {} \\ var good = {}; \\ if(true) ({}) else if(true) ({}) else ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if-else expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = if(true) {} else if(true) {} else {}; \\ var good = {}; \\ _ = if(true) {} else if(true) {} else {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - test statement", - \\export fn entry() { + \\extern fn entry() { \\ if (foo()) |_| {} \\ var good = {}; \\ if (foo()) |_| ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - test expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = if (foo()) |_| {}; \\ var good = {}; \\ _ = if (foo()) |_| {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while statement", - \\export fn entry() { + \\extern fn entry() { \\ while(true) {} \\ var good = {}; \\ while(true) ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = while(true) {}; \\ var good = {}; \\ _ = while(true) {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while-continue statement", - \\export fn entry() { + \\extern fn entry() { \\ while(true):({}) {} \\ var good = {}; \\ while(true):({}) ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while-continue expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = while(true):({}) {}; \\ var good = {}; \\ _ = while(true):({}) {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - for statement", - \\export fn entry() { + \\extern fn entry() { \\ for(foo()) {} \\ var good = {}; \\ for(foo()) ({}) \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - for expression", - \\export fn entry() { + \\extern fn entry() { \\ _ = for(foo()) {}; \\ var good = {}; \\ _ = for(foo()) {} \\ var bad = {}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("multiple function definitions", \\fn a() {} \\fn a() {} - \\export fn entry() { a(); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { a(); } , ".tmp_source.zig:2:1: error: redefinition of 'a'"); cases.add("unreachable with return", \\fn a() -> noreturn {return;} - \\export fn entry() { a(); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { a(); } , ".tmp_source.zig:1:21: error: expected type 'noreturn', found 'void'"); cases.add("control reaches end of non-void function", \\fn a() -> i32 {} - \\export fn entry() { _ = a(); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { _ = a(); } , ".tmp_source.zig:1:15: error: expected type 'i32', found 'void'"); cases.add("undefined function call", - \\export fn a() { + \\extern fn a() { \\ b(); \\} + \\comptime {@export("a", a);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'"); cases.add("wrong number of arguments", - \\export fn a() { + \\extern fn a() { \\ b(1); \\} \\fn b(a: i32, b: i32, c: i32) { } + \\comptime {@export("a", a);} , ".tmp_source.zig:2:6: error: expected 3 arguments, found 1"); cases.add("invalid type", \\fn a() -> bogus {} - \\export fn entry() { _ = a(); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { _ = a(); } , ".tmp_source.zig:1:11: error: use of undeclared identifier 'bogus'"); cases.add("pointer to unreachable", \\fn a() -> &noreturn {} - \\export fn entry() { _ = a(); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { _ = a(); } , ".tmp_source.zig:1:12: error: pointer to unreachable not allowed"); cases.add("unreachable code", - \\export fn a() { + \\extern fn a() { \\ return; \\ b(); \\} \\ \\fn b() {} + \\comptime {@export("a", a);} , ".tmp_source.zig:3:5: error: unreachable code"); cases.add("bad import", \\const bogus = @import("bogus-does-not-exist.zig"); - \\export fn entry() { bogus.bogo(); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { bogus.bogo(); } , ".tmp_source.zig:1:15: error: unable to find 'bogus-does-not-exist.zig'"); cases.add("undeclared identifier", - \\export fn a() { + \\extern fn a() { \\ b + \\ c \\} + \\comptime {@export("a", a);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'", ".tmp_source.zig:3:5: error: use of undeclared identifier 'c'"); @@ -255,99 +330,114 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("parameter redeclaration", \\fn f(a : i32, a : i32) { \\} - \\export fn entry() { f(1, 2); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { f(1, 2); } , ".tmp_source.zig:1:15: error: redeclaration of variable 'a'"); cases.add("local variable redeclaration", - \\export fn f() { + \\extern fn f() { \\ const a : i32 = 0; \\ const a = 0; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:3:5: error: redeclaration of variable 'a'"); cases.add("local variable redeclares parameter", \\fn f(a : i32) { \\ const a = 0; \\} - \\export fn entry() { f(1); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { f(1); } , ".tmp_source.zig:2:5: error: redeclaration of variable 'a'"); cases.add("variable has wrong type", - \\export fn f() -> i32 { + \\extern fn f() -> i32 { \\ const a = c"a"; \\ a \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:3:5: error: expected type 'i32', found '&const u8'"); cases.add("if condition is bool, not int", - \\export fn f() { + \\extern fn f() { \\ if (0) {} \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:9: error: integer value 0 cannot be implicitly casted to type 'bool'"); cases.add("assign unreachable", - \\export fn f() { + \\extern fn f() { \\ const a = return; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: unreachable code"); cases.add("unreachable variable", - \\export fn f() { + \\extern fn f() { \\ const a: noreturn = {}; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:14: error: variable of type 'noreturn' not allowed"); cases.add("unreachable parameter", \\fn f(a: noreturn) {} - \\export fn entry() { f(); } + \\comptime {@export("entry", entry);} + \\extern fn entry() { f(); } , ".tmp_source.zig:1:9: error: parameter of type 'noreturn' not allowed"); cases.add("bad assignment target", - \\export fn f() { + \\extern fn f() { \\ 3 = 3; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:7: error: cannot assign to constant"); cases.add("assign to constant variable", - \\export fn f() { + \\extern fn f() { \\ const a = 3; \\ a = 4; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:3:7: error: cannot assign to constant"); cases.add("use of undeclared identifier", - \\export fn f() { + \\extern fn f() { \\ b = 3; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'"); cases.add("const is a statement, not an expression", - \\export fn f() { + \\extern fn f() { \\ (const a = 0); \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:6: error: invalid token: 'const'"); cases.add("array access of undeclared identifier", - \\export fn f() { + \\extern fn f() { \\ i[i] = i[i]; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'i'", ".tmp_source.zig:2:12: error: use of undeclared identifier 'i'"); cases.add("array access of non array", - \\export fn f() { + \\extern fn f() { \\ var bad : bool = undefined; \\ bad[bad] = bad[bad]; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:3:8: error: array access of non-array type 'bool'", ".tmp_source.zig:3:19: error: array access of non-array type 'bool'"); cases.add("array access with non integer index", - \\export fn f() { + \\extern fn f() { \\ var array = "aoeu"; \\ var bad = false; \\ array[bad] = array[bad]; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:4:11: error: expected type 'usize', found 'bool'", ".tmp_source.zig:4:24: error: expected type 'usize', found 'bool'"); @@ -356,7 +446,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn f() { \\ x = 1; \\} - \\export fn entry() { f(); } + \\extern fn entry() { f(); } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:7: error: cannot assign to constant"); @@ -365,29 +456,33 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ const x : i32 = if (b) { 1 }; \\ const y = if (b) { i32(1) }; \\} - \\export fn entry() { f(true); } + \\extern fn entry() { f(true); } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:30: error: integer value 1 cannot be implicitly casted to type 'void'", ".tmp_source.zig:3:15: error: incompatible types: 'i32' and 'void'"); cases.add("direct struct loop", \\const A = struct { a : A, }; - \\export fn entry() -> usize { @sizeOf(A) } + \\extern fn entry() -> usize { @sizeOf(A) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:11: error: struct 'A' contains itself"); cases.add("indirect struct loop", \\const A = struct { b : B, }; \\const B = struct { c : C, }; \\const C = struct { a : A, }; - \\export fn entry() -> usize { @sizeOf(A) } + \\extern fn entry() -> usize { @sizeOf(A) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:11: error: struct 'A' contains itself"); cases.add("invalid struct field", \\const A = struct { x : i32, }; - \\export fn f() { + \\extern fn f() { \\ var a : A = undefined; \\ a.foo = 1; \\ const y = a.bar; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:4:6: error: no member named 'foo' in struct 'A'", ".tmp_source.zig:5:16: error: no member named 'bar' in struct 'A'"); @@ -415,7 +510,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ y : i32, \\ z : i32, \\}; - \\export fn f() { + \\extern fn f() { \\ const a = A { \\ .z = 1, \\ .y = 2, @@ -423,6 +518,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ .z = 4, \\ }; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:11:9: error: duplicate field"); cases.add("missing field in struct value expression", @@ -431,7 +527,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ y : i32, \\ z : i32, \\}; - \\export fn f() { + \\extern fn f() { \\ // we want the error on the '{' not the 'A' because \\ // the A could be a complicated expression \\ const a = A { @@ -439,6 +535,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ .y = 2, \\ }; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:9:17: error: missing field: 'x'"); cases.add("invalid field in struct value expression", @@ -447,69 +544,79 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ y : i32, \\ z : i32, \\}; - \\export fn f() { + \\extern fn f() { \\ const a = A { \\ .z = 4, \\ .y = 2, \\ .foo = 42, \\ }; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:10:9: error: no member named 'foo' in struct 'A'"); cases.add("invalid break expression", - \\export fn f() { + \\extern fn f() { \\ break; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: break expression outside loop"); cases.add("invalid continue expression", - \\export fn f() { + \\extern fn f() { \\ continue; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: continue expression outside loop"); cases.add("invalid maybe type", - \\export fn f() { + \\extern fn f() { \\ if (true) |x| { } \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:9: error: expected nullable type, found 'bool'"); cases.add("cast unreachable", \\fn f() -> i32 { \\ i32(return 1) \\} - \\export fn entry() { _ = f(); } + \\extern fn entry() { _ = f(); } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:8: error: unreachable code"); cases.add("invalid builtin fn", \\fn f() -> @bogus(foo) { \\} - \\export fn entry() { _ = f(); } + \\extern fn entry() { _ = f(); } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:11: error: invalid builtin function: 'bogus'"); cases.add("top level decl dependency loop", \\const a : @typeOf(b) = 0; \\const b : @typeOf(a) = 0; - \\export fn entry() { + \\extern fn entry() { \\ const c = a + b; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:1: error: 'a' depends on itself"); cases.add("noalias on non pointer param", \\fn f(noalias x: i32) {} - \\export fn entry() { f(1234); } + \\extern fn entry() { f(1234); } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:6: error: noalias on non-pointer parameter"); cases.add("struct init syntax for array", \\const foo = []u16{.x = 1024,}; - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:18: error: type '[]u16' does not support struct initialization syntax"); cases.add("type variables must be constant", \\var foo = u8; - \\export fn entry() -> foo { + \\extern fn entry() -> foo { \\ return 1; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:1: error: variable of type 'type' must be constant"); @@ -521,9 +628,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ var Bar : i32 = undefined; \\} \\ - \\export fn entry() { + \\extern fn entry() { \\ f(1234); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:6: error: redefinition of 'Foo'", ".tmp_source.zig:1:1: note: previous definition is here", @@ -545,7 +653,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch"); cases.add("switch expression - duplicate enumeration prong", @@ -565,7 +674,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:13:15: error: duplicate switch value", ".tmp_source.zig:10:15: note: other value is here"); @@ -587,7 +697,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:13:15: error: duplicate switch value", ".tmp_source.zig:10:15: note: other value is here"); @@ -599,9 +710,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ else => true, \\ }; \\} - \\export fn entry() { + \\extern fn entry() { \\ f(1234); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:9: error: multiple else prongs in switch expression"); cases.add("switch expression - non exhaustive integer prongs", @@ -610,7 +722,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ 0 => {}, \\ } \\} - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: switch must handle all possibilities"); @@ -623,7 +736,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ 206 ... 255 => 3, \\ } \\} - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:9: error: duplicate switch value", ".tmp_source.zig:5:14: note: previous value is here"); @@ -635,14 +749,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\const y: u8 = 100; - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: else prong required when switching on type '&u8'"); cases.add("global variable initializer must be constant expression", \\extern fn foo() -> i32; \\const x = foo(); - \\export fn entry() -> i32 { x } + \\extern fn entry() -> i32 { x } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:11: error: unable to evaluate constant expression"); cases.add("array concatenation with wrong type", @@ -650,7 +766,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const derp = usize(1234); \\const a = derp ++ "foo"; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:11: error: expected array or C string literal, found 'usize'"); cases.add("non compile time array concatenation", @@ -658,12 +775,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ s ++ "foo" \\} \\var s: [10]u8 = undefined; - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: unable to evaluate constant expression"); cases.add("@cImport with bogus include", \\const c = @cImport(@cInclude("bogus.h")); - \\export fn entry() -> usize { @sizeOf(@typeOf(c.bogo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(c.bogo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:11: error: C import failed", ".h:1:10: note: 'bogus.h' file not found"); @@ -671,17 +790,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const x = 3; \\const y = &x; \\fn foo() -> &const i32 { y } - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:26: error: expected type '&const i32', found '&const (integer literal)'"); cases.add("integer overflow error", \\const x : u8 = 300; - \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:16: error: integer value 300 cannot be implicitly casted to type 'u8'"); cases.add("incompatible number literals", \\const x = 2 == 2.0; - \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:11: error: integer value 2 cannot be implicitly casted to type '(float literal)'"); cases.add("missing function call param", @@ -707,13 +829,15 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ const result = members[index](); \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:20:34: error: expected 1 arguments, found 0"); cases.add("missing function name and param name", \\fn () {} \\fn f(i32) {} - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:1: error: missing function name", ".tmp_source.zig:2:6: error: missing parameter name"); @@ -723,16 +847,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn a() -> i32 {0} \\fn b() -> i32 {1} \\fn c() -> i32 {2} - \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(fns)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:21: error: expected type 'fn()', found 'fn() -> i32'"); cases.add("extern function pointer mismatch", \\const fns = [](fn(i32)->i32){ a, b, c }; \\pub fn a(x: i32) -> i32 {x + 0} \\pub fn b(x: i32) -> i32 {x + 1} - \\export fn c(x: i32) -> i32 {x + 2} + \\extern fn c(x: i32) -> i32 {x + 2} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(fns)) } + \\ + \\comptime {@export("entry", entry);} + \\comptime {@export("c", c);} , ".tmp_source.zig:1:37: error: expected type 'fn(i32) -> i32', found 'extern fn(i32) -> i32'"); @@ -740,14 +868,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const x : f64 = 1.0; \\const y : f32 = x; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:17: error: expected type 'f32', found 'f64'"); cases.add("colliding invalid top level functions", \\fn func() -> bogus {} \\fn func() -> bogus {} - \\export fn entry() -> usize { @sizeOf(@typeOf(func)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(func)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:1: error: redefinition of 'func'", ".tmp_source.zig:1:14: error: use of undeclared identifier 'bogus'"); @@ -755,7 +885,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("bogus compile var", \\const x = @import("builtin").bogus; - \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:29: error: no member named 'bogus' in '"); @@ -766,7 +897,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\var global_var: usize = 1; \\fn get() -> usize { global_var } \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(Foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(Foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:21: error: unable to evaluate constant expression", ".tmp_source.zig:2:12: note: called from here", @@ -779,7 +911,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\}; \\const x = Foo {.field = 1} + Foo {.field = 2}; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:28: error: invalid operands to binary expression: 'Foo' and 'Foo'"); @@ -789,10 +922,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const int_x = u32(1) / u32(0); \\const float_x = f32(1.0) / f32(0.0); \\ - \\export fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) } - \\export fn entry2() -> usize { @sizeOf(@typeOf(lit_float_x)) } - \\export fn entry3() -> usize { @sizeOf(@typeOf(int_x)) } - \\export fn entry4() -> usize { @sizeOf(@typeOf(float_x)) } + \\extern fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) } + \\extern fn entry2() -> usize { @sizeOf(@typeOf(lit_float_x)) } + \\extern fn entry3() -> usize { @sizeOf(@typeOf(int_x)) } + \\extern fn entry4() -> usize { @sizeOf(@typeOf(float_x)) } + \\comptime {@export("entry1", entry1);} + \\comptime {@export("entry2", entry2);} + \\comptime {@export("entry3", entry3);} + \\comptime {@export("entry4", entry4);} , ".tmp_source.zig:1:21: error: division by zero is undefined", ".tmp_source.zig:2:25: error: division by zero is undefined", @@ -804,14 +941,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const foo = "a \\b"; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: newline not allowed in string literal"); cases.add("invalid comparison for function pointers", \\fn foo() {} \\const invalid = foo > foo; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(invalid)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(invalid)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:21: error: operator not allowed for type 'fn()'"); cases.add("generic function instance with non-constant expression", @@ -820,16 +959,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return foo(a, b); \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(test1)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(test1)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:16: error: unable to evaluate constant expression"); cases.add("goto jumping into block", - \\export fn f() { + \\extern fn f() { \\ { \\a_label: \\ } \\ goto a_label; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:5:5: error: no label in scope named 'a_label'"); cases.add("goto jumping past a defer", @@ -840,20 +981,23 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} \\fn derp(){} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:12: error: no label in scope named 'label'"); cases.add("assign null to non-nullable pointer", \\const a: &u8 = null; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:16: error: expected type '&u8', found '(null)'"); cases.add("indexing an array of size zero", \\const array = []u8{}; - \\export fn foo() { + \\extern fn foo() { \\ const pointer = &array[0]; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:27: error: index 0 outside array of size 0"); cases.add("compile time division by zero", @@ -862,7 +1006,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ 1 / x \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:7: error: division by zero is undefined", ".tmp_source.zig:1:14: note: called from here"); @@ -870,7 +1015,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("branch on undefined value", \\const x = if (undefined) true else false; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:15: error: use of undefined value"); @@ -880,7 +1026,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return fibbonaci(x - 1) + fibbonaci(x - 2); \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(seventh_fib_number)) } + \\comptime {@export("entry", entry);} + \\extern fn entry() -> usize { @sizeOf(@typeOf(seventh_fib_number)) } , ".tmp_source.zig:3:21: error: evaluation exceeded 1000 backwards branches", ".tmp_source.zig:3:21: note: called from here"); @@ -888,7 +1035,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("@embedFile with bogus file", \\const resource = @embedFile("bogus.txt"); \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(resource)) } + \\comptime {@export("entry", entry);} + \\extern fn entry() -> usize { @sizeOf(@typeOf(resource)) } , ".tmp_source.zig:1:29: error: unable to find '", "bogus.txt'"); cases.add("non-const expression in struct literal outside function", @@ -898,7 +1046,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const a = Foo {.x = get_it()}; \\extern fn get_it() -> i32; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\comptime {@export("entry", entry);} + \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } , ".tmp_source.zig:4:21: error: unable to evaluate constant expression"); cases.add("non-const expression function call with struct return value outside function", @@ -912,18 +1061,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} \\var global_side_effect = false; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\comptime {@export("entry", entry);} + \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } , ".tmp_source.zig:6:24: error: unable to evaluate constant expression", ".tmp_source.zig:4:17: note: called from here"); cases.add("undeclared identifier error should mark fn as impure", - \\export fn foo() { + \\extern fn foo() { \\ test_a_thing(); \\} \\fn test_a_thing() { \\ bad_fn_call(); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:5: error: use of undeclared identifier 'bad_fn_call'"); cases.add("illegal comparison of types", @@ -938,14 +1089,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ *a == *b \\} \\ - \\export fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) } - \\export fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) } + \\extern fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) } + \\extern fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) } + \\comptime {@export("entry1", entry1);} + \\comptime {@export("entry2", entry2);} , ".tmp_source.zig:2:7: error: operator not allowed for type '[]u8'", ".tmp_source.zig:9:8: error: operator not allowed for type 'EnumWithData'"); cases.add("non-const switch number literal", - \\export fn foo() { + \\extern fn foo() { \\ const x = switch (bar()) { \\ 1, 2 => 1, \\ 3, 4 => 2, @@ -955,22 +1108,25 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar() -> i32 { \\ 2 \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: unable to infer expression type"); cases.add("atomic orderings of cmpxchg - failure stricter than success", \\const AtomicOrder = @import("builtin").AtomicOrder; - \\export fn f() { + \\extern fn f() { \\ var x: i32 = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Monotonic, AtomicOrder.SeqCst)) {} \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:4:72: error: failure atomic ordering must be no stricter than success"); cases.add("atomic orderings of cmpxchg - success Monotonic or stricter", \\const AtomicOrder = @import("builtin").AtomicOrder; - \\export fn f() { + \\extern fn f() { \\ var x: i32 = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Unordered, AtomicOrder.Unordered)) {} \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:4:49: error: success atomic ordering must be Monotonic or stricter"); cases.add("negation overflow in function evaluation", @@ -979,7 +1135,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ -x \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:5: error: negation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -990,7 +1147,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ a + b \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:7: error: operation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -1002,7 +1160,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ a - b \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:7: error: operation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -1013,7 +1172,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ a * b \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:7: error: operation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -1024,40 +1184,35 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ @truncate(i8, x) \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:19: error: expected signed integer type, found 'u32'"); cases.add("%return in function with non error return type", - \\export fn f() { + \\extern fn f() { \\ %return something(); \\} \\fn something() -> %void { } + \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: expected type 'void', found 'error'"); - cases.add("wrong return type for main", - \\pub fn main() { } - , ".tmp_source.zig:1:15: error: expected return type of main to be '%void', instead is 'void'"); - - cases.add("double ?? on main return value", - \\pub fn main() -> ??void { - \\} - , ".tmp_source.zig:1:18: error: expected return type of main to be '%void', instead is '??void'"); - cases.add("invalid pointer for var type", \\extern fn ext() -> usize; \\var bytes: [ext()]u8 = undefined; - \\export fn f() { + \\extern fn f() { \\ for (bytes) |*b, i| { \\ *b = u8(i); \\ } \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:2:13: error: unable to evaluate constant expression"); cases.add("export function with comptime parameter", - \\export fn foo(comptime x: i32, y: i32) -> i32{ + \\extern fn foo(comptime x: i32, y: i32) -> i32{ \\ x + y \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:1:15: error: comptime parameter not allowed in function with calling convention 'ccc'"); cases.add("extern function with comptime parameter", @@ -1065,14 +1220,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn f() -> i32 { \\ foo(1, 2) \\} - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:15: error: comptime parameter not allowed in function with calling convention 'ccc'"); cases.add("convert fixed size array to slice with invalid size", - \\export fn f() { + \\extern fn f() { \\ var array: [5]u8 = undefined; \\ var foo = ([]const u32)(array)[0]; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:3:28: error: unable to convert [5]u8 to []const u32: size mismatch"); cases.add("non-pure function returns type", @@ -1090,10 +1247,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\export fn function_with_return_type_type() { + \\extern fn function_with_return_type_type() { \\ var list: List(i32) = undefined; \\ list.length = 10; \\} + \\comptime {@export("function_with_return_type_type", function_with_return_type_type);} , ".tmp_source.zig:3:7: error: unable to evaluate constant expression", ".tmp_source.zig:16:19: note: called from here"); @@ -1102,7 +1260,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn f(m: []const u8) { \\ m.copy(u8, self[0..], m); \\} - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:6: error: no member named 'copy' in '[]const u8'"); cases.add("wrong number of arguments for method fn call", @@ -1113,21 +1272,24 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ \\ foo.method(1, 2); \\} - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:15: error: expected 2 arguments, found 3"); cases.add("assign through constant pointer", - \\export fn f() { + \\extern fn f() { \\ var cstr = c"Hat"; \\ cstr[0] = 'W'; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:3:11: error: cannot assign to constant"); cases.add("assign through constant slice", - \\export fn f() { + \\extern fn f() { \\ var cstr: []const u8 = "Hat"; \\ cstr[0] = 'W'; \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:3:11: error: cannot assign to constant"); cases.add("main function with bogus args type", @@ -1138,7 +1300,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn foo(blah: []u8) { \\ for (blah) { } \\} - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: for loop expression missing element parameter"); cases.add("misspelled type with pointer only reference", @@ -1171,7 +1334,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ var jd = JsonNode {.kind = JsonType.JSONArray , .jobject = JsonOA.JSONArray {jll} }; \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:16: error: use of undeclared identifier 'JsonList'"); cases.add("method call with first arg type primitive", @@ -1185,11 +1349,12 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\}; \\ - \\export fn f() { + \\extern fn f() { \\ const derp = Foo.init(3); \\ \\ derp.init(); \\} + \\comptime {@export("f", f);} , ".tmp_source.zig:14:5: error: expected type 'i32', found '&const Foo'"); cases.add("method call with first arg type wrong container", @@ -1213,10 +1378,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ field: i32, \\}; \\ - \\export fn foo() { + \\extern fn foo() { \\ var x = List.init(&global_allocator); \\ x.init(); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:23:5: error: expected type '&Allocator', found '&List'"); cases.add("binary not on number literal", @@ -1224,16 +1390,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const TINY_QUANTUM_SIZE = 1 << TINY_QUANTUM_SHIFT; \\var block_aligned_stuff: usize = (4 + TINY_QUANTUM_SIZE) & ~(TINY_QUANTUM_SIZE - 1); \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(block_aligned_stuff)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(block_aligned_stuff)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:60: error: unable to perform binary not operation on type '(integer literal)'"); cases.addCase({ const tc = cases.create("multiple files with private function error", \\const foo = @import("foo.zig"); \\ - \\export fn callPrivFunction() { + \\extern fn callPrivFunction() { \\ foo.privateFunction(); \\} + \\comptime {@export("callPrivFunction", callPrivFunction);} , ".tmp_source.zig:4:8: error: 'privateFunction' is private", "foo.zig:1:1: note: declared here"); @@ -1249,17 +1417,19 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const zero: i32 = 0; \\const a = zero{1}; \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:11: error: expected type, found 'i32'"); cases.add("assign to constant field", \\const Foo = struct { \\ field: i32, \\}; - \\export fn derp() { + \\extern fn derp() { \\ const f = Foo {.field = 1234,}; \\ f.field = 0; \\} + \\comptime {@export("derp", derp);} , ".tmp_source.zig:6:13: error: cannot assign to constant"); cases.add("return from defer expression", @@ -1277,7 +1447,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return 0; \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(testTrickyDefer)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(testTrickyDefer)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:11: error: cannot return from defer expression"); cases.add("attempt to access var args out of bounds", @@ -1289,7 +1460,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ add(i32(1234)) \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:2:19: error: index 1 outside argument list of size 1", ".tmp_source.zig:6:8: note: called from here"); @@ -1307,27 +1479,31 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ add(1, 2, 3, 4) \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(bar)) } + \\comptime {@export("entry", entry);} + \\extern fn entry() -> usize { @sizeOf(@typeOf(bar)) } , ".tmp_source.zig:10:9: error: parameter of type '(integer literal)' requires comptime"); cases.add("assign too big number to u16", - \\export fn foo() { + \\extern fn foo() { \\ var vga_mem: u16 = 0xB8000; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:24: error: integer value 753664 cannot be implicitly casted to type 'u16'"); cases.add("global variable alignment non power of 2", \\const some_data: [100]u8 align(3) = undefined; - \\export fn entry() -> usize { @sizeOf(@typeOf(some_data)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(some_data)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:32: error: alignment value 3 is not a power of 2"); cases.add("function alignment non power of 2", \\extern fn foo() align(3); - \\export fn entry() { foo() } + \\extern fn entry() { foo() } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:23: error: alignment value 3 is not a power of 2"); cases.add("compile log", - \\export fn foo() { + \\extern fn foo() { \\ comptime bar(12, "hi"); \\} \\fn bar(a: i32, b: []const u8) { @@ -1335,6 +1511,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ @compileLog("a", a, "b", b); \\ @compileLog("end"); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:5: error: found compile log statement", ".tmp_source.zig:2:17: note: called from here", @@ -1358,7 +1535,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return *x; \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:8:26: error: expected type '&const u3', found '&align(1:3:6) const u3'"); cases.add("referring to a struct that is invalid", @@ -1366,19 +1544,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Type: u8, \\}; \\ - \\export fn foo() { + \\extern fn foo() { \\ comptime assert(@sizeOf(UsbDeviceRequest) == 0x8); \\} \\ \\fn assert(ok: bool) { \\ if (!ok) unreachable; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:10:14: error: unable to evaluate constant expression", ".tmp_source.zig:6:20: note: called from here"); cases.add("control flow uses comptime var at runtime", - \\export fn foo() { + \\extern fn foo() { \\ comptime var i = 0; \\ while (i < 5) : (i += 1) { \\ bar(); @@ -1386,53 +1565,61 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} \\ \\fn bar() { } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:5: error: control flow attempts to use compile-time variable at runtime", ".tmp_source.zig:3:24: note: compile-time variable assigned here"); cases.add("ignored return value", - \\export fn foo() { + \\extern fn foo() { \\ bar(); \\} \\fn bar() -> i32 { 0 } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:8: error: expression value is ignored"); cases.add("ignored assert-err-ok return value", - \\export fn foo() { + \\extern fn foo() { \\ %%bar(); \\} \\fn bar() -> %i32 { 0 } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:5: error: expression value is ignored"); cases.add("ignored statement value", - \\export fn foo() { + \\extern fn foo() { \\ 1; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:5: error: expression value is ignored"); cases.add("ignored comptime statement value", - \\export fn foo() { + \\extern fn foo() { \\ comptime {1;} \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expression value is ignored"); cases.add("ignored comptime value", - \\export fn foo() { + \\extern fn foo() { \\ comptime 1; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:5: error: expression value is ignored"); cases.add("ignored defered statement value", - \\export fn foo() { + \\extern fn foo() { \\ defer {1;} \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:12: error: expression value is ignored"); cases.add("ignored defered statement value", - \\export fn foo() { + \\extern fn foo() { \\ defer bar(); \\} \\fn bar() -> %i32 { 0 } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:14: error: expression value is ignored"); cases.add("dereference an array", @@ -1443,7 +1630,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return (*out)[0..1]; \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(pass)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(pass)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:5: error: attempt to dereference non pointer type '[10]u8'"); cases.add("pass const ptr to mutable ptr fn", @@ -1456,46 +1644,31 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return true; \\} \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:19: error: expected type '&[]const u8', found '&const []const u8'"); - cases.addCase({ - const tc = cases.create("export collision", - \\const foo = @import("foo.zig"); - \\ - \\export fn bar() -> usize { - \\ return foo.baz; - \\} - , - "foo.zig:1:8: error: exported symbol collision: 'bar'", - ".tmp_source.zig:3:8: note: other symbol is here"); - - tc.addSourceFile("foo.zig", - \\export fn bar() {} - \\pub const baz = 1234; - ); - - tc - }); - cases.add("pass non-copyable type by value to function", \\const Point = struct { x: i32, y: i32, }; \\fn foo(p: Point) { } - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:11: error: type 'Point' is not copyable; cannot pass by value"); cases.add("implicit cast from array to mutable slice", \\var global_array: [10]i32 = undefined; \\fn foo(param: []i32) {} - \\export fn entry() { + \\extern fn entry() { \\ foo(global_array); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:9: error: expected type '[]i32', found '[10]i32'"); cases.add("ptrcast to non-pointer", - \\export fn entry(a: &i32) -> usize { + \\extern fn entry(a: &i32) -> usize { \\ return @ptrCast(usize, a); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:21: error: expected pointer, found 'usize'"); cases.add("too many error values to cast to small integer", @@ -1504,7 +1677,8 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn foo(e: error) -> u2 { \\ return u2(e); \\} - \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:14: error: too many error values to fit in 'u2'"); cases.add("asm at compile time", @@ -1523,41 +1697,46 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("invalid member of builtin enum", \\const builtin = @import("builtin"); - \\export fn entry() { + \\extern fn entry() { \\ const foo = builtin.Arch.x86; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:29: error: container 'Arch' has no member called 'x86'"); cases.add("int to ptr of 0 bits", - \\export fn foo() { + \\extern fn foo() { \\ var x: usize = 0x1000; \\ var y: &void = @intToPtr(&void, x); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:31: error: type '&void' has 0 bits and cannot store information"); cases.add("@fieldParentPtr - non struct", \\const Foo = i32; - \\export fn foo(a: &i32) -> &Foo { + \\extern fn foo(a: &i32) -> &Foo { \\ return @fieldParentPtr(Foo, "a", a); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:28: error: expected struct type, found 'i32'"); cases.add("@fieldParentPtr - bad field name", \\const Foo = struct { \\ derp: i32, \\}; - \\export fn foo(a: &i32) -> &Foo { + \\extern fn foo(a: &i32) -> &Foo { \\ return @fieldParentPtr(Foo, "a", a); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:33: error: struct 'Foo' has no field 'a'"); cases.add("@fieldParentPtr - field pointer is not pointer", \\const Foo = struct { \\ a: i32, \\}; - \\export fn foo(a: i32) -> &Foo { + \\extern fn foo(a: i32) -> &Foo { \\ return @fieldParentPtr(Foo, "a", a); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:38: error: expected pointer, found 'i32'"); cases.add("@fieldParentPtr - comptime field ptr not based on struct", @@ -1587,18 +1766,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("@offsetOf - non struct", \\const Foo = i32; - \\export fn foo() -> usize { + \\extern fn foo() -> usize { \\ return @offsetOf(Foo, "a"); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:22: error: expected struct type, found 'i32'"); cases.add("@offsetOf - bad field name", \\const Foo = struct { \\ derp: i32, \\}; - \\export fn foo() -> usize { + \\extern fn foo() -> usize { \\ return @offsetOf(Foo, "a"); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:27: error: struct 'Foo' has no field 'a'"); cases.addExe("missing main fn in executable", @@ -1611,38 +1792,22 @@ pub fn addCases(cases: &tests.CompileErrorContext) { "error: 'main' is private", ".tmp_source.zig:1:1: note: declared here"); - cases.add("@setGlobalSection extern variable", - \\extern var foo: i32; - \\comptime { - \\ @setGlobalSection(foo, ".text2"); - \\} - , - ".tmp_source.zig:3:5: error: cannot set section of external variable 'foo'", - ".tmp_source.zig:1:8: note: declared here"); - - cases.add("@setGlobalSection extern fn", - \\extern fn foo(); - \\comptime { - \\ @setGlobalSection(foo, ".text2"); - \\} - , - ".tmp_source.zig:3:5: error: cannot set section of external function 'foo'", - ".tmp_source.zig:1:8: note: declared here"); - cases.add("returning address of local variable - simple", - \\export fn foo() -> &i32 { + \\extern fn foo() -> &i32 { \\ var a: i32 = undefined; \\ return &a; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:13: error: function returns address of local variable"); cases.add("returning address of local variable - phi", - \\export fn foo(c: bool) -> &i32 { + \\extern fn foo(c: bool) -> &i32 { \\ var a: i32 = undefined; \\ var b: i32 = undefined; \\ return if (c) &a else &b; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:12: error: function returns address of local variable"); @@ -1671,55 +1836,61 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:5:9: note: previous definition is here"); cases.add("while expected bool, got nullable", - \\export fn foo() { + \\extern fn foo() { \\ while (bar()) {} \\} \\fn bar() -> ?i32 { 1 } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected type 'bool', found '?i32'"); cases.add("while expected bool, got error union", - \\export fn foo() { + \\extern fn foo() { \\ while (bar()) {} \\} \\fn bar() -> %i32 { 1 } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected type 'bool', found '%i32'"); cases.add("while expected nullable, got bool", - \\export fn foo() { + \\extern fn foo() { \\ while (bar()) |x| {} \\} \\fn bar() -> bool { true } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected nullable type, found 'bool'"); cases.add("while expected nullable, got error union", - \\export fn foo() { + \\extern fn foo() { \\ while (bar()) |x| {} \\} \\fn bar() -> %i32 { 1 } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected nullable type, found '%i32'"); cases.add("while expected error union, got bool", - \\export fn foo() { + \\extern fn foo() { \\ while (bar()) |x| {} else |err| {} \\} \\fn bar() -> bool { true } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected error union type, found 'bool'"); cases.add("while expected error union, got nullable", - \\export fn foo() { + \\extern fn foo() { \\ while (bar()) |x| {} else |err| {} \\} \\fn bar() -> ?i32 { 1 } + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected error union type, found '?i32'"); cases.add("inline fn calls itself indirectly", - \\export fn foo() { + \\extern fn foo() { \\ bar(); \\} \\inline fn bar() { @@ -1731,29 +1902,33 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ quux(); \\} \\extern fn quux(); + \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:8: error: unable to inline function"); cases.add("save reference to inline function", - \\export fn foo() { + \\extern fn foo() { \\ quux(@ptrToInt(bar)); \\} \\inline fn bar() { } \\extern fn quux(usize); + \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:8: error: unable to inline function"); cases.add("signed integer division", - \\export fn foo(a: i32, b: i32) -> i32 { + \\extern fn foo(a: i32, b: i32) -> i32 { \\ a / b \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:7: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExact"); cases.add("signed integer remainder division", - \\export fn foo(a: i32, b: i32) -> i32 { + \\extern fn foo(a: i32, b: i32) -> i32 { \\ a % b \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:7: error: remainder division with 'i32' and 'i32': signed integers and floats must use @rem or @mod"); @@ -1792,59 +1967,65 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:3:20: error: cast from 'u16' to 'u8' truncates bits"); cases.add("@setDebugSafety twice for same scope", - \\export fn foo() { + \\extern fn foo() { \\ @setDebugSafety(this, false); \\ @setDebugSafety(this, false); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:5: error: debug safety set twice for same scope", ".tmp_source.zig:2:5: note: first set here"); cases.add("@setFloatMode twice for same scope", - \\export fn foo() { + \\extern fn foo() { \\ @setFloatMode(this, @import("builtin").FloatMode.Optimized); \\ @setFloatMode(this, @import("builtin").FloatMode.Optimized); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:5: error: float mode set twice for same scope", ".tmp_source.zig:2:5: note: first set here"); cases.add("array access of type", - \\export fn foo() { + \\extern fn foo() { \\ var b: u8[40] = undefined; \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:14: error: array access of non-array type 'type'"); cases.add("cannot break out of defer expression", - \\export fn foo() { + \\extern fn foo() { \\ while (true) { \\ defer { \\ break; \\ } \\ } \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:13: error: cannot break out of defer expression"); cases.add("cannot continue out of defer expression", - \\export fn foo() { + \\extern fn foo() { \\ while (true) { \\ defer { \\ continue; \\ } \\ } \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:13: error: cannot continue out of defer expression"); cases.add("cannot goto out of defer expression", - \\export fn foo() { + \\extern fn foo() { \\ defer { \\ goto label; \\ }; \\label: \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:9: error: cannot goto out of defer expression"); @@ -1878,9 +2059,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const bar = baz + foo; \\const baz = 1; \\ - \\export fn entry() -> i32 { + \\extern fn entry() -> i32 { \\ return bar; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: aoeu", ".tmp_source.zig:3:19: note: referenced here", @@ -1893,9 +2075,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ \\var foo: Foo = undefined; \\ - \\export fn entry() -> usize { + \\extern fn entry() -> usize { \\ return @sizeOf(@typeOf(foo.x)); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: struct 'Foo' contains itself"); @@ -1914,16 +2097,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:15: error: float literal out of range of any type"); cases.add("explicit cast float literal to integer when there is a fraction component", - \\export fn entry() -> i32 { + \\extern fn entry() -> i32 { \\ i32(12.34) \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:9: error: fractional component prevents float value 12.340000 from being casted to type 'i32'"); cases.add("non pointer given to @ptrToInt", - \\export fn entry(x: i32) -> usize { + \\extern fn entry(x: i32) -> usize { \\ @ptrToInt(x) \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:15: error: expected pointer, found 'i32'"); @@ -1942,24 +2127,27 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:15: error: exact shift shifted out 1 bits"); cases.add("shifting without int type or comptime known", - \\export fn entry(x: u8) -> u8 { + \\extern fn entry(x: u8) -> u8 { \\ return 0x11 << x; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:17: error: LHS of shift must be an integer type, or RHS must be compile-time known"); cases.add("shifting RHS is log2 of LHS int bit width", - \\export fn entry(x: u8, y: u8) -> u8 { + \\extern fn entry(x: u8, y: u8) -> u8 { \\ return x << y; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:17: error: expected type 'u3', found 'u8'"); cases.add("globally shadowing a primitive type", \\const u16 = @intType(false, 8); - \\export fn entry() { + \\extern fn entry() { \\ const a: u16 = 300; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:1: error: declaration shadows type 'u16'"); @@ -1969,7 +2157,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ b: u32, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var foo = Foo { .a = 1, .b = 10 }; \\ bar(&foo.b); \\} @@ -1977,6 +2165,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar(x: &u32) { \\ *x += 1; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:8:13: error: expected type '&u32', found '&align(1) u32'"); @@ -1986,7 +2175,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ b: u32, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var foo = Foo { .a = 1, .b = 10 }; \\ foo.b += 1; \\ bar((&foo.b)[0..1]); @@ -1995,55 +2184,61 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar(x: []u32) { \\ x[0] += 1; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:17: error: expected type '[]u32', found '[]align(1) u32'"); cases.add("increase pointer alignment in @ptrCast", - \\export fn entry() -> u32 { + \\extern fn entry() -> u32 { \\ var bytes: [4]u8 = []u8{0x01, 0x02, 0x03, 0x04}; \\ const ptr = @ptrCast(&u32, &bytes[0]); \\ return *ptr; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:17: error: cast increases pointer alignment", ".tmp_source.zig:3:38: note: '&u8' has alignment 1", ".tmp_source.zig:3:27: note: '&u32' has alignment 4"); cases.add("increase pointer alignment in slice resize", - \\export fn entry() -> u32 { + \\extern fn entry() -> u32 { \\ var bytes = []u8{0x01, 0x02, 0x03, 0x04}; \\ return ([]u32)(bytes[0..])[0]; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:19: error: cast increases pointer alignment", ".tmp_source.zig:3:19: note: '[]u8' has alignment 1", ".tmp_source.zig:3:19: note: '[]u32' has alignment 4"); cases.add("@alignCast expects pointer or slice", - \\export fn entry() { + \\extern fn entry() { \\ @alignCast(4, u32(3)) \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:22: error: expected pointer or slice, found 'u32'"); cases.add("passing an under-aligned function pointer", - \\export fn entry() { + \\extern fn entry() { \\ testImplicitlyDecreaseFnAlign(alignedSmall, 1234); \\} \\fn testImplicitlyDecreaseFnAlign(ptr: fn () align(8) -> i32, answer: i32) { \\ if (ptr() != answer) unreachable; \\} \\fn alignedSmall() align(4) -> i32 { 1234 } + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:35: error: expected type 'fn() align(8) -> i32', found 'fn() align(4) -> i32'"); cases.add("passing a not-aligned-enough pointer to cmpxchg", \\const AtomicOrder = @import("builtin").AtomicOrder; - \\export fn entry() -> bool { + \\extern fn entry() -> bool { \\ var x: i32 align(1) = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.SeqCst, AtomicOrder.SeqCst)) {} \\ return x == 5678; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:23: error: expected pointer alignment of at least 4, found 1"); @@ -2069,17 +2264,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("wrong pointer implicitly casted to pointer to @OpaqueType()", \\const Derp = @OpaqueType(); \\extern fn bar(d: &Derp); - \\export fn foo() { + \\extern fn foo() { \\ const x = u8(1); \\ bar(@ptrCast(&c_void, &x)); \\} + \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:9: error: expected type '&Derp', found '&c_void'"); cases.add("non-const variables of things that require const variables", \\const Opaque = @OpaqueType(); \\ - \\export fn entry(opaque: &Opaque) { + \\extern fn entry(opaque: &Opaque) { \\ var m2 = &2; \\ const y: u32 = *m2; \\ @@ -2099,6 +2295,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = struct { \\ fn bar(self: &const Foo) {} \\}; + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:4: error: variable of type '&const (integer literal)' must be const or comptime", ".tmp_source.zig:7:4: error: variable of type '(undefined)' must be const or comptime", @@ -2113,20 +2310,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:17:4: error: unreachable code"); cases.add("wrong types given to atomic order args in cmpxchg", - \\export fn entry() { + \\extern fn entry() { \\ var x: i32 = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, u32(1234), u32(1234))) {} \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:41: error: expected type 'AtomicOrder', found 'u32'"); - cases.add("wrong types given to setGlobalLinkage", - \\export fn entry() { - \\ @setGlobalLinkage(entry, u32(1234)); - \\} - , - ".tmp_source.zig:2:33: error: expected type 'GlobalLinkage', found 'u32'"); - cases.add("struct with invalid field", \\const std = @import("std"); \\const Allocator = std.mem.Allocator; @@ -2145,12 +2336,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ }, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ const a = MdNode.Header { \\ .text = MdText.init(&std.debug.global_allocator), \\ .weight = HeaderWeight.H1, \\ }; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:14:17: error: use of undeclared identifier 'HeaderValue'"); @@ -2162,35 +2354,39 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:5: error: @setAlignStack outside function"); cases.add("@setAlignStack in naked function", - \\export nakedcc fn entry() { + \\nakedcc fn entry() { \\ @setAlignStack(16); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: @setAlignStack in naked function"); cases.add("@setAlignStack in inline function", - \\export fn entry() { + \\extern fn entry() { \\ foo(); \\} \\inline fn foo() { \\ @setAlignStack(16); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: @setAlignStack in inline function"); cases.add("@setAlignStack set twice", - \\export fn entry() { + \\extern fn entry() { \\ @setAlignStack(16); \\ @setAlignStack(16); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:5: error: alignstack set twice", ".tmp_source.zig:2:5: note: first set here"); cases.add("@setAlignStack too big", - \\export fn entry() { + \\extern fn entry() { \\ @setAlignStack(511 + 1); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: attempt to @setAlignStack(512); maximum is 256"); @@ -2221,7 +2417,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ LinkLibC, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ const tests = []TestCase { \\ Free("001"), \\ Free("002"), @@ -2236,13 +2432,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\ } \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:37:16: error: cannot store runtime value in compile time variable"); cases.add("field access of opaque type", \\const MyType = @OpaqueType(); \\ - \\export fn entry() -> bool { + \\extern fn entry() -> bool { \\ var x: i32 = 1; \\ return bar(@ptrCast(&MyType, &x)); \\} @@ -2250,6 +2447,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar(x: &MyType) -> bool { \\ return x.blah; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:13: error: type '&MyType' does not support field access"); @@ -2353,10 +2551,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:26: error: member index 1 out of bounds; 'Foo' has 1 members"); cases.add("calling var args extern function, passing array instead of pointer", - \\export fn entry() { + \\extern fn entry() { \\ foo("hello"); \\} \\pub extern fn foo(format: &const u8, ...); + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:9: error: expected type '&const u8', found '[5]u8'"); @@ -2371,9 +2570,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\export fn entry() { + \\extern fn entry() { \\ var allocator: ContextAllocator = undefined; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:25: error: aoeu", ".tmp_source.zig:1:36: note: called from here", @@ -2388,9 +2588,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Five, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var x = Small.One; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:20: error: 'u2' too small to hold all bits; must be at least 'u3'"); @@ -2401,9 +2602,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Three, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var x = Small.One; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:20: error: expected integer, found 'f32'"); @@ -2415,9 +2617,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var x: u2 = Small.Two; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:22: error: expected type 'u2', found 'Small'"); @@ -2429,9 +2632,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var x = u3(Small.Two); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:15: error: enum to integer cast to 'u3' instead of its tag type, 'u2'"); @@ -2443,10 +2647,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var y = u3(3); \\ var x = Small(y); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:10:18: error: integer to enum cast from 'u3' instead of its tag type, 'u2'"); @@ -2458,9 +2663,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\export fn entry() { + \\extern fn entry() { \\ var y = Small.Two; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:19: error: expected unsigned integer, found 'i2'"); @@ -2468,9 +2674,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const MultipleChoice = struct { \\ A: i32 = 20, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var x: MultipleChoice = undefined; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:14: error: enums, not structs, support field assignment"); @@ -2478,26 +2685,29 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const MultipleChoice = union { \\ A: i32 = 20, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var x: MultipleChoice = undefined; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:14: error: non-enum union field assignment", ".tmp_source.zig:1:24: note: consider 'union(enum)' here"); cases.add("enum with 0 fields", \\const Foo = enum {}; - \\export fn entry() -> usize { + \\extern fn entry() -> usize { \\ return @sizeOf(Foo); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: enums must have 1 or more fields"); cases.add("union with 0 fields", \\const Foo = union {}; - \\export fn entry() -> usize { + \\extern fn entry() -> usize { \\ return @sizeOf(Foo); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: unions must have 1 or more fields"); @@ -2509,9 +2719,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ D = 1000, \\ E = 60, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var x = MultipleChoice.C; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:9: error: enum tag value 60 already taken", ".tmp_source.zig:4:9: note: other occurrence here"); @@ -2526,9 +2737,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ A: i32, \\ B: f64, \\}; - \\export fn entry() -> usize { + \\extern fn entry() -> usize { \\ return @sizeOf(Payload); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:17: error: enum field missing: 'C'", ".tmp_source.zig:4:5: note: declared here"); @@ -2537,9 +2749,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = union { \\ A: i32, \\}; - \\export fn entry() { + \\extern fn entry() { \\ const x = @TagType(Foo); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:24: error: union 'Foo' has no tag", ".tmp_source.zig:1:13: note: consider 'union(enum)' here"); @@ -2548,9 +2761,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = union(enum(f32)) { \\ A: i32, \\}; - \\export fn entry() { + \\extern fn entry() { \\ const x = @TagType(Foo); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:23: error: expected integer tag type, found 'f32'"); @@ -2558,9 +2772,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = union(u32) { \\ A: i32, \\}; - \\export fn entry() { + \\extern fn entry() { \\ const x = @TagType(Foo); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:18: error: expected enum tag type, found 'u32'"); @@ -2572,9 +2787,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ D = 1000, \\ E = 60, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var x = MultipleChoice { .C = {} }; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:9: error: enum tag value 60 already taken", ".tmp_source.zig:4:9: note: other occurrence here"); @@ -2591,9 +2807,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ C: bool, \\ D: bool, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var a = Payload {.A = 1234}; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:10:5: error: enum field not found: 'D'", ".tmp_source.zig:1:16: note: enum declared here"); @@ -2604,9 +2821,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B, \\ C, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var b = Letter.B; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:8: error: structs and unions, not enums, support field types", ".tmp_source.zig:1:16: note: consider 'union(enum)' here"); @@ -2615,9 +2833,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Letter = struct { \\ A, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var a = Letter { .A = {} }; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: struct field missing type"); @@ -2625,9 +2844,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Letter = extern union { \\ A, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var a = Letter { .A = {} }; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: union field missing type"); @@ -2642,9 +2862,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B: f64, \\ C: bool, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var a = Payload { .A = { 1234 } }; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:29: error: extern union does not support enum tag type"); @@ -2659,9 +2880,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B: f64, \\ C: bool, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var a = Payload { .A = { 1234 } }; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:29: error: packed union does not support enum tag type"); @@ -2671,7 +2893,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B: f64, \\ C: bool, \\}; - \\export fn entry() { + \\extern fn entry() { \\ const a = Payload { .A = { 1234 } }; \\ foo(a); \\} @@ -2681,6 +2903,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ else => unreachable, \\ } \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:11:13: error: switch on union which has no attached enum", ".tmp_source.zig:1:17: note: consider 'union(enum)' here"); @@ -2690,9 +2913,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ A = 10, \\ B = 11, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var x = Foo(0); \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:16: error: enum 'Foo' has no tag matching integer value 0", ".tmp_source.zig:1:13: note: 'Foo' declared here"); @@ -2704,9 +2928,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B, \\ C, \\}; - \\export fn entry() { + \\extern fn entry() { \\ var x: Value = Letter.A; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:8:26: error: cast to union 'Value' must initialize 'i32' field 'A'", ".tmp_source.zig:3:5: note: field 'A' declared here"); @@ -2718,13 +2943,36 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B, \\ C, \\}; - \\export fn entry() { + \\extern fn entry() { \\ foo(Letter.A); \\} \\fn foo(l: Letter) { \\ var x: Value = l; \\} + \\comptime {@export("entry", entry);} , ".tmp_source.zig:11:20: error: runtime cast to union 'Value' which has non-void fields", ".tmp_source.zig:3:5: note: field 'A' has type 'i32'"); + + cases.addCase({ + const tc = cases.create("export collision", + \\const foo = @import("foo.zig"); + \\ + \\comptime {@export("bar", bar);} + \\extern fn bar() -> usize { + \\ return foo.baz; + \\} + , + "foo.zig:2:11: error: exported symbol collision: 'bar'", + ".tmp_source.zig:3:11: note: other symbol is here"); + + tc.addSourceFile("foo.zig", + \\extern fn bar() {} + \\comptime {@export("bar", bar);} + \\pub const baz = 1234; + ); + + tc + }); + } diff --git a/test/standalone/issue_339/test.zig b/test/standalone/issue_339/test.zig index c1faa015c6..a3058e58ed 100644 --- a/test/standalone/issue_339/test.zig +++ b/test/standalone/issue_339/test.zig @@ -2,6 +2,9 @@ pub fn panic(msg: []const u8) -> noreturn { @breakpoint(); while (true) {} } fn bar() -> %void {} -export fn foo() { +comptime { + @export("foo", foo); +} +extern fn foo() { %%bar(); } diff --git a/test/translate_c.zig b/test/translate_c.zig index d50c7b9691..01f6622a71 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -26,7 +26,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a < 0 ? -a : a; \\} , - \\export fn abs(a: c_int) -> c_int { + \\pub fn abs(a: c_int) -> c_int { \\ return if (a < 0) -a else a; \\} ); @@ -325,12 +325,12 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\export fn foo1(_arg_a: c_uint) -> c_uint { + \\pub fn foo1(_arg_a: c_uint) -> c_uint { \\ var a = _arg_a; \\ a +%= 1; \\ return a; \\} - \\export fn foo2(_arg_a: c_int) -> c_int { + \\pub fn foo2(_arg_a: c_int) -> c_int { \\ var a = _arg_a; \\ a += 1; \\ return a; @@ -346,7 +346,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return i; \\} , - \\export fn log2(_arg_a: c_uint) -> c_int { + \\pub fn log2(_arg_a: c_uint) -> c_int { \\ var a = _arg_a; \\ var i: c_int = 0; \\ while (a > c_uint(0)) { @@ -367,7 +367,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\export fn max(a: c_int, b: c_int) -> c_int { + \\pub fn max(a: c_int, b: c_int) -> c_int { \\ if (a < b) return b; \\ if (a < b) return b else return a; \\} @@ -382,7 +382,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\export fn max(a: c_int, b: c_int) -> c_int { + \\pub fn max(a: c_int, b: c_int) -> c_int { \\ if (a == b) return a; \\ if (a != b) return b; \\ return a; @@ -407,7 +407,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ c = a % b; \\} , - \\export fn s(a: c_int, b: c_int) -> c_int { + \\pub fn s(a: c_int, b: c_int) -> c_int { \\ var c: c_int; \\ c = (a + b); \\ c = (a - b); @@ -415,7 +415,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ c = @divTrunc(a, b); \\ c = @rem(a, b); \\} - \\export fn u(a: c_uint, b: c_uint) -> c_uint { + \\pub fn u(a: c_uint, b: c_uint) -> c_uint { \\ var c: c_uint; \\ c = (a +% b); \\ c = (a -% b); @@ -430,7 +430,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return (a & b) ^ (a | b); \\} , - \\export fn max(a: c_int, b: c_int) -> c_int { + \\pub fn max(a: c_int, b: c_int) -> c_int { \\ return (a & b) ^ (a | b); \\} ); @@ -444,7 +444,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\export fn max(a: c_int, b: c_int) -> c_int { + \\pub fn max(a: c_int, b: c_int) -> c_int { \\ if ((a < b) or (a == b)) return b; \\ if ((a >= b) and (a == b)) return a; \\ return a; @@ -458,7 +458,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a = tmp; \\} , - \\export fn max(_arg_a: c_int) -> c_int { + \\pub fn max(_arg_a: c_int) -> c_int { \\ var a = _arg_a; \\ var tmp: c_int; \\ tmp = a; @@ -472,7 +472,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ c = b = a; \\} , - \\export fn max(a: c_int) { + \\pub fn max(a: c_int) { \\ var b: c_int; \\ var c: c_int; \\ c = { @@ -493,7 +493,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return i; \\} , - \\export fn log2(_arg_a: u32) -> c_int { + \\pub fn log2(_arg_a: u32) -> c_int { \\ var a = _arg_a; \\ var i: c_int = 0; \\ while (a > c_uint(0)) { @@ -518,7 +518,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\void foo(void) { bar(); } , \\pub fn bar() {} - \\export fn foo() { + \\pub fn foo() { \\ bar(); \\} ); @@ -534,7 +534,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\pub const struct_Foo = extern struct { \\ field: c_int, \\}; - \\export fn read_field(foo: ?&struct_Foo) -> c_int { + \\pub fn read_field(foo: ?&struct_Foo) -> c_int { \\ return (??foo).field; \\} ); @@ -544,7 +544,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ ;;;;; \\} , - \\export fn foo() {} + \\pub fn foo() {} ); cases.add("undefined array global", @@ -560,7 +560,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\} , \\pub var array: [100]c_int = undefined; - \\export fn foo(index: c_int) -> c_int { + \\pub fn foo(index: c_int) -> c_int { \\ return array[index]; \\} ); @@ -571,7 +571,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return (int)a; \\} , - \\export fn float_to_int(a: f32) -> c_int { + \\pub fn float_to_int(a: f32) -> c_int { \\ return c_int(a); \\} ); @@ -581,7 +581,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return x; \\} , - \\export fn foo(x: ?&c_ushort) -> ?&c_void { + \\pub fn foo(x: ?&c_ushort) -> ?&c_void { \\ return @ptrCast(?&c_void, x); \\} ); @@ -592,7 +592,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return sizeof(int); \\} , - \\export fn size_of() -> usize { + \\pub fn size_of() -> usize { \\ return @sizeOf(c_int); \\} ); @@ -602,7 +602,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return 0; \\} , - \\export fn foo() -> ?&c_int { + \\pub fn foo() -> ?&c_int { \\ return null; \\} ); @@ -612,7 +612,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return 1, 2; \\} , - \\export fn foo() -> c_int { + \\pub fn foo() -> c_int { \\ return { \\ _ = 1; \\ 2 @@ -625,7 +625,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return (1 << 2) >> 1; \\} , - \\export fn foo() -> c_int { + \\pub fn foo() -> c_int { \\ return (1 << @import("std").math.Log2Int(c_int)(2)) >> @import("std").math.Log2Int(c_int)(1); \\} ); @@ -643,7 +643,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a <<= (a <<= 1); \\} , - \\export fn foo() { + \\pub fn foo() { \\ var a: c_int = 0; \\ a += { \\ const _ref = &a; @@ -701,7 +701,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a <<= (a <<= 1); \\} , - \\export fn foo() { + \\pub fn foo() { \\ var a: c_uint = c_uint(0); \\ a +%= { \\ const _ref = &a; @@ -771,7 +771,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ u = u--; \\} , - \\export fn foo() { + \\pub fn foo() { \\ var i: c_int = 0; \\ var u: c_uint = c_uint(0); \\ i += 1; @@ -819,7 +819,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ u = --u; \\} , - \\export fn foo() { + \\pub fn foo() { \\ var i: c_int = 0; \\ var u: c_uint = c_uint(0); \\ i += 1; @@ -862,7 +862,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ while (b != 0); \\} , - \\export fn foo() { + \\pub fn foo() { \\ var a: c_int = 2; \\ while (true) { \\ a -= 1; @@ -886,9 +886,9 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ baz(); \\} , - \\export fn foo() {} - \\export fn baz() {} - \\export fn bar() { + \\pub fn foo() {} + \\pub fn baz() {} + \\pub fn bar() { \\ var f: ?extern fn() = foo; \\ (??f)(); \\ (??f)(); @@ -901,7 +901,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ *x = 1; \\} , - \\export fn foo(x: ?&c_int) { + \\pub fn foo(x: ?&c_int) { \\ (*??x) = 1; \\} ); From c627f9ea18b5f194860c6bf3730f3f0407c224f2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Dec 2017 01:19:49 -0500 Subject: [PATCH 57/69] wip bring back export keyword --- doc/langref.html.in | 14 +++--- src/all_types.hpp | 10 ++-- src/analyze.cpp | 29 ++++++++++- src/ir.cpp | 2 +- src/parser.cpp | 114 +++++++++++++++++++++++++++----------------- src/tokenizer.cpp | 2 + src/tokenizer.hpp | 1 + 7 files changed, 112 insertions(+), 60 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index f302cbec5d..beb113682a 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5819,9 +5819,11 @@ TopLevelDecl = option("pub") (FnDef | ExternDecl | GlobalVarDecl | UseDecl) ErrorValueDecl = "error" Symbol ";" -GlobalVarDecl = VariableDeclaration ";" +GlobalVarDecl = option("export") VariableDeclaration ";" -VariableDeclaration = option("comptime") ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") option("section" "(" Expression ")") "=" Expression +LocalVarDecl = option("comptime") VariableDeclaration + +VariableDeclaration = ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") option("section" "(" Expression ")") "=" Expression ContainerMember = (ContainerField | FnDef | GlobalVarDecl) @@ -5831,9 +5833,9 @@ UseDecl = "use" Expression ";" ExternDecl = "extern" option(String) (FnProto | VariableDeclaration) ";" -FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) +FnProto = option("coldcc" | "nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) -FnDef = option("inline" | "extern") FnProto Block +FnDef = option("inline" | "export") FnProto Block ParamDeclList = "(" list(ParamDecl, ",") ")" @@ -5841,7 +5843,7 @@ ParamDecl = option("noalias" | "comptime") option(Symbol ":") (TypeExpr | "...") Block = "{" many(Statement) option(Expression) "}" -Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" +Statement = Label | LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" Label = Symbol ":" @@ -5947,7 +5949,7 @@ StructLiteralField = "." Symbol "=" Expression PrefixOp = "!" | "-" | "~" | "*" | ("&" option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "%" | "%%" | "??" | "-%" -PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | (option("extern") FnProto) | AsmExpression | ("error" "." Symbol) | ContainerDecl +PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl ArrayType : "[" option(Expression) "]" option("align" "(" Expression option(":" Integer ":" Integer) ")")) option("const") option("volatile") TypeExpr diff --git a/src/all_types.hpp b/src/all_types.hpp index 9256777428..106ccf6471 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -315,12 +315,6 @@ struct TldVar { VariableTableEntry *var; Buf *extern_lib_name; Buf *section_name; - - size_t export_count; - union { - TldExport *tld; // if export_count == 1 - TldExport **tld_list; // if export_count > 1 - } export_data; }; struct TldFn { @@ -428,6 +422,7 @@ struct AstNodeFnProto { AstNode *return_type; bool is_var_args; bool is_extern; + bool is_export; bool is_inline; CallingConvention cc; AstNode *fn_def_node; @@ -485,7 +480,8 @@ struct AstNodeVariableDeclaration { VisibMod visib_mod; Buf *symbol; bool is_const; - bool is_inline; + bool is_comptime; + bool is_export; bool is_extern; // one or both of type and expr will be non null AstNode *type; diff --git a/src/analyze.cpp b/src/analyze.cpp index 84d9b9feaf..470d54ff84 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1062,7 +1062,7 @@ void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_cou AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; if (fn_proto->cc == CallingConventionUnspecified) { - bool extern_abi = fn_proto->is_extern; + bool extern_abi = fn_proto->is_extern || fn_proto->is_export; fn_type_id->cc = extern_abi ? CallingConventionC : CallingConventionUnspecified; } else { fn_type_id->cc = fn_proto->cc; @@ -2675,6 +2675,26 @@ static void resolve_decl_comptime(CodeGen *g, TldCompTime *tld_comptime) { } static void add_top_level_decl(CodeGen *g, ScopeDecls *decls_scope, Tld *tld) { + bool is_export = false; + if (tld->id == TldIdVar) { + assert(tld->source_node->type == NodeTypeVariableDeclaration); + is_export = tld->source_node->data.variable_declaration.is_export; + } else if (tld->id == TldIdFn) { + assert(tld->source_node->type == NodeTypeFnProto); + is_export = tld->source_node->data.fn_proto.is_export; + } + if (is_export) { + g->resolve_queue.append(tld); + + auto entry = g->exported_symbol_names.put_unique(tld->name, tld->source_node); + if (entry) { + AstNode *other_source_node = entry->value; + ErrorMsg *msg = add_node_error(g, tld->source_node, + buf_sprintf("exported symbol collision: '%s'", buf_ptr(tld->name))); + add_error_note(g, msg, other_source_node, buf_sprintf("other symbol here")); + } + } + { auto entry = decls_scope->decl_table.put_unique(tld->name, tld); if (entry) { @@ -3006,6 +3026,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; TypeTableEntry *explicit_type = nullptr; if (var_decl->type) { @@ -3013,8 +3034,12 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { explicit_type = validate_var_type(g, var_decl->type, proposed_type); } + assert(!is_export || !is_extern); + VarLinkage linkage; - if (is_extern) { + if (is_export) { + linkage = VarLinkageExport; + } else if (is_extern) { linkage = VarLinkageExternal; } else { linkage = VarLinkageInternal; diff --git a/src/ir.cpp b/src/ir.cpp index 81b64a318d..78f7c25dca 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -5066,7 +5066,7 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod bool is_const = variable_declaration->is_const; bool is_extern = variable_declaration->is_extern; IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, - ir_should_inline(irb->exec, scope) || variable_declaration->is_inline); + ir_should_inline(irb->exec, scope) || variable_declaration->is_comptime); VariableTableEntry *var = ir_create_var(irb, node, scope, variable_declaration->symbol, is_const, is_const, is_shadowable, is_comptime); // we detect IrInstructionIdDeclVar in gen_block to make sure the next node diff --git a/src/parser.cpp b/src/parser.cpp index 07c221e287..a2b06410ea 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -676,7 +676,7 @@ static AstNode *ast_parse_comptime_expr(ParseContext *pc, size_t *token_index, b } /* -PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | (option("extern") FnProto) | AsmExpression | ("error" "." Symbol) | ContainerDecl +PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl KeywordLiteral = "true" | "false" | "null" | "continue" | "undefined" | "error" | "this" | "unreachable" */ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bool mandatory) { @@ -791,13 +791,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo if (container_decl) return container_decl; - if (token->id == TokenIdKeywordExtern) { - *token_index += 1; - AstNode *node = ast_parse_fn_proto(pc, token_index, true, VisibModPrivate); - node->data.fn_proto.is_extern = true; - return node; - } - if (!mandatory) return nullptr; @@ -1534,38 +1527,20 @@ static AstNode *ast_parse_defer_expr(ParseContext *pc, size_t *token_index) { } /* -VariableDeclaration = option("comptime") ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") "=" Expression +VariableDeclaration = ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") "=" Expression */ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, size_t *token_index, bool mandatory, - VisibMod visib_mod) + VisibMod visib_mod, bool is_comptime, bool is_export) { Token *first_token = &pc->tokens->at(*token_index); Token *var_token; bool is_const; - bool is_comptime; - if (first_token->id == TokenIdKeywordCompTime) { - is_comptime = true; - var_token = &pc->tokens->at(*token_index + 1); - - if (var_token->id == TokenIdKeywordVar) { - is_const = false; - } else if (var_token->id == TokenIdKeywordConst) { - is_const = true; - } else if (mandatory) { - ast_invalid_token_error(pc, var_token); - } else { - return nullptr; - } - - *token_index += 2; - } else if (first_token->id == TokenIdKeywordVar) { - is_comptime = false; + if (first_token->id == TokenIdKeywordVar) { is_const = false; var_token = first_token; *token_index += 1; } else if (first_token->id == TokenIdKeywordConst) { - is_comptime = false; is_const = true; var_token = first_token; *token_index += 1; @@ -1577,7 +1552,7 @@ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, size_t *to AstNode *node = ast_create_node(pc, NodeTypeVariableDeclaration, var_token); - node->data.variable_declaration.is_inline = is_comptime; + node->data.variable_declaration.is_comptime = is_comptime; node->data.variable_declaration.is_const = is_const; node->data.variable_declaration.visib_mod = visib_mod; @@ -1620,6 +1595,50 @@ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, size_t *to return node; } +/* +GlobalVarDecl = option("export") VariableDeclaration ";" +*/ +static AstNode *ast_parse_global_var_decl(ParseContext *pc, size_t *token_index, VisibMod visib_mod) { + Token *first_token = &pc->tokens->at(*token_index); + + bool is_export = false;; + if (first_token->id == TokenIdKeywordExport) { + *token_index += 1; + is_export = true; + } + + AstNode *node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod, false, is_export); + if (node == nullptr) { + if (is_export) { + *token_index -= 1; + } + return nullptr; + } + return node; +} + +/* +LocalVarDecl = option("comptime") VariableDeclaration +*/ +static AstNode *ast_parse_local_var_decl(ParseContext *pc, size_t *token_index) { + Token *first_token = &pc->tokens->at(*token_index); + + bool is_comptime = false;; + if (first_token->id == TokenIdKeywordCompTime) { + *token_index += 1; + is_comptime = true; + } + + AstNode *node = ast_parse_variable_declaration_expr(pc, token_index, false, VisibModPrivate, is_comptime, false); + if (node == nullptr) { + if (is_comptime) { + *token_index -= 1; + } + return nullptr; + } + return node; +} + /* BoolOrExpression = BoolAndExpression "or" BoolOrExpression | BoolAndExpression */ @@ -2171,7 +2190,7 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand for (;;) { AstNode *statement_node = ast_parse_label(pc, token_index, false); if (!statement_node) - statement_node = ast_parse_variable_declaration_expr(pc, token_index, false, VisibModPrivate); + statement_node = ast_parse_local_var_decl(pc, token_index); if (!statement_node) statement_node = ast_parse_defer_expr(pc, token_index); if (!statement_node) @@ -2213,13 +2232,14 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand } /* -FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) +FnProto = option("coldcc" | "nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) */ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) { Token *first_token = &pc->tokens->at(*token_index); Token *fn_token; CallingConvention cc; + bool is_extern = false; if (first_token->id == TokenIdKeywordColdCC) { *token_index += 1; fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn); @@ -2232,8 +2252,13 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m *token_index += 1; fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn); cc = CallingConventionStdcall; + } else if (first_token->id == TokenIdKeywordExtern) { + *token_index += 1; + fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn); + cc = CallingConventionC; } else if (first_token->id == TokenIdKeywordFn) { fn_token = first_token; + is_extern = true; *token_index += 1; cc = CallingConventionUnspecified; } else if (mandatory) { @@ -2246,6 +2271,7 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m AstNode *node = ast_create_node(pc, NodeTypeFnProto, fn_token); node->data.fn_proto.visib_mod = visib_mod; node->data.fn_proto.cc = cc; + node->data.fn_proto.is_extern = is_extern; Token *fn_name = &pc->tokens->at(*token_index); @@ -2286,35 +2312,35 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m } /* -FnDef = option("inline" | "extern") FnProto Block +FnDef = option("inline" | "export") FnProto Block */ static AstNode *ast_parse_fn_def(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) { Token *first_token = &pc->tokens->at(*token_index); bool is_inline; - bool is_extern; + bool is_export; if (first_token->id == TokenIdKeywordInline) { *token_index += 1; is_inline = true; - is_extern = false; - } else if (first_token->id == TokenIdKeywordExtern) { + is_export = false; + } else if (first_token->id == TokenIdKeywordExport) { *token_index += 1; - is_extern = true; + is_export = true; is_inline = false; } else { is_inline = false; - is_extern = false; + is_export = false; } AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, mandatory, visib_mod); if (!fn_proto) { - if (is_inline || is_extern) { + if (is_inline || is_export) { *token_index -= 1; } return nullptr; } fn_proto->data.fn_proto.is_inline = is_inline; - fn_proto->data.fn_proto.is_extern = is_extern; + fn_proto->data.fn_proto.is_export = is_export; Token *semi_token = &pc->tokens->at(*token_index); if (semi_token->id == TokenIdSemicolon) { @@ -2360,7 +2386,7 @@ static AstNode *ast_parse_extern_decl(ParseContext *pc, size_t *token_index, boo return fn_proto_node; } - AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod); + AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod, false, false); if (var_decl_node) { ast_eat_token(pc, token_index, TokenIdSemicolon); @@ -2473,7 +2499,7 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index, continue; } - AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod); + AstNode *var_decl_node = ast_parse_global_var_decl(pc, token_index, visib_mod); if (var_decl_node) { ast_eat_token(pc, token_index, TokenIdSemicolon); node->data.container_decl.decls.append(var_decl_node); @@ -2566,7 +2592,7 @@ static AstNode *ast_parse_test_decl_node(ParseContext *pc, size_t *token_index) /* TopLevelItem = ErrorValueDecl | CompTimeExpression(Block) | TopLevelDecl | TestDecl -TopLevelDecl = option(VisibleMod) (FnDef | ExternDecl | GlobalVarDecl | UseDecl) +TopLevelDecl = option("pub") (FnDef | ExternDecl | GlobalVarDecl | UseDecl) */ static void ast_parse_top_level_decls(ParseContext *pc, size_t *token_index, ZigList *top_level_decls) { for (;;) { @@ -2615,7 +2641,7 @@ static void ast_parse_top_level_decls(ParseContext *pc, size_t *token_index, Zig continue; } - AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod); + AstNode *var_decl_node = ast_parse_global_var_decl(pc, token_index, visib_mod); if (var_decl_node) { ast_eat_token(pc, token_index, TokenIdSemicolon); top_level_decls->append(var_decl_node); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index a14f709744..9bcc79ede7 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -119,6 +119,7 @@ static const struct ZigKeyword zig_keywords[] = { {"else", TokenIdKeywordElse}, {"enum", TokenIdKeywordEnum}, {"error", TokenIdKeywordError}, + {"export", TokenIdKeywordExport}, {"extern", TokenIdKeywordExtern}, {"false", TokenIdKeywordFalse}, {"fn", TokenIdKeywordFn}, @@ -1518,6 +1519,7 @@ const char * token_name(TokenId id) { case TokenIdKeywordElse: return "else"; case TokenIdKeywordEnum: return "enum"; case TokenIdKeywordError: return "error"; + case TokenIdKeywordExport: return "export"; case TokenIdKeywordExtern: return "extern"; case TokenIdKeywordFalse: return "false"; case TokenIdKeywordFn: return "fn"; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index b0444b9c3b..58a20adc1a 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -59,6 +59,7 @@ enum TokenId { TokenIdKeywordElse, TokenIdKeywordEnum, TokenIdKeywordError, + TokenIdKeywordExport, TokenIdKeywordExtern, TokenIdKeywordFalse, TokenIdKeywordFn, From 27ba4f0baf5168b2fb8f0dd72b04f528092f075a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Dec 2017 01:49:42 -0500 Subject: [PATCH 58/69] export keyword works again --- src/all_types.hpp | 1 - src/analyze.cpp | 33 +++++++++++++++++++++++++++++++++ src/analyze.hpp | 1 + src/ir.cpp | 30 +++--------------------------- src/parser.cpp | 1 + 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index 106ccf6471..1ec3c809a2 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1183,7 +1183,6 @@ enum FnInline { struct FnExport { Buf name; GlobalLinkageId linkage; - AstNode *source_node; }; struct FnTableEntry { diff --git a/src/analyze.cpp b/src/analyze.cpp index 470d54ff84..e2df4955da 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2577,6 +2577,34 @@ TypeTableEntry *get_test_fn_type(CodeGen *g) { return g->test_fn_type; } +void add_fn_export(CodeGen *g, FnTableEntry *fn_table_entry, Buf *symbol_name, GlobalLinkageId linkage, bool ccc) { + if (ccc) { + if (buf_eql_str(symbol_name, "main") && g->libc_link_lib != nullptr) { + g->have_c_main = true; + g->windows_subsystem_windows = false; + g->windows_subsystem_console = true; + } else if (buf_eql_str(symbol_name, "WinMain") && + g->zig_target.os == ZigLLVM_Win32) + { + g->have_winmain = true; + g->windows_subsystem_windows = true; + g->windows_subsystem_console = false; + } else if (buf_eql_str(symbol_name, "WinMainCRTStartup") && + g->zig_target.os == ZigLLVM_Win32) + { + g->have_winmain_crt_startup = true; + } else if (buf_eql_str(symbol_name, "DllMainCRTStartup") && + g->zig_target.os == ZigLLVM_Win32) + { + g->have_dllmain_crt_startup = true; + } + } + FnExport *fn_export = fn_table_entry->export_list.add_one(); + memset(fn_export, 0, sizeof(FnExport)); + buf_init_from_buf(&fn_export->name, symbol_name); + fn_export->linkage = linkage; +} + static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { ImportTableEntry *import = tld_fn->base.import; AstNode *source_node = tld_fn->base.source_node; @@ -2588,6 +2616,11 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { FnTableEntry *fn_table_entry = create_fn(source_node); get_fully_qualified_decl_name(&fn_table_entry->symbol_name, &tld_fn->base, '_'); + if (fn_proto->is_export) { + bool ccc = (fn_proto->cc == CallingConventionUnspecified || fn_proto->cc == CallingConventionC); + add_fn_export(g, fn_table_entry, &fn_table_entry->symbol_name, GlobalLinkageIdStrong, ccc); + } + tld_fn->fn_entry = fn_table_entry; if (fn_table_entry->body_node) { diff --git a/src/analyze.hpp b/src/analyze.hpp index 0e727568c6..6224e64dd5 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -183,5 +183,6 @@ TypeTableEntry *get_align_amt_type(CodeGen *g); PackageTableEntry *new_anonymous_package(void); Buf *const_value_to_buffer(ConstExprValue *const_val); +void add_fn_export(CodeGen *g, FnTableEntry *fn_table_entry, Buf *symbol_name, GlobalLinkageId linkage, bool ccc); #endif diff --git a/src/ir.cpp b/src/ir.cpp index 78f7c25dca..4afc1a2c60 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -10504,35 +10504,11 @@ static TypeTableEntry *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructi add_error_note(ira->codegen, msg, fn_entry->proto_node, buf_sprintf("declared here")); } break; case CallingConventionC: - if (buf_eql_str(symbol_name, "main") && ira->codegen->libc_link_lib != nullptr) { - ira->codegen->have_c_main = true; - ira->codegen->windows_subsystem_windows = false; - ira->codegen->windows_subsystem_console = true; - } else if (buf_eql_str(symbol_name, "WinMain") && - ira->codegen->zig_target.os == ZigLLVM_Win32) - { - ira->codegen->have_winmain = true; - ira->codegen->windows_subsystem_windows = true; - ira->codegen->windows_subsystem_console = false; - } else if (buf_eql_str(symbol_name, "WinMainCRTStartup") && - ira->codegen->zig_target.os == ZigLLVM_Win32) - { - ira->codegen->have_winmain_crt_startup = true; - } else if (buf_eql_str(symbol_name, "DllMainCRTStartup") && - ira->codegen->zig_target.os == ZigLLVM_Win32) - { - ira->codegen->have_dllmain_crt_startup = true; - } - // fallthrough case CallingConventionNaked: case CallingConventionCold: - case CallingConventionStdcall: { - FnExport *fn_export = fn_entry->export_list.add_one(); - memset(fn_export, 0, sizeof(FnExport)); - buf_init_from_buf(&fn_export->name, symbol_name); - fn_export->linkage = global_linkage_id; - fn_export->source_node = instruction->base.source_node; - } break; + case CallingConventionStdcall: + add_fn_export(ira->codegen, fn_entry, symbol_name, global_linkage_id, cc == CallingConventionC); + break; } } break; case TypeTableEntryIdStruct: diff --git a/src/parser.cpp b/src/parser.cpp index a2b06410ea..cc337471ba 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1553,6 +1553,7 @@ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, size_t *to AstNode *node = ast_create_node(pc, NodeTypeVariableDeclaration, var_token); node->data.variable_declaration.is_comptime = is_comptime; + node->data.variable_declaration.is_export = is_export; node->data.variable_declaration.is_const = is_const; node->data.variable_declaration.visib_mod = visib_mod; From 9d9201c3b48873e432dc6824d42b5ca96b236daa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Dec 2017 02:39:43 -0500 Subject: [PATCH 59/69] bring back code that uses export and fix tests partial revert of 1fdebc1dc4881a00766f7c2b4b2d8ee6ad6e79b6 --- doc/langref.html.in | 6 + example/hello_world/hello_libc.zig | 8 +- example/mix_o_files/base64.zig | 5 +- example/shared_library/mathtest.zig | 5 +- src/all_types.hpp | 1 - src/ast_render.cpp | 7 +- src/codegen.cpp | 3 +- src/ir.cpp | 14 +- src/parser.cpp | 30 +- src/translate_c.cpp | 5 +- std/special/bootstrap.zig | 7 +- std/special/builtin.zig | 35 +- std/special/compiler_rt/index.zig | 68 +-- test/cases/asm.zig | 13 +- test/cases/misc.zig | 20 +- test/compare_output.zig | 16 +- test/compile_errors.zig | 829 ++++++++++------------------ test/standalone/issue_339/test.zig | 5 +- test/translate_c.zig | 64 +-- 19 files changed, 443 insertions(+), 698 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index beb113682a..e17e1ecd8f 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -136,6 +136,7 @@
  • @divFloor
  • @divTrunc
  • @embedFile
  • +
  • @export
  • @tagName
  • @EnumTagType
  • @errorName
  • @@ -4368,6 +4369,11 @@ test.zig:6:2: error: found compile log statement +

    @export

    +
    @export(comptime name: []const u8, target: var, linkage: builtin.GlobalLinkage) -> []const u8
    +

    + Creates a symbol in the output object file. +

    @tagName

    @tagName(value: var) -> []const u8

    diff --git a/example/hello_world/hello_libc.zig b/example/hello_world/hello_libc.zig index 16d0f303cc..ea8aec1e4f 100644 --- a/example/hello_world/hello_libc.zig +++ b/example/hello_world/hello_libc.zig @@ -5,13 +5,9 @@ const c = @cImport({ @cInclude("string.h"); }); -comptime { - @export("main", main); -} - -extern fn main(argc: c_int, argv: &&u8) -> c_int { - const msg = c"Hello, world!\n"; +const msg = c"Hello, world!\n"; +export fn main(argc: c_int, argv: &&u8) -> c_int { if (c.printf(msg) != c_int(c.strlen(msg))) return -1; diff --git a/example/mix_o_files/base64.zig b/example/mix_o_files/base64.zig index a8358e9685..49c9bc6012 100644 --- a/example/mix_o_files/base64.zig +++ b/example/mix_o_files/base64.zig @@ -1,9 +1,6 @@ const base64 = @import("std").base64; -comptime { - @export("decode_base_64", decode_base_64); -} -extern fn decode_base_64(dest_ptr: &u8, dest_len: usize, source_ptr: &const u8, source_len: usize) -> usize { +export fn decode_base_64(dest_ptr: &u8, dest_len: usize, source_ptr: &const u8, source_len: usize) -> usize { const src = source_ptr[0..source_len]; const dest = dest_ptr[0..dest_len]; const base64_decoder = base64.standard_decoder_unsafe; diff --git a/example/shared_library/mathtest.zig b/example/shared_library/mathtest.zig index f6d0a61c90..a11642554f 100644 --- a/example/shared_library/mathtest.zig +++ b/example/shared_library/mathtest.zig @@ -1,6 +1,3 @@ -comptime { - @export("add", add); -} -extern fn add(a: i32, b: i32) -> i32 { +export fn add(a: i32, b: i32) -> i32 { a + b } diff --git a/src/all_types.hpp b/src/all_types.hpp index 1ec3c809a2..28477c8107 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1285,7 +1285,6 @@ enum BuiltinFnId { BuiltinFnIdSetAlignStack, BuiltinFnIdArgType, BuiltinFnIdExport, - BuiltinFnIdExportWithLinkage, }; struct BuiltinFnEntry { diff --git a/src/ast_render.cpp b/src/ast_render.cpp index fc01c79ca6..c22c16d90a 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -111,6 +111,10 @@ static const char *extern_string(bool is_extern) { return is_extern ? "extern " : ""; } +static const char *export_string(bool is_export) { + return is_export ? "export " : ""; +} + //static const char *calling_convention_string(CallingConvention cc) { // switch (cc) { // case CallingConventionUnspecified: return ""; @@ -410,8 +414,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { { const char *pub_str = visib_mod_string(node->data.fn_proto.visib_mod); const char *extern_str = extern_string(node->data.fn_proto.is_extern); + const char *export_str = export_string(node->data.fn_proto.is_export); const char *inline_str = inline_string(node->data.fn_proto.is_inline); - fprintf(ar->f, "%s%s%sfn", pub_str, inline_str, extern_str); + fprintf(ar->f, "%s%s%s%sfn", pub_str, inline_str, export_str, extern_str); if (node->data.fn_proto.name != nullptr) { fprintf(ar->f, " "); print_symbol(ar, node->data.fn_proto.name); diff --git a/src/codegen.cpp b/src/codegen.cpp index 5be26eb445..7fe4f95f85 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5016,8 +5016,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdOpaqueType, "OpaqueType", 0); create_builtin_fn(g, BuiltinFnIdSetAlignStack, "setAlignStack", 1); create_builtin_fn(g, BuiltinFnIdArgType, "ArgType", 2); - create_builtin_fn(g, BuiltinFnIdExport, "export", 2); - create_builtin_fn(g, BuiltinFnIdExportWithLinkage, "exportWithLinkage", 3); + create_builtin_fn(g, BuiltinFnIdExport, "export", 3); } static const char *bool_to_str(bool b) { diff --git a/src/ir.cpp b/src/ir.cpp index 4afc1a2c60..7bd045bd92 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -4739,7 +4739,6 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_arg_type(irb, scope, node, arg0_value, arg1_value); } case BuiltinFnIdExport: - case BuiltinFnIdExportWithLinkage: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); @@ -4751,15 +4750,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo if (arg1_value == irb->codegen->invalid_instruction) return arg1_value; - IrInstruction *arg2_value; - if (builtin_fn->id == BuiltinFnIdExportWithLinkage) { - AstNode *arg2_node = node->data.fn_call_expr.params.at(2); - arg2_value = ir_gen_node(irb, arg2_node, scope); - if (arg2_value == irb->codegen->invalid_instruction) - return arg2_value; - } else { - arg2_value = nullptr; - } + AstNode *arg2_node = node->data.fn_call_expr.params.at(2); + IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope); + if (arg2_value == irb->codegen->invalid_instruction) + return arg2_value; return ir_build_export(irb, scope, node, arg0_value, arg1_value, arg2_value); } diff --git a/src/parser.cpp b/src/parser.cpp index cc337471ba..579fe85f3b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -740,9 +740,21 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo return node; } else if (token->id == TokenIdAtSign) { *token_index += 1; - Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol); + Token *name_tok = &pc->tokens->at(*token_index); + Buf *name_buf; + if (name_tok->id == TokenIdKeywordExport) { + name_buf = buf_create_from_str("export"); + *token_index += 1; + } else if (name_tok->id == TokenIdSymbol) { + name_buf = token_buf(name_tok); + *token_index += 1; + } else { + ast_expect_token(pc, name_tok, TokenIdSymbol); + zig_unreachable(); + } + AstNode *name_node = ast_create_node(pc, NodeTypeSymbol, name_tok); - name_node->data.symbol_expr.symbol = token_buf(name_tok); + name_node->data.symbol_expr.symbol = name_buf; AstNode *node = ast_create_node(pc, NodeTypeFnCallExpr, token); node->data.fn_call_expr.fn_ref_expr = name_node; @@ -2254,12 +2266,22 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn); cc = CallingConventionStdcall; } else if (first_token->id == TokenIdKeywordExtern) { + is_extern = true; *token_index += 1; - fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn); + Token *next_token = &pc->tokens->at(*token_index); + if (next_token->id == TokenIdKeywordFn) { + fn_token = next_token; + *token_index += 1; + } else if (mandatory) { + ast_expect_token(pc, next_token, TokenIdKeywordFn); + zig_unreachable(); + } else { + *token_index -= 1; + return nullptr; + } cc = CallingConventionC; } else if (first_token->id == TokenIdKeywordFn) { fn_token = first_token; - is_extern = true; *token_index += 1; cc = CallingConventionUnspecified; } else if (mandatory) { diff --git a/src/translate_c.cpp b/src/translate_c.cpp index 77afc38a51..eba594e085 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -73,6 +73,7 @@ struct Context { ImportTableEntry *import; ZigList *errors; VisibMod visib_mod; + bool want_export; AstNode *root; HashMap decl_table; HashMap macro_table; @@ -3250,8 +3251,8 @@ static void visit_fn_decl(Context *c, const FunctionDecl *fn_decl) { StorageClass sc = fn_decl->getStorageClass(); if (sc == SC_None) { - // TODO add export decl proto_node->data.fn_proto.visib_mod = c->visib_mod; + proto_node->data.fn_proto.is_export = fn_decl->hasBody() ? c->want_export : false; } else if (sc == SC_Extern || sc == SC_Static) { proto_node->data.fn_proto.visib_mod = c->visib_mod; } else if (sc == SC_PrivateExtern) { @@ -4274,8 +4275,10 @@ int parse_h_file(ImportTableEntry *import, ZigList *errors, const ch c->errors = errors; if (buf_ends_with_str(buf_create_from_str(target_file), ".h")) { c->visib_mod = VisibModPub; + c->want_export = false; } else { c->visib_mod = VisibModPub; + c->want_export = true; } c->decl_table.init(8); c->macro_table.init(8); diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index ee63467305..177e245400 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -8,12 +8,13 @@ const builtin = @import("builtin"); var argc_ptr: &usize = undefined; comptime { + const strong_linkage = builtin.GlobalLinkage.Strong; if (builtin.link_libc) { - @export("main", main); + @export("main", main, strong_linkage); } else if (builtin.os == builtin.Os.windows) { - @export("WinMainCRTStartup", WinMainCRTStartup); + @export("WinMainCRTStartup", WinMainCRTStartup, strong_linkage); } else { - @export("_start", _start); + @export("_start", _start, strong_linkage); } } diff --git a/std/special/builtin.zig b/std/special/builtin.zig index 9ace9b46ca..a2455a9690 100644 --- a/std/special/builtin.zig +++ b/std/special/builtin.zig @@ -13,24 +13,10 @@ pub coldcc fn panic(msg: []const u8) -> noreturn { } } -comptime { - @export("memset", memset); - @export("memcpy", memcpy); - @export("fmodf", fmodf); - @export("fmod", fmod); - @export("floorf", floorf); - @export("ceilf", ceilf); - @export("floor", floor); - @export("ceil", ceil); - if (builtin.mode != builtin.Mode.ReleaseFast and builtin.os != builtin.Os.windows) { - @export("__stack_chk_fail", __stack_chk_fail); - } -} - // Note that memset does not return `dest`, like the libc API. // The semantics of memset is dictated by the corresponding // LLVM intrinsics, not by the libc API. -extern fn memset(dest: ?&u8, c: u8, n: usize) { +export fn memset(dest: ?&u8, c: u8, n: usize) { @setDebugSafety(this, false); var index: usize = 0; @@ -41,7 +27,7 @@ extern fn memset(dest: ?&u8, c: u8, n: usize) { // Note that memcpy does not return `dest`, like the libc API. // The semantics of memcpy is dictated by the corresponding // LLVM intrinsics, not by the libc API. -extern fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) { +export fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) { @setDebugSafety(this, false); var index: usize = 0; @@ -49,21 +35,26 @@ extern fn memcpy(noalias dest: ?&u8, noalias src: ?&const u8, n: usize) { (??dest)[index] = (??src)[index]; } +comptime { + if (builtin.mode != builtin.Mode.ReleaseFast and builtin.os != builtin.Os.windows) { + @export("__stack_chk_fail", __stack_chk_fail, builtin.GlobalLinkage.Strong); + } +} extern fn __stack_chk_fail() -> noreturn { @panic("stack smashing detected"); } const math = @import("../math/index.zig"); -extern fn fmodf(x: f32, y: f32) -> f32 { generic_fmod(f32, x, y) } -extern fn fmod(x: f64, y: f64) -> f64 { generic_fmod(f64, x, y) } +export fn fmodf(x: f32, y: f32) -> f32 { generic_fmod(f32, x, y) } +export fn fmod(x: f64, y: f64) -> f64 { generic_fmod(f64, x, y) } // TODO add intrinsics for these (and probably the double version too) // and have the math stuff use the intrinsic. same as @mod and @rem -extern fn floorf(x: f32) -> f32 { math.floor(x) } -extern fn ceilf(x: f32) -> f32 { math.ceil(x) } -extern fn floor(x: f64) -> f64 { math.floor(x) } -extern fn ceil(x: f64) -> f64 { math.ceil(x) } +export fn floorf(x: f32) -> f32 { math.floor(x) } +export fn ceilf(x: f32) -> f32 { math.ceil(x) } +export fn floor(x: f64) -> f64 { math.floor(x) } +export fn ceil(x: f64) -> f64 { math.ceil(x) } fn generic_fmod(comptime T: type, x: T, y: T) -> T { @setDebugSafety(this, false); diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index 717c6934f5..fab7b7c878 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -5,62 +5,62 @@ comptime { const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak; const strong_linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong; - @exportWithLinkage("__letf2", @import("comparetf2.zig").__letf2, linkage); - @exportWithLinkage("__getf2", @import("comparetf2.zig").__getf2, linkage); + @export("__letf2", @import("comparetf2.zig").__letf2, linkage); + @export("__getf2", @import("comparetf2.zig").__getf2, linkage); if (!is_test) { // only create these aliases when not testing - @exportWithLinkage("__cmptf2", @import("comparetf2.zig").__letf2, linkage); - @exportWithLinkage("__eqtf2", @import("comparetf2.zig").__letf2, linkage); - @exportWithLinkage("__lttf2", @import("comparetf2.zig").__letf2, linkage); - @exportWithLinkage("__netf2", @import("comparetf2.zig").__letf2, linkage); - @exportWithLinkage("__gttf2", @import("comparetf2.zig").__getf2, linkage); + @export("__cmptf2", @import("comparetf2.zig").__letf2, linkage); + @export("__eqtf2", @import("comparetf2.zig").__letf2, linkage); + @export("__lttf2", @import("comparetf2.zig").__letf2, linkage); + @export("__netf2", @import("comparetf2.zig").__letf2, linkage); + @export("__gttf2", @import("comparetf2.zig").__getf2, linkage); } - @exportWithLinkage("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); + @export("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); - @exportWithLinkage("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); - @exportWithLinkage("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); - @exportWithLinkage("__fixunssfti", @import("fixunssfti.zig").__fixunssfti, linkage); + @export("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); + @export("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); + @export("__fixunssfti", @import("fixunssfti.zig").__fixunssfti, linkage); - @exportWithLinkage("__fixunsdfsi", @import("fixunsdfsi.zig").__fixunsdfsi, linkage); - @exportWithLinkage("__fixunsdfdi", @import("fixunsdfdi.zig").__fixunsdfdi, linkage); - @exportWithLinkage("__fixunsdfti", @import("fixunsdfti.zig").__fixunsdfti, linkage); + @export("__fixunsdfsi", @import("fixunsdfsi.zig").__fixunsdfsi, linkage); + @export("__fixunsdfdi", @import("fixunsdfdi.zig").__fixunsdfdi, linkage); + @export("__fixunsdfti", @import("fixunsdfti.zig").__fixunsdfti, linkage); - @exportWithLinkage("__fixunstfsi", @import("fixunstfsi.zig").__fixunstfsi, linkage); - @exportWithLinkage("__fixunstfdi", @import("fixunstfdi.zig").__fixunstfdi, linkage); - @exportWithLinkage("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage); + @export("__fixunstfsi", @import("fixunstfsi.zig").__fixunstfsi, linkage); + @export("__fixunstfdi", @import("fixunstfdi.zig").__fixunstfdi, linkage); + @export("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage); - @exportWithLinkage("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage); - @exportWithLinkage("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage); + @export("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage); + @export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage); - @exportWithLinkage("__udivti3", @import("udivti3.zig").__udivti3, linkage); - @exportWithLinkage("__umodti3", @import("umodti3.zig").__umodti3, linkage); + @export("__udivti3", @import("udivti3.zig").__udivti3, linkage); + @export("__umodti3", @import("umodti3.zig").__umodti3, linkage); - @exportWithLinkage("__udivsi3", __udivsi3, linkage); - @exportWithLinkage("__udivdi3", __udivdi3, linkage); - @exportWithLinkage("__umoddi3", __umoddi3, linkage); - @exportWithLinkage("__udivmodsi4", __udivmodsi4, linkage); + @export("__udivsi3", __udivsi3, linkage); + @export("__udivdi3", __udivdi3, linkage); + @export("__umoddi3", __umoddi3, linkage); + @export("__udivmodsi4", __udivmodsi4, linkage); if (isArmArch()) { - @exportWithLinkage("__aeabi_uldivmod", __aeabi_uldivmod, linkage); - @exportWithLinkage("__aeabi_uidivmod", __aeabi_uidivmod, linkage); - @exportWithLinkage("__aeabi_uidiv", __udivsi3, linkage); + @export("__aeabi_uldivmod", __aeabi_uldivmod, linkage); + @export("__aeabi_uidivmod", __aeabi_uidivmod, linkage); + @export("__aeabi_uidiv", __udivsi3, linkage); } if (builtin.os == builtin.Os.windows) { switch (builtin.arch) { builtin.Arch.i386 => { if (!builtin.link_libc) { - @exportWithLinkage("_chkstk", _chkstk, strong_linkage); - @exportWithLinkage("__chkstk_ms", __chkstk_ms, linkage); + @export("_chkstk", _chkstk, strong_linkage); + @export("__chkstk_ms", __chkstk_ms, linkage); } - @exportWithLinkage("_aulldiv", @import("aulldiv.zig")._aulldiv, strong_linkage); - @exportWithLinkage("_aullrem", @import("aullrem.zig")._aullrem, strong_linkage); + @export("_aulldiv", @import("aulldiv.zig")._aulldiv, strong_linkage); + @export("_aullrem", @import("aullrem.zig")._aullrem, strong_linkage); }, builtin.Arch.x86_64 => { if (!builtin.link_libc) { - @exportWithLinkage("__chkstk", __chkstk, strong_linkage); - @exportWithLinkage("___chkstk_ms", ___chkstk_ms, linkage); + @export("__chkstk", __chkstk, strong_linkage); + @export("___chkstk_ms", ___chkstk_ms, linkage); } }, else => {}, diff --git a/test/cases/asm.zig b/test/cases/asm.zig index d7b88bd69e..8a3020fe23 100644 --- a/test/cases/asm.zig +++ b/test/cases/asm.zig @@ -2,24 +2,23 @@ const config = @import("builtin"); const assert = @import("std").debug.assert; comptime { - @export("derp", derp); if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { asm volatile ( - \\.globl my_aoeu_symbol_asdf; - \\.type my_aoeu_symbol_asdf, @function; - \\.set my_aoeu_symbol_asdf, derp; + \\.globl aoeu; + \\.type aoeu, @function; + \\.set aoeu, derp; ); } } test "module level assembly" { if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { - assert(my_aoeu_symbol_asdf() == 1234); + assert(aoeu() == 1234); } } -extern fn my_aoeu_symbol_asdf() -> i32; +extern fn aoeu() -> i32; -extern fn derp() -> i32 { +export fn derp() -> i32 { return 1234; } diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 1511c84b0c..daa7c3eb98 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -13,8 +13,9 @@ test "empty function with comments" { } comptime { - @exportWithLinkage("disabledExternFn", disabledExternFn, builtin.GlobalLinkage.Internal) + @export("disabledExternFn", disabledExternFn, builtin.GlobalLinkage.Internal); } + extern fn disabledExternFn() { } @@ -535,10 +536,7 @@ var global_ptr = &gdt[0]; // can't really run this test but we can make sure it has no compile error // and generates code const vram = @intToPtr(&volatile u8, 0x20000000)[0..0x8000]; -comptime { - @export("writeToVRam", writeToVRam); -} -extern fn writeToVRam() { +export fn writeToVRam() { vram[0] = 'X'; } @@ -562,15 +560,3 @@ fn hereIsAnOpaqueType(ptr: &OpaqueA) -> &OpaqueA { var a = ptr; return a; } - -test "function and variable in weird section" { - if (builtin.os == builtin.Os.linux or builtin.os == builtin.Os.windows) { - // macos can't handle this - assert(fnInWeirdSection() == 1234); - } -} - -var varInWeirdSection: i32 section(".data2") = 1234; -fn fnInWeirdSection() section(".text2") -> i32 { - return varInWeirdSection; -} diff --git a/test/compare_output.zig b/test/compare_output.zig index 4829a39fb6..ad9c91ff20 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -4,8 +4,7 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompareOutputContext) { cases.addC("hello world with libc", \\const c = @cImport(@cInclude("stdio.h")); - \\comptime { @export("main", main); } - \\extern fn main(argc: c_int, argv: &&u8) -> c_int { + \\export fn main(argc: c_int, argv: &&u8) -> c_int { \\ _ = c.puts(c"Hello, world!"); \\ return 0; \\} @@ -138,8 +137,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ @cInclude("stdio.h"); \\}); \\ - \\comptime { @export("main", main); } - \\extern fn main(argc: c_int, argv: &&u8) -> c_int { + \\export fn main(argc: c_int, argv: &&u8) -> c_int { \\ if (is_windows) { \\ // we want actual \n, not \r\n \\ _ = c._setmode(1, c._O_BINARY); @@ -284,10 +282,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { cases.addC("expose function pointer to C land", \\const c = @cImport(@cInclude("stdlib.h")); \\ - \\comptime { - \\ @export("main", main); - \\} - \\extern fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int { + \\export fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int { \\ const a_int = @ptrCast(&align(1) i32, a ?? unreachable); \\ const b_int = @ptrCast(&align(1) i32, b ?? unreachable); \\ if (*a_int < *b_int) { @@ -299,7 +294,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ } \\} \\ - \\extern fn main() -> c_int { + \\export fn main() -> c_int { \\ var array = []u32 { 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 }; \\ \\ c.qsort(@ptrCast(&c_void, &array[0]), c_ulong(array.len), @sizeOf(i32), compare_fn); @@ -327,8 +322,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ @cInclude("stdio.h"); \\}); \\ - \\comptime { @export("main", main); } - \\extern fn main(argc: c_int, argv: &&u8) -> c_int { + \\export fn main(argc: c_int, argv: &&u8) -> c_int { \\ if (is_windows) { \\ // we want actual \n, not \r\n \\ _ = c._setmode(1, c._O_BINARY); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 8aa57c4468..22520802fb 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,328 +1,253 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) { - cases.add("wrong return type for main", - \\pub fn main() { } - , ".tmp_source.zig:1:15: error: expected return type of main to be '%void', instead is 'void'"); - - cases.add("double ?? on main return value", - \\pub fn main() -> ??void { - \\} - , ".tmp_source.zig:1:18: error: expected return type of main to be '%void', instead is '??void'"); - - cases.add("setting a section on an extern variable", - \\extern var foo: i32 section(".text2"); - \\extern fn entry() -> i32 { - \\ return foo; - \\} - \\comptime { @export("entry", entry); } - , - ".tmp_source.zig:1:29: error: cannot set section of external variable 'foo'"); - - cases.add("setting a section on a local variable", - \\extern fn entry() -> i32 { - \\ var foo: i32 section(".text2") = 1234; - \\ return foo; - \\} - \\comptime { @export("entry", entry); } - , - ".tmp_source.zig:2:26: error: cannot set section of local variable 'foo'"); - - cases.add("setting a section on an extern fn", - \\extern fn foo() section(".text2"); - \\extern fn entry() { - \\ foo(); - \\} - \\comptime { @export("entry", entry); } - , - ".tmp_source.zig:1:25: error: cannot set section of external function 'foo'"); - - cases.add("wrong types given to exportWithLinkage", - \\extern fn entry() { } - \\comptime { - \\ @exportWithLinkage("entry", entry, u32(1234)); - \\} - , - ".tmp_source.zig:3:43: error: expected type 'GlobalLinkage', found 'u32'"); - cases.add("implicit semicolon - block statement", - \\extern fn entry() { + \\export fn entry() { \\ {} \\ var good = {}; \\ ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - block expr", - \\extern fn entry() { + \\export fn entry() { \\ _ = {}; \\ var good = {}; \\ _ = {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - comptime statement", - \\extern fn entry() { + \\export fn entry() { \\ comptime {} \\ var good = {}; \\ comptime ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - comptime expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = comptime {}; \\ var good = {}; \\ _ = comptime {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - defer", - \\extern fn entry() { + \\export fn entry() { \\ defer {} \\ var good = {}; \\ defer ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: expected token ';', found 'var'"); cases.add("implicit semicolon - if statement", - \\extern fn entry() { + \\export fn entry() { \\ if(true) {} \\ var good = {}; \\ if(true) ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = if(true) {}; \\ var good = {}; \\ _ = if(true) {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else statement", - \\extern fn entry() { + \\export fn entry() { \\ if(true) {} else {} \\ var good = {}; \\ if(true) ({}) else ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = if(true) {} else {}; \\ var good = {}; \\ _ = if(true) {} else {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if statement", - \\extern fn entry() { + \\export fn entry() { \\ if(true) {} else if(true) {} \\ var good = {}; \\ if(true) ({}) else if(true) ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = if(true) {} else if(true) {}; \\ var good = {}; \\ _ = if(true) {} else if(true) {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if-else statement", - \\extern fn entry() { + \\export fn entry() { \\ if(true) {} else if(true) {} else {} \\ var good = {}; \\ if(true) ({}) else if(true) ({}) else ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - if-else-if-else expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = if(true) {} else if(true) {} else {}; \\ var good = {}; \\ _ = if(true) {} else if(true) {} else {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - test statement", - \\extern fn entry() { + \\export fn entry() { \\ if (foo()) |_| {} \\ var good = {}; \\ if (foo()) |_| ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - test expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = if (foo()) |_| {}; \\ var good = {}; \\ _ = if (foo()) |_| {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while statement", - \\extern fn entry() { + \\export fn entry() { \\ while(true) {} \\ var good = {}; \\ while(true) ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = while(true) {}; \\ var good = {}; \\ _ = while(true) {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while-continue statement", - \\extern fn entry() { + \\export fn entry() { \\ while(true):({}) {} \\ var good = {}; \\ while(true):({}) ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - while-continue expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = while(true):({}) {}; \\ var good = {}; \\ _ = while(true):({}) {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - for statement", - \\extern fn entry() { + \\export fn entry() { \\ for(foo()) {} \\ var good = {}; \\ for(foo()) ({}) \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("implicit semicolon - for expression", - \\extern fn entry() { + \\export fn entry() { \\ _ = for(foo()) {}; \\ var good = {}; \\ _ = for(foo()) {} \\ var bad = {}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: invalid token: 'var'"); cases.add("multiple function definitions", \\fn a() {} \\fn a() {} - \\comptime {@export("entry", entry);} - \\extern fn entry() { a(); } + \\export fn entry() { a(); } , ".tmp_source.zig:2:1: error: redefinition of 'a'"); cases.add("unreachable with return", \\fn a() -> noreturn {return;} - \\comptime {@export("entry", entry);} - \\extern fn entry() { a(); } + \\export fn entry() { a(); } , ".tmp_source.zig:1:21: error: expected type 'noreturn', found 'void'"); cases.add("control reaches end of non-void function", \\fn a() -> i32 {} - \\comptime {@export("entry", entry);} - \\extern fn entry() { _ = a(); } + \\export fn entry() { _ = a(); } , ".tmp_source.zig:1:15: error: expected type 'i32', found 'void'"); cases.add("undefined function call", - \\extern fn a() { + \\export fn a() { \\ b(); \\} - \\comptime {@export("a", a);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'"); cases.add("wrong number of arguments", - \\extern fn a() { + \\export fn a() { \\ b(1); \\} \\fn b(a: i32, b: i32, c: i32) { } - \\comptime {@export("a", a);} , ".tmp_source.zig:2:6: error: expected 3 arguments, found 1"); cases.add("invalid type", \\fn a() -> bogus {} - \\comptime {@export("entry", entry);} - \\extern fn entry() { _ = a(); } + \\export fn entry() { _ = a(); } , ".tmp_source.zig:1:11: error: use of undeclared identifier 'bogus'"); cases.add("pointer to unreachable", \\fn a() -> &noreturn {} - \\comptime {@export("entry", entry);} - \\extern fn entry() { _ = a(); } + \\export fn entry() { _ = a(); } , ".tmp_source.zig:1:12: error: pointer to unreachable not allowed"); cases.add("unreachable code", - \\extern fn a() { + \\export fn a() { \\ return; \\ b(); \\} \\ \\fn b() {} - \\comptime {@export("a", a);} , ".tmp_source.zig:3:5: error: unreachable code"); cases.add("bad import", \\const bogus = @import("bogus-does-not-exist.zig"); - \\comptime {@export("entry", entry);} - \\extern fn entry() { bogus.bogo(); } + \\export fn entry() { bogus.bogo(); } , ".tmp_source.zig:1:15: error: unable to find 'bogus-does-not-exist.zig'"); cases.add("undeclared identifier", - \\extern fn a() { + \\export fn a() { \\ b + \\ c \\} - \\comptime {@export("a", a);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'", ".tmp_source.zig:3:5: error: use of undeclared identifier 'c'"); @@ -330,114 +255,99 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("parameter redeclaration", \\fn f(a : i32, a : i32) { \\} - \\comptime {@export("entry", entry);} - \\extern fn entry() { f(1, 2); } + \\export fn entry() { f(1, 2); } , ".tmp_source.zig:1:15: error: redeclaration of variable 'a'"); cases.add("local variable redeclaration", - \\extern fn f() { + \\export fn f() { \\ const a : i32 = 0; \\ const a = 0; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:3:5: error: redeclaration of variable 'a'"); cases.add("local variable redeclares parameter", \\fn f(a : i32) { \\ const a = 0; \\} - \\comptime {@export("entry", entry);} - \\extern fn entry() { f(1); } + \\export fn entry() { f(1); } , ".tmp_source.zig:2:5: error: redeclaration of variable 'a'"); cases.add("variable has wrong type", - \\extern fn f() -> i32 { + \\export fn f() -> i32 { \\ const a = c"a"; \\ a \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:3:5: error: expected type 'i32', found '&const u8'"); cases.add("if condition is bool, not int", - \\extern fn f() { + \\export fn f() { \\ if (0) {} \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:9: error: integer value 0 cannot be implicitly casted to type 'bool'"); cases.add("assign unreachable", - \\extern fn f() { + \\export fn f() { \\ const a = return; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: unreachable code"); cases.add("unreachable variable", - \\extern fn f() { + \\export fn f() { \\ const a: noreturn = {}; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:14: error: variable of type 'noreturn' not allowed"); cases.add("unreachable parameter", \\fn f(a: noreturn) {} - \\comptime {@export("entry", entry);} - \\extern fn entry() { f(); } + \\export fn entry() { f(); } , ".tmp_source.zig:1:9: error: parameter of type 'noreturn' not allowed"); cases.add("bad assignment target", - \\extern fn f() { + \\export fn f() { \\ 3 = 3; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:7: error: cannot assign to constant"); cases.add("assign to constant variable", - \\extern fn f() { + \\export fn f() { \\ const a = 3; \\ a = 4; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:3:7: error: cannot assign to constant"); cases.add("use of undeclared identifier", - \\extern fn f() { + \\export fn f() { \\ b = 3; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'"); cases.add("const is a statement, not an expression", - \\extern fn f() { + \\export fn f() { \\ (const a = 0); \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:6: error: invalid token: 'const'"); cases.add("array access of undeclared identifier", - \\extern fn f() { + \\export fn f() { \\ i[i] = i[i]; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: use of undeclared identifier 'i'", ".tmp_source.zig:2:12: error: use of undeclared identifier 'i'"); cases.add("array access of non array", - \\extern fn f() { + \\export fn f() { \\ var bad : bool = undefined; \\ bad[bad] = bad[bad]; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:3:8: error: array access of non-array type 'bool'", ".tmp_source.zig:3:19: error: array access of non-array type 'bool'"); cases.add("array access with non integer index", - \\extern fn f() { + \\export fn f() { \\ var array = "aoeu"; \\ var bad = false; \\ array[bad] = array[bad]; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:4:11: error: expected type 'usize', found 'bool'", ".tmp_source.zig:4:24: error: expected type 'usize', found 'bool'"); @@ -446,8 +356,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn f() { \\ x = 1; \\} - \\extern fn entry() { f(); } - \\comptime {@export("entry", entry);} + \\export fn entry() { f(); } , ".tmp_source.zig:3:7: error: cannot assign to constant"); @@ -456,33 +365,29 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ const x : i32 = if (b) { 1 }; \\ const y = if (b) { i32(1) }; \\} - \\extern fn entry() { f(true); } - \\comptime {@export("entry", entry);} + \\export fn entry() { f(true); } , ".tmp_source.zig:2:30: error: integer value 1 cannot be implicitly casted to type 'void'", ".tmp_source.zig:3:15: error: incompatible types: 'i32' and 'void'"); cases.add("direct struct loop", \\const A = struct { a : A, }; - \\extern fn entry() -> usize { @sizeOf(A) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(A) } , ".tmp_source.zig:1:11: error: struct 'A' contains itself"); cases.add("indirect struct loop", \\const A = struct { b : B, }; \\const B = struct { c : C, }; \\const C = struct { a : A, }; - \\extern fn entry() -> usize { @sizeOf(A) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(A) } , ".tmp_source.zig:1:11: error: struct 'A' contains itself"); cases.add("invalid struct field", \\const A = struct { x : i32, }; - \\extern fn f() { + \\export fn f() { \\ var a : A = undefined; \\ a.foo = 1; \\ const y = a.bar; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:4:6: error: no member named 'foo' in struct 'A'", ".tmp_source.zig:5:16: error: no member named 'bar' in struct 'A'"); @@ -510,7 +415,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ y : i32, \\ z : i32, \\}; - \\extern fn f() { + \\export fn f() { \\ const a = A { \\ .z = 1, \\ .y = 2, @@ -518,7 +423,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ .z = 4, \\ }; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:11:9: error: duplicate field"); cases.add("missing field in struct value expression", @@ -527,7 +431,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ y : i32, \\ z : i32, \\}; - \\extern fn f() { + \\export fn f() { \\ // we want the error on the '{' not the 'A' because \\ // the A could be a complicated expression \\ const a = A { @@ -535,7 +439,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ .y = 2, \\ }; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:9:17: error: missing field: 'x'"); cases.add("invalid field in struct value expression", @@ -544,79 +447,69 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ y : i32, \\ z : i32, \\}; - \\extern fn f() { + \\export fn f() { \\ const a = A { \\ .z = 4, \\ .y = 2, \\ .foo = 42, \\ }; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:10:9: error: no member named 'foo' in struct 'A'"); cases.add("invalid break expression", - \\extern fn f() { + \\export fn f() { \\ break; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: break expression outside loop"); cases.add("invalid continue expression", - \\extern fn f() { + \\export fn f() { \\ continue; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: continue expression outside loop"); cases.add("invalid maybe type", - \\extern fn f() { + \\export fn f() { \\ if (true) |x| { } \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:9: error: expected nullable type, found 'bool'"); cases.add("cast unreachable", \\fn f() -> i32 { \\ i32(return 1) \\} - \\extern fn entry() { _ = f(); } - \\comptime {@export("entry", entry);} + \\export fn entry() { _ = f(); } , ".tmp_source.zig:2:8: error: unreachable code"); cases.add("invalid builtin fn", \\fn f() -> @bogus(foo) { \\} - \\extern fn entry() { _ = f(); } - \\comptime {@export("entry", entry);} + \\export fn entry() { _ = f(); } , ".tmp_source.zig:1:11: error: invalid builtin function: 'bogus'"); cases.add("top level decl dependency loop", \\const a : @typeOf(b) = 0; \\const b : @typeOf(a) = 0; - \\extern fn entry() { + \\export fn entry() { \\ const c = a + b; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:1: error: 'a' depends on itself"); cases.add("noalias on non pointer param", \\fn f(noalias x: i32) {} - \\extern fn entry() { f(1234); } - \\comptime {@export("entry", entry);} + \\export fn entry() { f(1234); } , ".tmp_source.zig:1:6: error: noalias on non-pointer parameter"); cases.add("struct init syntax for array", \\const foo = []u16{.x = 1024,}; - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:1:18: error: type '[]u16' does not support struct initialization syntax"); cases.add("type variables must be constant", \\var foo = u8; - \\extern fn entry() -> foo { + \\export fn entry() -> foo { \\ return 1; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:1: error: variable of type 'type' must be constant"); @@ -628,10 +521,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ var Bar : i32 = undefined; \\} \\ - \\extern fn entry() { + \\export fn entry() { \\ f(1234); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:6: error: redefinition of 'Foo'", ".tmp_source.zig:1:1: note: previous definition is here", @@ -653,8 +545,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch"); cases.add("switch expression - duplicate enumeration prong", @@ -674,8 +565,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:13:15: error: duplicate switch value", ".tmp_source.zig:10:15: note: other value is here"); @@ -697,8 +587,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:13:15: error: duplicate switch value", ".tmp_source.zig:10:15: note: other value is here"); @@ -710,10 +599,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ else => true, \\ }; \\} - \\extern fn entry() { + \\export fn entry() { \\ f(1234); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:9: error: multiple else prongs in switch expression"); cases.add("switch expression - non exhaustive integer prongs", @@ -722,8 +610,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ 0 => {}, \\ } \\} - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:2:5: error: switch must handle all possibilities"); @@ -736,8 +623,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ 206 ... 255 => 3, \\ } \\} - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:6:9: error: duplicate switch value", ".tmp_source.zig:5:14: note: previous value is here"); @@ -749,16 +635,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\const y: u8 = 100; - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:2:5: error: else prong required when switching on type '&u8'"); cases.add("global variable initializer must be constant expression", \\extern fn foo() -> i32; \\const x = foo(); - \\extern fn entry() -> i32 { x } - \\comptime {@export("entry", entry);} + \\export fn entry() -> i32 { x } , ".tmp_source.zig:2:11: error: unable to evaluate constant expression"); cases.add("array concatenation with wrong type", @@ -766,8 +650,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const derp = usize(1234); \\const a = derp ++ "foo"; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } , ".tmp_source.zig:3:11: error: expected array or C string literal, found 'usize'"); cases.add("non compile time array concatenation", @@ -775,14 +658,12 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ s ++ "foo" \\} \\var s: [10]u8 = undefined; - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:2:5: error: unable to evaluate constant expression"); cases.add("@cImport with bogus include", \\const c = @cImport(@cInclude("bogus.h")); - \\extern fn entry() -> usize { @sizeOf(@typeOf(c.bogo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(c.bogo)) } , ".tmp_source.zig:1:11: error: C import failed", ".h:1:10: note: 'bogus.h' file not found"); @@ -790,20 +671,17 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const x = 3; \\const y = &x; \\fn foo() -> &const i32 { y } - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:3:26: error: expected type '&const i32', found '&const (integer literal)'"); cases.add("integer overflow error", \\const x : u8 = 300; - \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } , ".tmp_source.zig:1:16: error: integer value 300 cannot be implicitly casted to type 'u8'"); cases.add("incompatible number literals", \\const x = 2 == 2.0; - \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } , ".tmp_source.zig:1:11: error: integer value 2 cannot be implicitly casted to type '(float literal)'"); cases.add("missing function call param", @@ -829,15 +707,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ const result = members[index](); \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:20:34: error: expected 1 arguments, found 0"); cases.add("missing function name and param name", \\fn () {} \\fn f(i32) {} - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:1:1: error: missing function name", ".tmp_source.zig:2:6: error: missing parameter name"); @@ -847,20 +723,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn a() -> i32 {0} \\fn b() -> i32 {1} \\fn c() -> i32 {2} - \\extern fn entry() -> usize { @sizeOf(@typeOf(fns)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) } , ".tmp_source.zig:1:21: error: expected type 'fn()', found 'fn() -> i32'"); cases.add("extern function pointer mismatch", \\const fns = [](fn(i32)->i32){ a, b, c }; \\pub fn a(x: i32) -> i32 {x + 0} \\pub fn b(x: i32) -> i32 {x + 1} - \\extern fn c(x: i32) -> i32 {x + 2} + \\export fn c(x: i32) -> i32 {x + 2} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(fns)) } - \\ - \\comptime {@export("entry", entry);} - \\comptime {@export("c", c);} + \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) } , ".tmp_source.zig:1:37: error: expected type 'fn(i32) -> i32', found 'extern fn(i32) -> i32'"); @@ -868,16 +740,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const x : f64 = 1.0; \\const y : f32 = x; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } , ".tmp_source.zig:2:17: error: expected type 'f32', found 'f64'"); cases.add("colliding invalid top level functions", \\fn func() -> bogus {} \\fn func() -> bogus {} - \\extern fn entry() -> usize { @sizeOf(@typeOf(func)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(func)) } , ".tmp_source.zig:2:1: error: redefinition of 'func'", ".tmp_source.zig:1:14: error: use of undeclared identifier 'bogus'"); @@ -885,8 +755,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("bogus compile var", \\const x = @import("builtin").bogus; - \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } , ".tmp_source.zig:1:29: error: no member named 'bogus' in '"); @@ -897,8 +766,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\var global_var: usize = 1; \\fn get() -> usize { global_var } \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(Foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(Foo)) } , ".tmp_source.zig:5:21: error: unable to evaluate constant expression", ".tmp_source.zig:2:12: note: called from here", @@ -911,8 +779,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\}; \\const x = Foo {.field = 1} + Foo {.field = 2}; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } , ".tmp_source.zig:4:28: error: invalid operands to binary expression: 'Foo' and 'Foo'"); @@ -922,14 +789,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const int_x = u32(1) / u32(0); \\const float_x = f32(1.0) / f32(0.0); \\ - \\extern fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) } - \\extern fn entry2() -> usize { @sizeOf(@typeOf(lit_float_x)) } - \\extern fn entry3() -> usize { @sizeOf(@typeOf(int_x)) } - \\extern fn entry4() -> usize { @sizeOf(@typeOf(float_x)) } - \\comptime {@export("entry1", entry1);} - \\comptime {@export("entry2", entry2);} - \\comptime {@export("entry3", entry3);} - \\comptime {@export("entry4", entry4);} + \\export fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) } + \\export fn entry2() -> usize { @sizeOf(@typeOf(lit_float_x)) } + \\export fn entry3() -> usize { @sizeOf(@typeOf(int_x)) } + \\export fn entry4() -> usize { @sizeOf(@typeOf(float_x)) } , ".tmp_source.zig:1:21: error: division by zero is undefined", ".tmp_source.zig:2:25: error: division by zero is undefined", @@ -941,16 +804,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const foo = "a \\b"; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:1:13: error: newline not allowed in string literal"); cases.add("invalid comparison for function pointers", \\fn foo() {} \\const invalid = foo > foo; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(invalid)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(invalid)) } , ".tmp_source.zig:2:21: error: operator not allowed for type 'fn()'"); cases.add("generic function instance with non-constant expression", @@ -959,18 +820,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return foo(a, b); \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(test1)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(test1)) } , ".tmp_source.zig:3:16: error: unable to evaluate constant expression"); cases.add("goto jumping into block", - \\extern fn f() { + \\export fn f() { \\ { \\a_label: \\ } \\ goto a_label; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:5:5: error: no label in scope named 'a_label'"); cases.add("goto jumping past a defer", @@ -981,23 +840,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} \\fn derp(){} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:2:12: error: no label in scope named 'label'"); cases.add("assign null to non-nullable pointer", \\const a: &u8 = null; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } , ".tmp_source.zig:1:16: error: expected type '&u8', found '(null)'"); cases.add("indexing an array of size zero", \\const array = []u8{}; - \\extern fn foo() { + \\export fn foo() { \\ const pointer = &array[0]; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:27: error: index 0 outside array of size 0"); cases.add("compile time division by zero", @@ -1006,8 +862,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ 1 / x \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } , ".tmp_source.zig:3:7: error: division by zero is undefined", ".tmp_source.zig:1:14: note: called from here"); @@ -1015,8 +870,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("branch on undefined value", \\const x = if (undefined) true else false; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(x)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } , ".tmp_source.zig:1:15: error: use of undefined value"); @@ -1026,8 +880,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return fibbonaci(x - 1) + fibbonaci(x - 2); \\} \\ - \\comptime {@export("entry", entry);} - \\extern fn entry() -> usize { @sizeOf(@typeOf(seventh_fib_number)) } + \\export fn entry() -> usize { @sizeOf(@typeOf(seventh_fib_number)) } , ".tmp_source.zig:3:21: error: evaluation exceeded 1000 backwards branches", ".tmp_source.zig:3:21: note: called from here"); @@ -1035,8 +888,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("@embedFile with bogus file", \\const resource = @embedFile("bogus.txt"); \\ - \\comptime {@export("entry", entry);} - \\extern fn entry() -> usize { @sizeOf(@typeOf(resource)) } + \\export fn entry() -> usize { @sizeOf(@typeOf(resource)) } , ".tmp_source.zig:1:29: error: unable to find '", "bogus.txt'"); cases.add("non-const expression in struct literal outside function", @@ -1046,8 +898,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const a = Foo {.x = get_it()}; \\extern fn get_it() -> i32; \\ - \\comptime {@export("entry", entry);} - \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } , ".tmp_source.zig:4:21: error: unable to evaluate constant expression"); cases.add("non-const expression function call with struct return value outside function", @@ -1061,20 +912,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} \\var global_side_effect = false; \\ - \\comptime {@export("entry", entry);} - \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } , ".tmp_source.zig:6:24: error: unable to evaluate constant expression", ".tmp_source.zig:4:17: note: called from here"); cases.add("undeclared identifier error should mark fn as impure", - \\extern fn foo() { + \\export fn foo() { \\ test_a_thing(); \\} \\fn test_a_thing() { \\ bad_fn_call(); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:5: error: use of undeclared identifier 'bad_fn_call'"); cases.add("illegal comparison of types", @@ -1089,16 +938,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ *a == *b \\} \\ - \\extern fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) } - \\extern fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) } - \\comptime {@export("entry1", entry1);} - \\comptime {@export("entry2", entry2);} + \\export fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) } + \\export fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) } , ".tmp_source.zig:2:7: error: operator not allowed for type '[]u8'", ".tmp_source.zig:9:8: error: operator not allowed for type 'EnumWithData'"); cases.add("non-const switch number literal", - \\extern fn foo() { + \\export fn foo() { \\ const x = switch (bar()) { \\ 1, 2 => 1, \\ 3, 4 => 2, @@ -1108,25 +955,22 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar() -> i32 { \\ 2 \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: unable to infer expression type"); cases.add("atomic orderings of cmpxchg - failure stricter than success", \\const AtomicOrder = @import("builtin").AtomicOrder; - \\extern fn f() { + \\export fn f() { \\ var x: i32 = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Monotonic, AtomicOrder.SeqCst)) {} \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:4:72: error: failure atomic ordering must be no stricter than success"); cases.add("atomic orderings of cmpxchg - success Monotonic or stricter", \\const AtomicOrder = @import("builtin").AtomicOrder; - \\extern fn f() { + \\export fn f() { \\ var x: i32 = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Unordered, AtomicOrder.Unordered)) {} \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:4:49: error: success atomic ordering must be Monotonic or stricter"); cases.add("negation overflow in function evaluation", @@ -1135,8 +979,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ -x \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } , ".tmp_source.zig:3:5: error: negation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -1147,8 +990,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ a + b \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } , ".tmp_source.zig:3:7: error: operation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -1160,8 +1002,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ a - b \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } , ".tmp_source.zig:3:7: error: operation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -1172,8 +1013,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ a * b \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(y)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } , ".tmp_source.zig:3:7: error: operation caused overflow", ".tmp_source.zig:1:14: note: called from here"); @@ -1184,35 +1024,40 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ @truncate(i8, x) \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:3:19: error: expected signed integer type, found 'u32'"); cases.add("%return in function with non error return type", - \\extern fn f() { + \\export fn f() { \\ %return something(); \\} \\fn something() -> %void { } - \\comptime {@export("f", f);} , ".tmp_source.zig:2:5: error: expected type 'void', found 'error'"); + cases.add("wrong return type for main", + \\pub fn main() { } + , ".tmp_source.zig:1:15: error: expected return type of main to be '%void', instead is 'void'"); + + cases.add("double ?? on main return value", + \\pub fn main() -> ??void { + \\} + , ".tmp_source.zig:1:18: error: expected return type of main to be '%void', instead is '??void'"); + cases.add("invalid pointer for var type", \\extern fn ext() -> usize; \\var bytes: [ext()]u8 = undefined; - \\extern fn f() { + \\export fn f() { \\ for (bytes) |*b, i| { \\ *b = u8(i); \\ } \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:2:13: error: unable to evaluate constant expression"); cases.add("export function with comptime parameter", - \\extern fn foo(comptime x: i32, y: i32) -> i32{ + \\export fn foo(comptime x: i32, y: i32) -> i32{ \\ x + y \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:1:15: error: comptime parameter not allowed in function with calling convention 'ccc'"); cases.add("extern function with comptime parameter", @@ -1220,16 +1065,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn f() -> i32 { \\ foo(1, 2) \\} - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:1:15: error: comptime parameter not allowed in function with calling convention 'ccc'"); cases.add("convert fixed size array to slice with invalid size", - \\extern fn f() { + \\export fn f() { \\ var array: [5]u8 = undefined; \\ var foo = ([]const u32)(array)[0]; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:3:28: error: unable to convert [5]u8 to []const u32: size mismatch"); cases.add("non-pure function returns type", @@ -1247,11 +1090,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\extern fn function_with_return_type_type() { + \\export fn function_with_return_type_type() { \\ var list: List(i32) = undefined; \\ list.length = 10; \\} - \\comptime {@export("function_with_return_type_type", function_with_return_type_type);} , ".tmp_source.zig:3:7: error: unable to evaluate constant expression", ".tmp_source.zig:16:19: note: called from here"); @@ -1260,8 +1102,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn f(m: []const u8) { \\ m.copy(u8, self[0..], m); \\} - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:3:6: error: no member named 'copy' in '[]const u8'"); cases.add("wrong number of arguments for method fn call", @@ -1272,24 +1113,21 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ \\ foo.method(1, 2); \\} - \\extern fn entry() -> usize { @sizeOf(@typeOf(f)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } , ".tmp_source.zig:6:15: error: expected 2 arguments, found 3"); cases.add("assign through constant pointer", - \\extern fn f() { + \\export fn f() { \\ var cstr = c"Hat"; \\ cstr[0] = 'W'; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:3:11: error: cannot assign to constant"); cases.add("assign through constant slice", - \\extern fn f() { + \\export fn f() { \\ var cstr: []const u8 = "Hat"; \\ cstr[0] = 'W'; \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:3:11: error: cannot assign to constant"); cases.add("main function with bogus args type", @@ -1300,8 +1138,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn foo(blah: []u8) { \\ for (blah) { } \\} - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:2:5: error: for loop expression missing element parameter"); cases.add("misspelled type with pointer only reference", @@ -1334,8 +1171,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ var jd = JsonNode {.kind = JsonType.JSONArray , .jobject = JsonOA.JSONArray {jll} }; \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:5:16: error: use of undeclared identifier 'JsonList'"); cases.add("method call with first arg type primitive", @@ -1349,12 +1185,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\}; \\ - \\extern fn f() { + \\export fn f() { \\ const derp = Foo.init(3); \\ \\ derp.init(); \\} - \\comptime {@export("f", f);} , ".tmp_source.zig:14:5: error: expected type 'i32', found '&const Foo'"); cases.add("method call with first arg type wrong container", @@ -1378,11 +1213,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ field: i32, \\}; \\ - \\extern fn foo() { + \\export fn foo() { \\ var x = List.init(&global_allocator); \\ x.init(); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:23:5: error: expected type '&Allocator', found '&List'"); cases.add("binary not on number literal", @@ -1390,18 +1224,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const TINY_QUANTUM_SIZE = 1 << TINY_QUANTUM_SHIFT; \\var block_aligned_stuff: usize = (4 + TINY_QUANTUM_SIZE) & ~(TINY_QUANTUM_SIZE - 1); \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(block_aligned_stuff)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(block_aligned_stuff)) } , ".tmp_source.zig:3:60: error: unable to perform binary not operation on type '(integer literal)'"); cases.addCase({ const tc = cases.create("multiple files with private function error", \\const foo = @import("foo.zig"); \\ - \\extern fn callPrivFunction() { + \\export fn callPrivFunction() { \\ foo.privateFunction(); \\} - \\comptime {@export("callPrivFunction", callPrivFunction);} , ".tmp_source.zig:4:8: error: 'privateFunction' is private", "foo.zig:1:1: note: declared here"); @@ -1417,19 +1249,17 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const zero: i32 = 0; \\const a = zero{1}; \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(a)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } , ".tmp_source.zig:2:11: error: expected type, found 'i32'"); cases.add("assign to constant field", \\const Foo = struct { \\ field: i32, \\}; - \\extern fn derp() { + \\export fn derp() { \\ const f = Foo {.field = 1234,}; \\ f.field = 0; \\} - \\comptime {@export("derp", derp);} , ".tmp_source.zig:6:13: error: cannot assign to constant"); cases.add("return from defer expression", @@ -1447,8 +1277,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return 0; \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(testTrickyDefer)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(testTrickyDefer)) } , ".tmp_source.zig:4:11: error: cannot return from defer expression"); cases.add("attempt to access var args out of bounds", @@ -1460,8 +1289,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ add(i32(1234)) \\} \\ - \\comptime {@export("entry", entry);} - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:2:19: error: index 1 outside argument list of size 1", ".tmp_source.zig:6:8: note: called from here"); @@ -1479,31 +1307,27 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ add(1, 2, 3, 4) \\} \\ - \\comptime {@export("entry", entry);} - \\extern fn entry() -> usize { @sizeOf(@typeOf(bar)) } + \\export fn entry() -> usize { @sizeOf(@typeOf(bar)) } , ".tmp_source.zig:10:9: error: parameter of type '(integer literal)' requires comptime"); cases.add("assign too big number to u16", - \\extern fn foo() { + \\export fn foo() { \\ var vga_mem: u16 = 0xB8000; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:24: error: integer value 753664 cannot be implicitly casted to type 'u16'"); cases.add("global variable alignment non power of 2", \\const some_data: [100]u8 align(3) = undefined; - \\extern fn entry() -> usize { @sizeOf(@typeOf(some_data)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(some_data)) } , ".tmp_source.zig:1:32: error: alignment value 3 is not a power of 2"); cases.add("function alignment non power of 2", \\extern fn foo() align(3); - \\extern fn entry() { foo() } - \\comptime {@export("entry", entry);} + \\export fn entry() { foo() } , ".tmp_source.zig:1:23: error: alignment value 3 is not a power of 2"); cases.add("compile log", - \\extern fn foo() { + \\export fn foo() { \\ comptime bar(12, "hi"); \\} \\fn bar(a: i32, b: []const u8) { @@ -1511,7 +1335,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ @compileLog("a", a, "b", b); \\ @compileLog("end"); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:5: error: found compile log statement", ".tmp_source.zig:2:17: note: called from here", @@ -1535,8 +1358,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return *x; \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:8:26: error: expected type '&const u3', found '&align(1:3:6) const u3'"); cases.add("referring to a struct that is invalid", @@ -1544,20 +1366,19 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Type: u8, \\}; \\ - \\extern fn foo() { + \\export fn foo() { \\ comptime assert(@sizeOf(UsbDeviceRequest) == 0x8); \\} \\ \\fn assert(ok: bool) { \\ if (!ok) unreachable; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:10:14: error: unable to evaluate constant expression", ".tmp_source.zig:6:20: note: called from here"); cases.add("control flow uses comptime var at runtime", - \\extern fn foo() { + \\export fn foo() { \\ comptime var i = 0; \\ while (i < 5) : (i += 1) { \\ bar(); @@ -1565,61 +1386,53 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} \\ \\fn bar() { } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:5: error: control flow attempts to use compile-time variable at runtime", ".tmp_source.zig:3:24: note: compile-time variable assigned here"); cases.add("ignored return value", - \\extern fn foo() { + \\export fn foo() { \\ bar(); \\} \\fn bar() -> i32 { 0 } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:8: error: expression value is ignored"); cases.add("ignored assert-err-ok return value", - \\extern fn foo() { + \\export fn foo() { \\ %%bar(); \\} \\fn bar() -> %i32 { 0 } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:5: error: expression value is ignored"); cases.add("ignored statement value", - \\extern fn foo() { + \\export fn foo() { \\ 1; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:5: error: expression value is ignored"); cases.add("ignored comptime statement value", - \\extern fn foo() { + \\export fn foo() { \\ comptime {1;} \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expression value is ignored"); cases.add("ignored comptime value", - \\extern fn foo() { + \\export fn foo() { \\ comptime 1; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:5: error: expression value is ignored"); cases.add("ignored defered statement value", - \\extern fn foo() { + \\export fn foo() { \\ defer {1;} \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:12: error: expression value is ignored"); cases.add("ignored defered statement value", - \\extern fn foo() { + \\export fn foo() { \\ defer bar(); \\} \\fn bar() -> %i32 { 0 } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:14: error: expression value is ignored"); cases.add("dereference an array", @@ -1630,8 +1443,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return (*out)[0..1]; \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(pass)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(pass)) } , ".tmp_source.zig:4:5: error: attempt to dereference non pointer type '[10]u8'"); cases.add("pass const ptr to mutable ptr fn", @@ -1644,31 +1456,46 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ return true; \\} \\ - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:4:19: error: expected type '&[]const u8', found '&const []const u8'"); + cases.addCase({ + const tc = cases.create("export collision", + \\const foo = @import("foo.zig"); + \\ + \\export fn bar() -> usize { + \\ return foo.baz; + \\} + , + "foo.zig:1:8: error: exported symbol collision: 'bar'", + ".tmp_source.zig:3:8: note: other symbol here"); + + tc.addSourceFile("foo.zig", + \\export fn bar() {} + \\pub const baz = 1234; + ); + + tc + }); + cases.add("pass non-copyable type by value to function", \\const Point = struct { x: i32, y: i32, }; \\fn foo(p: Point) { } - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:2:11: error: type 'Point' is not copyable; cannot pass by value"); cases.add("implicit cast from array to mutable slice", \\var global_array: [10]i32 = undefined; \\fn foo(param: []i32) {} - \\extern fn entry() { + \\export fn entry() { \\ foo(global_array); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:9: error: expected type '[]i32', found '[10]i32'"); cases.add("ptrcast to non-pointer", - \\extern fn entry(a: &i32) -> usize { + \\export fn entry(a: &i32) -> usize { \\ return @ptrCast(usize, a); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:21: error: expected pointer, found 'usize'"); cases.add("too many error values to cast to small integer", @@ -1677,8 +1504,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn foo(e: error) -> u2 { \\ return u2(e); \\} - \\extern fn entry() -> usize { @sizeOf(@typeOf(foo)) } - \\comptime {@export("entry", entry);} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } , ".tmp_source.zig:4:14: error: too many error values to fit in 'u2'"); cases.add("asm at compile time", @@ -1697,46 +1523,41 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("invalid member of builtin enum", \\const builtin = @import("builtin"); - \\extern fn entry() { + \\export fn entry() { \\ const foo = builtin.Arch.x86; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:29: error: container 'Arch' has no member called 'x86'"); cases.add("int to ptr of 0 bits", - \\extern fn foo() { + \\export fn foo() { \\ var x: usize = 0x1000; \\ var y: &void = @intToPtr(&void, x); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:31: error: type '&void' has 0 bits and cannot store information"); cases.add("@fieldParentPtr - non struct", \\const Foo = i32; - \\extern fn foo(a: &i32) -> &Foo { + \\export fn foo(a: &i32) -> &Foo { \\ return @fieldParentPtr(Foo, "a", a); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:28: error: expected struct type, found 'i32'"); cases.add("@fieldParentPtr - bad field name", \\const Foo = struct { \\ derp: i32, \\}; - \\extern fn foo(a: &i32) -> &Foo { + \\export fn foo(a: &i32) -> &Foo { \\ return @fieldParentPtr(Foo, "a", a); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:33: error: struct 'Foo' has no field 'a'"); cases.add("@fieldParentPtr - field pointer is not pointer", \\const Foo = struct { \\ a: i32, \\}; - \\extern fn foo(a: i32) -> &Foo { + \\export fn foo(a: i32) -> &Foo { \\ return @fieldParentPtr(Foo, "a", a); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:38: error: expected pointer, found 'i32'"); cases.add("@fieldParentPtr - comptime field ptr not based on struct", @@ -1766,20 +1587,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("@offsetOf - non struct", \\const Foo = i32; - \\extern fn foo() -> usize { + \\export fn foo() -> usize { \\ return @offsetOf(Foo, "a"); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:22: error: expected struct type, found 'i32'"); cases.add("@offsetOf - bad field name", \\const Foo = struct { \\ derp: i32, \\}; - \\extern fn foo() -> usize { + \\export fn foo() -> usize { \\ return @offsetOf(Foo, "a"); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:27: error: struct 'Foo' has no field 'a'"); cases.addExe("missing main fn in executable", @@ -1792,22 +1611,44 @@ pub fn addCases(cases: &tests.CompileErrorContext) { "error: 'main' is private", ".tmp_source.zig:1:1: note: declared here"); + cases.add("setting a section on an extern variable", + \\extern var foo: i32 section(".text2"); + \\export fn entry() -> i32 { + \\ return foo; + \\} + , + ".tmp_source.zig:1:29: error: cannot set section of external variable 'foo'"); + + cases.add("setting a section on a local variable", + \\export fn entry() -> i32 { + \\ var foo: i32 section(".text2") = 1234; + \\ return foo; + \\} + , + ".tmp_source.zig:2:26: error: cannot set section of local variable 'foo'"); + + cases.add("setting a section on an extern fn", + \\extern fn foo() section(".text2"); + \\export fn entry() { + \\ foo(); + \\} + , + ".tmp_source.zig:1:25: error: cannot set section of external function 'foo'"); + cases.add("returning address of local variable - simple", - \\extern fn foo() -> &i32 { + \\export fn foo() -> &i32 { \\ var a: i32 = undefined; \\ return &a; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:13: error: function returns address of local variable"); cases.add("returning address of local variable - phi", - \\extern fn foo(c: bool) -> &i32 { + \\export fn foo(c: bool) -> &i32 { \\ var a: i32 = undefined; \\ var b: i32 = undefined; \\ return if (c) &a else &b; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:12: error: function returns address of local variable"); @@ -1836,61 +1677,55 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:5:9: note: previous definition is here"); cases.add("while expected bool, got nullable", - \\extern fn foo() { + \\export fn foo() { \\ while (bar()) {} \\} \\fn bar() -> ?i32 { 1 } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected type 'bool', found '?i32'"); cases.add("while expected bool, got error union", - \\extern fn foo() { + \\export fn foo() { \\ while (bar()) {} \\} \\fn bar() -> %i32 { 1 } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected type 'bool', found '%i32'"); cases.add("while expected nullable, got bool", - \\extern fn foo() { + \\export fn foo() { \\ while (bar()) |x| {} \\} \\fn bar() -> bool { true } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected nullable type, found 'bool'"); cases.add("while expected nullable, got error union", - \\extern fn foo() { + \\export fn foo() { \\ while (bar()) |x| {} \\} \\fn bar() -> %i32 { 1 } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected nullable type, found '%i32'"); cases.add("while expected error union, got bool", - \\extern fn foo() { + \\export fn foo() { \\ while (bar()) |x| {} else |err| {} \\} \\fn bar() -> bool { true } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected error union type, found 'bool'"); cases.add("while expected error union, got nullable", - \\extern fn foo() { + \\export fn foo() { \\ while (bar()) |x| {} else |err| {} \\} \\fn bar() -> ?i32 { 1 } - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:15: error: expected error union type, found '?i32'"); cases.add("inline fn calls itself indirectly", - \\extern fn foo() { + \\export fn foo() { \\ bar(); \\} \\inline fn bar() { @@ -1902,33 +1737,29 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ quux(); \\} \\extern fn quux(); - \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:8: error: unable to inline function"); cases.add("save reference to inline function", - \\extern fn foo() { + \\export fn foo() { \\ quux(@ptrToInt(bar)); \\} \\inline fn bar() { } \\extern fn quux(usize); - \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:8: error: unable to inline function"); cases.add("signed integer division", - \\extern fn foo(a: i32, b: i32) -> i32 { + \\export fn foo(a: i32, b: i32) -> i32 { \\ a / b \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:7: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExact"); cases.add("signed integer remainder division", - \\extern fn foo(a: i32, b: i32) -> i32 { + \\export fn foo(a: i32, b: i32) -> i32 { \\ a % b \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:7: error: remainder division with 'i32' and 'i32': signed integers and floats must use @rem or @mod"); @@ -1967,65 +1798,59 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:3:20: error: cast from 'u16' to 'u8' truncates bits"); cases.add("@setDebugSafety twice for same scope", - \\extern fn foo() { + \\export fn foo() { \\ @setDebugSafety(this, false); \\ @setDebugSafety(this, false); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:5: error: debug safety set twice for same scope", ".tmp_source.zig:2:5: note: first set here"); cases.add("@setFloatMode twice for same scope", - \\extern fn foo() { + \\export fn foo() { \\ @setFloatMode(this, @import("builtin").FloatMode.Optimized); \\ @setFloatMode(this, @import("builtin").FloatMode.Optimized); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:5: error: float mode set twice for same scope", ".tmp_source.zig:2:5: note: first set here"); cases.add("array access of type", - \\extern fn foo() { + \\export fn foo() { \\ var b: u8[40] = undefined; \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:2:14: error: array access of non-array type 'type'"); cases.add("cannot break out of defer expression", - \\extern fn foo() { + \\export fn foo() { \\ while (true) { \\ defer { \\ break; \\ } \\ } \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:13: error: cannot break out of defer expression"); cases.add("cannot continue out of defer expression", - \\extern fn foo() { + \\export fn foo() { \\ while (true) { \\ defer { \\ continue; \\ } \\ } \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:4:13: error: cannot continue out of defer expression"); cases.add("cannot goto out of defer expression", - \\extern fn foo() { + \\export fn foo() { \\ defer { \\ goto label; \\ }; \\label: \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:3:9: error: cannot goto out of defer expression"); @@ -2059,10 +1884,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const bar = baz + foo; \\const baz = 1; \\ - \\extern fn entry() -> i32 { + \\export fn entry() -> i32 { \\ return bar; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: aoeu", ".tmp_source.zig:3:19: note: referenced here", @@ -2075,10 +1899,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ \\var foo: Foo = undefined; \\ - \\extern fn entry() -> usize { + \\export fn entry() -> usize { \\ return @sizeOf(@typeOf(foo.x)); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: struct 'Foo' contains itself"); @@ -2097,18 +1920,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:15: error: float literal out of range of any type"); cases.add("explicit cast float literal to integer when there is a fraction component", - \\extern fn entry() -> i32 { + \\export fn entry() -> i32 { \\ i32(12.34) \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:9: error: fractional component prevents float value 12.340000 from being casted to type 'i32'"); cases.add("non pointer given to @ptrToInt", - \\extern fn entry(x: i32) -> usize { + \\export fn entry(x: i32) -> usize { \\ @ptrToInt(x) \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:15: error: expected pointer, found 'i32'"); @@ -2127,27 +1948,24 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:15: error: exact shift shifted out 1 bits"); cases.add("shifting without int type or comptime known", - \\extern fn entry(x: u8) -> u8 { + \\export fn entry(x: u8) -> u8 { \\ return 0x11 << x; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:17: error: LHS of shift must be an integer type, or RHS must be compile-time known"); cases.add("shifting RHS is log2 of LHS int bit width", - \\extern fn entry(x: u8, y: u8) -> u8 { + \\export fn entry(x: u8, y: u8) -> u8 { \\ return x << y; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:17: error: expected type 'u3', found 'u8'"); cases.add("globally shadowing a primitive type", \\const u16 = @intType(false, 8); - \\extern fn entry() { + \\export fn entry() { \\ const a: u16 = 300; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:1: error: declaration shadows type 'u16'"); @@ -2157,7 +1975,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ b: u32, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var foo = Foo { .a = 1, .b = 10 }; \\ bar(&foo.b); \\} @@ -2165,7 +1983,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar(x: &u32) { \\ *x += 1; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:8:13: error: expected type '&u32', found '&align(1) u32'"); @@ -2175,7 +1992,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ b: u32, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var foo = Foo { .a = 1, .b = 10 }; \\ foo.b += 1; \\ bar((&foo.b)[0..1]); @@ -2184,61 +2001,55 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar(x: []u32) { \\ x[0] += 1; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:17: error: expected type '[]u32', found '[]align(1) u32'"); cases.add("increase pointer alignment in @ptrCast", - \\extern fn entry() -> u32 { + \\export fn entry() -> u32 { \\ var bytes: [4]u8 = []u8{0x01, 0x02, 0x03, 0x04}; \\ const ptr = @ptrCast(&u32, &bytes[0]); \\ return *ptr; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:17: error: cast increases pointer alignment", ".tmp_source.zig:3:38: note: '&u8' has alignment 1", ".tmp_source.zig:3:27: note: '&u32' has alignment 4"); cases.add("increase pointer alignment in slice resize", - \\extern fn entry() -> u32 { + \\export fn entry() -> u32 { \\ var bytes = []u8{0x01, 0x02, 0x03, 0x04}; \\ return ([]u32)(bytes[0..])[0]; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:19: error: cast increases pointer alignment", ".tmp_source.zig:3:19: note: '[]u8' has alignment 1", ".tmp_source.zig:3:19: note: '[]u32' has alignment 4"); cases.add("@alignCast expects pointer or slice", - \\extern fn entry() { + \\export fn entry() { \\ @alignCast(4, u32(3)) \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:22: error: expected pointer or slice, found 'u32'"); cases.add("passing an under-aligned function pointer", - \\extern fn entry() { + \\export fn entry() { \\ testImplicitlyDecreaseFnAlign(alignedSmall, 1234); \\} \\fn testImplicitlyDecreaseFnAlign(ptr: fn () align(8) -> i32, answer: i32) { \\ if (ptr() != answer) unreachable; \\} \\fn alignedSmall() align(4) -> i32 { 1234 } - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:35: error: expected type 'fn() align(8) -> i32', found 'fn() align(4) -> i32'"); cases.add("passing a not-aligned-enough pointer to cmpxchg", \\const AtomicOrder = @import("builtin").AtomicOrder; - \\extern fn entry() -> bool { + \\export fn entry() -> bool { \\ var x: i32 align(1) = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.SeqCst, AtomicOrder.SeqCst)) {} \\ return x == 5678; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:23: error: expected pointer alignment of at least 4, found 1"); @@ -2264,18 +2075,17 @@ pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("wrong pointer implicitly casted to pointer to @OpaqueType()", \\const Derp = @OpaqueType(); \\extern fn bar(d: &Derp); - \\extern fn foo() { + \\export fn foo() { \\ const x = u8(1); \\ bar(@ptrCast(&c_void, &x)); \\} - \\comptime {@export("foo", foo);} , ".tmp_source.zig:5:9: error: expected type '&Derp', found '&c_void'"); cases.add("non-const variables of things that require const variables", \\const Opaque = @OpaqueType(); \\ - \\extern fn entry(opaque: &Opaque) { + \\export fn entry(opaque: &Opaque) { \\ var m2 = &2; \\ const y: u32 = *m2; \\ @@ -2295,7 +2105,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = struct { \\ fn bar(self: &const Foo) {} \\}; - \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:4: error: variable of type '&const (integer literal)' must be const or comptime", ".tmp_source.zig:7:4: error: variable of type '(undefined)' must be const or comptime", @@ -2310,14 +2119,21 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:17:4: error: unreachable code"); cases.add("wrong types given to atomic order args in cmpxchg", - \\extern fn entry() { + \\export fn entry() { \\ var x: i32 = 1234; \\ while (!@cmpxchg(&x, 1234, 5678, u32(1234), u32(1234))) {} \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:41: error: expected type 'AtomicOrder', found 'u32'"); + cases.add("wrong types given to @export", + \\extern fn entry() { } + \\comptime { + \\ @export("entry", entry, u32(1234)); + \\} + , + ".tmp_source.zig:3:32: error: expected type 'GlobalLinkage', found 'u32'"); + cases.add("struct with invalid field", \\const std = @import("std"); \\const Allocator = std.mem.Allocator; @@ -2336,13 +2152,12 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ }, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ const a = MdNode.Header { \\ .text = MdText.init(&std.debug.global_allocator), \\ .weight = HeaderWeight.H1, \\ }; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:14:17: error: use of undeclared identifier 'HeaderValue'"); @@ -2354,39 +2169,35 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:5: error: @setAlignStack outside function"); cases.add("@setAlignStack in naked function", - \\nakedcc fn entry() { + \\export nakedcc fn entry() { \\ @setAlignStack(16); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: @setAlignStack in naked function"); cases.add("@setAlignStack in inline function", - \\extern fn entry() { + \\export fn entry() { \\ foo(); \\} \\inline fn foo() { \\ @setAlignStack(16); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:5: error: @setAlignStack in inline function"); cases.add("@setAlignStack set twice", - \\extern fn entry() { + \\export fn entry() { \\ @setAlignStack(16); \\ @setAlignStack(16); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:3:5: error: alignstack set twice", ".tmp_source.zig:2:5: note: first set here"); cases.add("@setAlignStack too big", - \\extern fn entry() { + \\export fn entry() { \\ @setAlignStack(511 + 1); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: attempt to @setAlignStack(512); maximum is 256"); @@ -2417,7 +2228,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ LinkLibC, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ const tests = []TestCase { \\ Free("001"), \\ Free("002"), @@ -2432,14 +2243,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\ } \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:37:16: error: cannot store runtime value in compile time variable"); cases.add("field access of opaque type", \\const MyType = @OpaqueType(); \\ - \\extern fn entry() -> bool { + \\export fn entry() -> bool { \\ var x: i32 = 1; \\ return bar(@ptrCast(&MyType, &x)); \\} @@ -2447,7 +2257,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\fn bar(x: &MyType) -> bool { \\ return x.blah; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:13: error: type '&MyType' does not support field access"); @@ -2551,11 +2360,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:2:26: error: member index 1 out of bounds; 'Foo' has 1 members"); cases.add("calling var args extern function, passing array instead of pointer", - \\extern fn entry() { + \\export fn entry() { \\ foo("hello"); \\} \\pub extern fn foo(format: &const u8, ...); - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:9: error: expected type '&const u8', found '[5]u8'"); @@ -2570,10 +2378,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ } \\} \\ - \\extern fn entry() { + \\export fn entry() { \\ var allocator: ContextAllocator = undefined; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:4:25: error: aoeu", ".tmp_source.zig:1:36: note: called from here", @@ -2588,10 +2395,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Five, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var x = Small.One; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:20: error: 'u2' too small to hold all bits; must be at least 'u3'"); @@ -2602,10 +2408,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Three, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var x = Small.One; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:20: error: expected integer, found 'f32'"); @@ -2617,10 +2422,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var x: u2 = Small.Two; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:22: error: expected type 'u2', found 'Small'"); @@ -2632,10 +2436,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var x = u3(Small.Two); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:9:15: error: enum to integer cast to 'u3' instead of its tag type, 'u2'"); @@ -2647,11 +2450,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var y = u3(3); \\ var x = Small(y); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:10:18: error: integer to enum cast from 'u3' instead of its tag type, 'u2'"); @@ -2663,10 +2465,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ Four, \\}; \\ - \\extern fn entry() { + \\export fn entry() { \\ var y = Small.Two; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:19: error: expected unsigned integer, found 'i2'"); @@ -2674,10 +2475,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const MultipleChoice = struct { \\ A: i32 = 20, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var x: MultipleChoice = undefined; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:14: error: enums, not structs, support field assignment"); @@ -2685,29 +2485,26 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const MultipleChoice = union { \\ A: i32 = 20, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var x: MultipleChoice = undefined; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:14: error: non-enum union field assignment", ".tmp_source.zig:1:24: note: consider 'union(enum)' here"); cases.add("enum with 0 fields", \\const Foo = enum {}; - \\extern fn entry() -> usize { + \\export fn entry() -> usize { \\ return @sizeOf(Foo); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: enums must have 1 or more fields"); cases.add("union with 0 fields", \\const Foo = union {}; - \\extern fn entry() -> usize { + \\export fn entry() -> usize { \\ return @sizeOf(Foo); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:13: error: unions must have 1 or more fields"); @@ -2719,10 +2516,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ D = 1000, \\ E = 60, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var x = MultipleChoice.C; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:9: error: enum tag value 60 already taken", ".tmp_source.zig:4:9: note: other occurrence here"); @@ -2737,10 +2533,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ A: i32, \\ B: f64, \\}; - \\extern fn entry() -> usize { + \\export fn entry() -> usize { \\ return @sizeOf(Payload); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:17: error: enum field missing: 'C'", ".tmp_source.zig:4:5: note: declared here"); @@ -2749,10 +2544,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = union { \\ A: i32, \\}; - \\extern fn entry() { + \\export fn entry() { \\ const x = @TagType(Foo); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:5:24: error: union 'Foo' has no tag", ".tmp_source.zig:1:13: note: consider 'union(enum)' here"); @@ -2761,10 +2555,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = union(enum(f32)) { \\ A: i32, \\}; - \\extern fn entry() { + \\export fn entry() { \\ const x = @TagType(Foo); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:23: error: expected integer tag type, found 'f32'"); @@ -2772,10 +2565,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Foo = union(u32) { \\ A: i32, \\}; - \\extern fn entry() { + \\export fn entry() { \\ const x = @TagType(Foo); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:1:18: error: expected enum tag type, found 'u32'"); @@ -2787,10 +2579,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ D = 1000, \\ E = 60, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var x = MultipleChoice { .C = {} }; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:9: error: enum tag value 60 already taken", ".tmp_source.zig:4:9: note: other occurrence here"); @@ -2807,10 +2598,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ C: bool, \\ D: bool, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var a = Payload {.A = 1234}; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:10:5: error: enum field not found: 'D'", ".tmp_source.zig:1:16: note: enum declared here"); @@ -2821,10 +2611,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B, \\ C, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var b = Letter.B; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:8: error: structs and unions, not enums, support field types", ".tmp_source.zig:1:16: note: consider 'union(enum)' here"); @@ -2833,10 +2622,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Letter = struct { \\ A, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var a = Letter { .A = {} }; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: struct field missing type"); @@ -2844,10 +2632,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\const Letter = extern union { \\ A, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var a = Letter { .A = {} }; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:2:5: error: union field missing type"); @@ -2862,10 +2649,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B: f64, \\ C: bool, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var a = Payload { .A = { 1234 } }; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:29: error: extern union does not support enum tag type"); @@ -2880,10 +2666,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B: f64, \\ C: bool, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var a = Payload { .A = { 1234 } }; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:29: error: packed union does not support enum tag type"); @@ -2893,7 +2678,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B: f64, \\ C: bool, \\}; - \\extern fn entry() { + \\export fn entry() { \\ const a = Payload { .A = { 1234 } }; \\ foo(a); \\} @@ -2903,7 +2688,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ else => unreachable, \\ } \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:11:13: error: switch on union which has no attached enum", ".tmp_source.zig:1:17: note: consider 'union(enum)' here"); @@ -2913,10 +2697,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ A = 10, \\ B = 11, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var x = Foo(0); \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:6:16: error: enum 'Foo' has no tag matching integer value 0", ".tmp_source.zig:1:13: note: 'Foo' declared here"); @@ -2928,10 +2711,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B, \\ C, \\}; - \\extern fn entry() { + \\export fn entry() { \\ var x: Value = Letter.A; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:8:26: error: cast to union 'Value' must initialize 'i32' field 'A'", ".tmp_source.zig:3:5: note: field 'A' declared here"); @@ -2943,36 +2725,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ B, \\ C, \\}; - \\extern fn entry() { + \\export fn entry() { \\ foo(Letter.A); \\} \\fn foo(l: Letter) { \\ var x: Value = l; \\} - \\comptime {@export("entry", entry);} , ".tmp_source.zig:11:20: error: runtime cast to union 'Value' which has non-void fields", ".tmp_source.zig:3:5: note: field 'A' has type 'i32'"); - - cases.addCase({ - const tc = cases.create("export collision", - \\const foo = @import("foo.zig"); - \\ - \\comptime {@export("bar", bar);} - \\extern fn bar() -> usize { - \\ return foo.baz; - \\} - , - "foo.zig:2:11: error: exported symbol collision: 'bar'", - ".tmp_source.zig:3:11: note: other symbol is here"); - - tc.addSourceFile("foo.zig", - \\extern fn bar() {} - \\comptime {@export("bar", bar);} - \\pub const baz = 1234; - ); - - tc - }); - } diff --git a/test/standalone/issue_339/test.zig b/test/standalone/issue_339/test.zig index a3058e58ed..c1faa015c6 100644 --- a/test/standalone/issue_339/test.zig +++ b/test/standalone/issue_339/test.zig @@ -2,9 +2,6 @@ pub fn panic(msg: []const u8) -> noreturn { @breakpoint(); while (true) {} } fn bar() -> %void {} -comptime { - @export("foo", foo); -} -extern fn foo() { +export fn foo() { %%bar(); } diff --git a/test/translate_c.zig b/test/translate_c.zig index 01f6622a71..aff2140f8d 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -26,7 +26,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a < 0 ? -a : a; \\} , - \\pub fn abs(a: c_int) -> c_int { + \\export fn abs(a: c_int) -> c_int { \\ return if (a < 0) -a else a; \\} ); @@ -325,12 +325,12 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\pub fn foo1(_arg_a: c_uint) -> c_uint { + \\pub export fn foo1(_arg_a: c_uint) -> c_uint { \\ var a = _arg_a; \\ a +%= 1; \\ return a; \\} - \\pub fn foo2(_arg_a: c_int) -> c_int { + \\pub export fn foo2(_arg_a: c_int) -> c_int { \\ var a = _arg_a; \\ a += 1; \\ return a; @@ -346,7 +346,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return i; \\} , - \\pub fn log2(_arg_a: c_uint) -> c_int { + \\pub export fn log2(_arg_a: c_uint) -> c_int { \\ var a = _arg_a; \\ var i: c_int = 0; \\ while (a > c_uint(0)) { @@ -367,7 +367,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\pub fn max(a: c_int, b: c_int) -> c_int { + \\pub export fn max(a: c_int, b: c_int) -> c_int { \\ if (a < b) return b; \\ if (a < b) return b else return a; \\} @@ -382,7 +382,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\pub fn max(a: c_int, b: c_int) -> c_int { + \\pub export fn max(a: c_int, b: c_int) -> c_int { \\ if (a == b) return a; \\ if (a != b) return b; \\ return a; @@ -407,7 +407,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ c = a % b; \\} , - \\pub fn s(a: c_int, b: c_int) -> c_int { + \\pub export fn s(a: c_int, b: c_int) -> c_int { \\ var c: c_int; \\ c = (a + b); \\ c = (a - b); @@ -415,7 +415,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ c = @divTrunc(a, b); \\ c = @rem(a, b); \\} - \\pub fn u(a: c_uint, b: c_uint) -> c_uint { + \\pub export fn u(a: c_uint, b: c_uint) -> c_uint { \\ var c: c_uint; \\ c = (a +% b); \\ c = (a -% b); @@ -430,7 +430,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return (a & b) ^ (a | b); \\} , - \\pub fn max(a: c_int, b: c_int) -> c_int { + \\pub export fn max(a: c_int, b: c_int) -> c_int { \\ return (a & b) ^ (a | b); \\} ); @@ -444,7 +444,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\pub fn max(a: c_int, b: c_int) -> c_int { + \\pub export fn max(a: c_int, b: c_int) -> c_int { \\ if ((a < b) or (a == b)) return b; \\ if ((a >= b) and (a == b)) return a; \\ return a; @@ -458,7 +458,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a = tmp; \\} , - \\pub fn max(_arg_a: c_int) -> c_int { + \\pub export fn max(_arg_a: c_int) -> c_int { \\ var a = _arg_a; \\ var tmp: c_int; \\ tmp = a; @@ -472,7 +472,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ c = b = a; \\} , - \\pub fn max(a: c_int) { + \\pub export fn max(a: c_int) { \\ var b: c_int; \\ var c: c_int; \\ c = { @@ -493,7 +493,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return i; \\} , - \\pub fn log2(_arg_a: u32) -> c_int { + \\pub export fn log2(_arg_a: u32) -> c_int { \\ var a = _arg_a; \\ var i: c_int = 0; \\ while (a > c_uint(0)) { @@ -518,7 +518,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\void foo(void) { bar(); } , \\pub fn bar() {} - \\pub fn foo() { + \\pub export fn foo() { \\ bar(); \\} ); @@ -534,7 +534,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\pub const struct_Foo = extern struct { \\ field: c_int, \\}; - \\pub fn read_field(foo: ?&struct_Foo) -> c_int { + \\pub export fn read_field(foo: ?&struct_Foo) -> c_int { \\ return (??foo).field; \\} ); @@ -544,7 +544,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ ;;;;; \\} , - \\pub fn foo() {} + \\pub export fn foo() {} ); cases.add("undefined array global", @@ -560,7 +560,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\} , \\pub var array: [100]c_int = undefined; - \\pub fn foo(index: c_int) -> c_int { + \\pub export fn foo(index: c_int) -> c_int { \\ return array[index]; \\} ); @@ -571,7 +571,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return (int)a; \\} , - \\pub fn float_to_int(a: f32) -> c_int { + \\pub export fn float_to_int(a: f32) -> c_int { \\ return c_int(a); \\} ); @@ -581,7 +581,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return x; \\} , - \\pub fn foo(x: ?&c_ushort) -> ?&c_void { + \\pub export fn foo(x: ?&c_ushort) -> ?&c_void { \\ return @ptrCast(?&c_void, x); \\} ); @@ -592,7 +592,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return sizeof(int); \\} , - \\pub fn size_of() -> usize { + \\pub export fn size_of() -> usize { \\ return @sizeOf(c_int); \\} ); @@ -602,7 +602,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return 0; \\} , - \\pub fn foo() -> ?&c_int { + \\pub export fn foo() -> ?&c_int { \\ return null; \\} ); @@ -612,7 +612,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return 1, 2; \\} , - \\pub fn foo() -> c_int { + \\pub export fn foo() -> c_int { \\ return { \\ _ = 1; \\ 2 @@ -625,7 +625,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return (1 << 2) >> 1; \\} , - \\pub fn foo() -> c_int { + \\pub export fn foo() -> c_int { \\ return (1 << @import("std").math.Log2Int(c_int)(2)) >> @import("std").math.Log2Int(c_int)(1); \\} ); @@ -643,7 +643,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a <<= (a <<= 1); \\} , - \\pub fn foo() { + \\pub export fn foo() { \\ var a: c_int = 0; \\ a += { \\ const _ref = &a; @@ -701,7 +701,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a <<= (a <<= 1); \\} , - \\pub fn foo() { + \\pub export fn foo() { \\ var a: c_uint = c_uint(0); \\ a +%= { \\ const _ref = &a; @@ -771,7 +771,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ u = u--; \\} , - \\pub fn foo() { + \\pub export fn foo() { \\ var i: c_int = 0; \\ var u: c_uint = c_uint(0); \\ i += 1; @@ -819,7 +819,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ u = --u; \\} , - \\pub fn foo() { + \\pub export fn foo() { \\ var i: c_int = 0; \\ var u: c_uint = c_uint(0); \\ i += 1; @@ -862,7 +862,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ while (b != 0); \\} , - \\pub fn foo() { + \\pub export fn foo() { \\ var a: c_int = 2; \\ while (true) { \\ a -= 1; @@ -886,9 +886,9 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ baz(); \\} , - \\pub fn foo() {} - \\pub fn baz() {} - \\pub fn bar() { + \\pub export fn foo() {} + \\pub export fn baz() {} + \\pub export fn bar() { \\ var f: ?extern fn() = foo; \\ (??f)(); \\ (??f)(); @@ -901,7 +901,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ *x = 1; \\} , - \\pub fn foo(x: ?&c_int) { + \\pub export fn foo(x: ?&c_int) { \\ (*??x) = 1; \\} ); From 1435604b84dbf338c1b6096f473bc89aef144be0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 15 Dec 2017 11:15:58 -0500 Subject: [PATCH 60/69] add sort.min and sort.max functions to stdlib --- std/mem.zig | 10 ++++------ std/sort.zig | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/std/mem.zig b/std/mem.zig index 59b01fab17..4b3516b051 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -511,9 +511,8 @@ fn testWriteIntImpl() { pub fn min(comptime T: type, slice: []const T) -> T { var best = slice[0]; - var i: usize = 1; - while (i < slice.len) : (i += 1) { - best = math.min(best, slice[i]); + for (slice[1..]) |item| { + best = math.min(best, item); } return best; } @@ -524,9 +523,8 @@ test "mem.min" { pub fn max(comptime T: type, slice: []const T) -> T { var best = slice[0]; - var i: usize = 1; - while (i < slice.len) : (i += 1) { - best = math.max(best, slice[i]); + for (slice[1..]) |item| { + best = math.max(best, item); } return best; } diff --git a/std/sort.zig b/std/sort.zig index ec6f94bd8e..c6b1500b8e 100644 --- a/std/sort.zig +++ b/std/sort.zig @@ -1132,3 +1132,25 @@ fn fuzzTest(rng: &std.rand.Rand) { } } } + +pub fn min(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool) -> T { + var i: usize = 0; + var smallest = items[0]; + for (items[1..]) |item| { + if (lessThan(item, smallest)) { + smallest = item; + } + } + return smallest; +} + +pub fn max(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool) -> T { + var i: usize = 0; + var biggest = items[0]; + for (items[1..]) |item| { + if (lessThan(biggest, item)) { + biggest = item; + } + } + return biggest; +} From 1cc450e6e70008eb2eaf62f2992d9d3e8b3ab87a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Dec 2017 18:21:42 -0500 Subject: [PATCH 61/69] fix assert when wrapping zero bit type in nullable closes #659 --- src/analyze.cpp | 11 ++++++++++- test/compile_errors.zig | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index e2df4955da..d6719cf52c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -429,7 +429,7 @@ TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) { ensure_complete_type(g, child_type); TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdMaybe); - assert(child_type->type_ref); + assert(child_type->type_ref || child_type->zero_bits); assert(child_type->di_type); entry->is_copyable = type_is_copyable(g, child_type); @@ -1162,6 +1162,15 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c } TypeTableEntry *type_entry = analyze_type_expr(g, child_scope, param_node->data.param_decl.type); + if (fn_type_id.cc != CallingConventionUnspecified) { + type_ensure_zero_bits_known(g, type_entry); + if (!type_has_bits(type_entry)) { + add_node_error(g, param_node->data.param_decl.type, + buf_sprintf("parameter of type '%s' has 0 bits; not allowed in function with calling convention '%s'", + buf_ptr(&type_entry->name), calling_convention_name(fn_type_id.cc))); + return g->builtin_types.entry_invalid; + } + } switch (type_entry->id) { case TypeTableEntryIdInvalid: diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 22520802fb..fb7daea481 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,16 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) { + cases.add("attempt to use 0 bit type in extern fn", + \\extern fn foo(ptr: extern fn(&void)); + \\ + \\export fn entry() { + \\ foo(bar); + \\} + \\ + \\extern fn bar(x: &void) { } + , ".tmp_source.zig:7:18: error: parameter of type '&void' has 0 bits; not allowed in function with calling convention 'ccc'"); + cases.add("implicit semicolon - block statement", \\export fn entry() { \\ {} From d686113bd2b2e2207137de6ef81e515bc4a3aa07 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Dec 2017 22:38:02 -0500 Subject: [PATCH 62/69] fix crash when implicitly casting array of len 0 to slice closes #660 --- src/codegen.cpp | 12 ++++++++++++ src/ir.cpp | 18 ++++++++++-------- test/cases/slice.zig | 9 +++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 7fe4f95f85..7546416090 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2722,6 +2722,9 @@ static LLVMValueRef ir_render_phi(CodeGen *g, IrExecutable *executable, IrInstru } static LLVMValueRef ir_render_ref(CodeGen *g, IrExecutable *executable, IrInstructionRef *instruction) { + if (!type_has_bits(instruction->base.value.type)) { + return nullptr; + } LLVMValueRef value = ir_llvm_value(g, instruction->value); if (handle_is_ptr(instruction->value->value.type)) { return value; @@ -3013,6 +3016,15 @@ static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutable *executable, IrInst add_bounds_check(g, end_val, LLVMIntEQ, nullptr, LLVMIntULE, array_end); } } + if (!type_has_bits(array_type)) { + LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, slice_len_index, ""); + + // TODO if debug safety is on, store 0xaaaaaaa in ptr field + LLVMValueRef len_value = LLVMBuildNSWSub(g->builder, end_val, start_val, ""); + gen_store_untyped(g, len_value, len_field_ptr, 0, false); + return tmp_struct_ptr; + } + LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, slice_ptr_index, ""); LLVMValueRef indices[] = { diff --git a/src/ir.cpp b/src/ir.cpp index 7bd045bd92..db919350d1 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -7748,8 +7748,9 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod static void ir_add_alloca(IrAnalyze *ira, IrInstruction *instruction, TypeTableEntry *type_entry) { if (type_has_bits(type_entry) && handle_is_ptr(type_entry)) { FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec); - assert(fn_entry); - fn_entry->alloca_list.append(instruction); + if (fn_entry != nullptr) { + fn_entry->alloca_list.append(instruction); + } } } @@ -7851,9 +7852,7 @@ static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_inst IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type, value, cast_op); result->value.type = wanted_type; if (need_alloca) { - FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec); - if (fn_entry) - fn_entry->alloca_list.append(result); + ir_add_alloca(ira, result, wanted_type); } return result; } @@ -8287,6 +8286,7 @@ static IrInstruction *ir_analyze_cast_ref(IrAnalyze *ira, IrInstruction *source_ assert(fn_entry); fn_entry->alloca_list.append(new_instruction); } + ir_add_alloca(ira, new_instruction, child_type); return new_instruction; } } @@ -8330,13 +8330,15 @@ static IrInstruction *ir_get_ref(IrAnalyze *ira, IrInstruction *source_instructi TypeTableEntry *ptr_type = get_pointer_to_type_extra(ira->codegen, value->value.type, is_const, is_volatile, get_abi_alignment(ira->codegen, value->value.type), 0, 0); - FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec); - assert(fn_entry); IrInstruction *new_instruction = ir_build_ref(&ira->new_irb, source_instruction->scope, source_instruction->source_node, value, is_const, is_volatile); new_instruction->value.type = ptr_type; new_instruction->value.data.rh_ptr = RuntimeHintPtrStack; - fn_entry->alloca_list.append(new_instruction); + if (type_has_bits(ptr_type)) { + FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec); + assert(fn_entry); + fn_entry->alloca_list.append(new_instruction); + } return new_instruction; } diff --git a/test/cases/slice.zig b/test/cases/slice.zig index 44df8aa612..c47de5b09e 100644 --- a/test/cases/slice.zig +++ b/test/cases/slice.zig @@ -25,3 +25,12 @@ test "debug safety lets us slice from len..len" { fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) -> []u8 { return a_slice[start..end]; } + +test "implicitly cast array of size 0 to slice" { + var msg = []u8 {}; + assertLenIsZero(msg); +} + +fn assertLenIsZero(msg: []const u8) { + assert(msg.len == 0); +} From 8bc523219c66427951e5339550502871547f2138 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 20 Dec 2017 22:55:24 -0500 Subject: [PATCH 63/69] add labeled loops, labeled break, labeled continue. remove goto closes #346 closes #630 regression: translate-c can no longer translate switch statements. after #629 we can ressurect and modify the code to utilize arbitrarily returning from blocks. --- doc/langref.html.in | 18 ++-- src/all_types.hpp | 15 ++- src/analyze.cpp | 10 +- src/ast_render.cpp | 32 +++---- src/ir.cpp | 183 +++++++----------------------------- src/parser.cpp | 203 +++++++++++++++++++++------------------- src/translate_c.cpp | 184 ++---------------------------------- std/elf.zig | 6 +- std/os/index.zig | 158 ++++++++++++++++--------------- test/behavior.zig | 1 - test/cases/for.zig | 34 +++++++ test/cases/goto.zig | 37 -------- test/cases/while.zig | 27 ++++++ test/compile_errors.zig | 51 +++++----- test/translate_c.zig | 80 ---------------- 15 files changed, 345 insertions(+), 694 deletions(-) delete mode 100644 test/cases/goto.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index e17e1ecd8f..faba4f8b10 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5847,11 +5847,9 @@ ParamDeclList = "(" list(ParamDecl, ",") ")" ParamDecl = option("noalias" | "comptime") option(Symbol ":") (TypeExpr | "...") -Block = "{" many(Statement) option(Expression) "}" +Block = option(Symbol ":") "{" many(Statement) option(Expression) "}" -Statement = Label | LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" - -Label = Symbol ":" +Statement = LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" TypeExpr = PrefixOpExpression | "var" @@ -5891,13 +5889,13 @@ SwitchProng = (list(SwitchItem, ",") | "else") "=>" option("|" option("*") Sy SwitchItem = Expression | (Expression "..." Expression) -ForExpression(body) = option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body)) +ForExpression(body) = option(Symbol ":") option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body)) BoolOrExpression = BoolAndExpression "or" BoolOrExpression | BoolAndExpression ReturnExpression = option("%") "return" option(Expression) -BreakExpression = "break" option(Expression) +BreakExpression = "break" option(":" Symbol) option(Expression) Defer(body) = option("%") "defer" body @@ -5907,7 +5905,7 @@ TryExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|") TestExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|") body option("else" BlockExpression(body)) -WhileExpression(body) = option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body)) +WhileExpression(body) = option(Symbol ":") option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body)) BoolAndExpression = ComparisonExpression "and" BoolAndExpression | ComparisonExpression @@ -5955,15 +5953,13 @@ StructLiteralField = "." Symbol "=" Expression PrefixOp = "!" | "-" | "~" | "*" | ("&" option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "%" | "%%" | "??" | "-%" -PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl +PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl | ("continue" option(":" Symbol)) ArrayType : "[" option(Expression) "]" option("align" "(" Expression option(":" Integer ":" Integer) ")")) option("const") option("volatile") TypeExpr -GotoExpression = "goto" Symbol - GroupedExpression = "(" Expression ")" -KeywordLiteral = "true" | "false" | "null" | "continue" | "undefined" | "error" | "this" | "unreachable" +KeywordLiteral = "true" | "false" | "null" | "undefined" | "error" | "this" | "unreachable" ContainerDecl = option("extern" | "packed") ("struct" option(GroupedExpression) | "union" option("enum" option(GroupedExpression) | GroupedExpression) | ("enum" option(GroupedExpression))) diff --git a/src/all_types.hpp b/src/all_types.hpp index 28477c8107..87541bb918 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -386,8 +386,6 @@ enum NodeType { NodeTypeSwitchExpr, NodeTypeSwitchProng, NodeTypeSwitchRange, - NodeTypeLabel, - NodeTypeGoto, NodeTypeCompTime, NodeTypeBreak, NodeTypeContinue, @@ -452,6 +450,7 @@ struct AstNodeParamDecl { }; struct AstNodeBlock { + Buf *name; ZigList statements; bool last_statement_is_result_expression; }; @@ -662,6 +661,7 @@ struct AstNodeTestExpr { }; struct AstNodeWhileExpr { + Buf *name; AstNode *condition; Buf *var_symbol; bool var_is_ptr; @@ -673,6 +673,7 @@ struct AstNodeWhileExpr { }; struct AstNodeForExpr { + Buf *name; AstNode *array_expr; AstNode *elem_node; // always a symbol AstNode *index_node; // always a symbol, might be null @@ -704,11 +705,6 @@ struct AstNodeLabel { Buf *name; }; -struct AstNodeGoto { - Buf *name; - bool is_inline; -}; - struct AstNodeCompTime { AstNode *expr; }; @@ -836,11 +832,14 @@ struct AstNodeBoolLiteral { }; struct AstNodeBreakExpr { + Buf *name; AstNode *expr; // may be null }; struct AstNodeContinueExpr { + Buf *name; }; + struct AstNodeUnreachableExpr { }; @@ -886,7 +885,6 @@ struct AstNode { AstNodeSwitchProng switch_prong; AstNodeSwitchRange switch_range; AstNodeLabel label; - AstNodeGoto goto_expr; AstNodeCompTime comptime_expr; AstNodeAsmExpr asm_expr; AstNodeFieldAccessExpr field_access_expr; @@ -1741,6 +1739,7 @@ struct ScopeCImport { struct ScopeLoop { Scope base; + Buf *name; IrBasicBlock *break_block; IrBasicBlock *continue_block; IrInstruction *is_comptime; diff --git a/src/analyze.cpp b/src/analyze.cpp index d6719cf52c..23301cc4de 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -144,9 +144,15 @@ ScopeCImport *create_cimport_scope(AstNode *node, Scope *parent) { } ScopeLoop *create_loop_scope(AstNode *node, Scope *parent) { - assert(node->type == NodeTypeWhileExpr || node->type == NodeTypeForExpr); ScopeLoop *scope = allocate(1); init_scope(&scope->base, ScopeIdLoop, node, parent); + if (node->type == NodeTypeWhileExpr) { + scope->name = node->data.while_expr.name; + } else if (node->type == NodeTypeForExpr) { + scope->name = node->data.for_expr.name; + } else { + zig_unreachable(); + } return scope; } @@ -2916,8 +2922,6 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) { case NodeTypeSwitchExpr: case NodeTypeSwitchProng: case NodeTypeSwitchRange: - case NodeTypeLabel: - case NodeTypeGoto: case NodeTypeBreak: case NodeTypeContinue: case NodeTypeUnreachable: diff --git a/src/ast_render.cpp b/src/ast_render.cpp index c22c16d90a..e64a19d42d 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -215,10 +215,6 @@ static const char *node_type_str(NodeType node_type) { return "SwitchProng"; case NodeTypeSwitchRange: return "SwitchRange"; - case NodeTypeLabel: - return "Label"; - case NodeTypeGoto: - return "Goto"; case NodeTypeCompTime: return "CompTime"; case NodeTypeBreak: @@ -391,7 +387,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { switch (node->type) { case NodeTypeSwitchProng: case NodeTypeSwitchRange: - case NodeTypeLabel: case NodeTypeStructValueField: zig_unreachable(); case NodeTypeRoot: @@ -470,6 +465,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { break; } case NodeTypeBlock: + if (node->data.block.name != nullptr) { + fprintf(ar->f, "%s: ", buf_ptr(node->data.block.name)); + } if (node->data.block.statements.length == 0) { fprintf(ar->f, "{}"); break; @@ -478,13 +476,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { ar->indent += ar->indent_size; for (size_t i = 0; i < node->data.block.statements.length; i += 1) { AstNode *statement = node->data.block.statements.at(i); - if (statement->type == NodeTypeLabel) { - ar->indent -= ar->indent_size; - print_indent(ar); - fprintf(ar->f, "%s:\n", buf_ptr(statement->data.label.name)); - ar->indent += ar->indent_size; - continue; - } print_indent(ar); render_node_grouped(ar, statement); if (!(i == node->data.block.statements.length - 1 && @@ -515,6 +506,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { case NodeTypeBreak: { fprintf(ar->f, "break"); + if (node->data.break_expr.name != nullptr) { + fprintf(ar->f, " :%s", buf_ptr(node->data.break_expr.name)); + } if (node->data.break_expr.expr) { fprintf(ar->f, " "); render_node_grouped(ar, node->data.break_expr.expr); @@ -828,6 +822,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { } case NodeTypeWhileExpr: { + if (node->data.while_expr.name != nullptr) { + fprintf(ar->f, "%s: ", buf_ptr(node->data.while_expr.name)); + } const char *inline_str = node->data.while_expr.is_inline ? "inline " : ""; fprintf(ar->f, "%swhile (", inline_str); render_node_grouped(ar, node->data.while_expr.condition); @@ -957,11 +954,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { fprintf(ar->f, "}"); break; } - case NodeTypeGoto: - { - fprintf(ar->f, "goto %s", buf_ptr(node->data.goto_expr.name)); - break; - } case NodeTypeCompTime: { fprintf(ar->f, "comptime "); @@ -970,6 +962,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { } case NodeTypeForExpr: { + if (node->data.for_expr.name != nullptr) { + fprintf(ar->f, "%s: ", buf_ptr(node->data.for_expr.name)); + } const char *inline_str = node->data.for_expr.is_inline ? "inline " : ""; fprintf(ar->f, "%sfor (", inline_str); render_node_grouped(ar, node->data.for_expr.array_expr); @@ -995,6 +990,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { case NodeTypeContinue: { fprintf(ar->f, "continue"); + if (node->data.continue_expr.name != nullptr) { + fprintf(ar->f, " :%s", buf_ptr(node->data.continue_expr.name)); + } break; } case NodeTypeUnreachable: diff --git a/src/ir.cpp b/src/ir.cpp index db919350d1..d3dd58aaff 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -3511,29 +3511,6 @@ static VariableTableEntry *ir_create_var(IrBuilder *irb, AstNode *node, Scope *s return var; } -static LabelTableEntry *find_label(IrExecutable *exec, Scope *scope, Buf *name) { - while (scope) { - if (scope->id == ScopeIdBlock) { - ScopeBlock *block_scope = (ScopeBlock *)scope; - auto entry = block_scope->label_table.maybe_get(name); - if (entry) - return entry->value; - } - scope = scope->parent; - } - - return nullptr; -} - -static ScopeBlock *find_block_scope(IrExecutable *exec, Scope *scope) { - while (scope) { - if (scope->id == ScopeIdBlock) - return (ScopeBlock *)scope; - scope = scope->parent; - } - return nullptr; -} - static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode *block_node) { assert(block_node->type == NodeTypeBlock); @@ -3557,38 +3534,6 @@ static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode for (size_t i = 0; i < block_node->data.block.statements.length; i += 1) { AstNode *statement_node = block_node->data.block.statements.at(i); - if (statement_node->type == NodeTypeLabel) { - Buf *label_name = statement_node->data.label.name; - IrBasicBlock *label_block = ir_build_basic_block(irb, child_scope, buf_ptr(label_name)); - LabelTableEntry *label = allocate(1); - label->decl_node = statement_node; - label->bb = label_block; - irb->exec->all_labels.append(label); - - LabelTableEntry *existing_label = find_label(irb->exec, child_scope, label_name); - if (existing_label) { - ErrorMsg *msg = add_node_error(irb->codegen, statement_node, - buf_sprintf("duplicate label name '%s'", buf_ptr(label_name))); - add_error_note(irb->codegen, msg, existing_label->decl_node, buf_sprintf("other label here")); - return irb->codegen->invalid_instruction; - } else { - ScopeBlock *scope_block = find_block_scope(irb->exec, child_scope); - scope_block->label_table.put(label_name, label); - } - - if (!is_continuation_unreachable) { - // fall through into new labeled basic block - IrInstruction *is_comptime = ir_mark_gen(ir_build_const_bool(irb, child_scope, statement_node, - ir_should_inline(irb->exec, child_scope))); - ir_mark_gen(ir_build_br(irb, child_scope, statement_node, label_block, is_comptime)); - } - ir_set_cursor_at_end(irb, label_block); - - // a label is an entry point - is_continuation_unreachable = false; - continue; - } - IrInstruction *statement_value = ir_gen_node(irb, statement_node, child_scope); is_continuation_unreachable = instr_is_unreachable(statement_value); if (is_continuation_unreachable) { @@ -6000,22 +5945,6 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items); } -static IrInstruction *ir_gen_goto(IrBuilder *irb, Scope *scope, AstNode *node) { - assert(node->type == NodeTypeGoto); - - // make a placeholder unreachable statement and a note to come back and - // replace the instruction with a branch instruction - IrGotoItem *goto_item = irb->exec->goto_list.add_one(); - goto_item->bb = irb->current_basic_block; - goto_item->instruction_index = irb->current_basic_block->instruction_list.length; - goto_item->source_node = node; - goto_item->scope = scope; - - // we don't know if we need to generate defer expressions yet - // we do that later when we find out which label we're jumping to. - return ir_build_unreachable(irb, scope, node); -} - static IrInstruction *ir_gen_comptime(IrBuilder *irb, Scope *parent_scope, AstNode *node, LVal lval) { assert(node->type == NodeTypeCompTime); @@ -6033,16 +5962,28 @@ static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode * Scope *search_scope = break_scope; ScopeLoop *loop_scope; + bool saw_any_loop_scope = false; for (;;) { if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) { - add_node_error(irb->codegen, node, buf_sprintf("break expression outside loop")); - return irb->codegen->invalid_instruction; + if (saw_any_loop_scope) { + add_node_error(irb->codegen, node, buf_sprintf("labeled loop not found: '%s'", buf_ptr(node->data.break_expr.name))); + return irb->codegen->invalid_instruction; + } else { + add_node_error(irb->codegen, node, buf_sprintf("break expression outside loop")); + return irb->codegen->invalid_instruction; + } } else if (search_scope->id == ScopeIdDeferExpr) { add_node_error(irb->codegen, node, buf_sprintf("cannot break out of defer expression")); return irb->codegen->invalid_instruction; } else if (search_scope->id == ScopeIdLoop) { - loop_scope = (ScopeLoop *)search_scope; - break; + ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope; + saw_any_loop_scope = true; + if (node->data.break_expr.name == nullptr || + (this_loop_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_loop_scope->name))) + { + loop_scope = this_loop_scope; + break; + } } search_scope = search_scope->parent; } @@ -6081,16 +6022,28 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast Scope *search_scope = continue_scope; ScopeLoop *loop_scope; + bool saw_any_loop_scope = false; for (;;) { if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) { - add_node_error(irb->codegen, node, buf_sprintf("continue expression outside loop")); - return irb->codegen->invalid_instruction; + if (saw_any_loop_scope) { + add_node_error(irb->codegen, node, buf_sprintf("labeled loop not found: '%s'", buf_ptr(node->data.continue_expr.name))); + return irb->codegen->invalid_instruction; + } else { + add_node_error(irb->codegen, node, buf_sprintf("continue expression outside loop")); + return irb->codegen->invalid_instruction; + } } else if (search_scope->id == ScopeIdDeferExpr) { add_node_error(irb->codegen, node, buf_sprintf("cannot continue out of defer expression")); return irb->codegen->invalid_instruction; } else if (search_scope->id == ScopeIdLoop) { - loop_scope = (ScopeLoop *)search_scope; - break; + ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope; + saw_any_loop_scope = true; + if (node->data.continue_expr.name == nullptr || + (this_loop_scope->name != nullptr && buf_eql_buf(node->data.continue_expr.name, this_loop_scope->name))) + { + loop_scope = this_loop_scope; + break; + } } search_scope = search_scope->parent; } @@ -6332,7 +6285,6 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop case NodeTypeSwitchProng: case NodeTypeSwitchRange: case NodeTypeStructField: - case NodeTypeLabel: case NodeTypeFnDef: case NodeTypeFnDecl: case NodeTypeErrorValueDecl: @@ -6396,8 +6348,6 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop return ir_lval_wrap(irb, scope, ir_gen_test_expr(irb, scope, node), lval); case NodeTypeSwitchExpr: return ir_lval_wrap(irb, scope, ir_gen_switch_expr(irb, scope, node), lval); - case NodeTypeGoto: - return ir_lval_wrap(irb, scope, ir_gen_goto(irb, scope, node), lval); case NodeTypeCompTime: return ir_gen_comptime(irb, scope, node, lval); case NodeTypeErrorType: @@ -6432,70 +6382,6 @@ static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope) { return ir_gen_node_extra(irb, node, scope, LVAL_NONE); } -static bool ir_goto_pass2(IrBuilder *irb) { - for (size_t i = 0; i < irb->exec->goto_list.length; i += 1) { - IrGotoItem *goto_item = &irb->exec->goto_list.at(i); - AstNode *source_node = goto_item->source_node; - - // Since a goto will always end a basic block, we move the "current instruction" - // index back to over the placeholder unreachable instruction and begin overwriting - irb->current_basic_block = goto_item->bb; - irb->current_basic_block->instruction_list.resize(goto_item->instruction_index); - - Buf *label_name = source_node->data.goto_expr.name; - - // Search up the scope until we find one of these things: - // * A block scope with the label in it => OK - // * A defer expression scope => error, error, cannot leave defer expression - // * Top level scope => error, didn't find label - - LabelTableEntry *label; - Scope *search_scope = goto_item->scope; - for (;;) { - if (search_scope == nullptr) { - add_node_error(irb->codegen, source_node, - buf_sprintf("no label in scope named '%s'", buf_ptr(label_name))); - return false; - } else if (search_scope->id == ScopeIdBlock) { - ScopeBlock *block_scope = (ScopeBlock *)search_scope; - auto entry = block_scope->label_table.maybe_get(label_name); - if (entry) { - label = entry->value; - break; - } - } else if (search_scope->id == ScopeIdDeferExpr) { - add_node_error(irb->codegen, source_node, - buf_sprintf("cannot goto out of defer expression")); - return false; - } - search_scope = search_scope->parent; - } - - label->used = true; - - IrInstruction *is_comptime = ir_build_const_bool(irb, goto_item->scope, source_node, - ir_should_inline(irb->exec, goto_item->scope) || source_node->data.goto_expr.is_inline); - if (!ir_gen_defers_for_block(irb, goto_item->scope, label->bb->scope, false)) { - add_node_error(irb->codegen, source_node, - buf_sprintf("no label in scope named '%s'", buf_ptr(label_name))); - return false; - } - ir_build_br(irb, goto_item->scope, source_node, label->bb, is_comptime); - } - - for (size_t i = 0; i < irb->exec->all_labels.length; i += 1) { - LabelTableEntry *label = irb->exec->all_labels.at(i); - if (!label->used) { - add_node_error(irb->codegen, label->decl_node, - buf_sprintf("label '%s' defined but not used", - buf_ptr(label->decl_node->data.label.name))); - return false; - } - } - - return true; -} - static void invalidate_exec(IrExecutable *exec) { if (exec->invalid) return; @@ -6532,11 +6418,6 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec ir_mark_gen(ir_build_return(irb, scope, result->source_node, result)); } - if (!ir_goto_pass2(irb)) { - invalidate_exec(ir_executable); - return false; - } - return true; } diff --git a/src/parser.cpp b/src/parser.cpp index 579fe85f3b..b5fdd681e8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -632,27 +632,6 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, size_t *token_index, bool m return node; } -/* -GotoExpression = "goto" Symbol -*/ -static AstNode *ast_parse_goto_expr(ParseContext *pc, size_t *token_index, bool mandatory) { - Token *goto_token = &pc->tokens->at(*token_index); - if (goto_token->id == TokenIdKeywordGoto) { - *token_index += 1; - } else if (mandatory) { - ast_expect_token(pc, goto_token, TokenIdKeywordGoto); - zig_unreachable(); - } else { - return nullptr; - } - - AstNode *node = ast_create_node(pc, NodeTypeGoto, goto_token); - - Token *dest_symbol = ast_eat_token(pc, token_index, TokenIdSymbol); - node->data.goto_expr.name = token_buf(dest_symbol); - return node; -} - /* CompTimeExpression(body) = "comptime" body */ @@ -676,8 +655,8 @@ static AstNode *ast_parse_comptime_expr(ParseContext *pc, size_t *token_index, b } /* -PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl -KeywordLiteral = "true" | "false" | "null" | "continue" | "undefined" | "error" | "this" | "unreachable" +PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl | ("continue" option(":" Symbol)) +KeywordLiteral = "true" | "false" | "null" | "undefined" | "error" | "this" | "unreachable" */ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -721,6 +700,12 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo } else if (token->id == TokenIdKeywordContinue) { AstNode *node = ast_create_node(pc, NodeTypeContinue, token); *token_index += 1; + Token *maybe_colon_token = &pc->tokens->at(*token_index); + if (maybe_colon_token->id == TokenIdColon) { + *token_index += 1; + Token *name = ast_eat_token(pc, token_index, TokenIdSymbol); + node->data.continue_expr.name = token_buf(name); + } return node; } else if (token->id == TokenIdKeywordUndefined) { AstNode *node = ast_create_node(pc, NodeTypeUndefinedLiteral, token); @@ -770,10 +755,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo return node; } - AstNode *goto_node = ast_parse_goto_expr(pc, token_index, false); - if (goto_node) - return goto_node; - AstNode *grouped_expr_node = ast_parse_grouped_expr(pc, token_index, false); if (grouped_expr_node) { return grouped_expr_node; @@ -1488,7 +1469,7 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, size_t *token_index) { } /* -BreakExpression : "break" option(Expression) +BreakExpression = "break" option(":" Symbol) option(Expression) */ static AstNode *ast_parse_break_expr(ParseContext *pc, size_t *token_index) { Token *token = &pc->tokens->at(*token_index); @@ -1498,8 +1479,15 @@ static AstNode *ast_parse_break_expr(ParseContext *pc, size_t *token_index) { } else { return nullptr; } - AstNode *node = ast_create_node(pc, NodeTypeBreak, token); + + Token *maybe_colon_token = &pc->tokens->at(*token_index); + if (maybe_colon_token->id == TokenIdColon) { + *token_index += 1; + Token *name = ast_eat_token(pc, token_index, TokenIdSymbol); + node->data.break_expr.name = token_buf(name); + } + node->data.break_expr.expr = ast_parse_expression(pc, token_index, false); return node; @@ -1678,35 +1666,53 @@ static AstNode *ast_parse_bool_or_expr(ParseContext *pc, size_t *token_index, bo } /* -WhileExpression(body) = option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body)) +WhileExpression(body) = option(Symbol ":") option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body)) */ static AstNode *ast_parse_while_expr(ParseContext *pc, size_t *token_index, bool mandatory) { - Token *first_token = &pc->tokens->at(*token_index); - Token *while_token; + size_t orig_token_index = *token_index; - bool is_inline; - if (first_token->id == TokenIdKeywordInline) { - while_token = &pc->tokens->at(*token_index + 1); - if (while_token->id == TokenIdKeywordWhile) { - is_inline = true; - *token_index += 2; + Token *name_token = nullptr; + Token *token = &pc->tokens->at(*token_index); + + if (token->id == TokenIdSymbol) { + *token_index += 1; + Token *colon_token = &pc->tokens->at(*token_index); + if (colon_token->id == TokenIdColon) { + *token_index += 1; + name_token = token; + token = &pc->tokens->at(*token_index); } else if (mandatory) { - ast_expect_token(pc, while_token, TokenIdKeywordWhile); + ast_expect_token(pc, colon_token, TokenIdColon); zig_unreachable(); } else { + *token_index = orig_token_index; return nullptr; } - } else if (first_token->id == TokenIdKeywordWhile) { - while_token = first_token; - is_inline = false; + } + + bool is_inline = false; + if (token->id == TokenIdKeywordInline) { + is_inline = true; + *token_index += 1; + token = &pc->tokens->at(*token_index); + } + + Token *while_token; + if (token->id == TokenIdKeywordWhile) { + while_token = token; *token_index += 1; } else if (mandatory) { - ast_expect_token(pc, first_token, TokenIdKeywordWhile); + ast_expect_token(pc, token, TokenIdKeywordWhile); zig_unreachable(); } else { + *token_index = orig_token_index; return nullptr; } + AstNode *node = ast_create_node(pc, NodeTypeWhileExpr, while_token); + if (name_token != nullptr) { + node->data.while_expr.name = token_buf(name_token); + } node->data.while_expr.is_inline = is_inline; ast_eat_token(pc, token_index, TokenIdLParen); @@ -1766,36 +1772,53 @@ static AstNode *ast_parse_symbol(ParseContext *pc, size_t *token_index) { } /* -ForExpression(body) = option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body)) +ForExpression(body) = option(Symbol ":") option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body)) */ static AstNode *ast_parse_for_expr(ParseContext *pc, size_t *token_index, bool mandatory) { - Token *first_token = &pc->tokens->at(*token_index); - Token *for_token; + size_t orig_token_index = *token_index; - bool is_inline; - if (first_token->id == TokenIdKeywordInline) { - is_inline = true; - for_token = &pc->tokens->at(*token_index + 1); - if (for_token->id == TokenIdKeywordFor) { - *token_index += 2; + Token *name_token = nullptr; + Token *token = &pc->tokens->at(*token_index); + + if (token->id == TokenIdSymbol) { + *token_index += 1; + Token *colon_token = &pc->tokens->at(*token_index); + if (colon_token->id == TokenIdColon) { + *token_index += 1; + name_token = token; + token = &pc->tokens->at(*token_index); } else if (mandatory) { - ast_expect_token(pc, first_token, TokenIdKeywordFor); + ast_expect_token(pc, colon_token, TokenIdColon); zig_unreachable(); } else { + *token_index = orig_token_index; return nullptr; } - } else if (first_token->id == TokenIdKeywordFor) { - for_token = first_token; - is_inline = false; + } + + bool is_inline = false; + if (token->id == TokenIdKeywordInline) { + is_inline = true; + *token_index += 1; + token = &pc->tokens->at(*token_index); + } + + Token *for_token; + if (token->id == TokenIdKeywordFor) { + for_token = token; *token_index += 1; } else if (mandatory) { - ast_expect_token(pc, first_token, TokenIdKeywordFor); + ast_expect_token(pc, token, TokenIdKeywordFor); zig_unreachable(); } else { + *token_index = orig_token_index; return nullptr; } AstNode *node = ast_create_node(pc, NodeTypeForExpr, for_token); + if (name_token != nullptr) { + node->data.for_expr.name = token_buf(name_token); + } node->data.for_expr.is_inline = is_inline; ast_eat_token(pc, token_index, TokenIdLParen); @@ -2125,32 +2148,6 @@ static AstNode *ast_parse_expression(ParseContext *pc, size_t *token_index, bool /* Label: token(Symbol) token(Colon) */ -static AstNode *ast_parse_label(ParseContext *pc, size_t *token_index, bool mandatory) { - Token *symbol_token = &pc->tokens->at(*token_index); - if (symbol_token->id != TokenIdSymbol) { - if (mandatory) { - ast_expect_token(pc, symbol_token, TokenIdSymbol); - } else { - return nullptr; - } - } - - Token *colon_token = &pc->tokens->at(*token_index + 1); - if (colon_token->id != TokenIdColon) { - if (mandatory) { - ast_expect_token(pc, colon_token, TokenIdColon); - } else { - return nullptr; - } - } - - *token_index += 2; - - AstNode *node = ast_create_node(pc, NodeTypeLabel, symbol_token); - node->data.label.name = token_buf(symbol_token); - return node; -} - static bool statement_terminates_without_semicolon(AstNode *node) { switch (node->type) { case NodeTypeIfBoolExpr: @@ -2175,7 +2172,6 @@ static bool statement_terminates_without_semicolon(AstNode *node) { return node->data.defer.expr->type == NodeTypeBlock; case NodeTypeSwitchExpr: case NodeTypeBlock: - case NodeTypeLabel: return true; default: return false; @@ -2183,27 +2179,48 @@ static bool statement_terminates_without_semicolon(AstNode *node) { } /* -Block = "{" many(Statement) option(Expression) "}" +Block = option(Symbol ":") "{" many(Statement) option(Expression) "}" Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" | ExportDecl */ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mandatory) { + size_t orig_token_index = *token_index; + + Token *name_token = nullptr; Token *last_token = &pc->tokens->at(*token_index); + if (last_token->id == TokenIdSymbol) { + *token_index += 1; + Token *colon_token = &pc->tokens->at(*token_index); + if (colon_token->id == TokenIdColon) { + *token_index += 1; + name_token = last_token; + last_token = &pc->tokens->at(*token_index); + } else if (mandatory) { + ast_expect_token(pc, colon_token, TokenIdColon); + zig_unreachable(); + } else { + *token_index = orig_token_index; + return nullptr; + } + } + if (last_token->id != TokenIdLBrace) { if (mandatory) { ast_expect_token(pc, last_token, TokenIdLBrace); } else { + *token_index = orig_token_index; return nullptr; } } *token_index += 1; AstNode *node = ast_create_node(pc, NodeTypeBlock, last_token); + if (name_token != nullptr) { + node->data.block.name = token_buf(name_token); + } for (;;) { - AstNode *statement_node = ast_parse_label(pc, token_index, false); - if (!statement_node) - statement_node = ast_parse_local_var_decl(pc, token_index); + AstNode *statement_node = ast_parse_local_var_decl(pc, token_index); if (!statement_node) statement_node = ast_parse_defer_expr(pc, token_index); if (!statement_node) @@ -2225,9 +2242,7 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand } } - node->data.block.last_statement_is_result_expression = statement_node && !( - statement_node->type == NodeTypeLabel || - statement_node->type == NodeTypeDefer); + node->data.block.last_statement_is_result_expression = statement_node && statement_node->type != NodeTypeDefer; last_token = &pc->tokens->at(*token_index); if (last_token->id == TokenIdRBrace) { @@ -2860,12 +2875,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont visit_field(&node->data.switch_range.start, visit, context); visit_field(&node->data.switch_range.end, visit, context); break; - case NodeTypeLabel: - // none - break; - case NodeTypeGoto: - // none - break; case NodeTypeCompTime: visit_field(&node->data.comptime_expr.expr, visit, context); break; diff --git a/src/translate_c.cpp b/src/translate_c.cpp index eba594e085..bb319ccb54 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -104,10 +104,8 @@ static TransScopeRoot *trans_scope_root_create(Context *c); static TransScopeWhile *trans_scope_while_create(Context *c, TransScope *parent_scope); static TransScopeBlock *trans_scope_block_create(Context *c, TransScope *parent_scope); static TransScopeVar *trans_scope_var_create(Context *c, TransScope *parent_scope, Buf *wanted_name); -static TransScopeSwitch *trans_scope_switch_create(Context *c, TransScope *parent_scope); static TransScopeBlock *trans_scope_block_find(TransScope *scope); -static TransScopeSwitch *trans_scope_switch_find(TransScope *scope); static AstNode *resolve_record_decl(Context *c, const RecordDecl *record_decl); static AstNode *resolve_enum_decl(Context *c, const EnumDecl *enum_decl); @@ -265,18 +263,6 @@ static AstNode *trans_create_node_addr_of(Context *c, bool is_const, bool is_vol return node; } -static AstNode *trans_create_node_goto(Context *c, Buf *label_name) { - AstNode *goto_node = trans_create_node(c, NodeTypeGoto); - goto_node->data.goto_expr.name = label_name; - return goto_node; -} - -static AstNode *trans_create_node_label(Context *c, Buf *label_name) { - AstNode *label_node = trans_create_node(c, NodeTypeLabel); - label_node->data.label.name = label_name; - return label_node; -} - static AstNode *trans_create_node_bool(Context *c, bool value) { AstNode *bool_node = trans_create_node(c, NodeTypeBoolLiteral); bool_node->data.bool_literal.value = value; @@ -2379,145 +2365,6 @@ static AstNode *trans_do_loop(Context *c, TransScope *parent_scope, const DoStmt return while_scope->node; } -static AstNode *trans_switch_stmt(Context *c, TransScope *parent_scope, const SwitchStmt *stmt) { - TransScopeBlock *block_scope = trans_scope_block_create(c, parent_scope); - - TransScopeSwitch *switch_scope; - - const DeclStmt *var_decl_stmt = stmt->getConditionVariableDeclStmt(); - if (var_decl_stmt == nullptr) { - switch_scope = trans_scope_switch_create(c, &block_scope->base); - } else { - AstNode *vars_node; - TransScope *var_scope = trans_stmt(c, &block_scope->base, var_decl_stmt, &vars_node); - if (var_scope == nullptr) - return nullptr; - if (vars_node != nullptr) - block_scope->node->data.block.statements.append(vars_node); - switch_scope = trans_scope_switch_create(c, var_scope); - } - block_scope->node->data.block.statements.append(switch_scope->switch_node); - - // TODO avoid name collisions - Buf *end_label_name = buf_create_from_str("end"); - switch_scope->end_label_name = end_label_name; - - const Expr *cond_expr = stmt->getCond(); - assert(cond_expr != nullptr); - - AstNode *expr_node = trans_expr(c, ResultUsedYes, &block_scope->base, cond_expr, TransRValue); - if (expr_node == nullptr) - return nullptr; - switch_scope->switch_node->data.switch_expr.expr = expr_node; - - AstNode *body_node; - const Stmt *body_stmt = stmt->getBody(); - if (body_stmt->getStmtClass() == Stmt::CompoundStmtClass) { - if (trans_compound_stmt_inline(c, &switch_scope->base, (const CompoundStmt *)body_stmt, - block_scope->node, nullptr)) - { - return nullptr; - } - } else { - TransScope *body_scope = trans_stmt(c, &switch_scope->base, body_stmt, &body_node); - if (body_scope == nullptr) - return nullptr; - if (body_node != nullptr) - block_scope->node->data.block.statements.append(body_node); - } - - if (!switch_scope->found_default && !stmt->isAllEnumCasesCovered()) { - AstNode *prong_node = trans_create_node(c, NodeTypeSwitchProng); - prong_node->data.switch_prong.expr = trans_create_node_goto(c, end_label_name); - switch_scope->switch_node->data.switch_expr.prongs.append(prong_node); - } - - // This is necessary if the last switch case "falls through" the end of the switch block - block_scope->node->data.block.statements.append(trans_create_node_goto(c, end_label_name)); - - block_scope->node->data.block.statements.append(trans_create_node_label(c, end_label_name)); - - return block_scope->node; -} - -static int trans_switch_case(Context *c, TransScope *parent_scope, const CaseStmt *stmt, AstNode **out_node, - TransScope **out_scope) -{ - *out_node = nullptr; - - if (stmt->getRHS() != nullptr) { - emit_warning(c, stmt->getLocStart(), "TODO support GNU switch case a ... b extension"); - return ErrorUnexpected; - } - - TransScopeSwitch *switch_scope = trans_scope_switch_find(parent_scope); - assert(switch_scope != nullptr); - - Buf *label_name = buf_sprintf("case_%" PRIu32, switch_scope->case_index); - switch_scope->case_index += 1; - - { - // Add the prong - AstNode *prong_node = trans_create_node(c, NodeTypeSwitchProng); - AstNode *item_node = trans_expr(c, ResultUsedYes, &switch_scope->base, stmt->getLHS(), TransRValue); - if (item_node == nullptr) - return ErrorUnexpected; - prong_node->data.switch_prong.items.append(item_node); - - prong_node->data.switch_prong.expr = trans_create_node_goto(c, label_name); - - switch_scope->switch_node->data.switch_expr.prongs.append(prong_node); - } - - TransScopeBlock *scope_block = trans_scope_block_find(parent_scope); - scope_block->node->data.block.statements.append(trans_create_node_label(c, label_name)); - - AstNode *sub_stmt_node; - TransScope *new_scope = trans_stmt(c, parent_scope, stmt->getSubStmt(), &sub_stmt_node); - if (new_scope == nullptr) - return ErrorUnexpected; - if (sub_stmt_node != nullptr) - scope_block->node->data.block.statements.append(sub_stmt_node); - - *out_scope = new_scope; - return ErrorNone; -} - -static int trans_switch_default(Context *c, TransScope *parent_scope, const DefaultStmt *stmt, AstNode **out_node, - TransScope **out_scope) -{ - *out_node = nullptr; - - TransScopeSwitch *switch_scope = trans_scope_switch_find(parent_scope); - assert(switch_scope != nullptr); - - Buf *label_name = buf_sprintf("default"); - - { - // Add the prong - AstNode *prong_node = trans_create_node(c, NodeTypeSwitchProng); - - prong_node->data.switch_prong.expr = trans_create_node_goto(c, label_name); - - switch_scope->switch_node->data.switch_expr.prongs.append(prong_node); - switch_scope->found_default = true; - } - - TransScopeBlock *scope_block = trans_scope_block_find(parent_scope); - scope_block->node->data.block.statements.append(trans_create_node_label(c, label_name)); - - - AstNode *sub_stmt_node; - TransScope *new_scope = trans_stmt(c, parent_scope, stmt->getSubStmt(), &sub_stmt_node); - if (new_scope == nullptr) - return ErrorUnexpected; - if (sub_stmt_node != nullptr) - scope_block->node->data.block.statements.append(sub_stmt_node); - - *out_scope = new_scope; - return ErrorNone; -} - static AstNode *trans_for_loop(Context *c, TransScope *parent_scope, const ForStmt *stmt) { AstNode *loop_block_node; TransScopeWhile *while_scope; @@ -2595,8 +2442,7 @@ static AstNode *trans_break_stmt(Context *c, TransScope *scope, const BreakStmt if (cur_scope->id == TransScopeIdWhile) { return trans_create_node(c, NodeTypeBreak); } else if (cur_scope->id == TransScopeIdSwitch) { - TransScopeSwitch *switch_scope = (TransScopeSwitch *)cur_scope; - return trans_create_node_goto(c, switch_scope->end_label_name); + zig_panic("TODO"); } cur_scope = cur_scope->parent; } @@ -2696,12 +2542,14 @@ static int trans_stmt_extra(Context *c, TransScope *scope, const Stmt *stmt, return wrap_stmt(out_node, out_child_scope, scope, trans_expr(c, result_used, scope, ((const ParenExpr*)stmt)->getSubExpr(), lrvalue)); case Stmt::SwitchStmtClass: - return wrap_stmt(out_node, out_child_scope, scope, - trans_switch_stmt(c, scope, (const SwitchStmt *)stmt)); + emit_warning(c, stmt->getLocStart(), "TODO handle C SwitchStmtClass"); + return ErrorUnexpected; case Stmt::CaseStmtClass: - return trans_switch_case(c, scope, (const CaseStmt *)stmt, out_node, out_child_scope); + emit_warning(c, stmt->getLocStart(), "TODO handle C CaseStmtClass"); + return ErrorUnexpected; case Stmt::DefaultStmtClass: - return trans_switch_default(c, scope, (const DefaultStmt *)stmt, out_node, out_child_scope); + emit_warning(c, stmt->getLocStart(), "TODO handle C DefaultStmtClass"); + return ErrorUnexpected; case Stmt::NoStmtClass: emit_warning(c, stmt->getLocStart(), "TODO handle C NoStmtClass"); return ErrorUnexpected; @@ -3871,14 +3719,6 @@ static TransScopeVar *trans_scope_var_create(Context *c, TransScope *parent_scop return result; } -static TransScopeSwitch *trans_scope_switch_create(Context *c, TransScope *parent_scope) { - TransScopeSwitch *result = allocate(1); - result->base.id = TransScopeIdSwitch; - result->base.parent = parent_scope; - result->switch_node = trans_create_node(c, NodeTypeSwitchExpr); - return result; -} - static TransScopeBlock *trans_scope_block_find(TransScope *scope) { while (scope != nullptr) { if (scope->id == TransScopeIdBlock) { @@ -3889,16 +3729,6 @@ static TransScopeBlock *trans_scope_block_find(TransScope *scope) { return nullptr; } -static TransScopeSwitch *trans_scope_switch_find(TransScope *scope) { - while (scope != nullptr) { - if (scope->id == TransScopeIdSwitch) { - return (TransScopeSwitch *)scope; - } - scope = scope->parent; - } - return nullptr; -} - static void render_aliases(Context *c) { for (size_t i = 0; i < c->aliases.length; i += 1) { Alias *alias = &c->aliases.at(i); diff --git a/std/elf.zig b/std/elf.zig index 2c5b10b3f2..60b0119894 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -243,7 +243,7 @@ pub const Elf = struct { var file_stream = io.FileInStream.init(elf.in_file); const in = &file_stream.stream; - for (elf.section_headers) |*elf_section| { + section_loop: for (elf.section_headers) |*elf_section| { if (elf_section.sh_type == SHT_NULL) continue; const name_offset = elf.string_section.offset + elf_section.name; @@ -251,15 +251,13 @@ pub const Elf = struct { for (name) |expected_c| { const target_c = %return in.readByte(); - if (target_c == 0 or expected_c != target_c) goto next_section; + if (target_c == 0 or expected_c != target_c) continue :section_loop; } { const null_byte = %return in.readByte(); if (null_byte == 0) return elf_section; } - - next_section: } return null; diff --git a/std/os/index.zig b/std/os/index.zig index d26daed9fe..3eba15ef8a 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -902,40 +902,41 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) -> %void { /// this function recursively removes its entries and then tries again. // TODO non-recursive implementation pub fn deleteTree(allocator: &Allocator, full_path: []const u8) -> %void { -start_over: - // First, try deleting the item as a file. This way we don't follow sym links. - if (deleteFile(allocator, full_path)) { - return; - } else |err| { - if (err == error.FileNotFound) + start_over: while (true) { + // First, try deleting the item as a file. This way we don't follow sym links. + if (deleteFile(allocator, full_path)) { return; - if (err != error.IsDir) - return err; - } - { - var dir = Dir.open(allocator, full_path) %% |err| { + } else |err| { if (err == error.FileNotFound) return; - if (err == error.NotDir) - goto start_over; - return err; - }; - defer dir.close(); - - var full_entry_buf = ArrayList(u8).init(allocator); - defer full_entry_buf.deinit(); - - while (%return dir.next()) |entry| { - %return full_entry_buf.resize(full_path.len + entry.name.len + 1); - const full_entry_path = full_entry_buf.toSlice(); - mem.copy(u8, full_entry_path, full_path); - full_entry_path[full_path.len] = '/'; - mem.copy(u8, full_entry_path[full_path.len + 1..], entry.name); - - %return deleteTree(allocator, full_entry_path); + if (err != error.IsDir) + return err; } + { + var dir = Dir.open(allocator, full_path) %% |err| { + if (err == error.FileNotFound) + return; + if (err == error.NotDir) + continue :start_over; + return err; + }; + defer dir.close(); + + var full_entry_buf = ArrayList(u8).init(allocator); + defer full_entry_buf.deinit(); + + while (%return dir.next()) |entry| { + %return full_entry_buf.resize(full_path.len + entry.name.len + 1); + const full_entry_path = full_entry_buf.toSlice(); + mem.copy(u8, full_entry_path, full_path); + full_entry_path[full_path.len] = '/'; + mem.copy(u8, full_entry_path[full_path.len + 1..], entry.name); + + %return deleteTree(allocator, full_entry_path); + } + } + return deleteDir(allocator, full_path); } - return deleteDir(allocator, full_path); } pub const Dir = struct { @@ -988,58 +989,59 @@ pub const Dir = struct { /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to next, as well as when this ::Dir is deinitialized. pub fn next(self: &Dir) -> %?Entry { - start_over: - if (self.index >= self.end_index) { - if (self.buf.len == 0) { - self.buf = %return self.allocator.alloc(u8, page_size); - } - - while (true) { - const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len); - const err = linux.getErrno(result); - if (err > 0) { - switch (err) { - posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, - posix.EINVAL => { - self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2); - continue; - }, - else => return unexpectedErrorPosix(err), - }; + start_over: while (true) { + if (self.index >= self.end_index) { + if (self.buf.len == 0) { + self.buf = %return self.allocator.alloc(u8, page_size); + } + + while (true) { + const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len); + const err = linux.getErrno(result); + if (err > 0) { + switch (err) { + posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EINVAL => { + self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2); + continue; + }, + else => return unexpectedErrorPosix(err), + }; + } + if (result == 0) + return null; + self.index = 0; + self.end_index = result; + break; } - if (result == 0) - return null; - self.index = 0; - self.end_index = result; - break; } + const linux_entry = @ptrCast(& align(1) LinuxEntry, &self.buf[self.index]); + const next_index = self.index + linux_entry.d_reclen; + self.index = next_index; + + const name = cstr.toSlice(&linux_entry.d_name); + + // skip . and .. entries + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const type_char = self.buf[next_index - 1]; + const entry_kind = switch (type_char) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + else => Entry.Kind.Unknown, + }; + return Entry { + .name = name, + .kind = entry_kind, + }; } - const linux_entry = @ptrCast(& align(1) LinuxEntry, &self.buf[self.index]); - const next_index = self.index + linux_entry.d_reclen; - self.index = next_index; - - const name = cstr.toSlice(&linux_entry.d_name); - - // skip . and .. entries - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { - goto start_over; - } - - const type_char = self.buf[next_index - 1]; - const entry_kind = switch (type_char) { - posix.DT_BLK => Entry.Kind.BlockDevice, - posix.DT_CHR => Entry.Kind.CharacterDevice, - posix.DT_DIR => Entry.Kind.Directory, - posix.DT_FIFO => Entry.Kind.NamedPipe, - posix.DT_LNK => Entry.Kind.SymLink, - posix.DT_REG => Entry.Kind.File, - posix.DT_SOCK => Entry.Kind.UnixDomainSocket, - else => Entry.Kind.Unknown, - }; - return Entry { - .name = name, - .kind = entry_kind, - }; } }; diff --git a/test/behavior.zig b/test/behavior.zig index ecb8cf74c9..96a323a6c8 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -20,7 +20,6 @@ comptime { _ = @import("cases/fn.zig"); _ = @import("cases/for.zig"); _ = @import("cases/generics.zig"); - _ = @import("cases/goto.zig"); _ = @import("cases/if.zig"); _ = @import("cases/import.zig"); _ = @import("cases/incomplete_struct_param_tld.zig"); diff --git a/test/cases/for.zig b/test/cases/for.zig index e10e7acaca..e4b8094cf5 100644 --- a/test/cases/for.zig +++ b/test/cases/for.zig @@ -55,3 +55,37 @@ test "basic for loop" { assert(mem.eql(u8, buffer[0..buf_index], expected_result)); } + +test "break from outer for loop" { + testBreakOuter(); + comptime testBreakOuter(); +} + +fn testBreakOuter() { + var array = "aoeu"; + var count: usize = 0; + outer: for (array) |_| { + for (array) |_2| { // TODO shouldn't get error for redeclaring "_" + count += 1; + break :outer; + } + } + assert(count == 1); +} + +test "continue outer for loop" { + testContinueOuter(); + comptime testContinueOuter(); +} + +fn testContinueOuter() { + var array = "aoeu"; + var counter: usize = 0; + outer: for (array) |_| { + for (array) |_2| { // TODO shouldn't get error for redeclaring "_" + counter += 1; + continue :outer; + } + } + assert(counter == array.len); +} diff --git a/test/cases/goto.zig b/test/cases/goto.zig deleted file mode 100644 index 7713bc14aa..0000000000 --- a/test/cases/goto.zig +++ /dev/null @@ -1,37 +0,0 @@ -const assert = @import("std").debug.assert; - -test "goto and labels" { - gotoLoop(); - assert(goto_counter == 10); -} -fn gotoLoop() { - var i: i32 = 0; - goto cond; -loop: - i += 1; -cond: - if (!(i < 10)) goto end; - goto_counter += 1; - goto loop; -end: -} -var goto_counter: i32 = 0; - - - -test "goto leave defer scope" { - testGotoLeaveDeferScope(true); -} -fn testGotoLeaveDeferScope(b: bool) { - var it_worked = false; - - goto entry; -exit: - if (it_worked) { - return; - } - unreachable; -entry: - defer it_worked = true; - if (b) goto exit; -} diff --git a/test/cases/while.zig b/test/cases/while.zig index 33833cecfa..c16171d4a3 100644 --- a/test/cases/while.zig +++ b/test/cases/while.zig @@ -188,6 +188,33 @@ test "while on bool with else result follow break prong" { assert(result == 10); } +test "break from outer while loop" { + testBreakOuter(); + comptime testBreakOuter(); +} + +fn testBreakOuter() { + outer: while (true) { + while (true) { + break :outer; + } + } +} + +test "continue outer while loop" { + testContinueOuter(); + comptime testContinueOuter(); +} + +fn testContinueOuter() { + var i: usize = 0; + outer: while (i < 10) : (i += 1) { + while (true) { + continue :outer; + } + } +} + fn returnNull() -> ?i32 { null } fn returnMaybe(x: i32) -> ?i32 { x } error YouWantedAnError; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index fb7daea481..3446acda02 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,27 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) { + cases.add("labeled break not found", + \\export fn entry() { + \\ blah: while (true) { + \\ while (true) { + \\ break :outer; + \\ } + \\ } + \\} + , ".tmp_source.zig:4:13: error: labeled loop not found: 'outer'"); + + cases.add("labeled continue not found", + \\export fn entry() { + \\ var i: usize = 0; + \\ blah: while (i < 10) : (i += 1) { + \\ while (true) { + \\ continue :outer; + \\ } + \\ } + \\} + , ".tmp_source.zig:5:13: error: labeled loop not found: 'outer'"); + cases.add("attempt to use 0 bit type in extern fn", \\extern fn foo(ptr: extern fn(&void)); \\ @@ -833,26 +854,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\export fn entry() -> usize { @sizeOf(@typeOf(test1)) } , ".tmp_source.zig:3:16: error: unable to evaluate constant expression"); - cases.add("goto jumping into block", - \\export fn f() { - \\ { - \\a_label: - \\ } - \\ goto a_label; - \\} - , ".tmp_source.zig:5:5: error: no label in scope named 'a_label'"); - - cases.add("goto jumping past a defer", - \\fn f(b: bool) { - \\ if (b) goto label; - \\ defer derp(); - \\label: - \\} - \\fn derp(){} - \\ - \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } - , ".tmp_source.zig:2:12: error: no label in scope named 'label'"); - cases.add("assign null to non-nullable pointer", \\const a: &u8 = null; \\ @@ -1854,16 +1855,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { , ".tmp_source.zig:4:13: error: cannot continue out of defer expression"); - cases.add("cannot goto out of defer expression", - \\export fn foo() { - \\ defer { - \\ goto label; - \\ }; - \\label: - \\} - , - ".tmp_source.zig:3:9: error: cannot goto out of defer expression"); - cases.add("calling a var args function only known at runtime", \\var foos = []fn(...) { foo1, foo2 }; \\ diff --git a/test/translate_c.zig b/test/translate_c.zig index aff2140f8d..4f545139e7 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1005,48 +1005,6 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\} ); - cases.add("switch statement", - \\int foo(int x) { - \\ switch (x) { - \\ case 1: - \\ x += 1; - \\ case 2: - \\ break; - \\ case 3: - \\ case 4: - \\ return x + 1; - \\ default: - \\ return 10; - \\ } - \\ return x + 13; - \\} - , - \\fn foo(_arg_x: c_int) -> c_int { - \\ var x = _arg_x; - \\ { - \\ switch (x) { - \\ 1 => goto case_0, - \\ 2 => goto case_1, - \\ 3 => goto case_2, - \\ 4 => goto case_3, - \\ else => goto default, - \\ }; - \\ case_0: - \\ x += 1; - \\ case_1: - \\ goto end; - \\ case_2: - \\ case_3: - \\ return x + 1; - \\ default: - \\ return 10; - \\ goto end; - \\ end: - \\ }; - \\ return x + 13; - \\} - ); - cases.add("macros with field targets", \\typedef unsigned int GLbitfield; \\typedef void (*PFNGLCLEARPROC) (GLbitfield mask); @@ -1085,44 +1043,6 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\pub const OpenGLProcs = union_OpenGLProcs; ); - cases.add("switch statement with no default", - \\int foo(int x) { - \\ switch (x) { - \\ case 1: - \\ x += 1; - \\ case 2: - \\ break; - \\ case 3: - \\ case 4: - \\ return x + 1; - \\ } - \\ return x + 13; - \\} - , - \\fn foo(_arg_x: c_int) -> c_int { - \\ var x = _arg_x; - \\ { - \\ switch (x) { - \\ 1 => goto case_0, - \\ 2 => goto case_1, - \\ 3 => goto case_2, - \\ 4 => goto case_3, - \\ else => goto end, - \\ }; - \\ case_0: - \\ x += 1; - \\ case_1: - \\ goto end; - \\ case_2: - \\ case_3: - \\ return x + 1; - \\ goto end; - \\ end: - \\ }; - \\ return x + 13; - \\} - ); - cases.add("variable name shadowing", \\int foo(void) { \\ int x = 1; From d917815d8111b98dc237cbe2c723fa63018e02b1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 22 Dec 2017 00:50:30 -0500 Subject: [PATCH 64/69] explicitly return from blocks instead of last statement being expression value closes #629 --- doc/docgen.zig | 2 +- doc/langref.html.in | 5 +- example/shared_library/mathtest.zig | 2 +- src-self-hosted/parser.zig | 24 +- src/all_types.hpp | 16 +- src/analyze.cpp | 2 +- src/ast_render.cpp | 5 +- src/ir.cpp | 98 ++++-- src/parser.cpp | 53 ++-- src/translate_c.cpp | 86 ++++-- std/array_list.zig | 8 +- std/buffer.zig | 6 +- std/build.zig | 56 ++-- std/cstr.zig | 2 +- std/debug.zig | 86 +++--- std/endian.zig | 6 +- std/fmt/errol/enum3.zig | 4 +- std/fmt/index.zig | 7 +- std/hash_map.zig | 20 +- std/heap.zig | 13 +- std/io.zig | 29 +- std/linked_list.zig | 14 +- std/math/acos.zig | 12 +- std/math/acosh.zig | 16 +- std/math/asin.zig | 18 +- std/math/asinh.zig | 8 +- std/math/atan.zig | 12 +- std/math/atan2.zig | 16 +- std/math/atanh.zig | 10 +- std/math/cbrt.zig | 8 +- std/math/ceil.zig | 16 +- std/math/copysign.zig | 8 +- std/math/cos.zig | 24 +- std/math/cosh.zig | 8 +- std/math/exp.zig | 12 +- std/math/exp2.zig | 8 +- std/math/expm1.zig | 4 +- std/math/expo2.zig | 8 +- std/math/fabs.zig | 8 +- std/math/floor.zig | 18 +- std/math/fma.zig | 20 +- std/math/frexp.zig | 12 +- std/math/hypot.zig | 8 +- std/math/ilogb.zig | 8 +- std/math/index.zig | 16 +- std/math/inf.zig | 4 +- std/math/isfinite.zig | 4 +- std/math/isinf.zig | 12 +- std/math/isnan.zig | 6 +- std/math/isnormal.zig | 4 +- std/math/ln.zig | 8 +- std/math/log.zig | 2 +- std/math/log10.zig | 8 +- std/math/log1p.zig | 8 +- std/math/log2.zig | 8 +- std/math/modf.zig | 12 +- std/math/nan.zig | 8 +- std/math/pow.zig | 4 +- std/math/round.zig | 12 +- std/math/scalbn.zig | 8 +- std/math/signbit.zig | 8 +- std/math/sin.zig | 26 +- std/math/sinh.zig | 8 +- std/math/sqrt.zig | 10 +- std/math/tan.zig | 20 +- std/math/tanh.zig | 12 +- std/math/trunc.zig | 12 +- std/mem.zig | 8 +- std/net.zig | 22 +- std/os/child_process.zig | 70 ++--- std/os/darwin.zig | 80 +++-- std/os/index.zig | 19 +- std/os/linux.zig | 136 ++++----- std/os/linux_x86_64.zig | 32 +- std/os/path.zig | 36 +-- std/os/windows/util.zig | 4 +- std/rand.zig | 28 +- std/sort.zig | 8 +- std/special/build_runner.zig | 16 +- std/special/builtin.zig | 18 +- std/special/compiler_rt/comparetf2.zig | 40 ++- test/cases/align.zig | 18 +- test/cases/array.zig | 4 +- test/cases/bitcast.zig | 4 +- test/cases/bool.zig | 2 +- test/cases/cast.zig | 14 +- test/cases/defer.zig | 12 +- test/cases/enum.zig | 2 +- test/cases/enum_with_members.zig | 6 +- test/cases/error.zig | 14 +- test/cases/eval.zig | 26 +- test/cases/fn.zig | 20 +- test/cases/for.zig | 2 +- test/cases/generics.zig | 38 +-- test/cases/if.zig | 6 +- test/cases/import/a_namespace.zig | 2 +- test/cases/ir_block_deps.zig | 4 +- test/cases/math.zig | 22 +- test/cases/misc.zig | 30 +- test/cases/reflection.zig | 2 +- test/cases/struct.zig | 18 +- test/cases/switch.zig | 18 +- test/cases/switch_prong_err_enum.zig | 2 +- test/cases/switch_prong_implicit_cast.zig | 4 +- test/cases/this.zig | 12 +- test/cases/try.zig | 18 +- test/cases/var_args.zig | 4 +- test/cases/while.zig | 48 ++- test/compare_output.zig | 34 +-- test/compile_errors.zig | 357 +++++++++++----------- test/debug_safety.zig | 30 +- test/standalone/pkg_import/pkg.zig | 2 +- test/tests.zig | 6 +- test/translate_c.zig | 110 +++---- 114 files changed, 1203 insertions(+), 1231 deletions(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index bec12d98b7..d481baf4b3 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -49,7 +49,7 @@ fn gen(in: &io.InStream, out: &io.OutStream) { if (err == error.EndOfStream) { return; } - std.debug.panic("{}", err) + std.debug.panic("{}", err); }; switch (state) { State.Start => switch (byte) { diff --git a/doc/langref.html.in b/doc/langref.html.in index faba4f8b10..162c8b3e03 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -3021,14 +3021,13 @@ const assert = @import("std").debug.assert;

    const assert = @import("std").debug.assert;
     
     // Functions are declared like this
    -// The last expression in the function can be used as the return value.
     fn add(a: i8, b: i8) -> i8 {
         if (a == 0) {
             // You can still return manually if needed.
             return b;
         }
     
    -    a + b
    +    return a + b;
     }
     
     // The export specifier makes a function externally visible in the generated
    @@ -5847,7 +5846,7 @@ ParamDeclList = "(" list(ParamDecl, ",") ")"
     
     ParamDecl = option("noalias" | "comptime") option(Symbol ":") (TypeExpr | "...")
     
    -Block = option(Symbol ":") "{" many(Statement) option(Expression) "}"
    +Block = option(Symbol ":") "{" many(Statement) "}"
     
     Statement = LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";"
     
    diff --git a/example/shared_library/mathtest.zig b/example/shared_library/mathtest.zig
    index a11642554f..bb0175bff2 100644
    --- a/example/shared_library/mathtest.zig
    +++ b/example/shared_library/mathtest.zig
    @@ -1,3 +1,3 @@
     export fn add(a: i32, b: i32) -> i32 {
    -    a + b
    +    return a + b;
     }
    diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig
    index b34603f131..c997536ce2 100644
    --- a/src-self-hosted/parser.zig
    +++ b/src-self-hosted/parser.zig
    @@ -111,11 +111,11 @@ pub const Parser = struct {
         }
     
         pub fn parse(self: &Parser) -> %&ast.NodeRoot {
    -        const result = self.parseInner() %% |err| {
    +        const result = self.parseInner() %% |err| x: {
                 if (self.cleanup_root_node) |root_node| {
                     self.freeAst(root_node);
                 }
    -            err
    +            break :x err;
             };
             self.cleanup_root_node = null;
             return result;
    @@ -125,12 +125,12 @@ pub const Parser = struct {
             var stack = self.initUtilityArrayList(State);
             defer self.deinitUtilityArrayList(stack);
     
    -        const root_node = {
    +        const root_node = x: {
                 const root_node = %return self.createRoot();
                 %defer self.allocator.destroy(root_node);
                 // This stack append has to succeed for freeAst to work
                 %return stack.append(State.TopLevel);
    -            root_node
    +            break :x root_node;
             };
             assert(self.cleanup_root_node == null);
             self.cleanup_root_node = root_node;
    @@ -462,7 +462,7 @@ pub const Parser = struct {
                         } else if (token.id == Token.Id.Keyword_noalias) {
                             param_decl.noalias_token = token;
                             token = self.getNextToken();
    -                    };
    +                    }
                         if (token.id == Token.Id.Identifier) {
                             const next_token = self.getNextToken();
                             if (next_token.id == Token.Id.Colon) {
    @@ -793,14 +793,14 @@ pub const Parser = struct {
         }
     
         fn getNextToken(self: &Parser) -> Token {
    -        return if (self.put_back_count != 0) {
    +        if (self.put_back_count != 0) {
                 const put_back_index = self.put_back_count - 1;
                 const put_back_token = self.put_back_tokens[put_back_index];
                 self.put_back_count = put_back_index;
    -            put_back_token
    +            return put_back_token;
             } else {
    -            self.tokenizer.next()
    -        };
    +            return self.tokenizer.next();
    +        }
         }
     
         const RenderAstFrame = struct {
    @@ -873,7 +873,7 @@ pub const Parser = struct {
                                         Token.Id.Keyword_pub => %return stream.print("pub "),
                                         Token.Id.Keyword_export => %return stream.print("export "),
                                         else => unreachable,
    -                                };
    +                                }
                                 }
                                 if (fn_proto.extern_token) |extern_token| {
                                     %return stream.print("{} ", self.tokenizer.getTokenSlice(extern_token));
    @@ -1102,7 +1102,7 @@ fn testParse(source: []const u8, allocator: &mem.Allocator) -> %[]u8 {
     // TODO test for memory leaks
     // TODO test for valid frees
     fn testCanonical(source: []const u8) {
    -    const needed_alloc_count = {
    +    const needed_alloc_count = x: {
             // Try it once with unlimited memory, make sure it works
             var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
             var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, @maxValue(usize));
    @@ -1116,7 +1116,7 @@ fn testCanonical(source: []const u8) {
                 @panic("test failed");
             }
             failing_allocator.allocator.free(result_source);
    -        failing_allocator.index
    +        break :x failing_allocator.index;
         };
     
         // TODO make this pass
    diff --git a/src/all_types.hpp b/src/all_types.hpp
    index 87541bb918..a582f561cc 100644
    --- a/src/all_types.hpp
    +++ b/src/all_types.hpp
    @@ -26,7 +26,6 @@ struct ScopeFnDef;
     struct TypeTableEntry;
     struct VariableTableEntry;
     struct ErrorTableEntry;
    -struct LabelTableEntry;
     struct BuiltinFnEntry;
     struct TypeStructField;
     struct CodeGen;
    @@ -54,7 +53,6 @@ struct IrExecutable {
         size_t *backward_branch_count;
         size_t backward_branch_quota;
         bool invalid;
    -    ZigList all_labels;
         ZigList goto_list;
         bool is_inline;
         FnTableEntry *fn_entry;
    @@ -452,7 +450,6 @@ struct AstNodeParamDecl {
     struct AstNodeBlock {
         Buf *name;
         ZigList statements;
    -    bool last_statement_is_result_expression;
     };
     
     enum ReturnKind {
    @@ -1644,12 +1641,6 @@ struct ErrorTableEntry {
         ConstExprValue *cached_error_name_val;
     };
     
    -struct LabelTableEntry {
    -    AstNode *decl_node;
    -    IrBasicBlock *bb;
    -    bool used;
    -};
    -
     enum ScopeId {
         ScopeIdDecls,
         ScopeIdBlock,
    @@ -1693,7 +1684,12 @@ struct ScopeDecls {
     struct ScopeBlock {
         Scope base;
     
    -    HashMap label_table;
    +    Buf *name;
    +    IrBasicBlock *end_block;
    +    IrInstruction *is_comptime;
    +    ZigList *incoming_values;
    +    ZigList *incoming_blocks;
    +
         bool safety_off;
         AstNode *safety_set_node;
         bool fast_math_off;
    diff --git a/src/analyze.cpp b/src/analyze.cpp
    index 23301cc4de..9ec4db824c 100644
    --- a/src/analyze.cpp
    +++ b/src/analyze.cpp
    @@ -110,7 +110,7 @@ ScopeBlock *create_block_scope(AstNode *node, Scope *parent) {
         assert(node->type == NodeTypeBlock);
         ScopeBlock *scope = allocate(1);
         init_scope(&scope->base, ScopeIdBlock, node, parent);
    -    scope->label_table.init(1);
    +    scope->name = node->data.block.name;
         return scope;
     }
     
    diff --git a/src/ast_render.cpp b/src/ast_render.cpp
    index e64a19d42d..f0912285f0 100644
    --- a/src/ast_render.cpp
    +++ b/src/ast_render.cpp
    @@ -478,10 +478,7 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                     AstNode *statement = node->data.block.statements.at(i);
                     print_indent(ar);
                     render_node_grouped(ar, statement);
    -                if (!(i == node->data.block.statements.length - 1 &&
    -                      node->data.block.last_statement_is_result_expression)) {
    -                    fprintf(ar->f, ";");
    -                }
    +                fprintf(ar->f, ";");
                     fprintf(ar->f, "\n");
                 }
                 ar->indent -= ar->indent_size;
    diff --git a/src/ir.cpp b/src/ir.cpp
    index d3dd58aaff..a0f5a9be47 100644
    --- a/src/ir.cpp
    +++ b/src/ir.cpp
    @@ -3514,7 +3514,11 @@ static VariableTableEntry *ir_create_var(IrBuilder *irb, AstNode *node, Scope *s
     static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode *block_node) {
         assert(block_node->type == NodeTypeBlock);
     
    +    ZigList incoming_values = {0};
    +    ZigList incoming_blocks = {0};
    +
         ScopeBlock *scope_block = create_block_scope(block_node, parent_scope);
    +
         Scope *outer_block_scope = &scope_block->base;
         Scope *child_scope = outer_block_scope;
     
    @@ -3528,9 +3532,15 @@ static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode
             return ir_mark_gen(ir_build_const_void(irb, child_scope, block_node));
         }
     
    +    if (block_node->data.block.name != nullptr) {
    +        scope_block->incoming_blocks = &incoming_blocks;
    +        scope_block->incoming_values = &incoming_values;
    +        scope_block->end_block = ir_build_basic_block(irb, parent_scope, "BlockEnd");
    +        scope_block->is_comptime = ir_build_const_bool(irb, parent_scope, block_node, ir_should_inline(irb->exec, parent_scope));
    +    }
    +
         bool is_continuation_unreachable = false;
         IrInstruction *noreturn_return_value = nullptr;
    -    IrInstruction *return_value = nullptr;
         for (size_t i = 0; i < block_node->data.block.statements.length; i += 1) {
             AstNode *statement_node = block_node->data.block.statements.at(i);
     
    @@ -3548,39 +3558,31 @@ static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode
                 // variable declarations start a new scope
                 IrInstructionDeclVar *decl_var_instruction = (IrInstructionDeclVar *)statement_value;
                 child_scope = decl_var_instruction->var->child_scope;
    -        } else {
    -            // label, defer, variable declaration will never be the result expression
    -            if (block_node->data.block.last_statement_is_result_expression &&
    -                i == block_node->data.block.statements.length - 1) {
    -                // this is the result value statement
    -                return_value = statement_value;
    -            } else {
    -                // there are more statements ahead of this one. this statement's value must be void
    -                if (statement_value != irb->codegen->invalid_instruction) {
    -                    ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, statement_node, statement_value));
    -                }
    -            }
    +        } else if (statement_value != irb->codegen->invalid_instruction) {
    +            // this statement's value must be void
    +            ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, statement_node, statement_value));
             }
         }
     
         if (is_continuation_unreachable) {
             assert(noreturn_return_value != nullptr);
    -        return noreturn_return_value;
    -    }
    -    // control flow falls out of block
    -
    -    if (block_node->data.block.last_statement_is_result_expression) {
    -        // return value was determined by the last statement
    -        assert(return_value != nullptr);
    +        if (block_node->data.block.name == nullptr || incoming_blocks.length == 0) {
    +            return noreturn_return_value;
    +        }
         } else {
    -        // return value is implicitly void
    -        assert(return_value == nullptr);
    -        return_value = ir_mark_gen(ir_build_const_void(irb, child_scope, block_node));
    +        incoming_blocks.append(irb->current_basic_block);
    +        incoming_values.append(ir_mark_gen(ir_build_const_void(irb, parent_scope, block_node)));
         }
     
    -    ir_gen_defers_for_block(irb, child_scope, outer_block_scope, false);
    -
    -    return return_value;
    +    if (block_node->data.block.name != nullptr) {
    +        ir_gen_defers_for_block(irb, child_scope, outer_block_scope, false);
    +        ir_mark_gen(ir_build_br(irb, parent_scope, block_node, scope_block->end_block, scope_block->is_comptime));
    +        ir_set_cursor_at_end(irb, scope_block->end_block);
    +        return ir_build_phi(irb, parent_scope, block_node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
    +    } else {
    +        ir_gen_defers_for_block(irb, child_scope, outer_block_scope, false);
    +        return ir_mark_gen(ir_mark_gen(ir_build_const_void(irb, child_scope, block_node)));
    +    }
     }
     
     static IrInstruction *ir_gen_bin_op_id(IrBuilder *irb, Scope *scope, AstNode *node, IrBinOp op_id) {
    @@ -5952,6 +5954,31 @@ static IrInstruction *ir_gen_comptime(IrBuilder *irb, Scope *parent_scope, AstNo
         return ir_gen_node_extra(irb, node->data.comptime_expr.expr, child_scope, lval);
     }
     
    +static IrInstruction *ir_gen_return_from_block(IrBuilder *irb, Scope *break_scope, AstNode *node, ScopeBlock *block_scope) {
    +    IrInstruction *is_comptime;
    +    if (ir_should_inline(irb->exec, break_scope)) {
    +        is_comptime = ir_build_const_bool(irb, break_scope, node, true);
    +    } else {
    +        is_comptime = block_scope->is_comptime;
    +    }
    +
    +    IrInstruction *result_value;
    +    if (node->data.break_expr.expr) {
    +        result_value = ir_gen_node(irb, node->data.break_expr.expr, break_scope);
    +        if (result_value == irb->codegen->invalid_instruction)
    +            return irb->codegen->invalid_instruction;
    +    } else {
    +        result_value = ir_build_const_void(irb, break_scope, node);
    +    }
    +
    +    IrBasicBlock *dest_block = block_scope->end_block;
    +    ir_gen_defers_for_block(irb, break_scope, dest_block->scope, false);
    +
    +    block_scope->incoming_blocks->append(irb->current_basic_block);
    +    block_scope->incoming_values->append(result_value);
    +    return ir_build_br(irb, break_scope, node, dest_block, is_comptime);
    +}
    +
     static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *node) {
         assert(node->type == NodeTypeBreak);
     
    @@ -5959,14 +5986,14 @@ static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *
         // * function definition scope or global scope => error, break outside loop
         // * defer expression scope => error, cannot break out of defer expression
         // * loop scope => OK
    +    // * (if it's a labeled break) labeled block => OK
     
         Scope *search_scope = break_scope;
         ScopeLoop *loop_scope;
    -    bool saw_any_loop_scope = false;
         for (;;) {
             if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) {
    -            if (saw_any_loop_scope) {
    -                add_node_error(irb->codegen, node, buf_sprintf("labeled loop not found: '%s'", buf_ptr(node->data.break_expr.name)));
    +            if (node->data.break_expr.name != nullptr) {
    +                add_node_error(irb->codegen, node, buf_sprintf("label not found: '%s'", buf_ptr(node->data.break_expr.name)));
                     return irb->codegen->invalid_instruction;
                 } else {
                     add_node_error(irb->codegen, node, buf_sprintf("break expression outside loop"));
    @@ -5977,13 +6004,20 @@ static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *
                 return irb->codegen->invalid_instruction;
             } else if (search_scope->id == ScopeIdLoop) {
                 ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope;
    -            saw_any_loop_scope = true;
                 if (node->data.break_expr.name == nullptr ||
                     (this_loop_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_loop_scope->name)))
                 {
                     loop_scope = this_loop_scope;
                     break;
                 }
    +        } else if (search_scope->id == ScopeIdBlock) {
    +            ScopeBlock *this_block_scope = (ScopeBlock *)search_scope;
    +            if (node->data.break_expr.name != nullptr &&
    +                (this_block_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_block_scope->name)))
    +            {
    +                assert(this_block_scope->end_block != nullptr);
    +                return ir_gen_return_from_block(irb, break_scope, node, this_block_scope);
    +            }
             }
             search_scope = search_scope->parent;
         }
    @@ -6022,10 +6056,9 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast
     
         Scope *search_scope = continue_scope;
         ScopeLoop *loop_scope;
    -    bool saw_any_loop_scope = false;
         for (;;) {
             if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) {
    -            if (saw_any_loop_scope) {
    +            if (node->data.continue_expr.name != nullptr) {
                     add_node_error(irb->codegen, node, buf_sprintf("labeled loop not found: '%s'", buf_ptr(node->data.continue_expr.name)));
                     return irb->codegen->invalid_instruction;
                 } else {
    @@ -6037,7 +6070,6 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast
                 return irb->codegen->invalid_instruction;
             } else if (search_scope->id == ScopeIdLoop) {
                 ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope;
    -            saw_any_loop_scope = true;
                 if (node->data.continue_expr.name == nullptr ||
                     (this_loop_scope->name != nullptr && buf_eql_buf(node->data.continue_expr.name, this_loop_scope->name)))
                 {
    diff --git a/src/parser.cpp b/src/parser.cpp
    index b5fdd681e8..d069a23c5f 100644
    --- a/src/parser.cpp
    +++ b/src/parser.cpp
    @@ -748,7 +748,14 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo
             node->data.fn_call_expr.is_builtin = true;
     
             return node;
    -    } else if (token->id == TokenIdSymbol) {
    +    }
    +
    +    AstNode *block_expr_node = ast_parse_block_expr(pc, token_index, false);
    +    if (block_expr_node) {
    +        return block_expr_node;
    +    }
    +
    +    if (token->id == TokenIdSymbol) {
             *token_index += 1;
             AstNode *node = ast_create_node(pc, NodeTypeSymbol, token);
             node->data.symbol_expr.symbol = token_buf(token);
    @@ -760,11 +767,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo
             return grouped_expr_node;
         }
     
    -    AstNode *block_expr_node = ast_parse_block_expr(pc, token_index, false);
    -    if (block_expr_node) {
    -        return block_expr_node;
    -    }
    -
         AstNode *array_type_node = ast_parse_array_type_expr(pc, token_index, false);
         if (array_type_node) {
             return array_type_node;
    @@ -2145,9 +2147,6 @@ static AstNode *ast_parse_expression(ParseContext *pc, size_t *token_index, bool
         return nullptr;
     }
     
    -/*
    -Label: token(Symbol) token(Colon)
    -*/
     static bool statement_terminates_without_semicolon(AstNode *node) {
         switch (node->type) {
             case NodeTypeIfBoolExpr:
    @@ -2179,7 +2178,7 @@ static bool statement_terminates_without_semicolon(AstNode *node) {
     }
     
     /*
    -Block = option(Symbol ":") "{" many(Statement) option(Expression) "}"
    +Block = option(Symbol ":") "{" many(Statement) "}"
     Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" | ExportDecl
     */
     static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mandatory) {
    @@ -2220,6 +2219,12 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand
         }
     
         for (;;) {
    +        last_token = &pc->tokens->at(*token_index);
    +        if (last_token->id == TokenIdRBrace) {
    +            *token_index += 1;
    +            return node;
    +        }
    +
             AstNode *statement_node = ast_parse_local_var_decl(pc, token_index);
             if (!statement_node)
                 statement_node = ast_parse_defer_expr(pc, token_index);
    @@ -2228,32 +2233,14 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand
             if (!statement_node)
                 statement_node = ast_parse_expression(pc, token_index, false);
     
    -        bool semicolon_expected = true;
    -        if (statement_node) {
    -            node->data.block.statements.append(statement_node);
    -            if (statement_terminates_without_semicolon(statement_node)) {
    -                semicolon_expected = false;
    -            } else {
    -                if (statement_node->type == NodeTypeDefer) {
    -                    // defer without a block body requires a semicolon
    -                    Token *token = &pc->tokens->at(*token_index);
    -                    ast_expect_token(pc, token, TokenIdSemicolon);
    -                }
    -            }
    +        if (!statement_node) {
    +            ast_invalid_token_error(pc, last_token);
             }
     
    -        node->data.block.last_statement_is_result_expression = statement_node && statement_node->type != NodeTypeDefer;
    +        node->data.block.statements.append(statement_node);
     
    -        last_token = &pc->tokens->at(*token_index);
    -        if (last_token->id == TokenIdRBrace) {
    -            *token_index += 1;
    -            return node;
    -        } else if (!semicolon_expected) {
    -            continue;
    -        } else if (last_token->id == TokenIdSemicolon) {
    -            *token_index += 1;
    -        } else {
    -            ast_invalid_token_error(pc, last_token);
    +        if (!statement_terminates_without_semicolon(statement_node)) {
    +            ast_eat_token(pc, token_index, TokenIdSemicolon);
             }
         }
         zig_unreachable();
    diff --git a/src/translate_c.cpp b/src/translate_c.cpp
    index bb319ccb54..2d0772aeaa 100644
    --- a/src/translate_c.cpp
    +++ b/src/translate_c.cpp
    @@ -171,6 +171,20 @@ static AstNode * trans_create_node(Context *c, NodeType id) {
         return node;
     }
     
    +static AstNode *trans_create_node_break(Context *c, Buf *label_name, AstNode *value_node) {
    +    AstNode *node = trans_create_node(c, NodeTypeBreak);
    +    node->data.break_expr.name = label_name;
    +    node->data.break_expr.expr = value_node;
    +    return node;
    +}
    +
    +static AstNode *trans_create_node_return(Context *c, AstNode *value_node) {
    +    AstNode *node = trans_create_node(c, NodeTypeReturnExpr);
    +    node->data.return_expr.kind = ReturnKindUnconditional;
    +    node->data.return_expr.expr = value_node;
    +    return node;
    +}
    +
     static AstNode *trans_create_node_if(Context *c, AstNode *cond_node, AstNode *then_node, AstNode *else_node) {
         AstNode *node = trans_create_node(c, NodeTypeIfBoolExpr);
         node->data.if_bool_expr.condition = cond_node;
    @@ -372,8 +386,7 @@ static AstNode *trans_create_node_inline_fn(Context *c, Buf *fn_name, AstNode *r
     
         AstNode *block = trans_create_node(c, NodeTypeBlock);
         block->data.block.statements.resize(1);
    -    block->data.block.statements.items[0] = fn_call_node;
    -    block->data.block.last_statement_is_result_expression = true;
    +    block->data.block.statements.items[0] = trans_create_node_return(c, fn_call_node);
     
         fn_def->data.fn_def.body = block;
         return fn_def;
    @@ -1140,13 +1153,15 @@ static AstNode *trans_create_assign(Context *c, ResultUsed result_used, TransSco
         } else {
             // worst case
             // c: lhs = rhs
    -        // zig: {
    +        // zig: x: {
             // zig:     const _tmp = rhs;
             // zig:     lhs = _tmp;
    -        // zig:     _tmp
    +        // zig:     break :x _tmp
             // zig: }
     
             TransScopeBlock *child_scope = trans_scope_block_create(c, scope);
    +        Buf *label_name = buf_create_from_str("x");
    +        child_scope->node->data.block.name = label_name;
     
             // const _tmp = rhs;
             AstNode *rhs_node = trans_expr(c, ResultUsedYes, &child_scope->base, rhs, TransRValue);
    @@ -1163,9 +1178,9 @@ static AstNode *trans_create_assign(Context *c, ResultUsed result_used, TransSco
                 trans_create_node_bin_op(c, lhs_node, BinOpTypeAssign,
                     trans_create_node_symbol(c, tmp_var_name)));
     
    -        // _tmp
    -        child_scope->node->data.block.statements.append(trans_create_node_symbol(c, tmp_var_name));
    -        child_scope->node->data.block.last_statement_is_result_expression = true;
    +        // break :x _tmp
    +        AstNode *tmp_symbol_node = trans_create_node_symbol(c, tmp_var_name);
    +        child_scope->node->data.block.statements.append(trans_create_node_break(c, label_name, tmp_symbol_node));
     
             return child_scope->node;
         }
    @@ -1270,6 +1285,9 @@ static AstNode *trans_binary_operator(Context *c, ResultUsed result_used, TransS
             case BO_Comma:
                 {
                     TransScopeBlock *scope_block = trans_scope_block_create(c, scope);
    +                Buf *label_name = buf_create_from_str("x");
    +                scope_block->node->data.block.name = label_name;
    +
                     AstNode *lhs = trans_expr(c, ResultUsedNo, &scope_block->base, stmt->getLHS(), TransRValue);
                     if (lhs == nullptr)
                         return nullptr;
    @@ -1278,9 +1296,7 @@ static AstNode *trans_binary_operator(Context *c, ResultUsed result_used, TransS
                     AstNode *rhs = trans_expr(c, result_used, &scope_block->base, stmt->getRHS(), TransRValue);
                     if (rhs == nullptr)
                         return nullptr;
    -                scope_block->node->data.block.statements.append(maybe_suppress_result(c, result_used, rhs));
    -
    -                scope_block->node->data.block.last_statement_is_result_expression = true;
    +                scope_block->node->data.block.statements.append(trans_create_node_break(c, label_name, maybe_suppress_result(c, result_used, rhs)));
                     return scope_block->node;
                 }
             case BO_MulAssign:
    @@ -1320,14 +1336,16 @@ static AstNode *trans_create_compound_assign_shift(Context *c, ResultUsed result
         } else {
             // need more complexity. worst case, this looks like this:
             // c:   lhs >>= rhs
    -        // zig: {
    +        // zig: x: {
             // zig:     const _ref = &lhs;
             // zig:     *_ref = result_type(operation_type(*_ref) >> u5(rhs));
    -        // zig:     *_ref
    +        // zig:     break :x *_ref
             // zig: }
             // where u5 is the appropriate type
     
             TransScopeBlock *child_scope = trans_scope_block_create(c, scope);
    +        Buf *label_name = buf_create_from_str("x");
    +        child_scope->node->data.block.name = label_name;
     
             // const _ref = &lhs;
             AstNode *lhs = trans_expr(c, ResultUsedYes, &child_scope->base, stmt->getLHS(), TransLValue);
    @@ -1369,11 +1387,11 @@ static AstNode *trans_create_compound_assign_shift(Context *c, ResultUsed result
             child_scope->node->data.block.statements.append(assign_statement);
     
             if (result_used == ResultUsedYes) {
    -            // *_ref
    +            // break :x *_ref
                 child_scope->node->data.block.statements.append(
    -                trans_create_node_prefix_op(c, PrefixOpDereference,
    -                    trans_create_node_symbol(c, tmp_var_name)));
    -            child_scope->node->data.block.last_statement_is_result_expression = true;
    +                trans_create_node_break(c, label_name, 
    +                    trans_create_node_prefix_op(c, PrefixOpDereference,
    +                        trans_create_node_symbol(c, tmp_var_name))));
             }
     
             return child_scope->node;
    @@ -1394,13 +1412,15 @@ static AstNode *trans_create_compound_assign(Context *c, ResultUsed result_used,
         } else {
             // need more complexity. worst case, this looks like this:
             // c:   lhs += rhs
    -        // zig: {
    +        // zig: x: {
             // zig:     const _ref = &lhs;
             // zig:     *_ref = *_ref + rhs;
    -        // zig:     *_ref
    +        // zig:     break :x *_ref
             // zig: }
     
             TransScopeBlock *child_scope = trans_scope_block_create(c, scope);
    +        Buf *label_name = buf_create_from_str("x");
    +        child_scope->node->data.block.name = label_name;
     
             // const _ref = &lhs;
             AstNode *lhs = trans_expr(c, ResultUsedYes, &child_scope->base, stmt->getLHS(), TransLValue);
    @@ -1427,11 +1447,11 @@ static AstNode *trans_create_compound_assign(Context *c, ResultUsed result_used,
                     rhs));
             child_scope->node->data.block.statements.append(assign_statement);
     
    -        // *_ref
    +        // break :x *_ref
             child_scope->node->data.block.statements.append(
    -            trans_create_node_prefix_op(c, PrefixOpDereference,
    -                trans_create_node_symbol(c, tmp_var_name)));
    -        child_scope->node->data.block.last_statement_is_result_expression = true;
    +            trans_create_node_break(c, label_name,
    +                trans_create_node_prefix_op(c, PrefixOpDereference,
    +                    trans_create_node_symbol(c, tmp_var_name))));
     
             return child_scope->node;
         }
    @@ -1726,13 +1746,15 @@ static AstNode *trans_create_post_crement(Context *c, ResultUsed result_used, Tr
         }
         // worst case
         // c: expr++
    -    // zig: {
    +    // zig: x: {
         // zig:     const _ref = &expr;
         // zig:     const _tmp = *_ref;
         // zig:     *_ref += 1;
    -    // zig:     _tmp
    +    // zig:     break :x _tmp
         // zig: }
         TransScopeBlock *child_scope = trans_scope_block_create(c, scope);
    +    Buf *label_name = buf_create_from_str("x");
    +    child_scope->node->data.block.name = label_name;
     
         // const _ref = &expr;
         AstNode *expr = trans_expr(c, ResultUsedYes, &child_scope->base, op_expr, TransLValue);
    @@ -1758,9 +1780,8 @@ static AstNode *trans_create_post_crement(Context *c, ResultUsed result_used, Tr
             trans_create_node_unsigned(c, 1));
         child_scope->node->data.block.statements.append(assign_statement);
     
    -    // _tmp
    -    child_scope->node->data.block.statements.append(trans_create_node_symbol(c, tmp_var_name));
    -    child_scope->node->data.block.last_statement_is_result_expression = true;
    +    // break :x _tmp
    +    child_scope->node->data.block.statements.append(trans_create_node_break(c, label_name, trans_create_node_symbol(c, tmp_var_name)));
     
         return child_scope->node;
     }
    @@ -1781,12 +1802,14 @@ static AstNode *trans_create_pre_crement(Context *c, ResultUsed result_used, Tra
         }
         // worst case
         // c: ++expr
    -    // zig: {
    +    // zig: x: {
         // zig:     const _ref = &expr;
         // zig:     *_ref += 1;
    -    // zig:     *_ref
    +    // zig:     break :x *_ref
         // zig: }
         TransScopeBlock *child_scope = trans_scope_block_create(c, scope);
    +    Buf *label_name = buf_create_from_str("x");
    +    child_scope->node->data.block.name = label_name;
     
         // const _ref = &expr;
         AstNode *expr = trans_expr(c, ResultUsedYes, &child_scope->base, op_expr, TransLValue);
    @@ -1805,11 +1828,10 @@ static AstNode *trans_create_pre_crement(Context *c, ResultUsed result_used, Tra
             trans_create_node_unsigned(c, 1));
         child_scope->node->data.block.statements.append(assign_statement);
     
    -    // *_ref
    +    // break :x *_ref
         AstNode *deref_expr = trans_create_node_prefix_op(c, PrefixOpDereference,
                 trans_create_node_symbol(c, ref_var_name));
    -    child_scope->node->data.block.statements.append(deref_expr);
    -    child_scope->node->data.block.last_statement_is_result_expression = true;
    +    child_scope->node->data.block.statements.append(trans_create_node_break(c, label_name, deref_expr));
     
         return child_scope->node;
     }
    diff --git a/std/array_list.zig b/std/array_list.zig
    index 5d043075c3..04db4dd280 100644
    --- a/std/array_list.zig
    +++ b/std/array_list.zig
    @@ -8,7 +8,7 @@ pub fn ArrayList(comptime T: type) -> type {
     }
     
     pub fn AlignedArrayList(comptime T: type, comptime A: u29) -> type{
    -    struct {
    +    return struct {
             const Self = this;
     
             /// Use toSlice instead of slicing this directly, because if you don't
    @@ -20,11 +20,11 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) -> type{
     
             /// Deinitialize with `deinit` or use `toOwnedSlice`.
             pub fn init(allocator: &Allocator) -> Self {
    -            Self {
    +            return Self {
                     .items = []align(A) T{},
                     .len = 0,
                     .allocator = allocator,
    -            }
    +            };
             }
     
             pub fn deinit(l: &Self) {
    @@ -107,7 +107,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) -> type{
                     return null;
                 return self.pop();
             }
    -    }
    +    };
     }
     
     test "basic ArrayList test" {
    diff --git a/std/buffer.zig b/std/buffer.zig
    index 73a38fff5b..267d9b6353 100644
    --- a/std/buffer.zig
    +++ b/std/buffer.zig
    @@ -30,9 +30,9 @@ pub const Buffer = struct {
         /// * ::replaceContentsBuffer
         /// * ::resize
         pub fn initNull(allocator: &Allocator) -> Buffer {
    -        Buffer {
    +        return Buffer {
                 .list = ArrayList(u8).init(allocator),
    -        }
    +        };
         }
     
         /// Must deinitialize with deinit.
    @@ -120,7 +120,7 @@ pub const Buffer = struct {
         }
     
         pub fn eql(self: &const Buffer, m: []const u8) -> bool {
    -        mem.eql(u8, self.toSliceConst(), m)
    +        return mem.eql(u8, self.toSliceConst(), m);
         }
     
         pub fn startsWith(self: &const Buffer, m: []const u8) -> bool {
    diff --git a/std/build.zig b/std/build.zig
    index c1c42a4bc1..0a23a77f80 100644
    --- a/std/build.zig
    +++ b/std/build.zig
    @@ -221,11 +221,11 @@ pub const Builder = struct {
         }
     
         pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version {
    -        Version {
    +        return Version {
                 .major = major,
                 .minor = minor,
                 .patch = patch,
    -        }
    +        };
         }
     
         pub fn addCIncludePath(self: &Builder, path: []const u8) {
    @@ -432,16 +432,16 @@ pub const Builder = struct {
             const release_safe = self.option(bool, "release-safe", "optimizations on and safety on") ?? false;
             const release_fast = self.option(bool, "release-fast", "optimizations on and safety off") ?? false;
     
    -        const mode = if (release_safe and !release_fast) {
    +        const mode = if (release_safe and !release_fast)
                 builtin.Mode.ReleaseSafe
    -        } else if (release_fast and !release_safe) {
    +        else if (release_fast and !release_safe)
                 builtin.Mode.ReleaseFast
    -        } else if (!release_fast and !release_safe) {
    +        else if (!release_fast and !release_safe)
                 builtin.Mode.Debug
    -        } else {
    +        else x: {
                 warn("Both -Drelease-safe and -Drelease-fast specified");
                 self.markInvalidUserInput();
    -            builtin.Mode.Debug
    +            break :x builtin.Mode.Debug;
             };
             self.release_mode = mode;
             return mode;
    @@ -506,7 +506,7 @@ pub const Builder = struct {
         }
     
         fn typeToEnum(comptime T: type) -> TypeId {
    -        switch (@typeId(T)) {
    +        return switch (@typeId(T)) {
                 builtin.TypeId.Int => TypeId.Int,
                 builtin.TypeId.Float => TypeId.Float,
                 builtin.TypeId.Bool => TypeId.Bool,
    @@ -515,7 +515,7 @@ pub const Builder = struct {
                     []const []const u8 => TypeId.List,
                     else => @compileError("Unsupported type: " ++ @typeName(T)),
                 },
    -        }
    +        };
         }
     
         fn markInvalidUserInput(self: &Builder) {
    @@ -590,8 +590,7 @@ pub const Builder = struct {
     
                     return error.UncleanExit;
                 },
    -        };
    -
    +        }
         }
     
         pub fn makePath(self: &Builder, path: []const u8) -> %void {
    @@ -662,13 +661,12 @@ pub const Builder = struct {
             if (builtin.environ == builtin.Environ.msvc) {
                 return "cl.exe";
             } else {
    -            return os.getEnvVarOwned(self.allocator, "CC") %% |err| {
    -                if (err == error.EnvironmentVariableNotFound) {
    +            return os.getEnvVarOwned(self.allocator, "CC") %% |err| 
    +                if (err == error.EnvironmentVariableNotFound)
                         ([]const u8)("cc")
    -                } else {
    -                    debug.panic("Unable to get environment variable: {}", err);
    -                }
    -            };
    +                else
    +                    debug.panic("Unable to get environment variable: {}", err)
    +            ;
             }
         }
     
    @@ -1079,11 +1077,10 @@ pub const LibExeObjStep = struct {
         }
     
         pub fn getOutputPath(self: &LibExeObjStep) -> []const u8 {
    -        if (self.output_path) |output_path| {
    +        return if (self.output_path) |output_path|
                 output_path
    -        } else {
    -            %%os.path.join(self.builder.allocator, self.builder.cache_root, self.out_filename)
    -        }
    +        else
    +            %%os.path.join(self.builder.allocator, self.builder.cache_root, self.out_filename);
         }
     
         pub fn setOutputHPath(self: &LibExeObjStep, file_path: []const u8) {
    @@ -1096,11 +1093,10 @@ pub const LibExeObjStep = struct {
         }
     
         pub fn getOutputHPath(self: &LibExeObjStep) -> []const u8 {
    -        if (self.output_h_path) |output_h_path| {
    +        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)
    -        }
    +        else
    +            %%os.path.join(self.builder.allocator, self.builder.cache_root, self.out_h_filename);
         }
     
         pub fn addAssemblyFile(self: &LibExeObjStep, path: []const u8) {
    @@ -1618,7 +1614,7 @@ pub const TestStep = struct {
     
         pub fn init(builder: &Builder, root_src: []const u8) -> TestStep {
             const step_name = builder.fmt("test {}", root_src);
    -        TestStep {
    +        return TestStep {
                 .step = Step.init(step_name, builder.allocator, make),
                 .builder = builder,
                 .root_src = root_src,
    @@ -1629,7 +1625,7 @@ pub const TestStep = struct {
                 .link_libs = BufSet.init(builder.allocator),
                 .target = Target { .Native = {} },
                 .exec_cmd_args = null,
    -        }
    +        };
         }
     
         pub fn setVerbose(self: &TestStep, value: bool) {
    @@ -1936,16 +1932,16 @@ pub const Step = struct {
         done_flag: bool,
     
         pub fn init(name: []const u8, allocator: &Allocator, makeFn: fn (&Step)->%void) -> Step {
    -        Step {
    +        return Step {
                 .name = name,
                 .makeFn = makeFn,
                 .dependencies = ArrayList(&Step).init(allocator),
                 .loop_flag = false,
                 .done_flag = false,
    -        }
    +        };
         }
         pub fn initNoOp(name: []const u8, allocator: &Allocator) -> Step {
    -        init(name, allocator, makeNoOp)
    +        return init(name, allocator, makeNoOp);
         }
     
         pub fn make(self: &Step) -> %void {
    diff --git a/std/cstr.zig b/std/cstr.zig
    index e29f90fc01..445f7ab892 100644
    --- a/std/cstr.zig
    +++ b/std/cstr.zig
    @@ -17,7 +17,7 @@ pub fn cmp(a: &const u8, b: &const u8) -> i8 {
             return -1;
         } else {
             return 0;
    -    };
    +    }
     }
     
     pub fn toSliceConst(str: &const u8) -> []const u8 {
    diff --git a/std/debug.zig b/std/debug.zig
    index 1035948e3e..a683b5ae15 100644
    --- a/std/debug.zig
    +++ b/std/debug.zig
    @@ -32,7 +32,7 @@ fn getStderrStream() -> %&io.OutStream {
             const st = &stderr_file_out_stream.stream;
             stderr_stream = st;
             return st;
    -    };
    +    }
     }
     
     /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned.
    @@ -52,9 +52,9 @@ pub fn assert(ok: bool) {
             // we insert an explicit call to @panic instead of unreachable.
             // TODO we should use `assertOrPanic` in tests and remove this logic.
             if (builtin.is_test) {
    -            @panic("assertion failure")
    +            @panic("assertion failure");
             } else {
    -            unreachable // assertion failure
    +            unreachable; // assertion failure
             }
         }
     }
    @@ -175,7 +175,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty
                                 return_address, compile_unit_name);
                         },
                         else => return err,
    -                };
    +                }
                 }
             },
             builtin.ObjectFormat.coff => {
    @@ -357,7 +357,7 @@ const Die = struct {
                 FormValue.String => |value| value,
                 FormValue.StrPtr => |offset| getString(st, offset),
                 else => error.InvalidDebugInfo,
    -        }
    +        };
         }
     };
     
    @@ -403,7 +403,7 @@ const LineNumberProgram = struct {
         pub fn init(is_stmt: bool, include_dirs: []const []const u8,
             file_entries: &ArrayList(FileEntry), target_address: usize) -> LineNumberProgram
         {
    -        LineNumberProgram {
    +        return LineNumberProgram {
                 .address = 0,
                 .file = 1,
                 .line = 1,
    @@ -421,7 +421,7 @@ const LineNumberProgram = struct {
                 .prev_is_stmt = undefined,
                 .prev_basic_block = undefined,
                 .prev_end_sequence = undefined,
    -        }
    +        };
         }
     
         pub fn checkLineMatch(self: &LineNumberProgram) -> %?LineInfo {
    @@ -430,14 +430,11 @@ const LineNumberProgram = struct {
                     return error.MissingDebugInfo;
                 } else if (self.prev_file - 1 >= self.file_entries.len) {
                     return error.InvalidDebugInfo;
    -            } else {
    -                &self.file_entries.items[self.prev_file - 1]
    -            };
    +            } else &self.file_entries.items[self.prev_file - 1];
    +
                 const dir_name = if (file_entry.dir_index >= self.include_dirs.len) {
                     return error.InvalidDebugInfo;
    -            } else {
    -                self.include_dirs[file_entry.dir_index]
    -            };
    +            } else self.include_dirs[file_entry.dir_index];
                 const file_name = %return os.path.join(self.file_entries.allocator, dir_name, file_entry.file_name);
                 %defer self.file_entries.allocator.free(file_name);
                 return LineInfo {
    @@ -494,28 +491,21 @@ fn parseFormValueBlock(allocator: &mem.Allocator, in_stream: &io.InStream, size:
     }
     
     fn parseFormValueConstant(allocator: &mem.Allocator, in_stream: &io.InStream, signed: bool, size: usize) -> %FormValue {
    -    FormValue { .Const = Constant {
    +    return FormValue { .Const = Constant {
             .signed = signed,
             .payload = %return readAllocBytes(allocator, in_stream, size),
    -    }}
    +    }};
     }
     
     fn parseFormValueDwarfOffsetSize(in_stream: &io.InStream, is_64: bool) -> %u64 {
    -    return if (is_64) {
    -        %return in_stream.readIntLe(u64)
    -    } else {
    -        u64(%return in_stream.readIntLe(u32))
    -    };
    +    return if (is_64) %return in_stream.readIntLe(u64)
    +    else u64(%return in_stream.readIntLe(u32)) ;
     }
     
     fn parseFormValueTargetAddrSize(in_stream: &io.InStream) -> %u64 {
    -    return if (@sizeOf(usize) == 4) {
    -        u64(%return in_stream.readIntLe(u32))
    -    } else if (@sizeOf(usize) == 8) {
    -        %return in_stream.readIntLe(u64)
    -    } else {
    -        unreachable;
    -    };
    +    return if (@sizeOf(usize) == 4) u64(%return in_stream.readIntLe(u32))
    +    else if (@sizeOf(usize) == 8) %return in_stream.readIntLe(u64)
    +    else unreachable;
     }
     
     fn parseFormValueRefLen(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) -> %FormValue {
    @@ -534,9 +524,9 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u
             DW.FORM_block1 => parseFormValueBlock(allocator, in_stream, 1),
             DW.FORM_block2 => parseFormValueBlock(allocator, in_stream, 2),
             DW.FORM_block4 => parseFormValueBlock(allocator, in_stream, 4),
    -        DW.FORM_block => {
    +        DW.FORM_block => x: {
                 const block_len = %return readULeb128(in_stream);
    -            parseFormValueBlockLen(allocator, in_stream, block_len)
    +            return parseFormValueBlockLen(allocator, in_stream, block_len);
             },
             DW.FORM_data1 => parseFormValueConstant(allocator, in_stream, false, 1),
             DW.FORM_data2 => parseFormValueConstant(allocator, in_stream, false, 2),
    @@ -545,7 +535,7 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u
             DW.FORM_udata, DW.FORM_sdata => {
                 const block_len = %return readULeb128(in_stream);
                 const signed = form_id == DW.FORM_sdata;
    -            parseFormValueConstant(allocator, in_stream, signed, block_len)
    +            return parseFormValueConstant(allocator, in_stream, signed, block_len);
             },
             DW.FORM_exprloc => {
                 const size = %return readULeb128(in_stream);
    @@ -562,7 +552,7 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u
             DW.FORM_ref8 => parseFormValueRef(allocator, in_stream, u64),
             DW.FORM_ref_udata => {
                 const ref_len = %return readULeb128(in_stream);
    -            parseFormValueRefLen(allocator, in_stream, ref_len)
    +            return parseFormValueRefLen(allocator, in_stream, ref_len);
             },
     
             DW.FORM_ref_addr => FormValue { .RefAddr = %return parseFormValueDwarfOffsetSize(in_stream, is_64) },
    @@ -572,10 +562,10 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u
             DW.FORM_strp => FormValue { .StrPtr = %return parseFormValueDwarfOffsetSize(in_stream, is_64) },
             DW.FORM_indirect => {
                 const child_form_id = %return readULeb128(in_stream);
    -            parseFormValue(allocator, in_stream, child_form_id, is_64)
    +            return parseFormValue(allocator, in_stream, child_form_id, is_64);
             },
             else => error.InvalidDebugInfo,
    -    }
    +    };
     }
     
     fn parseAbbrevTable(st: &ElfStackTrace) -> %AbbrevTable {
    @@ -852,11 +842,9 @@ fn scanAllCompileUnits(st: &ElfStackTrace) -> %void {
             const version = %return in_stream.readInt(st.elf.endian, u16);
             if (version < 2 or version > 5) return error.InvalidDebugInfo;
     
    -        const debug_abbrev_offset = if (is_64) {
    -            %return in_stream.readInt(st.elf.endian, u64)
    -        } else {
    -            %return in_stream.readInt(st.elf.endian, u32)
    -        };
    +        const debug_abbrev_offset =
    +            if (is_64) %return in_stream.readInt(st.elf.endian, u64)
    +            else %return in_stream.readInt(st.elf.endian, u32);
     
             const address_size = %return in_stream.readByte();
             if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
    @@ -872,28 +860,28 @@ fn scanAllCompileUnits(st: &ElfStackTrace) -> %void {
             if (compile_unit_die.tag_id != DW.TAG_compile_unit)
                 return error.InvalidDebugInfo;
     
    -        const pc_range = {
    +        const pc_range = x: {
                 if (compile_unit_die.getAttrAddr(DW.AT_low_pc)) |low_pc| {
                     if (compile_unit_die.getAttr(DW.AT_high_pc)) |high_pc_value| {
                         const pc_end = switch (*high_pc_value) {
                             FormValue.Address => |value| value,
    -                        FormValue.Const => |value| {
    +                        FormValue.Const => |value| b: {
                                 const offset = %return value.asUnsignedLe();
    -                            low_pc + offset
    +                            break :b (low_pc + offset);
                             },
                             else => return error.InvalidDebugInfo,
                         };
    -                    PcRange {
    +                    break :x PcRange {
                             .start = low_pc,
                             .end = pc_end,
    -                    }
    +                    };
                     } else {
    -                    null
    +                    break :x null;
                     }
                 } else |err| {
                     if (err != error.MissingDebugInfo)
                         return err;
    -                null
    +                break :x null;
                 }
             };
     
    @@ -949,12 +937,12 @@ fn findCompileUnit(st: &ElfStackTrace, target_address: u64) -> %&const CompileUn
     fn readInitialLength(in_stream: &io.InStream, is_64: &bool) -> %u64 {
         const first_32_bits = %return in_stream.readIntLe(u32);
         *is_64 = (first_32_bits == 0xffffffff);
    -    return if (*is_64) {
    -        %return in_stream.readIntLe(u64)
    +    if (*is_64) {
    +        return in_stream.readIntLe(u64);
         } else {
             if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo;
    -        u64(first_32_bits)
    -    };
    +        return u64(first_32_bits);
    +    }
     }
     
     fn readULeb128(in_stream: &io.InStream) -> %u64 {
    diff --git a/std/endian.zig b/std/endian.zig
    index 2dc6b8d34e..9f2d2c8dcd 100644
    --- a/std/endian.zig
    +++ b/std/endian.zig
    @@ -2,15 +2,15 @@ const mem = @import("mem.zig");
     const builtin = @import("builtin");
     
     pub fn swapIfLe(comptime T: type, x: T) -> T {
    -    swapIf(false, T, x)
    +    return swapIf(false, T, x);
     }
     
     pub fn swapIfBe(comptime T: type, x: T) -> T {
    -    swapIf(true, T, x)
    +    return swapIf(true, T, x);
     }
     
     pub fn swapIf(endian: builtin.Endian, comptime T: type, x: T) -> T {
    -    if (builtin.endian == endian) swap(T, x) else x
    +    return if (builtin.endian == endian) swap(T, x) else x;
     }
     
     pub fn swap(comptime T: type, x: T) -> T {
    diff --git a/std/fmt/errol/enum3.zig b/std/fmt/errol/enum3.zig
    index 93861cce74..feb84da9a4 100644
    --- a/std/fmt/errol/enum3.zig
    +++ b/std/fmt/errol/enum3.zig
    @@ -439,10 +439,10 @@ const Slab = struct {
     };
     
     fn slab(str: []const u8, exp: i32) -> Slab {
    -    Slab {
    +    return Slab {
             .str = str,
             .exp = exp,
    -    }
    +    };
     }
     
     pub const enum3_data = []Slab {
    diff --git a/std/fmt/index.zig b/std/fmt/index.zig
    index 59376e0458..53fd085488 100644
    --- a/std/fmt/index.zig
    +++ b/std/fmt/index.zig
    @@ -251,11 +251,10 @@ pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []cons
         %return output(context, float_decimal.digits[0..1]);
         %return output(context, ".");
         if (float_decimal.digits.len > 1) {
    -        const num_digits = if (@typeOf(value) == f32) {
    +        const num_digits = if (@typeOf(value) == f32)
                 math.min(usize(9), float_decimal.digits.len)
    -        } else {
    -            float_decimal.digits.len
    -        };
    +        else
    +            float_decimal.digits.len;
             %return output(context, float_decimal.digits[1 .. num_digits]);
         } else {
             %return output(context, "0");
    diff --git a/std/hash_map.zig b/std/hash_map.zig
    index d124d7b573..837ee07423 100644
    --- a/std/hash_map.zig
    +++ b/std/hash_map.zig
    @@ -12,7 +12,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
         comptime hash: fn(key: K)->u32,
         comptime eql: fn(a: K, b: K)->bool) -> type
     {
    -    struct {
    +    return struct {
             entries: []Entry,
             size: usize,
             max_distance_from_start_index: usize,
    @@ -51,19 +51,19 @@ pub fn HashMap(comptime K: type, comptime V: type,
                             return entry;
                         }
                     }
    -                unreachable // no next item
    +                unreachable; // no next item
                 }
             };
     
             pub fn init(allocator: &Allocator) -> Self {
    -            Self {
    +            return Self {
                     .entries = []Entry{},
                     .allocator = allocator,
                     .size = 0,
                     .max_distance_from_start_index = 0,
                     // it doesn't actually matter what we set this to since we use wrapping integer arithmetic
                     .modification_count = undefined,
    -            }
    +            };
             }
     
             pub fn deinit(hm: &Self) {
    @@ -133,7 +133,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
                         entry.distance_from_start_index -= 1;
                         entry = next_entry;
                     }
    -                unreachable // shifting everything in the table
    +                unreachable; // shifting everything in the table
                 }}
                 return null;
             }
    @@ -169,7 +169,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
                 const start_index = hm.keyToIndex(key);
                 var roll_over: usize = 0;
                 var distance_from_start_index: usize = 0;
    -            while (roll_over < hm.entries.len) : ({roll_over += 1; distance_from_start_index += 1}) {
    +            while (roll_over < hm.entries.len) : ({roll_over += 1; distance_from_start_index += 1;}) {
                     const index = (start_index + roll_over) % hm.entries.len;
                     const entry = &hm.entries[index];
     
    @@ -210,7 +210,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
                     };
                     return result;
                 }
    -            unreachable // put into a full map
    +            unreachable; // put into a full map
             }
     
             fn internalGet(hm: &Self, key: K) -> ?&Entry {
    @@ -228,7 +228,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
             fn keyToIndex(hm: &Self, key: K) -> usize {
                 return usize(hash(key)) % hm.entries.len;
             }
    -    }
    +    };
     }
     
     test "basicHashMapTest" {
    @@ -251,9 +251,9 @@ test "basicHashMapTest" {
     }
     
     fn hash_i32(x: i32) -> u32 {
    -    @bitCast(u32, x)
    +    return @bitCast(u32, x);
     }
     
     fn eql_i32(a: i32, b: i32) -> bool {
    -    a == b
    +    return a == b;
     }
    diff --git a/std/heap.zig b/std/heap.zig
    index d54d921856..605eddb40b 100644
    --- a/std/heap.zig
    +++ b/std/heap.zig
    @@ -17,22 +17,21 @@ pub var c_allocator = Allocator {
     };
     
     fn cAlloc(self: &Allocator, n: usize, alignment: u29) -> %[]u8 {
    -    if (c.malloc(usize(n))) |buf| {
    +    return if (c.malloc(usize(n))) |buf|
             @ptrCast(&u8, buf)[0..n]
    -    } else {
    -        error.OutOfMemory
    -    }
    +    else
    +        error.OutOfMemory;
     }
     
     fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 {
         if (new_size <= old_mem.len) {
    -        old_mem[0..new_size]
    +        return old_mem[0..new_size];
         } else {
             const old_ptr = @ptrCast(&c_void, old_mem.ptr);
             if (c.realloc(old_ptr, usize(new_size))) |buf| {
    -            @ptrCast(&u8, buf)[0..new_size]
    +            return @ptrCast(&u8, buf)[0..new_size];
             } else {
    -            error.OutOfMemory
    +            return error.OutOfMemory;
             }
         }
     }
    diff --git a/std/io.zig b/std/io.zig
    index 0ba3d06a01..cbf2e0c216 100644
    --- a/std/io.zig
    +++ b/std/io.zig
    @@ -50,35 +50,32 @@ error Unseekable;
     error EndOfFile;
     
     pub fn getStdErr() -> %File {
    -    const handle = if (is_windows) {
    +    const handle = if (is_windows)
             %return os.windowsGetStdHandle(system.STD_ERROR_HANDLE)
    -    } else if (is_posix) {
    +    else if (is_posix)
             system.STDERR_FILENO
    -    } else {
    -        unreachable
    -    };
    +    else
    +        unreachable;
         return File.openHandle(handle);
     }
     
     pub fn getStdOut() -> %File {
    -    const handle = if (is_windows) {
    +    const handle = if (is_windows)
             %return os.windowsGetStdHandle(system.STD_OUTPUT_HANDLE)
    -    } else if (is_posix) {
    +    else if (is_posix)
             system.STDOUT_FILENO
    -    } else {
    -        unreachable
    -    };
    +    else
    +        unreachable;
         return File.openHandle(handle);
     }
     
     pub fn getStdIn() -> %File {
    -    const handle = if (is_windows) {
    +    const handle = if (is_windows)
             %return os.windowsGetStdHandle(system.STD_INPUT_HANDLE)
    -    } else if (is_posix) {
    +    else if (is_posix)
             system.STDIN_FILENO
    -    } else {
    -        unreachable
    -    };
    +    else
    +        unreachable;
         return File.openHandle(handle);
     }
     
    @@ -261,7 +258,7 @@ pub const File = struct {
                         system.EBADF => error.BadFd,
                         system.ENOMEM => error.SystemResources,
                         else => os.unexpectedErrorPosix(err),
    -                }
    +                };
                 }
     
                 return usize(stat.size);
    diff --git a/std/linked_list.zig b/std/linked_list.zig
    index cbcef793de..f4b6d274c9 100644
    --- a/std/linked_list.zig
    +++ b/std/linked_list.zig
    @@ -5,7 +5,7 @@ const Allocator = mem.Allocator;
     
     /// Generic doubly linked list.
     pub fn LinkedList(comptime T: type) -> type {
    -    struct {
    +    return struct {
             const Self = this;
     
             /// Node inside the linked list wrapping the actual data.
    @@ -15,11 +15,11 @@ pub fn LinkedList(comptime T: type) -> type {
                 data: T,
     
                 pub fn init(data: &const T) -> Node {
    -                Node {
    +                return Node {
                         .prev = null,
                         .next = null,
                         .data = *data,
    -                }
    +                };
                 }
             };
     
    @@ -32,11 +32,11 @@ pub fn LinkedList(comptime T: type) -> type {
             /// Returns:
             ///     An empty linked list.
             pub fn init() -> Self {
    -            Self {
    +            return Self {
                     .first = null,
                     .last  = null,
                     .len   = 0,
    -            }
    +            };
             }
     
             /// Insert a new node after an existing one.
    @@ -166,7 +166,7 @@ pub fn LinkedList(comptime T: type) -> type {
             /// Returns:
             ///     A pointer to the new node.
             pub fn allocateNode(list: &Self, allocator: &Allocator) -> %&Node {
    -            allocator.create(Node)
    +            return allocator.create(Node);
             }
     
             /// Deallocate a node.
    @@ -191,7 +191,7 @@ pub fn LinkedList(comptime T: type) -> type {
                 *node = Node.init(data);
                 return node;
             }
    -    }
    +    };
     }
     
     test "basic linked list test" {
    diff --git a/std/math/acos.zig b/std/math/acos.zig
    index 941643db82..ae7afce032 100644
    --- a/std/math/acos.zig
    +++ b/std/math/acos.zig
    @@ -7,11 +7,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn acos(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(acos32, x),
             f64 => @inlineCall(acos64, x),
             else => @compileError("acos not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn r32(z: f32) -> f32 {
    @@ -22,7 +22,7 @@ fn r32(z: f32) -> f32 {
     
         const p = z * (pS0 + z * (pS1 + z * pS2));
         const q = 1.0 + z * qS1;
    -    p / q
    +    return p / q;
     }
     
     fn acos32(x: f32) -> f32 {
    @@ -69,7 +69,7 @@ fn acos32(x: f32) -> f32 {
         const df = @bitCast(f32, jx & 0xFFFFF000);
         const c = (z - df * df) / (s + df);
         const w = r32(z) * s + c;
    -    2 * (df + w)
    +    return 2 * (df + w);
     }
     
     fn r64(z: f64) -> f64 {
    @@ -86,7 +86,7 @@ fn r64(z: f64) -> f64 {
     
         const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * pS5)))));
         const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * qS4)));
    -    p / q
    +    return p / q;
     }
     
     fn acos64(x: f64) -> f64 {
    @@ -138,7 +138,7 @@ fn acos64(x: f64) -> f64 {
         const df = @bitCast(f64, jx & 0xFFFFFFFF00000000);
         const c = (z - df * df) / (s + df);
         const w = r64(z) * s + c;
    -    2 * (df + w)
    +    return 2 * (df + w);
     }
     
     test "math.acos" {
    diff --git a/std/math/acosh.zig b/std/math/acosh.zig
    index 9f43edeb5d..0fdf9e41e0 100644
    --- a/std/math/acosh.zig
    +++ b/std/math/acosh.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn acosh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(acosh32, x),
             f64 => @inlineCall(acosh64, x),
             else => @compileError("acosh not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // acosh(x) = log(x + sqrt(x * x - 1))
    @@ -23,15 +23,15 @@ fn acosh32(x: f32) -> f32 {
     
         // |x| < 2, invalid if x < 1 or nan
         if (i < 0x3F800000 + (1 << 23)) {
    -        math.log1p(x - 1 + math.sqrt((x - 1) * (x - 1) + 2 * (x - 1)))
    +        return math.log1p(x - 1 + math.sqrt((x - 1) * (x - 1) + 2 * (x - 1)));
         }
         // |x| < 0x1p12
         else if (i < 0x3F800000 + (12 << 23)) {
    -        math.ln(2 * x - 1 / (x + math.sqrt(x * x - 1)))
    +        return math.ln(2 * x - 1 / (x + math.sqrt(x * x - 1)));
         }
         // |x| >= 0x1p12
         else {
    -        math.ln(x) + 0.693147180559945309417232121458176568
    +        return math.ln(x) + 0.693147180559945309417232121458176568;
         }
     }
     
    @@ -41,15 +41,15 @@ fn acosh64(x: f64) -> f64 {
     
         // |x| < 2, invalid if x < 1 or nan
         if (e < 0x3FF + 1) {
    -        math.log1p(x - 1 + math.sqrt((x - 1) * (x - 1) + 2 * (x - 1)))
    +        return math.log1p(x - 1 + math.sqrt((x - 1) * (x - 1) + 2 * (x - 1)));
         }
         // |x| < 0x1p26
         else if (e < 0x3FF + 26) {
    -        math.ln(2 * x - 1 / (x + math.sqrt(x * x - 1)))
    +        return math.ln(2 * x - 1 / (x + math.sqrt(x * x - 1)));
         }
         // |x| >= 0x1p26 or nan
         else {
    -        math.ln(x) + 0.693147180559945309417232121458176568
    +        return math.ln(x) + 0.693147180559945309417232121458176568;
         }
     }
     
    diff --git a/std/math/asin.zig b/std/math/asin.zig
    index b0368b5d66..11aad04107 100644
    --- a/std/math/asin.zig
    +++ b/std/math/asin.zig
    @@ -8,11 +8,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn asin(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(asin32, x),
             f64 => @inlineCall(asin64, x),
             else => @compileError("asin not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn r32(z: f32) -> f32 {
    @@ -23,7 +23,7 @@ fn r32(z: f32) -> f32 {
     
         const p = z * (pS0 + z * (pS1 + z * pS2));
         const q = 1.0 + z * qS1;
    -    p / q
    +    return p / q;
     }
     
     fn asin32(x: f32) -> f32 {
    @@ -58,9 +58,9 @@ fn asin32(x: f32) -> f32 {
         const fx = pio2 - 2 * (s + s * r32(z));
     
         if (hx >> 31 != 0) {
    -        -fx
    +        return -fx;
         } else {
    -        fx
    +        return fx;
         }
     }
     
    @@ -78,7 +78,7 @@ fn r64(z: f64) -> f64 {
     
         const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * pS5)))));
         const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * qS4)));
    -    p / q
    +    return p / q;
     }
     
     fn asin64(x: f64) -> f64 {
    @@ -119,7 +119,7 @@ fn asin64(x: f64) -> f64 {
     
         // |x| > 0.975
         if (ix >= 0x3FEF3333) {
    -        fx = pio2_hi - 2 * (s + s * r)
    +        fx = pio2_hi - 2 * (s + s * r);
         } else {
             const jx = @bitCast(u64, s);
             const df = @bitCast(f64, jx & 0xFFFFFFFF00000000);
    @@ -128,9 +128,9 @@ fn asin64(x: f64) -> f64 {
         }
     
         if (hx >> 31 != 0) {
    -        -fx
    +        return -fx;
         } else {
    -        fx
    +        return fx;
         }
     }
     
    diff --git a/std/math/asinh.zig b/std/math/asinh.zig
    index dab047036a..eda7e15861 100644
    --- a/std/math/asinh.zig
    +++ b/std/math/asinh.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn asinh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(asinh32, x),
             f64 => @inlineCall(asinh64, x),
             else => @compileError("asinh not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // asinh(x) = sign(x) * log(|x| + sqrt(x * x + 1)) ~= x - x^3/6 + o(x^5)
    @@ -46,7 +46,7 @@ fn asinh32(x: f32) -> f32 {
             math.forceEval(x + 0x1.0p120);
         }
     
    -    if (s != 0) -rx else rx
    +    return if (s != 0) -rx else rx;
     }
     
     fn asinh64(x: f64) -> f64 {
    @@ -77,7 +77,7 @@ fn asinh64(x: f64) -> f64 {
             math.forceEval(x + 0x1.0p120);
         }
     
    -    if (s != 0) -rx else rx
    +    return if (s != 0) -rx else rx;
     }
     
     test "math.asinh" {
    diff --git a/std/math/atan.zig b/std/math/atan.zig
    index 5f3e207960..4527f6bf28 100644
    --- a/std/math/atan.zig
    +++ b/std/math/atan.zig
    @@ -8,11 +8,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn atan(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(atan32, x),
             f64 => @inlineCall(atan64, x),
             else => @compileError("atan not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn atan32(x_: f32) -> f32 {
    @@ -100,10 +100,10 @@ fn atan32(x_: f32) -> f32 {
         const s2 = w * (aT[1] + w * aT[3]);
     
         if (id == null) {
    -        x - x * (s1 + s2)
    +        return x - x * (s1 + s2);
         } else {
             const zz = atanhi[??id] - ((x * (s1 + s2) - atanlo[??id]) - x);
    -        if (sign != 0) -zz else zz
    +        return if (sign != 0) -zz else zz;
         }
     }
     
    @@ -199,10 +199,10 @@ fn atan64(x_: f64) -> f64 {
         const s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * aT[9]))));
     
         if (id == null) {
    -        x - x * (s1 + s2)
    +        return x - x * (s1 + s2);
         } else {
             const zz = atanhi[??id] - ((x * (s1 + s2) - atanlo[??id]) - x);
    -        if (sign != 0) -zz else zz
    +        return if (sign != 0) -zz else zz;
         }
     }
     
    diff --git a/std/math/atan2.zig b/std/math/atan2.zig
    index 0e566837ce..9124952b74 100644
    --- a/std/math/atan2.zig
    +++ b/std/math/atan2.zig
    @@ -22,11 +22,11 @@ const math = @import("index.zig");
     const assert = @import("../debug.zig").assert;
     
     fn atan2(comptime T: type, x: T, y: T) -> T {
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(atan2_32, x, y),
             f64 => @inlineCall(atan2_64, x, y),
             else => @compileError("atan2 not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn atan2_32(y: f32, x: f32) -> f32 {
    @@ -97,11 +97,11 @@ fn atan2_32(y: f32, x: f32) -> f32 {
         }
     
         // z = atan(|y / x|) with correct underflow
    -    var z = {
    +    var z = z: {
             if ((m & 2) != 0 and iy + (26 << 23) < ix) {
    -            0.0
    +            break :z 0.0;
             } else {
    -            math.atan(math.fabs(y / x))
    +            break :z math.atan(math.fabs(y / x));
             }
         };
     
    @@ -187,11 +187,11 @@ fn atan2_64(y: f64, x: f64) -> f64 {
         }
     
         // z = atan(|y / x|) with correct underflow
    -    var z = {
    +    var z = z: {
             if ((m & 2) != 0 and iy +% (64 << 20) < ix) {
    -            0.0
    +            break :z 0.0;
             } else {
    -            math.atan(math.fabs(y / x))
    +            break :z math.atan(math.fabs(y / x));
             }
         };
     
    diff --git a/std/math/atanh.zig b/std/math/atanh.zig
    index 8fe5ab55a7..2f87efbbd3 100644
    --- a/std/math/atanh.zig
    +++ b/std/math/atanh.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn atanh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(atanh_32, x),
             f64 => @inlineCall(atanh_64, x),
             else => @compileError("atanh not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // atanh(x) = log((1 + x) / (1 - x)) / 2 = log1p(2x / (1 - x)) / 2 ~= x + x^3 / 3 + o(x^5)
    @@ -32,7 +32,7 @@ fn atanh_32(x: f32) -> f32 {
             if (u < 0x3F800000 - (32 << 23)) {
                 // underflow
                 if (u < (1 << 23)) {
    -                math.forceEval(y * y)
    +                math.forceEval(y * y);
                 }
             }
             // |x| < 0.5
    @@ -43,7 +43,7 @@ fn atanh_32(x: f32) -> f32 {
             y = 0.5 * math.log1p(2 * (y / (1 - y)));
         }
     
    -    if (s != 0) -y else y
    +    return if (s != 0) -y else y;
     }
     
     fn atanh_64(x: f64) -> f64 {
    @@ -72,7 +72,7 @@ fn atanh_64(x: f64) -> f64 {
             y = 0.5 * math.log1p(2 * (y / (1 - y)));
         }
     
    -    if (s != 0) -y else y
    +    return if (s != 0) -y else y;
     }
     
     test "math.atanh" {
    diff --git a/std/math/cbrt.zig b/std/math/cbrt.zig
    index 273939c6b6..ff353655b5 100644
    --- a/std/math/cbrt.zig
    +++ b/std/math/cbrt.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn cbrt(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(cbrt32, x),
             f64 => @inlineCall(cbrt64, x),
             else => @compileError("cbrt not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn cbrt32(x: f32) -> f32 {
    @@ -53,7 +53,7 @@ fn cbrt32(x: f32) -> f32 {
         r = t * t * t;
         t = t * (f64(x) + x + r) / (x + r + r);
     
    -    f32(t)
    +    return f32(t);
     }
     
     fn cbrt64(x: f64) -> f64 {
    @@ -109,7 +109,7 @@ fn cbrt64(x: f64) -> f64 {
         var w = t + t;
         q = (q - t) / (w + q);
     
    -    t + t * q
    +    return t + t * q;
     }
     
     test "math.cbrt" {
    diff --git a/std/math/ceil.zig b/std/math/ceil.zig
    index 2c7b28cc93..a8db486f92 100644
    --- a/std/math/ceil.zig
    +++ b/std/math/ceil.zig
    @@ -10,11 +10,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn ceil(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(ceil32, x),
             f64 => @inlineCall(ceil64, x),
             else => @compileError("ceil not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn ceil32(x: f32) -> f32 {
    @@ -39,13 +39,13 @@ fn ceil32(x: f32) -> f32 {
                 u += m;
             }
             u &= ~m;
    -        @bitCast(f32, u)
    +        return @bitCast(f32, u);
         } else {
             math.forceEval(x + 0x1.0p120);
             if (u >> 31 != 0) {
                 return -0.0;
             } else {
    -            1.0
    +            return 1.0;
             }
         }
     }
    @@ -70,14 +70,14 @@ fn ceil64(x: f64) -> f64 {
         if (e <= 0x3FF-1) {
             math.forceEval(y);
             if (u >> 63 != 0) {
    -            return -0.0;    // Compiler requires return.
    +            return -0.0;
             } else {
    -            1.0
    +            return 1.0;
             }
         } else if (y < 0) {
    -        x + y + 1
    +        return x + y + 1;
         } else {
    -        x + y
    +        return x + y;
         }
     }
     
    diff --git a/std/math/copysign.zig b/std/math/copysign.zig
    index 109ee8c632..12e092c9ab 100644
    --- a/std/math/copysign.zig
    +++ b/std/math/copysign.zig
    @@ -2,11 +2,11 @@ const math = @import("index.zig");
     const assert = @import("../debug.zig").assert;
     
     pub fn copysign(comptime T: type, x: T, y: T) -> T {
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(copysign32, x, y),
             f64 => @inlineCall(copysign64, x, y),
             else => @compileError("copysign not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn copysign32(x: f32, y: f32) -> f32 {
    @@ -15,7 +15,7 @@ fn copysign32(x: f32, y: f32) -> f32 {
     
         const h1 = ux & (@maxValue(u32) / 2);
         const h2 = uy & (u32(1) << 31);
    -    @bitCast(f32, h1 | h2)
    +    return @bitCast(f32, h1 | h2);
     }
     
     fn copysign64(x: f64, y: f64) -> f64 {
    @@ -24,7 +24,7 @@ fn copysign64(x: f64, y: f64) -> f64 {
     
         const h1 = ux & (@maxValue(u64) / 2);
         const h2 = uy & (u64(1) << 63);
    -    @bitCast(f64, h1 | h2)
    +    return @bitCast(f64, h1 | h2);
     }
     
     test "math.copysign" {
    diff --git a/std/math/cos.zig b/std/math/cos.zig
    index 4b5c9193c3..285a2e538b 100644
    --- a/std/math/cos.zig
    +++ b/std/math/cos.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn cos(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(cos32, x),
             f64 => @inlineCall(cos64, x),
             else => @compileError("cos not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // sin polynomial coefficients
    @@ -73,18 +73,18 @@ fn cos32(x_: f32) -> f32 {
         const z = ((x - y * pi4a) - y * pi4b) - y * pi4c;
         const w = z * z;
     
    -    const r = {
    +    const r = r: {
             if (j == 1 or j == 2) {
    -            z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))))
    +            break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))));
             } else {
    -            1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))))
    +            break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))));
             }
         };
     
         if (sign) {
    -        -r
    +        return -r;
         } else {
    -        r
    +        return r;
         }
     }
     
    @@ -124,18 +124,18 @@ fn cos64(x_: f64) -> f64 {
         const z = ((x - y * pi4a) - y * pi4b) - y * pi4c;
         const w = z * z;
     
    -    const r = {
    +    const r = r: {
             if (j == 1 or j == 2) {
    -            z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))))
    +            break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))));
             } else {
    -            1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))))
    +            break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))));
             }
         };
     
         if (sign) {
    -        -r
    +        return -r;
         } else {
    -        r
    +        return r;
         }
     }
     
    diff --git a/std/math/cosh.zig b/std/math/cosh.zig
    index 34c647eaad..f7fea99bcc 100644
    --- a/std/math/cosh.zig
    +++ b/std/math/cosh.zig
    @@ -11,11 +11,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn cosh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(cosh32, x),
             f64 => @inlineCall(cosh64, x),
             else => @compileError("cosh not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // cosh(x) = (exp(x) + 1 / exp(x)) / 2
    @@ -43,7 +43,7 @@ fn cosh32(x: f32) -> f32 {
         }
     
         // |x| > log(FLT_MAX) or nan
    -    expo2(ax)
    +    return expo2(ax);
     }
     
     fn cosh64(x: f64) -> f64 {
    @@ -76,7 +76,7 @@ fn cosh64(x: f64) -> f64 {
         }
     
         // |x| > log(CBL_MAX) or nan
    -    expo2(ax)
    +    return expo2(ax);
     }
     
     test "math.cosh" {
    diff --git a/std/math/exp.zig b/std/math/exp.zig
    index 87c0a1882e..e51221a74a 100644
    --- a/std/math/exp.zig
    +++ b/std/math/exp.zig
    @@ -8,11 +8,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn exp(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(exp32, x),
             f64 => @inlineCall(exp64, x),
             else => @compileError("exp not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn exp32(x_: f32) -> f32 {
    @@ -86,9 +86,9 @@ fn exp32(x_: f32) -> f32 {
         const y = 1 + (x * c / (2 - c) - lo + hi);
     
         if (k == 0) {
    -        y
    +        return y;
         } else {
    -        math.scalbn(y, k)
    +        return math.scalbn(y, k);
         }
     }
     
    @@ -172,9 +172,9 @@ fn exp64(x_: f64) -> f64 {
         const y = 1 + (x * c / (2 - c) - lo + hi);
     
         if (k == 0) {
    -        y
    +        return y;
         } else {
    -        math.scalbn(y, k)
    +        return math.scalbn(y, k);
         }
     }
     
    diff --git a/std/math/exp2.zig b/std/math/exp2.zig
    index 111c1dad64..e867aabe23 100644
    --- a/std/math/exp2.zig
    +++ b/std/math/exp2.zig
    @@ -8,11 +8,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn exp2(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(exp2_32, x),
             f64 => @inlineCall(exp2_64, x),
             else => @compileError("exp2 not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     const exp2ft = []const f64 {
    @@ -88,7 +88,7 @@ fn exp2_32(x: f32) -> f32 {
         var r: f64 = exp2ft[i0];
         const t: f64 = r * z;
         r = r + t * (P1 + z * P2) + t * (z * z) * (P3 + z * P4);
    -    f32(r * uk)
    +    return f32(r * uk);
     }
     
     const exp2dt = []f64 {
    @@ -414,7 +414,7 @@ fn exp2_64(x: f64) -> f64 {
         z -= exp2dt[2 * i0 + 1];
         const r = t + t * z * (P1 + z * (P2 + z * (P3 + z * (P4 + z * P5))));
     
    -    math.scalbn(r, ik)
    +    return math.scalbn(r, ik);
     }
     
     test "math.exp2" {
    diff --git a/std/math/expm1.zig b/std/math/expm1.zig
    index ef0b2766b1..14a69958e8 100644
    --- a/std/math/expm1.zig
    +++ b/std/math/expm1.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn expm1(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(expm1_32, x),
             f64 => @inlineCall(expm1_64, x),
             else => @compileError("exp1m not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn expm1_32(x_: f32) -> f32 {
    diff --git a/std/math/expo2.zig b/std/math/expo2.zig
    index e0d8a8130d..93111391a2 100644
    --- a/std/math/expo2.zig
    +++ b/std/math/expo2.zig
    @@ -2,11 +2,11 @@ const math = @import("index.zig");
     
     pub fn expo2(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => expo2f(x),
             f64 => expo2d(x),
             else => @compileError("expo2 not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn expo2f(x: f32) -> f32 {
    @@ -15,7 +15,7 @@ fn expo2f(x: f32) -> f32 {
     
         const u = (0x7F + k / 2) << 23;
         const scale = @bitCast(f32, u);
    -    math.exp(x - kln2) * scale * scale
    +    return math.exp(x - kln2) * scale * scale;
     }
     
     fn expo2d(x: f64) -> f64 {
    @@ -24,5 +24,5 @@ fn expo2d(x: f64) -> f64 {
     
         const u = (0x3FF + k / 2) << 20;
         const scale = @bitCast(f64, u64(u) << 32);
    -    math.exp(x - kln2) * scale * scale
    +    return math.exp(x - kln2) * scale * scale;
     }
    diff --git a/std/math/fabs.zig b/std/math/fabs.zig
    index e9bd3eb689..58244f4f2e 100644
    --- a/std/math/fabs.zig
    +++ b/std/math/fabs.zig
    @@ -8,23 +8,23 @@ const assert = @import("../debug.zig").assert;
     
     pub fn fabs(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(fabs32, x),
             f64 => @inlineCall(fabs64, x),
             else => @compileError("fabs not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn fabs32(x: f32) -> f32 {
         var u = @bitCast(u32, x);
         u &= 0x7FFFFFFF;
    -    @bitCast(f32, u)
    +    return @bitCast(f32, u);
     }
     
     fn fabs64(x: f64) -> f64 {
         var u = @bitCast(u64, x);
         u &= @maxValue(u64) >> 1;
    -    @bitCast(f64, u)
    +    return @bitCast(f64, u);
     }
     
     test "math.fabs" {
    diff --git a/std/math/floor.zig b/std/math/floor.zig
    index 85ee2b4923..875070397d 100644
    --- a/std/math/floor.zig
    +++ b/std/math/floor.zig
    @@ -10,11 +10,11 @@ const math = @import("index.zig");
     
     pub fn floor(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(floor32, x),
             f64 => @inlineCall(floor64, x),
             else => @compileError("floor not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn floor32(x: f32) -> f32 {
    @@ -40,13 +40,13 @@ fn floor32(x: f32) -> f32 {
             if (u >> 31 != 0) {
                 u += m;
             }
    -        @bitCast(f32, u & ~m)
    +        return @bitCast(f32, u & ~m);
         } else {
             math.forceEval(x + 0x1.0p120);
             if (u >> 31 == 0) {
    -            return 0.0; // Compiler requires return
    +            return 0.0;
             } else {
    -            -1.0
    +            return -1.0;
             }
         }
     }
    @@ -71,14 +71,14 @@ fn floor64(x: f64) -> f64 {
         if (e <= 0x3FF-1) {
             math.forceEval(y);
             if (u >> 63 != 0) {
    -            return -1.0;    // Compiler requires return.
    +            return -1.0;
             } else {
    -            0.0
    +            return 0.0;
             }
         } else if (y > 0) {
    -        x + y - 1
    +        return x + y - 1;
         } else {
    -        x + y
    +        return x + y;
         }
     }
     
    diff --git a/std/math/fma.zig b/std/math/fma.zig
    index 8dbbc39ed0..c870dfd293 100644
    --- a/std/math/fma.zig
    +++ b/std/math/fma.zig
    @@ -2,11 +2,11 @@ const math = @import("index.zig");
     const assert = @import("../debug.zig").assert;
     
     pub fn fma(comptime T: type, x: T, y: T, z: T) -> T {
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(fma32, x, y, z),
             f64 => @inlineCall(fma64, x, y ,z),
             else => @compileError("fma not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn fma32(x: f32, y: f32, z: f32) -> f32 {
    @@ -16,10 +16,10 @@ fn fma32(x: f32, y: f32, z: f32) -> f32 {
         const e = (u >> 52) & 0x7FF;
     
         if ((u & 0x1FFFFFFF) != 0x10000000 or e == 0x7FF or xy_z - xy == z) {
    -        f32(xy_z)
    +        return f32(xy_z);
         } else {
             // TODO: Handle inexact case with double-rounding
    -        f32(xy_z)
    +        return f32(xy_z);
         }
     }
     
    @@ -64,9 +64,9 @@ fn fma64(x: f64, y: f64, z: f64) -> f64 {
     
         const adj = add_adjusted(r.lo, xy.lo);
         if (spread + math.ilogb(r.hi) > -1023) {
    -        math.scalbn(r.hi + adj, spread)
    +        return math.scalbn(r.hi + adj, spread);
         } else {
    -        add_and_denorm(r.hi, adj, spread)
    +        return add_and_denorm(r.hi, adj, spread);
         }
     }
     
    @@ -77,7 +77,7 @@ fn dd_add(a: f64, b: f64) -> dd {
         ret.hi = a + b;
         const s = ret.hi - a;
         ret.lo = (a - (ret.hi - s)) + (b - s);
    -    ret
    +    return ret;
     }
     
     fn dd_mul(a: f64, b: f64) -> dd {
    @@ -99,7 +99,7 @@ fn dd_mul(a: f64, b: f64) -> dd {
     
         ret.hi = p + q;
         ret.lo = p - ret.hi + q + la * lb;
    -    ret
    +    return ret;
     }
     
     fn add_adjusted(a: f64, b: f64) -> f64 {
    @@ -113,7 +113,7 @@ fn add_adjusted(a: f64, b: f64) -> f64 {
                 sum.hi = @bitCast(f64, uhii);
             }
         }
    -    sum.hi
    +    return sum.hi;
     }
     
     fn add_and_denorm(a: f64, b: f64, scale: i32) -> f64 {
    @@ -127,7 +127,7 @@ fn add_and_denorm(a: f64, b: f64, scale: i32) -> f64 {
                 sum.hi = @bitCast(f64, uhii);
             }
         }
    -    math.scalbn(sum.hi, scale)
    +    return math.scalbn(sum.hi, scale);
     }
     
     test "math.fma" {
    diff --git a/std/math/frexp.zig b/std/math/frexp.zig
    index a40a6dcc39..e648555e31 100644
    --- a/std/math/frexp.zig
    +++ b/std/math/frexp.zig
    @@ -8,21 +8,21 @@ const math = @import("index.zig");
     const assert = @import("../debug.zig").assert;
     
     fn frexp_result(comptime T: type) -> type {
    -    struct {
    +    return struct {
             significand: T,
             exponent: i32,
    -    }
    +    };
     }
     pub const frexp32_result = frexp_result(f32);
     pub const frexp64_result = frexp_result(f64);
     
     pub fn frexp(x: var) -> frexp_result(@typeOf(x)) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(frexp32, x),
             f64 => @inlineCall(frexp64, x),
             else => @compileError("frexp not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn frexp32(x: f32) -> frexp32_result {
    @@ -59,7 +59,7 @@ fn frexp32(x: f32) -> frexp32_result {
         y &= 0x807FFFFF;
         y |= 0x3F000000;
         result.significand = @bitCast(f32, y);
    -    result
    +    return result;
     }
     
     fn frexp64(x: f64) -> frexp64_result {
    @@ -96,7 +96,7 @@ fn frexp64(x: f64) -> frexp64_result {
         y &= 0x800FFFFFFFFFFFFF;
         y |= 0x3FE0000000000000;
         result.significand = @bitCast(f64, y);
    -    result
    +    return result;
     }
     
     test "math.frexp" {
    diff --git a/std/math/hypot.zig b/std/math/hypot.zig
    index ee8ce0f518..68794e24fe 100644
    --- a/std/math/hypot.zig
    +++ b/std/math/hypot.zig
    @@ -9,11 +9,11 @@ const math = @import("index.zig");
     const assert = @import("../debug.zig").assert;
     
     pub fn hypot(comptime T: type, x: T, y: T) -> T {
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(hypot32, x, y),
             f64 => @inlineCall(hypot64, x, y),
             else => @compileError("hypot not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn hypot32(x: f32, y: f32) -> f32 {
    @@ -48,7 +48,7 @@ fn hypot32(x: f32, y: f32) -> f32 {
             yy *= 0x1.0p-90;
         }
     
    -    z * math.sqrt(f32(f64(x) * x + f64(y) * y))
    +    return z * math.sqrt(f32(f64(x) * x + f64(y) * y));
     }
     
     fn sq(hi: &f64, lo: &f64, x: f64) {
    @@ -109,7 +109,7 @@ fn hypot64(x: f64, y: f64) -> f64 {
         sq(&hx, &lx, x);
         sq(&hy, &ly, y);
     
    -    z * math.sqrt(ly + lx + hy + hx)
    +    return z * math.sqrt(ly + lx + hy + hx);
     }
     
     test "math.hypot" {
    diff --git a/std/math/ilogb.zig b/std/math/ilogb.zig
    index 2a0ea7fc5f..e056ceb097 100644
    --- a/std/math/ilogb.zig
    +++ b/std/math/ilogb.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn ilogb(x: var) -> i32 {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(ilogb32, x),
             f64 => @inlineCall(ilogb64, x),
             else => @compileError("ilogb not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // NOTE: Should these be exposed publically?
    @@ -53,7 +53,7 @@ fn ilogb32(x: f32) -> i32 {
             }
         }
     
    -    e - 0x7F
    +    return e - 0x7F;
     }
     
     fn ilogb64(x: f64) -> i32 {
    @@ -88,7 +88,7 @@ fn ilogb64(x: f64) -> i32 {
             }
         }
     
    -    e - 0x3FF
    +    return e - 0x3FF;
     }
     
     test "math.ilogb" {
    diff --git a/std/math/index.zig b/std/math/index.zig
    index 029f2836b7..1991864f69 100644
    --- a/std/math/index.zig
    +++ b/std/math/index.zig
    @@ -36,7 +36,7 @@ pub const inf = @import("inf.zig").inf;
     
     pub fn approxEq(comptime T: type, x: T, y: T, epsilon: T) -> bool {
         assert(@typeId(T) == TypeId.Float);
    -    fabs(x - y) < epsilon
    +    return fabs(x - y) < epsilon;
     }
     
     // TODO: Hide the following in an internal module.
    @@ -175,7 +175,7 @@ test "math" {
     
     
     pub fn min(x: var, y: var) -> @typeOf(x + y) {
    -    if (x < y) x else y
    +    return if (x < y) x else y;
     }
     
     test "math.min" {
    @@ -183,7 +183,7 @@ test "math.min" {
     }
     
     pub fn max(x: var, y: var) -> @typeOf(x + y) {
    -    if (x > y) x else y
    +    return if (x > y) x else y;
     }
     
     test "math.max" {
    @@ -193,19 +193,19 @@ test "math.max" {
     error Overflow;
     pub fn mul(comptime T: type, a: T, b: T) -> %T {
         var answer: T = undefined;
    -    if (@mulWithOverflow(T, a, b, &answer)) error.Overflow else answer
    +    return if (@mulWithOverflow(T, a, b, &answer)) error.Overflow else answer;
     }
     
     error Overflow;
     pub fn add(comptime T: type, a: T, b: T) -> %T {
         var answer: T = undefined;
    -    if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer
    +    return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer;
     }
     
     error Overflow;
     pub fn sub(comptime T: type, a: T, b: T) -> %T {
         var answer: T = undefined;
    -    if (@subWithOverflow(T, a, b, &answer)) error.Overflow else answer
    +    return if (@subWithOverflow(T, a, b, &answer)) error.Overflow else answer;
     }
     
     pub fn negate(x: var) -> %@typeOf(x) {
    @@ -215,7 +215,7 @@ pub fn negate(x: var) -> %@typeOf(x) {
     error Overflow;
     pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) -> %T {
         var answer: T = undefined;
    -    if (@shlWithOverflow(T, a, shift_amt, &answer)) error.Overflow else answer
    +    return if (@shlWithOverflow(T, a, shift_amt, &answer)) error.Overflow else answer;
     }
     
     /// Shifts left. Overflowed bits are truncated.
    @@ -267,7 +267,7 @@ test "math.shr" {
     }
     
     pub fn Log2Int(comptime T: type) -> type {
    -    @IntType(false, log2(T.bit_count))
    +    return @IntType(false, log2(T.bit_count));
     }
     
     test "math overflow functions" {
    diff --git a/std/math/inf.zig b/std/math/inf.zig
    index ed559c313e..546e6f3af8 100644
    --- a/std/math/inf.zig
    +++ b/std/math/inf.zig
    @@ -2,9 +2,9 @@ const math = @import("index.zig");
     const assert = @import("../debug.zig").assert;
     
     pub fn inf(comptime T: type) -> T {
    -    switch (T) {
    +    return switch (T) {
             f32 => @bitCast(f32, math.inf_u32),
             f64 => @bitCast(f64, math.inf_u64),
             else => @compileError("inf not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
    diff --git a/std/math/isfinite.zig b/std/math/isfinite.zig
    index 6dbf984721..d44d373cf1 100644
    --- a/std/math/isfinite.zig
    +++ b/std/math/isfinite.zig
    @@ -6,11 +6,11 @@ pub fn isFinite(x: var) -> bool {
         switch (T) {
             f32 => {
                 const bits = @bitCast(u32, x);
    -            bits & 0x7FFFFFFF < 0x7F800000
    +            return bits & 0x7FFFFFFF < 0x7F800000;
             },
             f64 => {
                 const bits = @bitCast(u64, x);
    -            bits & (@maxValue(u64) >> 1) < (0x7FF << 52)
    +            return bits & (@maxValue(u64) >> 1) < (0x7FF << 52);
             },
             else => {
                 @compileError("isFinite not implemented for " ++ @typeName(T));
    diff --git a/std/math/isinf.zig b/std/math/isinf.zig
    index b388fabf10..98c90e72a9 100644
    --- a/std/math/isinf.zig
    +++ b/std/math/isinf.zig
    @@ -6,11 +6,11 @@ pub fn isInf(x: var) -> bool {
         switch (T) {
             f32 => {
                 const bits = @bitCast(u32, x);
    -            bits & 0x7FFFFFFF == 0x7F800000
    +            return bits & 0x7FFFFFFF == 0x7F800000;
             },
             f64 => {
                 const bits = @bitCast(u64, x);
    -            bits & (@maxValue(u64) >> 1) == (0x7FF << 52)
    +            return bits & (@maxValue(u64) >> 1) == (0x7FF << 52);
             },
             else => {
                 @compileError("isInf not implemented for " ++ @typeName(T));
    @@ -22,10 +22,10 @@ pub fn isPositiveInf(x: var) -> bool {
         const T = @typeOf(x);
         switch (T) {
             f32 => {
    -            @bitCast(u32, x) == 0x7F800000
    +            return @bitCast(u32, x) == 0x7F800000;
             },
             f64 => {
    -            @bitCast(u64, x) == 0x7FF << 52
    +            return @bitCast(u64, x) == 0x7FF << 52;
             },
             else => {
                 @compileError("isPositiveInf not implemented for " ++ @typeName(T));
    @@ -37,10 +37,10 @@ pub fn isNegativeInf(x: var) -> bool {
         const T = @typeOf(x);
         switch (T) {
             f32 => {
    -            @bitCast(u32, x) == 0xFF800000
    +            return @bitCast(u32, x) == 0xFF800000;
             },
             f64 => {
    -            @bitCast(u64, x) == 0xFFF << 52
    +            return @bitCast(u64, x) == 0xFFF << 52;
             },
             else => {
                 @compileError("isNegativeInf not implemented for " ++ @typeName(T));
    diff --git a/std/math/isnan.zig b/std/math/isnan.zig
    index 8bcb200a6a..e996a72693 100644
    --- a/std/math/isnan.zig
    +++ b/std/math/isnan.zig
    @@ -6,11 +6,11 @@ pub fn isNan(x: var) -> bool {
         switch (T) {
             f32 => {
                 const bits = @bitCast(u32, x);
    -            bits & 0x7FFFFFFF > 0x7F800000
    +            return bits & 0x7FFFFFFF > 0x7F800000;
             },
             f64 => {
                 const bits = @bitCast(u64, x);
    -            (bits & (@maxValue(u64) >> 1)) > (u64(0x7FF) << 52)
    +            return (bits & (@maxValue(u64) >> 1)) > (u64(0x7FF) << 52);
             },
             else => {
                 @compileError("isNan not implemented for " ++ @typeName(T));
    @@ -21,7 +21,7 @@ pub fn isNan(x: var) -> bool {
     // Note: A signalling nan is identical to a standard right now by may have a different bit
     // representation in the future when required.
     pub fn isSignalNan(x: var) -> bool {
    -    isNan(x)
    +    return isNan(x);
     }
     
     test "math.isNan" {
    diff --git a/std/math/isnormal.zig b/std/math/isnormal.zig
    index b212d204de..f815c2680b 100644
    --- a/std/math/isnormal.zig
    +++ b/std/math/isnormal.zig
    @@ -6,11 +6,11 @@ pub fn isNormal(x: var) -> bool {
         switch (T) {
             f32 => {
                 const bits = @bitCast(u32, x);
    -            (bits + 0x00800000) & 0x7FFFFFFF >= 0x01000000
    +            return (bits + 0x00800000) & 0x7FFFFFFF >= 0x01000000;
             },
             f64 => {
                 const bits = @bitCast(u64, x);
    -            (bits + (1 << 52)) & (@maxValue(u64) >> 1) >= (1 << 53)
    +            return (bits + (1 << 52)) & (@maxValue(u64) >> 1) >= (1 << 53);
             },
             else => {
                 @compileError("isNormal not implemented for " ++ @typeName(T));
    diff --git a/std/math/ln.zig b/std/math/ln.zig
    index 7ced6238ef..4ded89d328 100644
    --- a/std/math/ln.zig
    +++ b/std/math/ln.zig
    @@ -14,7 +14,7 @@ pub fn ln(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         switch (@typeId(T)) {
             TypeId.FloatLiteral => {
    -            return @typeOf(1.0)(ln_64(x))
    +            return @typeOf(1.0)(ln_64(x));
             },
             TypeId.Float => {
                 return switch (T) {
    @@ -84,7 +84,7 @@ pub fn ln_32(x_: f32) -> f32 {
         const hfsq = 0.5 * f * f;
         const dk = f32(k);
     
    -    s * (hfsq + R) + dk * ln2_lo - hfsq + f + dk * ln2_hi
    +    return s * (hfsq + R) + dk * ln2_lo - hfsq + f + dk * ln2_hi;
     }
     
     pub fn ln_64(x_: f64) -> f64 {
    @@ -116,7 +116,7 @@ pub fn ln_64(x_: f64) -> f64 {
             // subnormal, scale x
             k -= 54;
             x *= 0x1.0p54;
    -        hx = u32(@bitCast(u64, ix) >> 32)
    +        hx = u32(@bitCast(u64, ix) >> 32);
         }
         else if (hx >= 0x7FF00000) {
             return x;
    @@ -142,7 +142,7 @@ pub fn ln_64(x_: f64) -> f64 {
         const R = t2 + t1;
         const dk = f64(k);
     
    -    s * (hfsq + R) + dk * ln2_lo - hfsq + f + dk * ln2_hi
    +    return s * (hfsq + R) + dk * ln2_lo - hfsq + f + dk * ln2_hi;
     }
     
     test "math.ln" {
    diff --git a/std/math/log.zig b/std/math/log.zig
    index 075cecc890..1ff226f7e5 100644
    --- a/std/math/log.zig
    +++ b/std/math/log.zig
    @@ -29,7 +29,7 @@ pub fn log(comptime T: type, base: T, x: T) -> T {
                     f32 => return f32(math.ln(f64(x)) / math.ln(f64(base))),
                     f64 => return math.ln(x) / math.ln(f64(base)),
                     else => @compileError("log not implemented for " ++ @typeName(T)),
    -            };
    +            }
             },
     
             else => {
    diff --git a/std/math/log10.zig b/std/math/log10.zig
    index 75e1203ad4..73168dec8d 100644
    --- a/std/math/log10.zig
    +++ b/std/math/log10.zig
    @@ -14,7 +14,7 @@ pub fn log10(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         switch (@typeId(T)) {
             TypeId.FloatLiteral => {
    -            return @typeOf(1.0)(log10_64(x))
    +            return @typeOf(1.0)(log10_64(x));
             },
             TypeId.Float => {
                 return switch (T) {
    @@ -90,7 +90,7 @@ pub fn log10_32(x_: f32) -> f32 {
         const lo = f - hi - hfsq + s * (hfsq + R);
         const dk = f32(k);
     
    -    dk * log10_2lo + (lo + hi) * ivln10lo + lo * ivln10hi + hi * ivln10hi + dk * log10_2hi
    +    return dk * log10_2lo + (lo + hi) * ivln10lo + lo * ivln10hi + hi * ivln10hi + dk * log10_2hi;
     }
     
     pub fn log10_64(x_: f64) -> f64 {
    @@ -124,7 +124,7 @@ pub fn log10_64(x_: f64) -> f64 {
             // subnormal, scale x
             k -= 54;
             x *= 0x1.0p54;
    -        hx = u32(@bitCast(u64, x) >> 32)
    +        hx = u32(@bitCast(u64, x) >> 32);
         }
         else if (hx >= 0x7FF00000) {
             return x;
    @@ -167,7 +167,7 @@ pub fn log10_64(x_: f64) -> f64 {
         val_lo += (y - ww) + val_hi;
         val_hi = ww;
     
    -    val_lo + val_hi
    +    return val_lo + val_hi;
     }
     
     test "math.log10" {
    diff --git a/std/math/log1p.zig b/std/math/log1p.zig
    index 803726fdcc..433a7c6192 100644
    --- a/std/math/log1p.zig
    +++ b/std/math/log1p.zig
    @@ -11,11 +11,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn log1p(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(log1p_32, x),
             f64 => @inlineCall(log1p_64, x),
             else => @compileError("log1p not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn log1p_32(x: f32) -> f32 {
    @@ -91,7 +91,7 @@ fn log1p_32(x: f32) -> f32 {
         const hfsq = 0.5 * f * f;
         const dk = f32(k);
     
    -    s * (hfsq + R) + (dk * ln2_lo + c) - hfsq + f + dk * ln2_hi
    +    return s * (hfsq + R) + (dk * ln2_lo + c) - hfsq + f + dk * ln2_hi;
     }
     
     fn log1p_64(x: f64) -> f64 {
    @@ -172,7 +172,7 @@ fn log1p_64(x: f64) -> f64 {
         const R = t2 + t1;
         const dk = f64(k);
     
    -    s * (hfsq + R) + (dk * ln2_lo + c) - hfsq + f + dk * ln2_hi
    +    return s * (hfsq + R) + (dk * ln2_lo + c) - hfsq + f + dk * ln2_hi;
     }
     
     test "math.log1p" {
    diff --git a/std/math/log2.zig b/std/math/log2.zig
    index 2199d6bfa1..1b38a9ecee 100644
    --- a/std/math/log2.zig
    +++ b/std/math/log2.zig
    @@ -14,7 +14,7 @@ pub fn log2(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         switch (@typeId(T)) {
             TypeId.FloatLiteral => {
    -            return @typeOf(1.0)(log2_64(x))
    +            return @typeOf(1.0)(log2_64(x));
             },
             TypeId.Float => {
                 return switch (T) {
    @@ -26,7 +26,7 @@ pub fn log2(x: var) -> @typeOf(x) {
             TypeId.IntLiteral => comptime {
                 var result = 0;
                 var x_shifted = x;
    -            while ({x_shifted >>= 1; x_shifted != 0}) : (result += 1) {}
    +            while (b: {x_shifted >>= 1; break :b x_shifted != 0;}) : (result += 1) {}
                 return result;
             },
             TypeId.Int => {
    @@ -94,7 +94,7 @@ pub fn log2_32(x_: f32) -> f32 {
         u &= 0xFFFFF000;
         hi = @bitCast(f32, u);
         const lo = f - hi - hfsq + s * (hfsq + R);
    -    (lo + hi) * ivln2lo + lo * ivln2hi + hi * ivln2hi + f32(k)
    +    return (lo + hi) * ivln2lo + lo * ivln2hi + hi * ivln2hi + f32(k);
     }
     
     pub fn log2_64(x_: f64) -> f64 {
    @@ -165,7 +165,7 @@ pub fn log2_64(x_: f64) -> f64 {
         val_lo += (y - ww) + val_hi;
         val_hi = ww;
     
    -    val_lo + val_hi
    +    return val_lo + val_hi;
     }
     
     test "math.log2" {
    diff --git a/std/math/modf.zig b/std/math/modf.zig
    index 25eab7f99d..5b78680c51 100644
    --- a/std/math/modf.zig
    +++ b/std/math/modf.zig
    @@ -7,21 +7,21 @@ const math = @import("index.zig");
     const assert = @import("../debug.zig").assert;
     
     fn modf_result(comptime T: type) -> type {
    -    struct {
    +    return struct {
             fpart: T,
             ipart: T,
    -    }
    +    };
     }
     pub const modf32_result = modf_result(f32);
     pub const modf64_result = modf_result(f64);
     
     pub fn modf(x: var) -> modf_result(@typeOf(x)) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(modf32, x),
             f64 => @inlineCall(modf64, x),
             else => @compileError("modf not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn modf32(x: f32) -> modf32_result {
    @@ -66,7 +66,7 @@ fn modf32(x: f32) -> modf32_result {
         const uf = @bitCast(f32, u & ~mask);
         result.ipart = uf;
         result.fpart = x - uf;
    -    result
    +    return result;
     }
     
     fn modf64(x: f64) -> modf64_result {
    @@ -110,7 +110,7 @@ fn modf64(x: f64) -> modf64_result {
         const uf = @bitCast(f64, u & ~mask);
         result.ipart = uf;
         result.fpart = x - uf;
    -    result
    +    return result;
     }
     
     test "math.modf" {
    diff --git a/std/math/nan.zig b/std/math/nan.zig
    index a4899d6b82..e92bb04cb9 100644
    --- a/std/math/nan.zig
    +++ b/std/math/nan.zig
    @@ -1,19 +1,19 @@
     const math = @import("index.zig");
     
     pub fn nan(comptime T: type) -> T {
    -    switch (T) {
    +    return switch (T) {
             f32 => @bitCast(f32, math.nan_u32),
             f64 => @bitCast(f64, math.nan_u64),
             else => @compileError("nan not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // Note: A signalling nan is identical to a standard right now by may have a different bit
     // representation in the future when required.
     pub fn snan(comptime T: type) -> T {
    -    switch (T) {
    +    return switch (T) {
             f32 => @bitCast(f32, math.nan_u32),
             f64 => @bitCast(f64, math.nan_u64),
             else => @compileError("snan not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
    diff --git a/std/math/pow.zig b/std/math/pow.zig
    index 85c3a71d56..55a2cd8c3e 100644
    --- a/std/math/pow.zig
    +++ b/std/math/pow.zig
    @@ -166,12 +166,12 @@ pub fn pow(comptime T: type, x: T, y: T) -> T {
             ae = -ae;
         }
     
    -    math.scalbn(a1, ae)
    +    return math.scalbn(a1, ae);
     }
     
     fn isOddInteger(x: f64) -> bool {
         const r = math.modf(x);
    -    r.fpart == 0.0 and i64(r.ipart) & 1 == 1
    +    return r.fpart == 0.0 and i64(r.ipart) & 1 == 1;
     }
     
     test "math.pow" {
    diff --git a/std/math/round.zig b/std/math/round.zig
    index a16bedc3f5..8e604d1b68 100644
    --- a/std/math/round.zig
    +++ b/std/math/round.zig
    @@ -10,11 +10,11 @@ const math = @import("index.zig");
     
     pub fn round(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(round32, x),
             f64 => @inlineCall(round64, x),
             else => @compileError("round not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn round32(x_: f32) -> f32 {
    @@ -48,9 +48,9 @@ fn round32(x_: f32) -> f32 {
         }
     
         if (u >> 31 != 0) {
    -        -y
    +        return -y;
         } else {
    -        y
    +        return y;
         }
     }
     
    @@ -85,9 +85,9 @@ fn round64(x_: f64) -> f64 {
         }
     
         if (u >> 63 != 0) {
    -        -y
    +        return -y;
         } else {
    -        y
    +        return y;
         }
     }
     
    diff --git a/std/math/scalbn.zig b/std/math/scalbn.zig
    index 6e82194494..0c898a783c 100644
    --- a/std/math/scalbn.zig
    +++ b/std/math/scalbn.zig
    @@ -3,11 +3,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn scalbn(x: var, n: i32) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(scalbn32, x, n),
             f64 => @inlineCall(scalbn64, x, n),
             else => @compileError("scalbn not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn scalbn32(x: f32, n_: i32) -> f32 {
    @@ -37,7 +37,7 @@ fn scalbn32(x: f32, n_: i32) -> f32 {
         }
     
         const u = u32(n +% 0x7F) << 23;
    -    y * @bitCast(f32, u)
    +    return y * @bitCast(f32, u);
     }
     
     fn scalbn64(x: f64, n_: i32) -> f64 {
    @@ -67,7 +67,7 @@ fn scalbn64(x: f64, n_: i32) -> f64 {
         }
     
         const u = u64(n +% 0x3FF) << 52;
    -    y * @bitCast(f64, u)
    +    return y * @bitCast(f64, u);
     }
     
     test "math.scalbn" {
    diff --git a/std/math/signbit.zig b/std/math/signbit.zig
    index 75b087f539..6efbc4db54 100644
    --- a/std/math/signbit.zig
    +++ b/std/math/signbit.zig
    @@ -3,21 +3,21 @@ const assert = @import("../debug.zig").assert;
     
     pub fn signbit(x: var) -> bool {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(signbit32, x),
             f64 => @inlineCall(signbit64, x),
             else => @compileError("signbit not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn signbit32(x: f32) -> bool {
         const bits = @bitCast(u32, x);
    -    bits >> 31 != 0
    +    return bits >> 31 != 0;
     }
     
     fn signbit64(x: f64) -> bool {
         const bits = @bitCast(u64, x);
    -    bits >> 63 != 0
    +    return bits >> 63 != 0;
     }
     
     test "math.signbit" {
    diff --git a/std/math/sin.zig b/std/math/sin.zig
    index 508bf5fae4..6fbbaff291 100644
    --- a/std/math/sin.zig
    +++ b/std/math/sin.zig
    @@ -10,11 +10,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn sin(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(sin32, x),
             f64 => @inlineCall(sin64, x),
             else => @compileError("sin not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // sin polynomial coefficients
    @@ -75,18 +75,18 @@ fn sin32(x_: f32) -> f32 {
         const z = ((x - y * pi4a) - y * pi4b) - y * pi4c;
         const w = z * z;
     
    -    const r = {
    +    const r = r: {
             if (j == 1 or j == 2) {
    -            1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))))
    +            break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))));
             } else {
    -            z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))))
    +            break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))));
             }
         };
     
         if (sign) {
    -        -r
    +        return -r;
         } else {
    -        r
    +        return r;
         }
     }
     
    @@ -127,25 +127,25 @@ fn sin64(x_: f64) -> f64 {
         const z = ((x - y * pi4a) - y * pi4b) - y * pi4c;
         const w = z * z;
     
    -    const r = {
    +    const r = r: {
             if (j == 1 or j == 2) {
    -            1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))))
    +            break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0)))));
             } else {
    -            z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))))
    +            break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0)))));
             }
         };
     
         if (sign) {
    -        -r
    +        return -r;
         } else {
    -        r
    +        return r;
         }
     }
     
     test "math.sin" {
         assert(sin(f32(0.0)) == sin32(0.0));
         assert(sin(f64(0.0)) == sin64(0.0));
    -    assert(comptime {math.sin(f64(2))} == math.sin(f64(2)));
    +    assert(comptime (math.sin(f64(2))) == math.sin(f64(2)));
     }
     
     test "math.sin32" {
    diff --git a/std/math/sinh.zig b/std/math/sinh.zig
    index 32f67a49a8..095dd7ea06 100644
    --- a/std/math/sinh.zig
    +++ b/std/math/sinh.zig
    @@ -11,11 +11,11 @@ const expo2 = @import("expo2.zig").expo2;
     
     pub fn sinh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(sinh32, x),
             f64 => @inlineCall(sinh64, x),
             else => @compileError("sinh not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // sinh(x) = (exp(x) - 1 / exp(x)) / 2
    @@ -49,7 +49,7 @@ fn sinh32(x: f32) -> f32 {
         }
     
         // |x| > log(FLT_MAX) or nan
    -    2 * h * expo2(ax)
    +    return 2 * h * expo2(ax);
     }
     
     fn sinh64(x: f64) -> f64 {
    @@ -83,7 +83,7 @@ fn sinh64(x: f64) -> f64 {
         }
     
         // |x| > log(DBL_MAX) or nan
    -    2 * h * expo2(ax)
    +    return 2 * h * expo2(ax);
     }
     
     test "math.sinh" {
    diff --git a/std/math/sqrt.zig b/std/math/sqrt.zig
    index 86426af3aa..263e616617 100644
    --- a/std/math/sqrt.zig
    +++ b/std/math/sqrt.zig
    @@ -14,7 +14,7 @@ pub fn sqrt(x: var) -> (if (@typeId(@typeOf(x)) == TypeId.Int) @IntType(false, @
         const T = @typeOf(x);
         switch (@typeId(T)) {
             TypeId.FloatLiteral => {
    -            return T(sqrt64(x))
    +            return T(sqrt64(x));
             },
             TypeId.Float => {
                 return switch (T) {
    @@ -64,7 +64,7 @@ fn sqrt32(x: f32) -> f32 {
             // subnormal
             var i: i32 = 0;
             while (ix & 0x00800000 == 0) : (i += 1) {
    -            ix <<= 1
    +            ix <<= 1;
             }
             m -= i - 1;
         }
    @@ -112,7 +112,7 @@ fn sqrt32(x: f32) -> f32 {
     
         ix = (q >> 1) + 0x3f000000;
         ix += m << 23;
    -    @bitCast(f32, ix)
    +    return @bitCast(f32, ix);
     }
     
     // NOTE: The original code is full of implicit signed -> unsigned assumptions and u32 wraparound
    @@ -153,7 +153,7 @@ fn sqrt64(x: f64) -> f64 {
             // subnormal
             var i: u32 = 0;
             while (ix0 & 0x00100000 == 0) : (i += 1) {
    -            ix0 <<= 1
    +            ix0 <<= 1;
             }
             m -= i32(i) - 1;
             ix0 |= ix1 >> u5(32 - i);
    @@ -245,7 +245,7 @@ fn sqrt64(x: f64) -> f64 {
         iix0 = iix0 +% (m << 20);
     
         const uz = (u64(iix0) << 32) | ix1;
    -    @bitCast(f64, uz)
    +    return @bitCast(f64, uz);
     }
     
     test "math.sqrt" {
    diff --git a/std/math/tan.zig b/std/math/tan.zig
    index 6ac30fa667..2a3c46eb6f 100644
    --- a/std/math/tan.zig
    +++ b/std/math/tan.zig
    @@ -10,11 +10,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn tan(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(tan32, x),
             f64 => @inlineCall(tan64, x),
             else => @compileError("tan not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     const Tp0 = -1.30936939181383777646E4;
    @@ -62,11 +62,11 @@ fn tan32(x_: f32) -> f32 {
         const z = ((x - y * pi4a) - y * pi4b) - y * pi4c;
         const w = z * z;
     
    -    var r = {
    +    var r = r: {
             if (w > 1e-14) {
    -            z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4))
    +            break :r z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4));
             } else {
    -            z
    +            break :r z;
             }
         };
     
    @@ -77,7 +77,7 @@ fn tan32(x_: f32) -> f32 {
             r = -r;
         }
     
    -    r
    +    return r;
     }
     
     fn tan64(x_: f64) -> f64 {
    @@ -111,11 +111,11 @@ fn tan64(x_: f64) -> f64 {
         const z = ((x - y * pi4a) - y * pi4b) - y * pi4c;
         const w = z * z;
     
    -    var r = {
    +    var r = r: {
             if (w > 1e-14) {
    -            z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4))
    +            break :r z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4));
             } else {
    -            z
    +            break :r z;
             }
         };
     
    @@ -126,7 +126,7 @@ fn tan64(x_: f64) -> f64 {
             r = -r;
         }
     
    -    r
    +    return r;
     }
     
     test "math.tan" {
    diff --git a/std/math/tanh.zig b/std/math/tanh.zig
    index d9704f458a..c4fe8f2031 100644
    --- a/std/math/tanh.zig
    +++ b/std/math/tanh.zig
    @@ -11,11 +11,11 @@ const expo2 = @import("expo2.zig").expo2;
     
     pub fn tanh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(tanh32, x),
             f64 => @inlineCall(tanh64, x),
             else => @compileError("tanh not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     // tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x))
    @@ -59,9 +59,9 @@ fn tanh32(x: f32) -> f32 {
         }
     
         if (u >> 31 != 0) {
    -        -t
    +        return -t;
         } else {
    -        t
    +        return t;
         }
     }
     
    @@ -104,9 +104,9 @@ fn tanh64(x: f64) -> f64 {
         }
     
         if (u >> 63 != 0) {
    -        -t
    +        return -t;
         } else {
    -        t
    +        return t;
         }
     }
     
    diff --git a/std/math/trunc.zig b/std/math/trunc.zig
    index 937a8155e6..01cb1bb84a 100644
    --- a/std/math/trunc.zig
    +++ b/std/math/trunc.zig
    @@ -9,11 +9,11 @@ const assert = @import("../debug.zig").assert;
     
     pub fn trunc(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
    -    switch (T) {
    +    return switch (T) {
             f32 => @inlineCall(trunc32, x),
             f64 => @inlineCall(trunc64, x),
             else => @compileError("trunc not implemented for " ++ @typeName(T)),
    -    }
    +    };
     }
     
     fn trunc32(x: f32) -> f32 {
    @@ -30,10 +30,10 @@ fn trunc32(x: f32) -> f32 {
     
         m = u32(@maxValue(u32)) >> u5(e);
         if (u & m == 0) {
    -        x
    +        return x;
         } else {
             math.forceEval(x + 0x1p120);
    -        @bitCast(f32, u & ~m)
    +        return @bitCast(f32, u & ~m);
         }
     }
     
    @@ -51,10 +51,10 @@ fn trunc64(x: f64) -> f64 {
     
         m = u64(@maxValue(u64)) >> u6(e);
         if (u & m == 0) {
    -        x
    +        return x;
         } else {
             math.forceEval(x + 0x1p120);
    -        @bitCast(f64, u & ~m)
    +        return @bitCast(f64, u & ~m);
         }
     }
     
    diff --git a/std/mem.zig b/std/mem.zig
    index 4b3516b051..7438eba70a 100644
    --- a/std/mem.zig
    +++ b/std/mem.zig
    @@ -354,11 +354,11 @@ pub fn eql_slice_u8(a: []const u8, b: []const u8) -> bool {
     /// split("   abc def    ghi  ", " ")
     /// Will return slices for "abc", "def", "ghi", null, in that order.
     pub fn split(buffer: []const u8, split_bytes: []const u8) -> SplitIterator {
    -    SplitIterator {
    +    return SplitIterator {
             .index = 0,
             .buffer = buffer,
             .split_bytes = split_bytes,
    -    }
    +    };
     }
     
     test "mem.split" {
    @@ -552,7 +552,7 @@ test "std.mem.reverse" {
         var arr = []i32{ 5, 3, 1, 2, 4 };
         reverse(i32, arr[0..]);
     
    -    assert(eql(i32, arr, []i32{ 4, 2, 1, 3, 5 }))
    +    assert(eql(i32, arr, []i32{ 4, 2, 1, 3, 5 }));
     }
     
     /// In-place rotation of the values in an array ([0 1 2 3] becomes [1 2 3 0] if we rotate by 1)
    @@ -567,5 +567,5 @@ test "std.mem.rotate" {
         var arr = []i32{ 5, 3, 1, 2, 4 };
         rotate(i32, arr[0..], 2);
     
    -    assert(eql(i32, arr, []i32{ 1, 2, 4, 5, 3 }))
    +    assert(eql(i32, arr, []i32{ 1, 2, 4, 5, 3 }));
     }
    diff --git a/std/net.zig b/std/net.zig
    index 3551499c6b..a5fd4d6036 100644
    --- a/std/net.zig
    +++ b/std/net.zig
    @@ -72,7 +72,7 @@ pub fn lookup(hostname: []const u8, out_addrs: []Address) -> %[]Address {
     //		if (family != AF_INET)
     //			buf[cnt++] = (struct address){ .family = AF_INET6, .addr = { [15] = 1 } };
     //
    -        unreachable // TODO
    +        unreachable; // TODO
         }
     
         // TODO
    @@ -84,7 +84,7 @@ pub fn lookup(hostname: []const u8, out_addrs: []Address) -> %[]Address {
         //    else => {},
         //};
     
    -    unreachable // TODO
    +    unreachable; // TODO
     }
     
     pub fn connectAddr(addr: &Address, port: u16) -> %Connection {
    @@ -96,23 +96,23 @@ pub fn connectAddr(addr: &Address, port: u16) -> %Connection {
         }
         const socket_fd = i32(socket_ret);
     
    -    const connect_ret = if (addr.family == linux.AF_INET) {
    +    const connect_ret = if (addr.family == linux.AF_INET) x: {
             var os_addr: linux.sockaddr_in = undefined;
             os_addr.family = addr.family;
             os_addr.port = endian.swapIfLe(u16, port);
             @memcpy((&u8)(&os_addr.addr), &addr.addr[0], 4);
             @memset(&os_addr.zero[0], 0, @sizeOf(@typeOf(os_addr.zero)));
    -        linux.connect(socket_fd, (&linux.sockaddr)(&os_addr), @sizeOf(linux.sockaddr_in))
    -    } else if (addr.family == linux.AF_INET6) {
    +        break :x linux.connect(socket_fd, (&linux.sockaddr)(&os_addr), @sizeOf(linux.sockaddr_in));
    +    } else if (addr.family == linux.AF_INET6) x: {
             var os_addr: linux.sockaddr_in6 = undefined;
             os_addr.family = addr.family;
             os_addr.port = endian.swapIfLe(u16, port);
             os_addr.flowinfo = 0;
             os_addr.scope_id = addr.scope_id;
             @memcpy(&os_addr.addr[0], &addr.addr[0], 16);
    -        linux.connect(socket_fd, (&linux.sockaddr)(&os_addr), @sizeOf(linux.sockaddr_in6))
    +        break :x linux.connect(socket_fd, (&linux.sockaddr)(&os_addr), @sizeOf(linux.sockaddr_in6));
         } else {
    -        unreachable
    +        unreachable;
         };
         const connect_err = linux.getErrno(connect_ret);
         if (connect_err > 0) {
    @@ -165,13 +165,13 @@ pub fn parseIpLiteral(buf: []const u8) -> %Address {
     fn hexDigit(c: u8) -> u8 {
         // TODO use switch with range
         if ('0' <= c and c <= '9') {
    -        c - '0'
    +        return c - '0';
         } else if ('A' <= c and c <= 'Z') {
    -        c - 'A' + 10
    +        return c - 'A' + 10;
         } else if ('a' <= c and c <= 'z') {
    -        c - 'a' + 10
    +        return c - 'a' + 10;
         } else {
    -        @maxValue(u8)
    +        return @maxValue(u8);
         }
     }
     
    diff --git a/std/os/child_process.zig b/std/os/child_process.zig
    index 5aa1578583..e719af65a8 100644
    --- a/std/os/child_process.zig
    +++ b/std/os/child_process.zig
    @@ -115,7 +115,7 @@ pub const ChildProcess = struct {
                 return self.spawnWindows();
             } else {
                 return self.spawnPosix();
    -        };
    +        }
         }
     
         pub fn spawnAndWait(self: &ChildProcess) -> %Term {
    @@ -249,12 +249,12 @@ pub const ChildProcess = struct {
         fn waitUnwrappedWindows(self: &ChildProcess) -> %void {
             const result = os.windowsWaitSingle(self.handle, windows.INFINITE);
     
    -        self.term = (%Term)({
    +        self.term = (%Term)(x: {
                 var exit_code: windows.DWORD = undefined;
                 if (windows.GetExitCodeProcess(self.handle, &exit_code) == 0) {
    -                Term { .Unknown = 0 }
    +                break :x Term { .Unknown = 0 };
                 } else {
    -                Term { .Exited = @bitCast(i32, exit_code)}
    +                break :x Term { .Exited = @bitCast(i32, exit_code)};
                 }
             });
     
    @@ -300,7 +300,7 @@ pub const ChildProcess = struct {
             defer {
                 os.close(self.err_pipe[0]);
                 os.close(self.err_pipe[1]);
    -        };
    +        }
     
             // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after
             // waitpid, so this write is guaranteed to be after the child
    @@ -319,15 +319,15 @@ pub const ChildProcess = struct {
         }
     
         fn statusToTerm(status: i32) -> Term {
    -        return if (posix.WIFEXITED(status)) {
    +        return if (posix.WIFEXITED(status))
                 Term { .Exited = posix.WEXITSTATUS(status) }
    -        } else if (posix.WIFSIGNALED(status)) {
    +        else if (posix.WIFSIGNALED(status))
                 Term { .Signal = posix.WTERMSIG(status) }
    -        } else if (posix.WIFSTOPPED(status)) {
    +        else if (posix.WIFSTOPPED(status))
                 Term { .Stopped = posix.WSTOPSIG(status) }
    -        } else {
    +        else
                 Term { .Unknown = status }
    -        };
    +        ;
         }
     
         fn spawnPosix(self: &ChildProcess) -> %void {
    @@ -344,22 +344,22 @@ pub const ChildProcess = struct {
             %defer if (self.stderr_behavior == StdIo.Pipe) { destroyPipe(stderr_pipe); };
     
             const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
    -        const dev_null_fd = if (any_ignore) {
    +        const dev_null_fd = if (any_ignore)
                 %return os.posixOpen("/dev/null", posix.O_RDWR, 0, null)
    -        } else {
    +        else
                 undefined
    -        };
    -        defer { if (any_ignore) os.close(dev_null_fd); };
    +        ;
    +        defer { if (any_ignore) os.close(dev_null_fd); }
     
             var env_map_owned: BufMap = undefined;
             var we_own_env_map: bool = undefined;
    -        const env_map = if (self.env_map) |env_map| {
    +        const env_map = if (self.env_map) |env_map| x: {
                 we_own_env_map = false;
    -            env_map
    -        } else {
    +            break :x env_map;
    +        } else x: {
                 we_own_env_map = true;
                 env_map_owned = %return os.getEnvMap(self.allocator);
    -            &env_map_owned
    +            break :x &env_map_owned;
             };
             defer { if (we_own_env_map) env_map_owned.deinit(); }
     
    @@ -450,13 +450,13 @@ pub const ChildProcess = struct {
                 self.stdout_behavior == StdIo.Ignore or
                 self.stderr_behavior == StdIo.Ignore);
     
    -        const nul_handle = if (any_ignore) {
    +        const nul_handle = if (any_ignore)
                 %return os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ,
                     windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null)
    -        } else {
    +        else
                 undefined
    -        };
    -        defer { if (any_ignore) os.close(nul_handle); };
    +        ;
    +        defer { if (any_ignore) os.close(nul_handle); }
             if (any_ignore) {
                 %return windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0);
             }
    @@ -542,30 +542,30 @@ pub const ChildProcess = struct {
             };
             var piProcInfo: windows.PROCESS_INFORMATION = undefined;
     
    -        const cwd_slice = if (self.cwd) |cwd| {
    +        const cwd_slice = if (self.cwd) |cwd|
                 %return cstr.addNullByte(self.allocator, cwd)
    -        } else {
    +        else
                 null
    -        };
    +        ;
             defer if (cwd_slice) |cwd| self.allocator.free(cwd);
             const cwd_ptr = if (cwd_slice) |cwd| cwd.ptr else null;
     
    -        const maybe_envp_buf = if (self.env_map) |env_map| {
    +        const maybe_envp_buf = if (self.env_map) |env_map|
                 %return os.createWindowsEnvBlock(self.allocator, env_map)
    -        } else {
    +        else
                 null
    -        };
    +        ;
             defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
             const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
     
             // the cwd set in ChildProcess is in effect when choosing the executable path
             // to match posix semantics
    -        const app_name = if (self.cwd) |cwd| {
    +        const app_name = if (self.cwd) |cwd| x: {
                 const resolved = %return os.path.resolve(self.allocator, cwd, self.argv[0]);
                 defer self.allocator.free(resolved);
    -            %return cstr.addNullByte(self.allocator, resolved)
    -        } else {
    -            %return cstr.addNullByte(self.allocator, self.argv[0])
    +            break :x %return cstr.addNullByte(self.allocator, resolved);
    +        } else x: {
    +            break :x %return cstr.addNullByte(self.allocator, self.argv[0]);
             };
             defer self.allocator.free(app_name);
     
    @@ -741,7 +741,7 @@ fn makePipe() -> %[2]i32 {
             return switch (err) {
                 posix.EMFILE, posix.ENFILE => error.SystemResources,
                 else => os.unexpectedErrorPosix(err),
    -        }
    +        };
         }
         return fds;
     }
    @@ -800,10 +800,10 @@ fn handleTerm(pid: i32, status: i32) {
         }
     }
     
    -const sigchld_set = {
    +const sigchld_set = x: {
         var signal_set = posix.empty_sigset;
         posix.sigaddset(&signal_set, posix.SIGCHLD);
    -    signal_set
    +    break :x signal_set;
     };
     
     fn block_SIGCHLD() {
    diff --git a/std/os/darwin.zig b/std/os/darwin.zig
    index 9d80c64006..f4166c2151 100644
    --- a/std/os/darwin.zig
    +++ b/std/os/darwin.zig
    @@ -97,63 +97,63 @@ pub const SIGINFO   = 29; /// information request
     pub const SIGUSR1   = 30; /// user defined signal 1
     pub const SIGUSR2   = 31; /// user defined signal 2
     
    -fn wstatus(x: i32) -> i32 { x & 0o177 }
    +fn wstatus(x: i32) -> i32 { return x & 0o177; }
     const wstopped = 0o177;
    -pub fn WEXITSTATUS(x: i32) -> i32 { x >> 8 }
    -pub fn WTERMSIG(x: i32) -> i32 { wstatus(x) }
    -pub fn WSTOPSIG(x: i32) -> i32 { x >> 8 }
    -pub fn WIFEXITED(x: i32) -> bool { wstatus(x) == 0 }
    -pub fn WIFSTOPPED(x: i32) -> bool { wstatus(x) == wstopped and WSTOPSIG(x) != 0x13 }
    -pub fn WIFSIGNALED(x: i32) -> bool { wstatus(x) != wstopped and wstatus(x) != 0 }
    +pub fn WEXITSTATUS(x: i32) -> i32 { return x >> 8; }
    +pub fn WTERMSIG(x: i32) -> i32 { return wstatus(x); }
    +pub fn WSTOPSIG(x: i32) -> i32 { return x >> 8; }
    +pub fn WIFEXITED(x: i32) -> bool { return wstatus(x) == 0; }
    +pub fn WIFSTOPPED(x: i32) -> bool { return wstatus(x) == wstopped and WSTOPSIG(x) != 0x13; }
    +pub fn WIFSIGNALED(x: i32) -> bool { return wstatus(x) != wstopped and wstatus(x) != 0; }
     
     /// Get the errno from a syscall return value, or 0 for no error.
     pub fn getErrno(r: usize) -> usize {
         const signed_r = @bitCast(isize, r);
    -    if (signed_r > -4096 and signed_r < 0) usize(-signed_r) else 0
    +    return if (signed_r > -4096 and signed_r < 0) usize(-signed_r) else 0;
     }
     
     pub fn close(fd: i32) -> usize {
    -    errnoWrap(c.close(fd))
    +    return errnoWrap(c.close(fd));
     }
     
     pub fn abort() -> noreturn {
    -    c.abort()
    +    return c.abort();
     }
     
     pub fn exit(code: i32) -> noreturn {
    -    c.exit(code)
    +    return c.exit(code);
     }
     
     pub fn isatty(fd: i32) -> bool {
    -    c.isatty(fd) != 0
    +    return c.isatty(fd) != 0;
     }
     
     pub fn fstat(fd: i32, buf: &c.Stat) -> usize {
    -    errnoWrap(c.@"fstat$INODE64"(fd, buf))
    +    return errnoWrap(c.@"fstat$INODE64"(fd, buf));
     }
     
     pub fn lseek(fd: i32, offset: isize, whence: c_int) -> usize {
    -    errnoWrap(c.lseek(fd, offset, whence))
    +    return errnoWrap(c.lseek(fd, offset, whence));
     }
     
     pub fn open(path: &const u8, flags: u32, mode: usize) -> usize {
    -    errnoWrap(c.open(path, @bitCast(c_int, flags), mode))
    +    return errnoWrap(c.open(path, @bitCast(c_int, flags), mode));
     }
     
     pub fn raise(sig: i32) -> usize {
    -    errnoWrap(c.raise(sig))
    +    return errnoWrap(c.raise(sig));
     }
     
     pub fn read(fd: i32, buf: &u8, nbyte: usize) -> usize {
    -    errnoWrap(c.read(fd, @ptrCast(&c_void, buf), nbyte))
    +    return errnoWrap(c.read(fd, @ptrCast(&c_void, buf), nbyte));
     }
     
     pub fn stat(noalias path: &const u8, noalias buf: &stat) -> usize {
    -    errnoWrap(c.stat(path, buf))
    +    return errnoWrap(c.stat(path, buf));
     }
     
     pub fn write(fd: i32, buf: &const u8, nbyte: usize) -> usize {
    -    errnoWrap(c.write(fd, @ptrCast(&const c_void, buf), nbyte))
    +    return errnoWrap(c.write(fd, @ptrCast(&const c_void, buf), nbyte));
     }
     
     pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32,
    @@ -166,79 +166,79 @@ pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32,
     }
     
     pub fn munmap(address: &u8, length: usize) -> usize {
    -    errnoWrap(c.munmap(@ptrCast(&c_void, address), length))
    +    return errnoWrap(c.munmap(@ptrCast(&c_void, address), length));
     }
     
     pub fn unlink(path: &const u8) -> usize {
    -    errnoWrap(c.unlink(path))
    +    return errnoWrap(c.unlink(path));
     }
     
     pub fn getcwd(buf: &u8, size: usize) -> usize {
    -    if (c.getcwd(buf, size) == null) @bitCast(usize, -isize(*c._errno())) else 0
    +    return if (c.getcwd(buf, size) == null) @bitCast(usize, -isize(*c._errno())) else 0;
     }
     
     pub fn waitpid(pid: i32, status: &i32, options: u32) -> usize {
         comptime assert(i32.bit_count == c_int.bit_count);
    -    errnoWrap(c.waitpid(pid, @ptrCast(&c_int, status), @bitCast(c_int, options)))
    +    return errnoWrap(c.waitpid(pid, @ptrCast(&c_int, status), @bitCast(c_int, options)));
     }
     
     pub fn fork() -> usize {
    -    errnoWrap(c.fork())
    +    return errnoWrap(c.fork());
     }
     
     pub fn pipe(fds: &[2]i32) -> usize {
         comptime assert(i32.bit_count == c_int.bit_count);
    -    errnoWrap(c.pipe(@ptrCast(&c_int, fds)))
    +    return errnoWrap(c.pipe(@ptrCast(&c_int, fds)));
     }
     
     pub fn mkdir(path: &const u8, mode: u32) -> usize {
    -    errnoWrap(c.mkdir(path, mode))
    +    return errnoWrap(c.mkdir(path, mode));
     }
     
     pub fn symlink(existing: &const u8, new: &const u8) -> usize {
    -    errnoWrap(c.symlink(existing, new))
    +    return errnoWrap(c.symlink(existing, new));
     }
     
     pub fn rename(old: &const u8, new: &const u8) -> usize {
    -    errnoWrap(c.rename(old, new))
    +    return errnoWrap(c.rename(old, new));
     }
     
     pub fn chdir(path: &const u8) -> usize {
    -    errnoWrap(c.chdir(path))
    +    return errnoWrap(c.chdir(path));
     }
     
     pub fn execve(path: &const u8, argv: &const ?&const u8, envp: &const ?&const u8)
         -> usize
     {
    -    errnoWrap(c.execve(path, argv, envp))
    +    return errnoWrap(c.execve(path, argv, envp));
     }
     
     pub fn dup2(old: i32, new: i32) -> usize {
    -    errnoWrap(c.dup2(old, new))
    +    return errnoWrap(c.dup2(old, new));
     }
     
     pub fn readlink(noalias path: &const u8, noalias buf_ptr: &u8, buf_len: usize) -> usize {
    -    errnoWrap(c.readlink(path, buf_ptr, buf_len))
    +    return errnoWrap(c.readlink(path, buf_ptr, buf_len));
     }
     
     pub fn nanosleep(req: &const timespec, rem: ?×pec) -> usize {
    -    errnoWrap(c.nanosleep(req, rem))
    +    return errnoWrap(c.nanosleep(req, rem));
     }
     
     pub fn realpath(noalias filename: &const u8, noalias resolved_name: &u8) -> usize {
    -    if (c.realpath(filename, resolved_name) == null) @bitCast(usize, -isize(*c._errno())) else 0
    +    return if (c.realpath(filename, resolved_name) == null) @bitCast(usize, -isize(*c._errno())) else 0;
     }
     
     pub fn setreuid(ruid: u32, euid: u32) -> usize {
    -    errnoWrap(c.setreuid(ruid, euid))
    +    return errnoWrap(c.setreuid(ruid, euid));
     }
     
     pub fn setregid(rgid: u32, egid: u32) -> usize {
    -    errnoWrap(c.setregid(rgid, egid))
    +    return errnoWrap(c.setregid(rgid, egid));
     }
     
     pub fn sigprocmask(flags: u32, noalias set: &const sigset_t, noalias oldset: ?&sigset_t) -> usize {
    -    errnoWrap(c.sigprocmask(@bitCast(c_int, flags), set, oldset))
    +    return errnoWrap(c.sigprocmask(@bitCast(c_int, flags), set, oldset));
     }
     
     pub fn sigaction(sig: u5, noalias act: &const Sigaction, noalias oact: ?&Sigaction) -> usize {
    @@ -285,9 +285,5 @@ pub fn sigaddset(set: &sigset_t, signo: u5) {
     /// that the kernel represents it to libc. Errno was a mistake, let's make
     /// it go away forever.
     fn errnoWrap(value: isize) -> usize {
    -    @bitCast(usize, if (value == -1) {
    -        -isize(*c._errno())
    -    } else {
    -        value
    -    })
    +    return @bitCast(usize, if (value == -1) -isize(*c._errno()) else value);
     }
    diff --git a/std/os/index.zig b/std/os/index.zig
    index 3eba15ef8a..09109c3242 100644
    --- a/std/os/index.zig
    +++ b/std/os/index.zig
    @@ -84,7 +84,7 @@ pub fn getRandomBytes(buf: []u8) -> %void {
                         posix.EFAULT => unreachable,
                         posix.EINTR  => continue,
                         else         => unexpectedErrorPosix(err),
    -                }
    +                };
                 }
                 return;
             },
    @@ -151,18 +151,17 @@ pub coldcc fn exit(status: i32) -> noreturn {
         }
         switch (builtin.os) {
             Os.linux, Os.darwin, Os.macosx, Os.ios => {
    -            posix.exit(status)
    +            posix.exit(status);
             },
             Os.windows => {
                 // Map a possibly negative status code to a non-negative status for the systems default
                 // integer width.
    -            const p_status = if (@sizeOf(c_uint) < @sizeOf(u32)) {
    +            const p_status = if (@sizeOf(c_uint) < @sizeOf(u32))
                     @truncate(c_uint, @bitCast(u32, status))
    -            } else {
    -                c_uint(@bitCast(u32, status))
    -            };
    +            else
    +                c_uint(@bitCast(u32, status));
     
    -            windows.ExitProcess(p_status)
    +            windows.ExitProcess(p_status);
             },
             else => @compileError("Unsupported OS"),
         }
    @@ -289,7 +288,7 @@ pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize, allocator: ?&Al
                     posix.EPERM => error.AccessDenied,
                     posix.EEXIST => error.PathAlreadyExists,
                     else => unexpectedErrorPosix(err),
    -            }
    +            };
             }
             return i32(result);
         }
    @@ -680,7 +679,7 @@ pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) -> %void
                 windows.ERROR.ACCESS_DENIED => error.AccessDenied,
                 windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,
                 else => unexpectedErrorWindows(err),
    -        }
    +        };
         }
     }
     
    @@ -1006,7 +1005,7 @@ pub const Dir = struct {
                                     continue;
                                 },
                                 else => return unexpectedErrorPosix(err),
    -                        };
    +                        }
                         }
                         if (result == 0)
                             return null;
    diff --git a/std/os/linux.zig b/std/os/linux.zig
    index 4ba4db603f..f9baa43098 100644
    --- a/std/os/linux.zig
    +++ b/std/os/linux.zig
    @@ -367,14 +367,14 @@ pub const TFD_CLOEXEC = O_CLOEXEC;
     pub const TFD_TIMER_ABSTIME = 1;
     pub const TFD_TIMER_CANCEL_ON_SET = (1 << 1);
     
    -fn unsigned(s: i32) -> u32 { @bitCast(u32, s) }
    -fn signed(s: u32) -> i32 { @bitCast(i32, s) }
    -pub fn WEXITSTATUS(s: i32) -> i32 { signed((unsigned(s) & 0xff00) >> 8) }
    -pub fn WTERMSIG(s: i32) -> i32 { signed(unsigned(s) & 0x7f) }
    -pub fn WSTOPSIG(s: i32) -> i32 { WEXITSTATUS(s) }
    -pub fn WIFEXITED(s: i32) -> bool { WTERMSIG(s) == 0 }
    -pub fn WIFSTOPPED(s: i32) -> bool { (u16)(((unsigned(s)&0xffff)*%0x10001)>>8) > 0x7f00 }
    -pub fn WIFSIGNALED(s: i32) -> bool { (unsigned(s)&0xffff)-%1 < 0xff }
    +fn unsigned(s: i32) -> u32 { return @bitCast(u32, s); }
    +fn signed(s: u32) -> i32 { return @bitCast(i32, s); }
    +pub fn WEXITSTATUS(s: i32) -> i32 { return signed((unsigned(s) & 0xff00) >> 8); }
    +pub fn WTERMSIG(s: i32) -> i32 { return signed(unsigned(s) & 0x7f); }
    +pub fn WSTOPSIG(s: i32) -> i32 { return WEXITSTATUS(s); }
    +pub fn WIFEXITED(s: i32) -> bool { return WTERMSIG(s) == 0; }
    +pub fn WIFSTOPPED(s: i32) -> bool { return (u16)(((unsigned(s)&0xffff)*%0x10001)>>8) > 0x7f00; }
    +pub fn WIFSIGNALED(s: i32) -> bool { return (unsigned(s)&0xffff)-%1 < 0xff; }
     
     
     pub const winsize = extern struct {
    @@ -387,31 +387,31 @@ pub const winsize = extern struct {
     /// Get the errno from a syscall return value, or 0 for no error.
     pub fn getErrno(r: usize) -> usize {
         const signed_r = @bitCast(isize, r);
    -    if (signed_r > -4096 and signed_r < 0) usize(-signed_r) else 0
    +    return if (signed_r > -4096 and signed_r < 0) usize(-signed_r) else 0;
     }
     
     pub fn dup2(old: i32, new: i32) -> usize {
    -    arch.syscall2(arch.SYS_dup2, usize(old), usize(new))
    +    return arch.syscall2(arch.SYS_dup2, usize(old), usize(new));
     }
     
     pub fn chdir(path: &const u8) -> usize {
    -    arch.syscall1(arch.SYS_chdir, @ptrToInt(path))
    +    return arch.syscall1(arch.SYS_chdir, @ptrToInt(path));
     }
     
     pub fn execve(path: &const u8, argv: &const ?&const u8, envp: &const ?&const u8) -> usize {
    -    arch.syscall3(arch.SYS_execve, @ptrToInt(path), @ptrToInt(argv), @ptrToInt(envp))
    +    return arch.syscall3(arch.SYS_execve, @ptrToInt(path), @ptrToInt(argv), @ptrToInt(envp));
     }
     
     pub fn fork() -> usize {
    -    arch.syscall0(arch.SYS_fork)
    +    return arch.syscall0(arch.SYS_fork);
     }
     
     pub fn getcwd(buf: &u8, size: usize) -> usize {
    -    arch.syscall2(arch.SYS_getcwd, @ptrToInt(buf), size)
    +    return arch.syscall2(arch.SYS_getcwd, @ptrToInt(buf), size);
     }
     
     pub fn getdents(fd: i32, dirp: &u8, count: usize) -> usize {
    -    arch.syscall3(arch.SYS_getdents, usize(fd), @ptrToInt(dirp), count)
    +    return arch.syscall3(arch.SYS_getdents, usize(fd), @ptrToInt(dirp), count);
     }
     
     pub fn isatty(fd: i32) -> bool {
    @@ -420,123 +420,123 @@ pub fn isatty(fd: i32) -> bool {
     }
     
     pub fn readlink(noalias path: &const u8, noalias buf_ptr: &u8, buf_len: usize) -> usize {
    -    arch.syscall3(arch.SYS_readlink, @ptrToInt(path), @ptrToInt(buf_ptr), buf_len)
    +    return arch.syscall3(arch.SYS_readlink, @ptrToInt(path), @ptrToInt(buf_ptr), buf_len);
     }
     
     pub fn mkdir(path: &const u8, mode: u32) -> usize {
    -    arch.syscall2(arch.SYS_mkdir, @ptrToInt(path), mode)
    +    return arch.syscall2(arch.SYS_mkdir, @ptrToInt(path), mode);
     }
     
     pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: isize)
         -> usize
     {
    -    arch.syscall6(arch.SYS_mmap, @ptrToInt(address), length, prot, flags, usize(fd),
    -        @bitCast(usize, offset))
    +    return arch.syscall6(arch.SYS_mmap, @ptrToInt(address), length, prot, flags, usize(fd),
    +        @bitCast(usize, offset));
     }
     
     pub fn munmap(address: &u8, length: usize) -> usize {
    -    arch.syscall2(arch.SYS_munmap, @ptrToInt(address), length)
    +    return arch.syscall2(arch.SYS_munmap, @ptrToInt(address), length);
     }
     
     pub fn read(fd: i32, buf: &u8, count: usize) -> usize {
    -    arch.syscall3(arch.SYS_read, usize(fd), @ptrToInt(buf), count)
    +    return arch.syscall3(arch.SYS_read, usize(fd), @ptrToInt(buf), count);
     }
     
     pub fn rmdir(path: &const u8) -> usize {
    -    arch.syscall1(arch.SYS_rmdir, @ptrToInt(path))
    +    return arch.syscall1(arch.SYS_rmdir, @ptrToInt(path));
     }
     
     pub fn symlink(existing: &const u8, new: &const u8) -> usize {
    -    arch.syscall2(arch.SYS_symlink, @ptrToInt(existing), @ptrToInt(new))
    +    return arch.syscall2(arch.SYS_symlink, @ptrToInt(existing), @ptrToInt(new));
     }
     
     pub fn pread(fd: i32, buf: &u8, count: usize, offset: usize) -> usize {
    -    arch.syscall4(arch.SYS_pread, usize(fd), @ptrToInt(buf), count, offset)
    +    return arch.syscall4(arch.SYS_pread, usize(fd), @ptrToInt(buf), count, offset);
     }
     
     pub fn pipe(fd: &[2]i32) -> usize {
    -    pipe2(fd, 0)
    +    return pipe2(fd, 0);
     }
     
     pub fn pipe2(fd: &[2]i32, flags: usize) -> usize {
    -    arch.syscall2(arch.SYS_pipe2, @ptrToInt(fd), flags)
    +    return arch.syscall2(arch.SYS_pipe2, @ptrToInt(fd), flags);
     }
     
     pub fn write(fd: i32, buf: &const u8, count: usize) -> usize {
    -    arch.syscall3(arch.SYS_write, usize(fd), @ptrToInt(buf), count)
    +    return arch.syscall3(arch.SYS_write, usize(fd), @ptrToInt(buf), count);
     }
     
     pub fn pwrite(fd: i32, buf: &const u8, count: usize, offset: usize) -> usize {
    -    arch.syscall4(arch.SYS_pwrite, usize(fd), @ptrToInt(buf), count, offset)
    +    return arch.syscall4(arch.SYS_pwrite, usize(fd), @ptrToInt(buf), count, offset);
     }
     
     pub fn rename(old: &const u8, new: &const u8) -> usize {
    -    arch.syscall2(arch.SYS_rename, @ptrToInt(old), @ptrToInt(new))
    +    return arch.syscall2(arch.SYS_rename, @ptrToInt(old), @ptrToInt(new));
     }
     
     pub fn open(path: &const u8, flags: u32, perm: usize) -> usize {
    -    arch.syscall3(arch.SYS_open, @ptrToInt(path), flags, perm)
    +    return arch.syscall3(arch.SYS_open, @ptrToInt(path), flags, perm);
     }
     
     pub fn create(path: &const u8, perm: usize) -> usize {
    -    arch.syscall2(arch.SYS_creat, @ptrToInt(path), perm)
    +    return arch.syscall2(arch.SYS_creat, @ptrToInt(path), perm);
     }
     
     pub fn openat(dirfd: i32, path: &const u8, flags: usize, mode: usize) -> usize {
    -    arch.syscall4(arch.SYS_openat, usize(dirfd), @ptrToInt(path), flags, mode)
    +    return arch.syscall4(arch.SYS_openat, usize(dirfd), @ptrToInt(path), flags, mode);
     }
     
     pub fn close(fd: i32) -> usize {
    -    arch.syscall1(arch.SYS_close, usize(fd))
    +    return arch.syscall1(arch.SYS_close, usize(fd));
     }
     
     pub fn lseek(fd: i32, offset: isize, ref_pos: usize) -> usize {
    -    arch.syscall3(arch.SYS_lseek, usize(fd), @bitCast(usize, offset), ref_pos)
    +    return arch.syscall3(arch.SYS_lseek, usize(fd), @bitCast(usize, offset), ref_pos);
     }
     
     pub fn exit(status: i32) -> noreturn {
         _ = arch.syscall1(arch.SYS_exit, @bitCast(usize, isize(status)));
    -    unreachable
    +    unreachable;
     }
     
     pub fn getrandom(buf: &u8, count: usize, flags: u32) -> usize {
    -    arch.syscall3(arch.SYS_getrandom, @ptrToInt(buf), count, usize(flags))
    +    return arch.syscall3(arch.SYS_getrandom, @ptrToInt(buf), count, usize(flags));
     }
     
     pub fn kill(pid: i32, sig: i32) -> usize {
    -    arch.syscall2(arch.SYS_kill, @bitCast(usize, isize(pid)), usize(sig))
    +    return arch.syscall2(arch.SYS_kill, @bitCast(usize, isize(pid)), usize(sig));
     }
     
     pub fn unlink(path: &const u8) -> usize {
    -    arch.syscall1(arch.SYS_unlink, @ptrToInt(path))
    +    return arch.syscall1(arch.SYS_unlink, @ptrToInt(path));
     }
     
     pub fn waitpid(pid: i32, status: &i32, options: i32) -> usize {
    -    arch.syscall4(arch.SYS_wait4, @bitCast(usize, isize(pid)), @ptrToInt(status), @bitCast(usize, isize(options)), 0)
    +    return arch.syscall4(arch.SYS_wait4, @bitCast(usize, isize(pid)), @ptrToInt(status), @bitCast(usize, isize(options)), 0);
     }
     
     pub fn nanosleep(req: &const timespec, rem: ?×pec) -> usize {
    -    arch.syscall2(arch.SYS_nanosleep, @ptrToInt(req), @ptrToInt(rem))
    +    return arch.syscall2(arch.SYS_nanosleep, @ptrToInt(req), @ptrToInt(rem));
     }
     
     pub fn setuid(uid: u32) -> usize {
    -    arch.syscall1(arch.SYS_setuid, uid)
    +    return arch.syscall1(arch.SYS_setuid, uid);
     }
     
     pub fn setgid(gid: u32) -> usize {
    -    arch.syscall1(arch.SYS_setgid, gid)
    +    return arch.syscall1(arch.SYS_setgid, gid);
     }
     
     pub fn setreuid(ruid: u32, euid: u32) -> usize {
    -    arch.syscall2(arch.SYS_setreuid, ruid, euid)
    +    return arch.syscall2(arch.SYS_setreuid, ruid, euid);
     }
     
     pub fn setregid(rgid: u32, egid: u32) -> usize {
    -    arch.syscall2(arch.SYS_setregid, rgid, egid)
    +    return arch.syscall2(arch.SYS_setregid, rgid, egid);
     }
     
     pub fn sigprocmask(flags: u32, noalias set: &const sigset_t, noalias oldset: ?&sigset_t) -> usize {
    -    arch.syscall4(arch.SYS_rt_sigprocmask, flags, @ptrToInt(set), @ptrToInt(oldset), NSIG/8)
    +    return arch.syscall4(arch.SYS_rt_sigprocmask, flags, @ptrToInt(set), @ptrToInt(oldset), NSIG/8);
     }
     
     pub fn sigaction(sig: u6, noalias act: &const Sigaction, noalias oact: ?&Sigaction) -> usize {
    @@ -652,69 +652,69 @@ pub const iovec = extern struct {
     };
     
     pub fn getsockname(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t) -> usize {
    -    arch.syscall3(arch.SYS_getsockname, usize(fd), @ptrToInt(addr), @ptrToInt(len))
    +    return arch.syscall3(arch.SYS_getsockname, usize(fd), @ptrToInt(addr), @ptrToInt(len));
     }
     
     pub fn getpeername(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t) -> usize {
    -    arch.syscall3(arch.SYS_getpeername, usize(fd), @ptrToInt(addr), @ptrToInt(len))
    +    return arch.syscall3(arch.SYS_getpeername, usize(fd), @ptrToInt(addr), @ptrToInt(len));
     }
     
     pub fn socket(domain: i32, socket_type: i32, protocol: i32) -> usize {
    -    arch.syscall3(arch.SYS_socket, usize(domain), usize(socket_type), usize(protocol))
    +    return arch.syscall3(arch.SYS_socket, usize(domain), usize(socket_type), usize(protocol));
     }
     
     pub fn setsockopt(fd: i32, level: i32, optname: i32, optval: &const u8, optlen: socklen_t) -> usize {
    -    arch.syscall5(arch.SYS_setsockopt, usize(fd), usize(level), usize(optname), usize(optval), @ptrToInt(optlen))
    +    return arch.syscall5(arch.SYS_setsockopt, usize(fd), usize(level), usize(optname), usize(optval), @ptrToInt(optlen));
     }
     
     pub fn getsockopt(fd: i32, level: i32, optname: i32, noalias optval: &u8, noalias optlen: &socklen_t) -> usize {
    -    arch.syscall5(arch.SYS_getsockopt, usize(fd), usize(level), usize(optname), @ptrToInt(optval), @ptrToInt(optlen))
    +    return arch.syscall5(arch.SYS_getsockopt, usize(fd), usize(level), usize(optname), @ptrToInt(optval), @ptrToInt(optlen));
     }
     
     pub fn sendmsg(fd: i32, msg: &const arch.msghdr, flags: u32) -> usize {
    -    arch.syscall3(arch.SYS_sendmsg, usize(fd), @ptrToInt(msg), flags)
    +    return arch.syscall3(arch.SYS_sendmsg, usize(fd), @ptrToInt(msg), flags);
     }
     
     pub fn connect(fd: i32, addr: &const sockaddr, len: socklen_t) -> usize {
    -    arch.syscall3(arch.SYS_connect, usize(fd), @ptrToInt(addr), usize(len))
    +    return arch.syscall3(arch.SYS_connect, usize(fd), @ptrToInt(addr), usize(len));
     }
     
     pub fn recvmsg(fd: i32, msg: &arch.msghdr, flags: u32) -> usize {
    -    arch.syscall3(arch.SYS_recvmsg, usize(fd), @ptrToInt(msg), flags)
    +    return arch.syscall3(arch.SYS_recvmsg, usize(fd), @ptrToInt(msg), flags);
     }
     
     pub fn recvfrom(fd: i32, noalias buf: &u8, len: usize, flags: u32,
         noalias addr: ?&sockaddr, noalias alen: ?&socklen_t) -> usize
     {
    -    arch.syscall6(arch.SYS_recvfrom, usize(fd), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen))
    +    return arch.syscall6(arch.SYS_recvfrom, usize(fd), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen));
     }
     
     pub fn shutdown(fd: i32, how: i32) -> usize {
    -    arch.syscall2(arch.SYS_shutdown, usize(fd), usize(how))
    +    return arch.syscall2(arch.SYS_shutdown, usize(fd), usize(how));
     }
     
     pub fn bind(fd: i32, addr: &const sockaddr, len: socklen_t) -> usize {
    -    arch.syscall3(arch.SYS_bind, usize(fd), @ptrToInt(addr), usize(len))
    +    return arch.syscall3(arch.SYS_bind, usize(fd), @ptrToInt(addr), usize(len));
     }
     
     pub fn listen(fd: i32, backlog: i32) -> usize {
    -    arch.syscall2(arch.SYS_listen, usize(fd), usize(backlog))
    +    return arch.syscall2(arch.SYS_listen, usize(fd), usize(backlog));
     }
     
     pub fn sendto(fd: i32, buf: &const u8, len: usize, flags: u32, addr: ?&const sockaddr, alen: socklen_t) -> usize {
    -    arch.syscall6(arch.SYS_sendto, usize(fd), @ptrToInt(buf), len, flags, @ptrToInt(addr), usize(alen))
    +    return arch.syscall6(arch.SYS_sendto, usize(fd), @ptrToInt(buf), len, flags, @ptrToInt(addr), usize(alen));
     }
     
     pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) -> usize {
    -    arch.syscall4(arch.SYS_socketpair, usize(domain), usize(socket_type), usize(protocol), @ptrToInt(&fd[0]))
    +    return arch.syscall4(arch.SYS_socketpair, usize(domain), usize(socket_type), usize(protocol), @ptrToInt(&fd[0]));
     }
     
     pub fn accept(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t) -> usize {
    -    accept4(fd, addr, len, 0)
    +    return accept4(fd, addr, len, 0);
     }
     
     pub fn accept4(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t, flags: u32) -> usize {
    -    arch.syscall4(arch.SYS_accept4, usize(fd), @ptrToInt(addr), @ptrToInt(len), flags)
    +    return arch.syscall4(arch.SYS_accept4, usize(fd), @ptrToInt(addr), @ptrToInt(len), flags);
     }
     
     // error NameTooLong;
    @@ -749,7 +749,7 @@ pub const Stat = arch.Stat;
     pub const timespec = arch.timespec;
     
     pub fn fstat(fd: i32, stat_buf: &Stat) -> usize {
    -    arch.syscall2(arch.SYS_fstat, usize(fd), @ptrToInt(stat_buf))
    +    return arch.syscall2(arch.SYS_fstat, usize(fd), @ptrToInt(stat_buf));
     }
     
     pub const epoll_data = u64;
    @@ -760,19 +760,19 @@ pub const epoll_event = extern struct {
     };
     
     pub fn epoll_create() -> usize {
    -    arch.syscall1(arch.SYS_epoll_create, usize(1))
    +    return arch.syscall1(arch.SYS_epoll_create, usize(1));
     }
     
     pub fn epoll_ctl(epoll_fd: i32, op: i32, fd: i32, ev: &epoll_event) -> usize {
    -    arch.syscall4(arch.SYS_epoll_ctl, usize(epoll_fd), usize(op), usize(fd), @ptrToInt(ev))
    +    return arch.syscall4(arch.SYS_epoll_ctl, usize(epoll_fd), usize(op), usize(fd), @ptrToInt(ev));
     }
     
     pub fn epoll_wait(epoll_fd: i32, events: &epoll_event, maxevents: i32, timeout: i32) -> usize {
    -    arch.syscall4(arch.SYS_epoll_wait, usize(epoll_fd), @ptrToInt(events), usize(maxevents), usize(timeout))
    +    return arch.syscall4(arch.SYS_epoll_wait, usize(epoll_fd), @ptrToInt(events), usize(maxevents), usize(timeout));
     }
     
     pub fn timerfd_create(clockid: i32, flags: u32) -> usize {
    -    arch.syscall2(arch.SYS_timerfd_create, usize(clockid), usize(flags))
    +    return arch.syscall2(arch.SYS_timerfd_create, usize(clockid), usize(flags));
     }
     
     pub const itimerspec = extern struct {
    @@ -781,11 +781,11 @@ pub const itimerspec = extern struct {
     };
     
     pub fn timerfd_gettime(fd: i32, curr_value: &itimerspec) -> usize {
    -    arch.syscall2(arch.SYS_timerfd_gettime, usize(fd), @ptrToInt(curr_value))
    +    return arch.syscall2(arch.SYS_timerfd_gettime, usize(fd), @ptrToInt(curr_value));
     }
     
     pub fn timerfd_settime(fd: i32, flags: u32, new_value: &const itimerspec, old_value: ?&itimerspec) -> usize {
    -    arch.syscall4(arch.SYS_timerfd_settime, usize(fd), usize(flags), @ptrToInt(new_value), @ptrToInt(old_value))
    +    return arch.syscall4(arch.SYS_timerfd_settime, usize(fd), usize(flags), @ptrToInt(new_value), @ptrToInt(old_value));
     }
     
     test "import linux_test" {
    diff --git a/std/os/linux_x86_64.zig b/std/os/linux_x86_64.zig
    index 6c94528df0..db78decde2 100644
    --- a/std/os/linux_x86_64.zig
    +++ b/std/os/linux_x86_64.zig
    @@ -371,52 +371,52 @@ pub const F_GETOWN_EX = 16;
     pub const F_GETOWNER_UIDS = 17;
     
     pub fn syscall0(number: usize) -> usize {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             : [ret] "={rax}" (-> usize)
             : [number] "{rax}" (number)
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     pub fn syscall1(number: usize, arg1: usize) -> usize {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             : [ret] "={rax}" (-> usize)
             : [number] "{rax}" (number),
                 [arg1] "{rdi}" (arg1)
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     pub fn syscall2(number: usize, arg1: usize, arg2: usize) -> usize {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             : [ret] "={rax}" (-> usize)
             : [number] "{rax}" (number),
                 [arg1] "{rdi}" (arg1),
                 [arg2] "{rsi}" (arg2)
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) -> usize {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             : [ret] "={rax}" (-> usize)
             : [number] "{rax}" (number),
                 [arg1] "{rdi}" (arg1),
                 [arg2] "{rsi}" (arg2),
                 [arg3] "{rdx}" (arg3)
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     pub fn syscall4(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> usize {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             : [ret] "={rax}" (-> usize)
             : [number] "{rax}" (number),
                 [arg1] "{rdi}" (arg1),
                 [arg2] "{rsi}" (arg2),
                 [arg3] "{rdx}" (arg3),
                 [arg4] "{r10}" (arg4)
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     pub fn syscall5(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) -> usize {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             : [ret] "={rax}" (-> usize)
             : [number] "{rax}" (number),
                 [arg1] "{rdi}" (arg1),
    @@ -424,13 +424,13 @@ pub fn syscall5(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usiz
                 [arg3] "{rdx}" (arg3),
                 [arg4] "{r10}" (arg4),
                 [arg5] "{r8}" (arg5)
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     pub fn syscall6(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize,
         arg5: usize, arg6: usize) -> usize
     {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             : [ret] "={rax}" (-> usize)
             : [number] "{rax}" (number),
                 [arg1] "{rdi}" (arg1),
    @@ -439,14 +439,14 @@ pub fn syscall6(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usiz
                 [arg4] "{r10}" (arg4),
                 [arg5] "{r8}" (arg5),
                 [arg6] "{r9}" (arg6)
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     pub nakedcc fn restore_rt() {
    -    asm volatile ("syscall"
    +    return asm volatile ("syscall"
             :
             : [number] "{rax}" (usize(SYS_rt_sigreturn))
    -        : "rcx", "r11")
    +        : "rcx", "r11");
     }
     
     
    diff --git a/std/os/path.zig b/std/os/path.zig
    index 3fd7b5e2db..a42ebb3433 100644
    --- a/std/os/path.zig
    +++ b/std/os/path.zig
    @@ -749,21 +749,19 @@ pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8)
         const resolved_to = %return resolveWindows(allocator, [][]const u8{to});
         defer if (clean_up_resolved_to) allocator.free(resolved_to);
     
    -    const result_is_to = if (drive(resolved_to)) |to_drive| {
    -        if (drive(resolved_from)) |from_drive| {
    +    const result_is_to = if (drive(resolved_to)) |to_drive|
    +        if (drive(resolved_from)) |from_drive|
                 asciiUpper(from_drive[0]) != asciiUpper(to_drive[0])
    -        } else {
    +        else
                 true
    -        }
    -    } else if (networkShare(resolved_to)) |to_ns| {
    -        if (networkShare(resolved_from)) |from_ns| {
    +    else if (networkShare(resolved_to)) |to_ns|
    +        if (networkShare(resolved_from)) |from_ns|
                 !networkShareServersEql(to_ns, from_ns)
    -        } else {
    +        else
                 true
    -        }
    -    } else {
    -        unreachable
    -    };
    +    else
    +        unreachable;
    +
         if (result_is_to) {
             clean_up_resolved_to = false;
             return resolved_to;
    @@ -964,14 +962,16 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
     
                     // windows returns \\?\ prepended to the path
                     // we strip it because nobody wants \\?\ prepended to their path
    -                const final_len = if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
    -                    var i: usize = 4;
    -                    while (i < result) : (i += 1) {
    -                        buf[i - 4] = buf[i];
    +                const final_len = x: {
    +                    if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
    +                        var i: usize = 4;
    +                        while (i < result) : (i += 1) {
    +                            buf[i - 4] = buf[i];
    +                        }
    +                        break :x result - 4;
    +                    } else {
    +                        break :x result;
                         }
    -                    result - 4
    -                } else {
    -                    result
                     };
     
                     return allocator.shrink(u8, buf, final_len);
    diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig
    index b3fc095d43..0964adc16b 100644
    --- a/std/os/windows/util.zig
    +++ b/std/os/windows/util.zig
    @@ -122,7 +122,7 @@ pub fn windowsOpen(file_path: []const u8, desired_access: windows.DWORD, share_m
     /// Caller must free result.
     pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap) -> %[]u8 {
         // count bytes needed
    -    const bytes_needed = {
    +    const bytes_needed = x: {
             var bytes_needed: usize = 1; // 1 for the final null byte
             var it = env_map.iterator();
             while (it.next()) |pair| {
    @@ -130,7 +130,7 @@ pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap)
                 // +1 for null byte
                 bytes_needed += pair.key.len + pair.value.len + 2;
             }
    -        bytes_needed
    +        break :x bytes_needed;
         };
         const result = %return allocator.alloc(u8, bytes_needed);
         %defer allocator.free(result);
    diff --git a/std/rand.zig b/std/rand.zig
    index 09e0c8ac78..73801a078f 100644
    --- a/std/rand.zig
    +++ b/std/rand.zig
    @@ -28,9 +28,9 @@ pub const Rand = struct {
     
         /// Initialize random state with the given seed.
         pub fn init(seed: usize) -> Rand {
    -        Rand {
    +        return Rand {
                 .rng = Rng.init(seed),
    -        }
    +        };
         }
     
         /// Get an integer or boolean with random bits.
    @@ -78,13 +78,13 @@ pub const Rand = struct {
                     const end_uint = uint(end);
                     const total_range = math.absCast(start) + end_uint;
                     const value = r.range(uint, 0, total_range);
    -                const result = if (value < end_uint) {
    -                    T(value)
    -                } else if (value == end_uint) {
    -                    start
    -                } else {
    +                const result = if (value < end_uint) x: {
    +                    break :x T(value);
    +                } else if (value == end_uint) x: {
    +                    break :x start;
    +                } else x: {
                         // Can't overflow because the range is over signed ints
    -                    %%math.negateCast(value - end_uint)
    +                    break :x %%math.negateCast(value - end_uint);
                     };
                     return result;
                 } else {
    @@ -114,13 +114,13 @@ pub const Rand = struct {
             // const rand_bits = r.rng.scalar(int) & mask;
             // return @float_compose(T, false, 0, rand_bits) - 1.0
             const int_type = @IntType(false, @sizeOf(T) * 8);
    -        const precision = if (T == f32) {
    +        const precision = if (T == f32)
                 16777216
    -        } else if (T == f64) {
    +        else if (T == f64)
                 9007199254740992
    -        } else {
    +        else
                 @compileError("unknown floating point type")
    -        };
    +        ;
             return T(r.range(int_type, 0, precision)) / T(precision);
         }
     };
    @@ -133,7 +133,7 @@ fn MersenneTwister(
         comptime t: math.Log2Int(int), comptime c: int,
         comptime l: math.Log2Int(int), comptime f: int) -> type
     {
    -    struct {
    +    return struct {
             const Self = this;
     
             array: [n]int,
    @@ -189,7 +189,7 @@ fn MersenneTwister(
     
                 return x;
             }
    -    }
    +    };
     }
     
     test "rand float 32" {
    diff --git a/std/sort.zig b/std/sort.zig
    index c6b1500b8e..a36a5e1747 100644
    --- a/std/sort.zig
    +++ b/std/sort.zig
    @@ -355,7 +355,7 @@ pub fn sort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &cons
                     // these values will be pulled out to the start of A
                     last = A.start;
                     count = 1;
    -                while (count < find) : ({last = index; count += 1}) {
    +                while (count < find) : ({last = index; count += 1;}) {
                         index = findLastForward(T, items, items[last], Range.init(last + 1, A.end), lessThan, find - count);
                         if (index == A.end) break;
                     }
    @@ -410,7 +410,7 @@ pub fn sort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &cons
                     // these values will be pulled out to the end of B
                     last = B.end - 1;
                     count = 1;
    -                while (count < find) : ({last = index - 1; count += 1}) {
    +                while (count < find) : ({last = index - 1; count += 1;}) {
                         index = findFirstBackward(T, items, items[last], Range.init(B.start, last), lessThan, find - count);
                         if (index == B.start) break;
                     }
    @@ -547,7 +547,7 @@ pub fn sort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &cons
                         // swap the first value of each A block with the value in buffer1
                         var indexA = buffer1.start;
                         index = firstA.end;
    -                    while (index < blockA.end) : ({indexA += 1; index += block_size}) {
    +                    while (index < blockA.end) : ({indexA += 1; index += block_size;}) {
                             mem.swap(T, &items[indexA], &items[index]);
                         }
                         
    @@ -1093,7 +1093,7 @@ test "another sort case" {
         var arr = []i32{ 5, 3, 1, 2, 4 };
         sort(i32, arr[0..], i32asc);
     
    -    assert(mem.eql(i32, arr, []i32{ 1, 2, 3, 4, 5 }))
    +    assert(mem.eql(i32, arr, []i32{ 1, 2, 3, 4, 5 }));
     }
     
     test "sort fuzz testing" {
    diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig
    index 430eeb395e..e54d85e6ef 100644
    --- a/std/special/build_runner.zig
    +++ b/std/special/build_runner.zig
    @@ -45,21 +45,17 @@ pub fn main() -> %void {
     
         var stderr_file = io.getStdErr();
         var stderr_file_stream: io.FileOutStream = undefined;
    -    var stderr_stream: %&io.OutStream = if (stderr_file) |*f| {
    +    var stderr_stream: %&io.OutStream = if (stderr_file) |*f| x: {
             stderr_file_stream = io.FileOutStream.init(f);
    -        &stderr_file_stream.stream
    -    } else |err| {
    -        err
    -    };
    +        break :x &stderr_file_stream.stream;
    +    } else |err| err;
     
         var stdout_file = io.getStdOut();
         var stdout_file_stream: io.FileOutStream = undefined;
    -    var stdout_stream: %&io.OutStream = if (stdout_file) |*f| {
    +    var stdout_stream: %&io.OutStream = if (stdout_file) |*f| x: {
             stdout_file_stream = io.FileOutStream.init(f);
    -        &stdout_file_stream.stream
    -    } else |err| {
    -        err
    -    };
    +        break :x &stdout_file_stream.stream;
    +    } else |err| err;
     
         while (arg_it.next(allocator)) |err_or_arg| {
             const arg = %return unwrapArg(err_or_arg);
    diff --git a/std/special/builtin.zig b/std/special/builtin.zig
    index a2455a9690..e6c09863ca 100644
    --- a/std/special/builtin.zig
    +++ b/std/special/builtin.zig
    @@ -46,15 +46,15 @@ extern fn __stack_chk_fail() -> noreturn {
     
     const math = @import("../math/index.zig");
     
    -export fn fmodf(x: f32, y: f32) -> f32 { generic_fmod(f32, x, y) }
    -export fn fmod(x: f64, y: f64) -> f64 { generic_fmod(f64, x, y) }
    +export fn fmodf(x: f32, y: f32) -> f32 { return generic_fmod(f32, x, y); }
    +export fn fmod(x: f64, y: f64) -> f64 { return generic_fmod(f64, x, y); }
     
     // TODO add intrinsics for these (and probably the double version too)
     // and have the math stuff use the intrinsic. same as @mod and @rem
    -export fn floorf(x: f32) -> f32 { math.floor(x) }
    -export fn ceilf(x: f32) -> f32 { math.ceil(x) }
    -export fn floor(x: f64) -> f64 { math.floor(x) }
    -export fn ceil(x: f64) -> f64 { math.ceil(x) }
    +export fn floorf(x: f32) -> f32 { return math.floor(x); }
    +export fn ceilf(x: f32) -> f32 { return math.ceil(x); }
    +export fn floor(x: f64) -> f64 { return math.floor(x); }
    +export fn ceil(x: f64) -> f64 { return math.ceil(x); }
     
     fn generic_fmod(comptime T: type, x: T, y: T) -> T {
         @setDebugSafety(this, false);
    @@ -84,7 +84,7 @@ fn generic_fmod(comptime T: type, x: T, y: T) -> T {
         // normalize x and y
         if (ex == 0) {
             i = ux << exp_bits;
    -        while (i >> bits_minus_1 == 0) : ({ex -= 1; i <<= 1}) {}
    +        while (i >> bits_minus_1 == 0) : (b: {ex -= 1; break :b i <<= 1;}) {}
             ux <<= log2uint(@bitCast(u32, -ex + 1));
         } else {
             ux &= @maxValue(uint) >> exp_bits;
    @@ -92,7 +92,7 @@ fn generic_fmod(comptime T: type, x: T, y: T) -> T {
         }
         if (ey == 0) {
             i = uy << exp_bits;
    -        while (i >> bits_minus_1 == 0) : ({ey -= 1; i <<= 1}) {}
    +        while (i >> bits_minus_1 == 0) : (b: {ey -= 1; break :b i <<= 1;}) {}
             uy <<= log2uint(@bitCast(u32, -ey + 1));
         } else {
             uy &= @maxValue(uint) >> exp_bits;
    @@ -115,7 +115,7 @@ fn generic_fmod(comptime T: type, x: T, y: T) -> T {
                 return 0 * x;
             ux = i;
         }
    -    while (ux >> digits == 0) : ({ux <<= 1; ex -= 1}) {}
    +    while (ux >> digits == 0) : (b: {ux <<= 1; break :b ex -= 1;}) {}
     
         // scale result up
         if (ex > 0) {
    diff --git a/std/special/compiler_rt/comparetf2.zig b/std/special/compiler_rt/comparetf2.zig
    index 0834072672..b88c35019b 100644
    --- a/std/special/compiler_rt/comparetf2.zig
    +++ b/std/special/compiler_rt/comparetf2.zig
    @@ -38,27 +38,25 @@ pub extern fn __letf2(a: f128, b: f128) -> c_int {
     
         // If at least one of a and b is positive, we get the same result comparing
         // a and b as signed integers as we would with a floating-point compare.
    -    return if ((aInt & bInt) >= 0) {
    -        if (aInt < bInt) {
    +    return if ((aInt & bInt) >= 0)
    +        if (aInt < bInt)
                 LE_LESS
    -        } else if (aInt == bInt) {
    +        else if (aInt == bInt)
                 LE_EQUAL
    -        } else {
    +        else
                 LE_GREATER
    -        }
    -    } else {
    +    else
             // Otherwise, both are negative, so we need to flip the sense of the
             // comparison to get the correct result.  (This assumes a twos- or ones-
             // complement integer representation; if integers are represented in a
             // sign-magnitude representation, then this flip is incorrect).
    -        if (aInt > bInt) {
    +        if (aInt > bInt)
                 LE_LESS
    -        } else if (aInt == bInt) {
    +        else if (aInt == bInt)
                 LE_EQUAL
    -        } else {
    +        else
                 LE_GREATER
    -        }
    -    };
    +    ;
     }
     
     // TODO https://github.com/zig-lang/zig/issues/305
    @@ -78,23 +76,21 @@ pub extern fn __getf2(a: f128, b: f128) -> c_int {
     
         if (aAbs > infRep or bAbs > infRep) return GE_UNORDERED;
         if ((aAbs | bAbs) == 0) return GE_EQUAL;
    -    return if ((aInt & bInt) >= 0) {
    -        if (aInt < bInt) {
    +    return if ((aInt & bInt) >= 0)
    +        if (aInt < bInt)
                 GE_LESS
    -        } else if (aInt == bInt) {
    +        else if (aInt == bInt)
                 GE_EQUAL
    -        } else {
    +        else
                 GE_GREATER
    -        }
    -    } else {
    -        if (aInt > bInt) {
    +    else
    +        if (aInt > bInt)
                 GE_LESS
    -        } else if (aInt == bInt) {
    +        else if (aInt == bInt)
                 GE_EQUAL
    -        } else {
    +        else
                 GE_GREATER
    -        }
    -    };
    +    ;
     }
     
     pub extern fn __unordtf2(a: f128, b: f128) -> c_int {
    diff --git a/test/cases/align.zig b/test/cases/align.zig
    index 3bf0d9c9af..3105945e04 100644
    --- a/test/cases/align.zig
    +++ b/test/cases/align.zig
    @@ -10,7 +10,7 @@ test "global variable alignment" {
         assert(@typeOf(slice) == []align(4) u8);
     }
     
    -fn derp() align(@sizeOf(usize) * 2) -> i32 { 1234 }
    +fn derp() align(@sizeOf(usize) * 2) -> i32 { return 1234; }
     fn noop1() align(1) {}
     fn noop4() align(4) {}
     
    @@ -53,14 +53,14 @@ test "implicitly decreasing pointer alignment" {
         assert(addUnaligned(&a, &b) == 7);
     }
     
    -fn addUnaligned(a: &align(1) const u32, b: &align(1) const u32) -> u32 { *a + *b }
    +fn addUnaligned(a: &align(1) const u32, b: &align(1) const u32) -> u32 { return *a + *b; }
     
     test "implicitly decreasing slice alignment" {
         const a: u32 align(4) = 3;
         const b: u32 align(8) = 4;
         assert(addUnalignedSlice((&a)[0..1], (&b)[0..1]) == 7);
     }
    -fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) -> u32 { a[0] + b[0] }
    +fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) -> u32 { return a[0] + b[0]; }
     
     test "specifying alignment allows pointer cast" {
         testBytesAlign(0x33);
    @@ -115,20 +115,20 @@ fn testImplicitlyDecreaseFnAlign(ptr: fn () align(1) -> i32, answer: i32) {
         assert(ptr() == answer);
     }
     
    -fn alignedSmall() align(8) -> i32 { 1234 }
    -fn alignedBig() align(16) -> i32 { 5678 }
    +fn alignedSmall() align(8) -> i32 { return 1234; }
    +fn alignedBig() align(16) -> i32 { return 5678; }
     
     
     test "@alignCast functions" {
         assert(fnExpectsOnly1(simple4) == 0x19);
     }
     fn fnExpectsOnly1(ptr: fn()align(1) -> i32) -> i32 {
    -    fnExpects4(@alignCast(4, ptr))
    +    return fnExpects4(@alignCast(4, ptr));
     }
     fn fnExpects4(ptr: fn()align(4) -> i32) -> i32 {
    -    ptr()
    +    return ptr();
     }
    -fn simple4() align(4) -> i32 { 0x19 }
    +fn simple4() align(4) -> i32 { return 0x19; }
     
     
     test "generic function with align param" {
    @@ -137,7 +137,7 @@ test "generic function with align param" {
         assert(whyWouldYouEverDoThis(8) == 0x1);
     }
     
    -fn whyWouldYouEverDoThis(comptime align_bytes: u8) align(align_bytes) -> u8 { 0x1 }
    +fn whyWouldYouEverDoThis(comptime align_bytes: u8) align(align_bytes) -> u8 { return 0x1; }
     
     
     test "@ptrCast preserves alignment of bigger source" {
    diff --git a/test/cases/array.zig b/test/cases/array.zig
    index a6fa07b004..bf77e51fee 100644
    --- a/test/cases/array.zig
    +++ b/test/cases/array.zig
    @@ -22,7 +22,7 @@ test "arrays" {
         assert(getArrayLen(array) == 5);
     }
     fn getArrayLen(a: []const u32) -> usize {
    -    a.len
    +    return a.len;
     }
     
     test "void arrays" {
    @@ -41,7 +41,7 @@ test "array literal" {
     }
     
     test "array dot len const expr" {
    -    assert(comptime {some_array.len == 4});
    +    assert(comptime x: {break :x some_array.len == 4;});
     }
     
     const ArrayDotLenConstExpr = struct {
    diff --git a/test/cases/bitcast.zig b/test/cases/bitcast.zig
    index 72ca24cf5e..0a92d9d606 100644
    --- a/test/cases/bitcast.zig
    +++ b/test/cases/bitcast.zig
    @@ -10,5 +10,5 @@ fn testBitCast_i32_u32() {
         assert(conv2(@maxValue(u32)) == -1);
     }
     
    -fn conv(x: i32) -> u32 { @bitCast(u32, x) }
    -fn conv2(x: u32) -> i32 { @bitCast(i32, x) }
    +fn conv(x: i32) -> u32 { return @bitCast(u32, x); }
    +fn conv2(x: u32) -> i32 { return @bitCast(i32, x); }
    diff --git a/test/cases/bool.zig b/test/cases/bool.zig
    index 61bb3bf759..1203e696ba 100644
    --- a/test/cases/bool.zig
    +++ b/test/cases/bool.zig
    @@ -22,7 +22,7 @@ test "bool cmp" {
         assert(testBoolCmp(true, false) == false);
     }
     fn testBoolCmp(a: bool, b: bool) -> bool {
    -    a == b
    +    return a == b;
     }
     
     const global_f = false;
    diff --git a/test/cases/cast.zig b/test/cases/cast.zig
    index c93d2e7413..8b16cb44d4 100644
    --- a/test/cases/cast.zig
    +++ b/test/cases/cast.zig
    @@ -50,7 +50,7 @@ test "peer resolve arrays of different size to const slice" {
         comptime assert(mem.eql(u8, boolToStr(false), "false"));
     }
     fn boolToStr(b: bool) -> []const u8 {
    -    if (b) "true" else "false"
    +    return if (b) "true" else "false";
     }
     
     
    @@ -239,17 +239,17 @@ test "peer type resolution: error and [N]T" {
     
     error BadValue;
     fn testPeerErrorAndArray(x: u8) -> %[]const u8 {
    -    switch (x) {
    +    return switch (x) {
             0x00 => "OK",
             else => error.BadValue,
    -    }
    +    };
     }
     fn testPeerErrorAndArray2(x: u8) -> %[]const u8 {
    -    switch (x) {
    +    return switch (x) {
             0x00 => "OK",
             0x01 => "OKK",
             else => error.BadValue,
    -    }
    +    };
     }
     
     test "explicit cast float number literal to integer if no fraction component" {
    @@ -269,11 +269,11 @@ fn testCast128() {
     }
     
     fn cast128Int(x: f128) -> u128 {
    -    @bitCast(u128, x)
    +    return @bitCast(u128, x);
     }
     
     fn cast128Float(x: u128) -> f128 {
    -    @bitCast(f128, x)
    +    return @bitCast(f128, x);
     }
     
     test "const slice widen cast" {
    diff --git a/test/cases/defer.zig b/test/cases/defer.zig
    index 6cafe9f334..d4cb79ec46 100644
    --- a/test/cases/defer.zig
    +++ b/test/cases/defer.zig
    @@ -7,9 +7,9 @@ error FalseNotAllowed;
     
     fn runSomeErrorDefers(x: bool) -> %bool {
         index = 0;
    -    defer {result[index] = 'a'; index += 1;};
    -    %defer {result[index] = 'b'; index += 1;};
    -    defer {result[index] = 'c'; index += 1;};
    +    defer {result[index] = 'a'; index += 1;}
    +    %defer {result[index] = 'b'; index += 1;}
    +    defer {result[index] = 'c'; index += 1;}
         return if (x) x else error.FalseNotAllowed;
     }
     
    @@ -18,9 +18,9 @@ test "mixing normal and error defers" {
         assert(result[0] == 'c');
         assert(result[1] == 'a');
     
    -    const ok = runSomeErrorDefers(false) %% |err| {
    +    const ok = runSomeErrorDefers(false) %% |err| x: {
             assert(err == error.FalseNotAllowed);
    -        true
    +        break :x true;
         };
         assert(ok);
         assert(result[0] == 'c');
    @@ -41,5 +41,5 @@ fn testBreakContInDefer(x: usize) {
                 if (i == 5) break;
             }
             assert(i == 5);
    -    };
    +    }
     }
    diff --git a/test/cases/enum.zig b/test/cases/enum.zig
    index 6352a23afa..26aa8fb589 100644
    --- a/test/cases/enum.zig
    +++ b/test/cases/enum.zig
    @@ -41,7 +41,7 @@ const Bar = enum {
     };
     
     fn returnAnInt(x: i32) -> Foo {
    -    Foo { .One = x }
    +    return Foo { .One = x };
     }
     
     
    diff --git a/test/cases/enum_with_members.zig b/test/cases/enum_with_members.zig
    index ae48a266d0..c28692575e 100644
    --- a/test/cases/enum_with_members.zig
    +++ b/test/cases/enum_with_members.zig
    @@ -8,9 +8,9 @@ const ET = union(enum) {
     
         pub fn print(a: &const ET, buf: []u8) -> %usize {
             return switch (*a) {
    -            ET.SINT => |x| { fmt.formatIntBuf(buf, x, 10, false, 0) },
    -            ET.UINT => |x| { fmt.formatIntBuf(buf, x, 10, false, 0) },
    -        }
    +            ET.SINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0),
    +            ET.UINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0),
    +        };
         }
     };
     
    diff --git a/test/cases/error.zig b/test/cases/error.zig
    index 9e55f57b6d..3974e9dc7c 100644
    --- a/test/cases/error.zig
    +++ b/test/cases/error.zig
    @@ -3,7 +3,7 @@ const mem = @import("std").mem;
     
     pub fn foo() -> %i32 {
         const x = %return bar();
    -    return x + 1
    +    return x + 1;
     }
     
     pub fn bar() -> %i32 {
    @@ -21,7 +21,7 @@ test "error wrapping" {
     
     error ItBroke;
     fn gimmeItBroke() -> []const u8 {
    -    @errorName(error.ItBroke)
    +    return @errorName(error.ItBroke);
     }
     
     test "@errorName" {
    @@ -48,7 +48,7 @@ error AnError;
     error AnError;
     error SecondError;
     fn shouldBeNotEqual(a: error, b: error) {
    -    if (a == b) unreachable
    +    if (a == b) unreachable;
     }
     
     
    @@ -60,11 +60,7 @@ test "error binary operator" {
     }
     error ItBroke;
     fn errBinaryOperatorG(x: bool) -> %isize {
    -    if (x) {
    -        error.ItBroke
    -    } else {
    -        isize(10)
    -    }
    +    return if (x) error.ItBroke else isize(10);
     }
     
     
    @@ -72,7 +68,7 @@ test "unwrap simple value from error" {
         const i = %%unwrapSimpleValueFromErrorDo();
         assert(i == 13);
     }
    -fn unwrapSimpleValueFromErrorDo() -> %isize { 13 }
    +fn unwrapSimpleValueFromErrorDo() -> %isize { return 13; }
     
     
     test "error return in assignment" {
    diff --git a/test/cases/eval.zig b/test/cases/eval.zig
    index c657482d08..a2e015fba7 100644
    --- a/test/cases/eval.zig
    +++ b/test/cases/eval.zig
    @@ -44,7 +44,7 @@ test "static function evaluation" {
         assert(statically_added_number == 3);
     }
     const statically_added_number = staticAdd(1, 2);
    -fn staticAdd(a: i32, b: i32) -> i32 { a + b }
    +fn staticAdd(a: i32, b: i32) -> i32 { return a + b; }
     
     
     test "const expr eval on single expr blocks" {
    @@ -54,10 +54,10 @@ test "const expr eval on single expr blocks" {
     fn constExprEvalOnSingleExprBlocksFn(x: i32, b: bool) -> i32 {
         const literal = 3;
     
    -    const result = if (b) {
    -        literal
    -    } else {
    -        x
    +    const result = if (b) b: {
    +        break :b literal;
    +    } else b: {
    +        break :b x;
         };
     
         return result;
    @@ -94,9 +94,9 @@ pub const Vec3 = struct {
         data: [3]f32,
     };
     pub fn vec3(x: f32, y: f32, z: f32) -> Vec3 {
    -    Vec3 {
    +    return Vec3 {
             .data = []f32 { x, y, z, },
    -    }
    +    };
     }
     
     
    @@ -176,7 +176,7 @@ fn max(comptime T: type, a: T, b: T) -> T {
         }
     }
     fn letsTryToCompareBools(a: bool, b: bool) -> bool {
    -    max(bool, a, b)
    +    return max(bool, a, b);
     }
     test "inlined block and runtime block phi" {
         assert(letsTryToCompareBools(true, true));
    @@ -202,9 +202,9 @@ const cmd_fns = []CmdFn{
         CmdFn {.name = "two", .func = two},
         CmdFn {.name = "three", .func = three},
     };
    -fn one(value: i32) -> i32 { value + 1 }
    -fn two(value: i32) -> i32 { value + 2 }
    -fn three(value: i32) -> i32 { value + 3 }
    +fn one(value: i32) -> i32 { return value + 1; }
    +fn two(value: i32) -> i32 { return value + 2; }
    +fn three(value: i32) -> i32 { return value + 3; }
     
     fn performFn(comptime prefix_char: u8, start_value: i32) -> i32 {
         var result: i32 = start_value;
    @@ -317,12 +317,12 @@ test "create global array with for loop" {
         assert(global_array[9] == 9 * 9);
     }
     
    -const global_array = {
    +const global_array = x: {
         var result: [10]usize = undefined;
         for (result) |*item, index| {
             *item = index * index;
         }
    -    result
    +    break :x result;
     };
     
     test "compile-time downcast when the bits fit" {
    diff --git a/test/cases/fn.zig b/test/cases/fn.zig
    index c948d8af3d..aad68447b2 100644
    --- a/test/cases/fn.zig
    +++ b/test/cases/fn.zig
    @@ -4,7 +4,7 @@ test "params" {
         assert(testParamsAdd(22, 11) == 33);
     }
     fn testParamsAdd(a: i32, b: i32) -> i32 {
    -    a + b
    +    return a + b;
     }
     
     
    @@ -22,7 +22,7 @@ test "void parameters" {
     }
     fn voidFun(a: i32, b: void, c: i32, d: void) {
         const v = b;
    -    const vv: void = if (a == 1) {v} else {};
    +    const vv: void = if (a == 1) v else {};
         assert(a + c == 3);
         return vv;
     }
    @@ -45,9 +45,9 @@ test "separate block scopes" {
             assert(no_conflict == 5);
         }
     
    -    const c = {
    +    const c = x: {
             const no_conflict = i32(10);
    -        no_conflict
    +        break :x no_conflict;
         };
         assert(c == 10);
     }
    @@ -73,7 +73,7 @@ test "implicit cast function unreachable return" {
     fn wantsFnWithVoid(f: fn()) { }
     
     fn fnWithUnreachable() -> noreturn {
    -    unreachable
    +    unreachable;
     }
     
     
    @@ -83,14 +83,14 @@ test "function pointers" {
             assert(f() == u32(i) + 5);
         }
     }
    -fn fn1() -> u32 {5}
    -fn fn2() -> u32 {6}
    -fn fn3() -> u32 {7}
    -fn fn4() -> u32 {8}
    +fn fn1() -> u32 {return 5;}
    +fn fn2() -> u32 {return 6;}
    +fn fn3() -> u32 {return 7;}
    +fn fn4() -> u32 {return 8;}
     
     
     test "inline function call" {
         assert(@inlineCall(add, 3, 9) == 12);
     }
     
    -fn add(a: i32, b: i32) -> i32 { a + b }
    +fn add(a: i32, b: i32) -> i32 { return a + b; }
    diff --git a/test/cases/for.zig b/test/cases/for.zig
    index e4b8094cf5..5a7919541f 100644
    --- a/test/cases/for.zig
    +++ b/test/cases/for.zig
    @@ -12,7 +12,7 @@ test "continue in for loop" {
             }
             break;
         }
    -    if (sum != 6) unreachable
    +    if (sum != 6) unreachable;
     }
     
     test "for loop with pointer elem var" {
    diff --git a/test/cases/generics.zig b/test/cases/generics.zig
    index d6a3192a6b..96500e39b2 100644
    --- a/test/cases/generics.zig
    +++ b/test/cases/generics.zig
    @@ -11,7 +11,7 @@ fn max(comptime T: type, a: T, b: T) -> T {
     }
     
     fn add(comptime a: i32, b: i32) -> i32 {
    -    return (comptime {a}) + b;
    +    return (comptime a) + b;
     }
     
     const the_max = max(u32, 1234, 5678);
    @@ -20,15 +20,15 @@ test "compile time generic eval" {
     }
     
     fn gimmeTheBigOne(a: u32, b: u32) -> u32 {
    -    max(u32, a, b)
    +    return max(u32, a, b);
     }
     
     fn shouldCallSameInstance(a: u32, b: u32) -> u32 {
    -    max(u32, a, b)
    +    return max(u32, a, b);
     }
     
     fn sameButWithFloats(a: f64, b: f64) -> f64 {
    -    max(f64, a, b)
    +    return max(f64, a, b);
     }
     
     test "fn with comptime args" {
    @@ -49,28 +49,28 @@ comptime {
     }
     
     fn max_var(a: var, b: var) -> @typeOf(a + b) {
    -    if (a > b) a else b
    +    return if (a > b) a else b;
     }
     
     fn max_i32(a: i32, b: i32) -> i32 {
    -    max_var(a, b)
    +    return max_var(a, b);
     }
     
     fn max_f64(a: f64, b: f64) -> f64 {
    -    max_var(a, b)
    +    return max_var(a, b);
     }
     
     
     pub fn List(comptime T: type) -> type {
    -    SmallList(T, 8)
    +    return SmallList(T, 8);
     }
     
     pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) -> type {
    -    struct {
    +    return struct {
             items: []T,
             length: usize,
             prealloc_items: [STATIC_SIZE]T,
    -    }
    +    };
     }
     
     test "function with return type type" {
    @@ -91,20 +91,20 @@ test "generic struct" {
         assert(b1.getVal());
     }
     fn GenNode(comptime T: type) -> type {
    -    struct {
    +    return struct {
             value: T,
             next: ?&GenNode(T),
    -        fn getVal(n: &const GenNode(T)) -> T { n.value }
    -    }
    +        fn getVal(n: &const GenNode(T)) -> T { return n.value; }
    +    };
     }
     
     test "const decls in struct" {
         assert(GenericDataThing(3).count_plus_one == 4);
     }
     fn GenericDataThing(comptime count: isize) -> type {
    -    struct {
    +    return struct {
             const count_plus_one = count + 1;
    -    }
    +    };
     }
     
     
    @@ -120,16 +120,16 @@ test "generic fn with implicit cast" {
         assert(getFirstByte(u8, []u8 {13}) == 13);
         assert(getFirstByte(u16, []u16 {0, 13}) == 0);
     }
    -fn getByte(ptr: ?&const u8) -> u8 {*??ptr}
    +fn getByte(ptr: ?&const u8) -> u8 {return *??ptr;}
     fn getFirstByte(comptime T: type, mem: []const T) -> u8 {
    -    getByte(@ptrCast(&const u8, &mem[0]))
    +    return getByte(@ptrCast(&const u8, &mem[0]));
     }
     
     
     const foos = []fn(var) -> bool { foo1, foo2 };
     
    -fn foo1(arg: var) -> bool { arg }
    -fn foo2(arg: var) -> bool { !arg }
    +fn foo1(arg: var) -> bool { return arg; }
    +fn foo2(arg: var) -> bool { return !arg; }
     
     test "array of generic fns" {
         assert(foos[0](true));
    diff --git a/test/cases/if.zig b/test/cases/if.zig
    index 10fedcdea5..d1fa717b50 100644
    --- a/test/cases/if.zig
    +++ b/test/cases/if.zig
    @@ -29,10 +29,10 @@ test "else if expression" {
     }
     fn elseIfExpressionF(c: u8) -> u8 {
         if (c == 0) {
    -        0
    +        return 0;
         } else if (c == 1) {
    -        1
    +        return 1;
         } else {
    -        u8(2)
    +        return u8(2);
         }
     }
    diff --git a/test/cases/import/a_namespace.zig b/test/cases/import/a_namespace.zig
    index d6926fbb5e..40cdd69139 100644
    --- a/test/cases/import/a_namespace.zig
    +++ b/test/cases/import/a_namespace.zig
    @@ -1 +1 @@
    -pub fn foo() -> i32 { 1234 }
    +pub fn foo() -> i32 { return 1234; }
    diff --git a/test/cases/ir_block_deps.zig b/test/cases/ir_block_deps.zig
    index e6ce12bd65..a70dff0c84 100644
    --- a/test/cases/ir_block_deps.zig
    +++ b/test/cases/ir_block_deps.zig
    @@ -8,10 +8,10 @@ fn foo(id: u64) -> %i32 {
                 return %return getErrInt();
             },
             else => error.ItBroke,
    -    }
    +    };
     }
     
    -fn getErrInt() -> %i32 { 0 }
    +fn getErrInt() -> %i32 { return 0; }
     
     error ItBroke;
     
    diff --git a/test/cases/math.zig b/test/cases/math.zig
    index 1d29800aad..b4e0e4cfd6 100644
    --- a/test/cases/math.zig
    +++ b/test/cases/math.zig
    @@ -28,16 +28,16 @@ fn testDivision() {
         assert(divTrunc(f32, -5.0, 3.0) == -1.0);
     }
     fn div(comptime T: type, a: T, b: T) -> T {
    -    a / b
    +    return a / b;
     }
     fn divExact(comptime T: type, a: T, b: T) -> T {
    -    @divExact(a, b)
    +    return @divExact(a, b);
     }
     fn divFloor(comptime T: type, a: T, b: T) -> T {
    -    @divFloor(a, b)
    +    return @divFloor(a, b);
     }
     fn divTrunc(comptime T: type, a: T, b: T) -> T {
    -    @divTrunc(a, b)
    +    return @divTrunc(a, b);
     }
     
     test "@addWithOverflow" {
    @@ -71,7 +71,7 @@ fn testClz() {
     }
     
     fn clz(x: var) -> usize {
    -    @clz(x)
    +    return @clz(x);
     }
     
     test "@ctz" {
    @@ -86,7 +86,7 @@ fn testCtz() {
     }
     
     fn ctz(x: var) -> usize {
    -    @ctz(x)
    +    return @ctz(x);
     }
     
     test "assignment operators" {
    @@ -180,10 +180,10 @@ fn test_u64_div() {
         assert(result.remainder == 100663296);
     }
     fn divWithResult(a: u64, b: u64) -> DivResult {
    -    DivResult {
    +    return DivResult {
             .quotient = a / b,
             .remainder = a % b,
    -    }
    +    };
     }
     const DivResult = struct {
         quotient: u64,
    @@ -191,8 +191,8 @@ const DivResult = struct {
     };
     
     test "binary not" {
    -    assert(comptime {~u16(0b1010101010101010) == 0b0101010101010101});
    -    assert(comptime {~u64(2147483647) == 18446744071562067968});
    +    assert(comptime x: {break :x ~u16(0b1010101010101010) == 0b0101010101010101;});
    +    assert(comptime x: {break :x ~u64(2147483647) == 18446744071562067968;});
         testBinaryNot(0b1010101010101010);
     }
     
    @@ -331,7 +331,7 @@ test "f128" {
         comptime test_f128();
     }
     
    -fn make_f128(x: f128) -> f128 { x }
    +fn make_f128(x: f128) -> f128 { return x; }
     
     fn test_f128() {
         assert(@sizeOf(f128) == 16);
    diff --git a/test/cases/misc.zig b/test/cases/misc.zig
    index daa7c3eb98..e456ca529a 100644
    --- a/test/cases/misc.zig
    +++ b/test/cases/misc.zig
    @@ -110,17 +110,17 @@ fn testShortCircuit(f: bool, t: bool) {
         var hit_3 = f;
         var hit_4 = f;
     
    -    if (t or {assert(f); f}) {
    +    if (t or x: {assert(f); break :x f;}) {
             hit_1 = t;
         }
    -    if (f or { hit_2 = t; f }) {
    +    if (f or x: { hit_2 = t; break :x f; }) {
             assert(f);
         }
     
    -    if (t and { hit_3 = t; f }) {
    +    if (t and x: { hit_3 = t; break :x f; }) {
             assert(f);
         }
    -    if (f and {assert(f); f}) {
    +    if (f and x: {assert(f); break :x f;}) {
             assert(f);
         } else {
             hit_4 = t;
    @@ -135,11 +135,11 @@ test "truncate" {
         assert(testTruncate(0x10fd) == 0xfd);
     }
     fn testTruncate(x: u32) -> u8 {
    -    @truncate(u8, x)
    +    return @truncate(u8, x);
     }
     
     fn first4KeysOfHomeRow() -> []const u8 {
    -    "aoeu"
    +    return "aoeu";
     }
     
     test "return string from function" {
    @@ -167,7 +167,7 @@ test "memcpy and memset intrinsics" {
     }
     
     test "builtin static eval" {
    -    const x : i32 = comptime {1 + 2 + 3};
    +    const x : i32 = comptime x: {break :x 1 + 2 + 3;};
         assert(x == comptime 6);
     }
     
    @@ -190,7 +190,7 @@ test "slicing" {
     
     test "constant equal function pointers" {
         const alias = emptyFn;
    -    assert(comptime {emptyFn == alias});
    +    assert(comptime x: {break :x emptyFn == alias;});
     }
     
     fn emptyFn() {}
    @@ -280,14 +280,14 @@ test "cast small unsigned to larger signed" {
         assert(castSmallUnsignedToLargerSigned1(200) == i16(200));
         assert(castSmallUnsignedToLargerSigned2(9999) == i64(9999));
     }
    -fn castSmallUnsignedToLargerSigned1(x: u8) -> i16 { x }
    -fn castSmallUnsignedToLargerSigned2(x: u16) -> i64 { x }
    +fn castSmallUnsignedToLargerSigned1(x: u8) -> i16 { return x; }
    +fn castSmallUnsignedToLargerSigned2(x: u16) -> i64 { return x; }
     
     
     test "implicit cast after unreachable" {
         assert(outer() == 1234);
     }
    -fn inner() -> i32 { 1234 }
    +fn inner() -> i32 { return 1234; }
     fn outer() -> i64 {
         return inner();
     }
    @@ -310,8 +310,8 @@ test "call result of if else expression" {
     fn f2(x: bool) -> []const u8 {
         return (if (x) fA else fB)();
     }
    -fn fA() -> []const u8 { "a" }
    -fn fB() -> []const u8 { "b" }
    +fn fA() -> []const u8 { return "a"; }
    +fn fB() -> []const u8 { return "b"; }
     
     
     test "const expression eval handling of variables" {
    @@ -379,7 +379,7 @@ test "pointer comparison" {
         assert(ptrEql(b, b));
     }
     fn ptrEql(a: &const []const u8, b: &const []const u8) -> bool {
    -    a == b
    +    return a == b;
     }
     
     
    @@ -483,7 +483,7 @@ test "@typeId" {
             assert(@typeId(AUnion) == Tid.Union);
             assert(@typeId(fn()) == Tid.Fn);
             assert(@typeId(@typeOf(builtin)) == Tid.Namespace);
    -        assert(@typeId(@typeOf({this})) == Tid.Block);
    +        assert(@typeId(@typeOf(x: {break :x this;})) == Tid.Block);
             // TODO bound fn
             // TODO arg tuple
             // TODO opaque
    diff --git a/test/cases/reflection.zig b/test/cases/reflection.zig
    index cbd98d212f..9b298f8823 100644
    --- a/test/cases/reflection.zig
    +++ b/test/cases/reflection.zig
    @@ -22,7 +22,7 @@ test "reflection: function return type, var args, and param types" {
         }
     }
     
    -fn dummy(a: bool, b: i32, c: f32) -> i32 { 1234 }
    +fn dummy(a: bool, b: i32, c: f32) -> i32 { return 1234; }
     fn dummy_varargs(args: ...) {}
     
     test "reflection: struct member types and names" {
    diff --git a/test/cases/struct.zig b/test/cases/struct.zig
    index 1a9a03c71a..28792e9a73 100644
    --- a/test/cases/struct.zig
    +++ b/test/cases/struct.zig
    @@ -2,7 +2,7 @@ const assert = @import("std").debug.assert;
     const builtin = @import("builtin");
     
     const StructWithNoFields = struct {
    -    fn add(a: i32, b: i32) -> i32 { a + b }
    +    fn add(a: i32, b: i32) -> i32 { return a + b; }
     };
     const empty_global_instance = StructWithNoFields {};
     
    @@ -109,7 +109,7 @@ const Foo = struct {
         ptr: fn() -> i32,
     };
     
    -fn aFunc() -> i32 { 13 }
    +fn aFunc() -> i32 { return 13; }
     
     fn callStructField(foo: &const Foo) -> i32 {
         return foo.ptr();
    @@ -124,7 +124,7 @@ test "store member function in variable" {
     }
     const MemberFnTestFoo = struct {
         x: i32,
    -    fn member(foo: &const MemberFnTestFoo) -> i32 { foo.x }
    +    fn member(foo: &const MemberFnTestFoo) -> i32 { return foo.x; }
     };
     
     
    @@ -141,7 +141,7 @@ test "member functions" {
     const MemberFnRand = struct {
         seed: u32,
         pub fn getSeed(r: &const MemberFnRand) -> u32 {
    -        r.seed
    +        return r.seed;
         }
     };
     
    @@ -154,10 +154,10 @@ const Bar = struct {
         y: i32,
     };
     fn makeBar(x: i32, y: i32) -> Bar {
    -    Bar {
    +    return Bar {
             .x = x,
             .y = y,
    -    }
    +    };
     }
     
     test "empty struct method call" {
    @@ -166,7 +166,7 @@ test "empty struct method call" {
     }
     const EmptyStruct = struct {
         fn method(es: &const EmptyStruct) -> i32 {
    -        1234
    +        return 1234;
         }
     };
     
    @@ -176,14 +176,14 @@ test "return empty struct from fn" {
     }
     const EmptyStruct2 = struct {};
     fn testReturnEmptyStructFromFn() -> EmptyStruct2 {
    -    EmptyStruct2 {}
    +    return EmptyStruct2 {};
     }
     
     test "pass slice of empty struct to fn" {
         assert(testPassSliceOfEmptyStructToFn([]EmptyStruct2{ EmptyStruct2{} }) == 1);
     }
     fn testPassSliceOfEmptyStructToFn(slice: []const EmptyStruct2) -> usize {
    -    slice.len
    +    return slice.len;
     }
     
     const APackedStruct = packed struct {
    diff --git a/test/cases/switch.zig b/test/cases/switch.zig
    index 11c2178427..878c0af9e4 100644
    --- a/test/cases/switch.zig
    +++ b/test/cases/switch.zig
    @@ -21,12 +21,12 @@ test "switch with all ranges" {
     }
     
     fn testSwitchWithAllRanges(x: u32, y: u32) -> u32 {
    -    switch (x) {
    +    return switch (x) {
             0 ... 100 => 1,
             101 ... 200 => 2,
             201 ... 300 => 3,
             else => y,
    -    }
    +    };
     }
     
     test "implicit comptime switch" {
    @@ -132,7 +132,7 @@ test "switch with multiple expressions" {
         assert(x == 2);
     }
     fn returnsFive() -> i32 {
    -    5
    +    return 5;
     }
     
     
    @@ -161,10 +161,10 @@ test "switch on type" {
     }
     
     fn trueIfBoolFalseOtherwise(comptime T: type) -> bool {
    -    switch (T) {
    +    return switch (T) {
             bool => true,
             else => false,
    -    }
    +    };
     }
     
     test "switch handles all cases of number" {
    @@ -186,22 +186,22 @@ fn testSwitchHandleAllCases() {
     }
     
     fn testSwitchHandleAllCasesExhaustive(x: u2) -> u2 {
    -    switch (x) {
    +    return switch (x) {
             0 => u2(3),
             1 => 2,
             2 => 1,
             3 => 0,
    -    }
    +    };
     }
     
     fn testSwitchHandleAllCasesRange(x: u8) -> u8 {
    -    switch (x) {
    +    return switch (x) {
             0 ... 100 => u8(0),
             101 ... 200 => 1,
             201, 203 => 2,
             202 => 4,
             204 ... 255 => 3,
    -    }
    +    };
     }
     
     test "switch all prongs unreachable" {
    diff --git a/test/cases/switch_prong_err_enum.zig b/test/cases/switch_prong_err_enum.zig
    index 21f6b04037..be15193c74 100644
    --- a/test/cases/switch_prong_err_enum.zig
    +++ b/test/cases/switch_prong_err_enum.zig
    @@ -18,7 +18,7 @@ fn doThing(form_id: u64) -> %FormValue {
         return switch (form_id) {
             17 => FormValue { .Address = %return readOnce() },
             else => error.InvalidDebugInfo,
    -    }
    +    };
     }
     
     test "switch prong returns error enum" {
    diff --git a/test/cases/switch_prong_implicit_cast.zig b/test/cases/switch_prong_implicit_cast.zig
    index e7fe53b841..9e7c091494 100644
    --- a/test/cases/switch_prong_implicit_cast.zig
    +++ b/test/cases/switch_prong_implicit_cast.zig
    @@ -8,11 +8,11 @@ const FormValue = union(enum) {
     error Whatever;
     
     fn foo(id: u64) -> %FormValue {
    -    switch (id) {
    +    return switch (id) {
             2 => FormValue { .Two = true },
             1 => FormValue { .One = {} },
             else => return error.Whatever,
    -    }
    +    };
     }
     
     test "switch prong implicit cast" {
    diff --git a/test/cases/this.zig b/test/cases/this.zig
    index b64c52a098..b4e6b32a09 100644
    --- a/test/cases/this.zig
    +++ b/test/cases/this.zig
    @@ -3,7 +3,7 @@ const assert = @import("std").debug.assert;
     const module = this;
     
     fn Point(comptime T: type) -> type {
    -    struct {
    +    return struct {
             const Self = this;
             x: T,
             y: T,
    @@ -12,20 +12,16 @@ fn Point(comptime T: type) -> type {
                 self.x += 1;
                 self.y += 1;
             }
    -    }
    +    };
     }
     
     fn add(x: i32, y: i32) -> i32 {
    -    x + y
    +    return x + y;
     }
     
     fn factorial(x: i32) -> i32 {
         const selfFn = this;
    -    if (x == 0) {
    -        1
    -    } else {
    -        x * selfFn(x - 1)
    -    }
    +    return if (x == 0) 1 else x * selfFn(x - 1);
     }
     
     test "this refer to module call private fn" {
    diff --git a/test/cases/try.zig b/test/cases/try.zig
    index 2505443712..25b2f321fb 100644
    --- a/test/cases/try.zig
    +++ b/test/cases/try.zig
    @@ -7,9 +7,9 @@ test "try on error union" {
     }
     
     fn tryOnErrorUnionImpl() {
    -    const x = if (returnsTen()) |val| {
    +    const x = if (returnsTen()) |val|
             val + 1
    -    } else |err| switch (err) {
    +    else |err| switch (err) {
             error.ItBroke, error.NoMem => 1,
             error.CrappedOut => i32(2),
             else => unreachable,
    @@ -21,22 +21,14 @@ error ItBroke;
     error NoMem;
     error CrappedOut;
     fn returnsTen() -> %i32 {
    -    10
    +    return 10;
     }
     
     test "try without vars" {
    -    const result1 = if (failIfTrue(true)) {
    -        1
    -    } else |_| {
    -        i32(2)
    -    };
    +    const result1 = if (failIfTrue(true)) 1 else |_| i32(2);
         assert(result1 == 2);
     
    -    const result2 = if (failIfTrue(false)) {
    -        1
    -    } else |_| {
    -        i32(2)
    -    };
    +    const result2 = if (failIfTrue(false)) 1 else |_| i32(2);
         assert(result2 == 1);
     }
     
    diff --git a/test/cases/var_args.zig b/test/cases/var_args.zig
    index 363816bc8a..7d16106362 100644
    --- a/test/cases/var_args.zig
    +++ b/test/cases/var_args.zig
    @@ -58,8 +58,8 @@ fn extraFn(extra: u32, args: ...) -> usize {
     
     const foos = []fn(...) -> bool { foo1, foo2 };
     
    -fn foo1(args: ...) -> bool { true }
    -fn foo2(args: ...) -> bool { false }
    +fn foo1(args: ...) -> bool { return true; }
    +fn foo2(args: ...) -> bool { return false; }
     
     test "array of var args functions" {
         assert(foos[0]());
    diff --git a/test/cases/while.zig b/test/cases/while.zig
    index c16171d4a3..e61fe1f9d2 100644
    --- a/test/cases/while.zig
    +++ b/test/cases/while.zig
    @@ -118,73 +118,61 @@ test "while with error union condition" {
     var numbers_left: i32 = undefined;
     error OutOfNumbers;
     fn getNumberOrErr() -> %i32 {
    -    return if (numbers_left == 0) {
    +    return if (numbers_left == 0)
             error.OutOfNumbers
    -    } else {
    +    else x: {
             numbers_left -= 1;
    -        numbers_left
    +        break :x numbers_left;
         };
     }
     fn getNumberOrNull() -> ?i32 {
    -    return if (numbers_left == 0) {
    +    return if (numbers_left == 0)
             null
    -    } else {
    +    else x: {
             numbers_left -= 1;
    -        numbers_left
    +        break :x numbers_left;
         };
     }
     
     test "while on nullable with else result follow else prong" {
         const result = while (returnNull()) |value| {
             break value;
    -    } else {
    -        i32(2)
    -    };
    +    } else i32(2);
         assert(result == 2);
     }
     
     test "while on nullable with else result follow break prong" {
         const result = while (returnMaybe(10)) |value| {
             break value;
    -    } else {
    -        i32(2)
    -    };
    +    } else i32(2);
         assert(result == 10);
     }
     
     test "while on error union with else result follow else prong" {
         const result = while (returnError()) |value| {
             break value;
    -    } else |err| {
    -        i32(2)
    -    };
    +    } else |err| i32(2);
         assert(result == 2);
     }
     
     test "while on error union with else result follow break prong" {
         const result = while (returnSuccess(10)) |value| {
             break value;
    -    } else |err| {
    -        i32(2)
    -    };
    +    } else |err| i32(2);
         assert(result == 10);
     }
     
     test "while on bool with else result follow else prong" {
         const result = while (returnFalse()) {
             break i32(10);
    -    } else {
    -        i32(2)
    -    };
    +    } else i32(2);
         assert(result == 2);
     }
     
     test "while on bool with else result follow break prong" {
         const result = while (returnTrue()) {
             break i32(10);
    -    } else {
    -        i32(2)
    -    };
    +    } else i32(2);
         assert(result == 10);
     }
     
    @@ -215,10 +203,10 @@ fn testContinueOuter() {
         }
     }
     
    -fn returnNull() -> ?i32 { null }
    -fn returnMaybe(x: i32) -> ?i32 { x }
    +fn returnNull() -> ?i32 { return null; }
    +fn returnMaybe(x: i32) -> ?i32 { return x; }
     error YouWantedAnError;
    -fn returnError() -> %i32 { error.YouWantedAnError }
    -fn returnSuccess(x: i32) -> %i32 { x }
    -fn returnFalse() -> bool { false }
    -fn returnTrue() -> bool { true }
    +fn returnError() -> %i32 { return error.YouWantedAnError; }
    +fn returnSuccess(x: i32) -> %i32 { return x; }
    +fn returnFalse() -> bool { return false; }
    +fn returnTrue() -> bool { return true; }
    diff --git a/test/compare_output.zig b/test/compare_output.zig
    index ad9c91ff20..88e25bf40c 100644
    --- a/test/compare_output.zig
    +++ b/test/compare_output.zig
    @@ -10,7 +10,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\}
         , "Hello, world!" ++ os.line_sep);
     
    -    cases.addCase({
    +    cases.addCase(x: {
             var tc = cases.create("multiple files with private function",
                 \\use @import("std").io;
                 \\use @import("foo.zig");
    @@ -41,10 +41,10 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
                 \\}
             );
     
    -        tc
    +        break :x tc;
         });
     
    -    cases.addCase({
    +    cases.addCase(x: {
             var tc = cases.create("import segregation",
                 \\use @import("foo.zig");
                 \\use @import("bar.zig");
    @@ -82,10 +82,10 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
                 \\}
             );
     
    -        tc
    +        break :x tc;
         });
     
    -    cases.addCase({
    +    cases.addCase(x: {
             var tc = cases.create("two files use import each other",
                 \\use @import("a.zig");
                 \\
    @@ -112,7 +112,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
                 \\pub const b_text = a_text;
             );
     
    -        tc
    +        break :x tc;
         });
     
         cases.add("hello world without libc",
    @@ -286,11 +286,11 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    const a_int = @ptrCast(&align(1) i32, a ?? unreachable);
             \\    const b_int = @ptrCast(&align(1) i32, b ?? unreachable);
             \\    if (*a_int < *b_int) {
    -        \\        -1
    +        \\        return -1;
             \\    } else if (*a_int > *b_int) {
    -        \\        1
    +        \\        return 1;
             \\    } else {
    -        \\        c_int(0)
    +        \\        return 0;
             \\    }
             \\}
             \\
    @@ -342,13 +342,13 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\const Foo = struct {
             \\    field1: Bar,
             \\
    -        \\    fn method(a: &const Foo) -> bool { true }
    +        \\    fn method(a: &const Foo) -> bool { return true; }
             \\};
             \\
             \\const Bar = struct {
             \\    field2: i32,
             \\
    -        \\    fn method(b: &const Bar) -> bool { true }
    +        \\    fn method(b: &const Bar) -> bool { return true; }
             \\};
             \\
             \\pub fn main() -> %void {
    @@ -429,7 +429,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\fn its_gonna_pass() -> %void { }
         , "before\nafter\ndefer3\ndefer1\n");
     
    -    cases.addCase({
    +    cases.addCase(x: {
             var tc = cases.create("@embedFile",
                 \\const foo_txt = @embedFile("foo.txt");
                 \\const io = @import("std").io;
    @@ -442,10 +442,10 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
     
             tc.addSourceFile("foo.txt", "1234\nabcd\n");
     
    -        tc
    +        break :x tc;
         });
     
    -    cases.addCase({
    +    cases.addCase(x: {
             var tc = cases.create("parsing args",
                 \\const std = @import("std");
                 \\const io = std.io;
    @@ -483,10 +483,10 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
                 "last arg",
             });
     
    -        tc
    +        break :x tc;
         });
     
    -    cases.addCase({
    +    cases.addCase(x: {
             var tc = cases.create("parsing args new API",
                 \\const std = @import("std");
                 \\const io = std.io;
    @@ -524,6 +524,6 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
                 "last arg",
             });
     
    -        tc
    +        break :x tc;
         });
     }
    diff --git a/test/compile_errors.zig b/test/compile_errors.zig
    index 3446acda02..60e5c3614d 100644
    --- a/test/compile_errors.zig
    +++ b/test/compile_errors.zig
    @@ -9,7 +9,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\        }
             \\    }
             \\}
    -    , ".tmp_source.zig:4:13: error: labeled loop not found: 'outer'");
    +    , ".tmp_source.zig:4:13: error: label not found: 'outer'");
     
         cases.add("labeled continue not found",
             \\export fn entry() {
    @@ -39,7 +39,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - block expr",
             \\export fn entry() {
    @@ -48,7 +48,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - comptime statement",
             \\export fn entry() {
    @@ -57,7 +57,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    comptime ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - comptime expression",
             \\export fn entry() {
    @@ -66,7 +66,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = comptime {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - defer",
             \\export fn entry() {
    @@ -84,7 +84,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    if(true) ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - if expression",
             \\export fn entry() {
    @@ -93,7 +93,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = if(true) {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - if-else statement",
             \\export fn entry() {
    @@ -102,7 +102,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    if(true) ({}) else ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - if-else expression",
             \\export fn entry() {
    @@ -111,7 +111,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = if(true) {} else {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - if-else-if statement",
             \\export fn entry() {
    @@ -120,7 +120,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    if(true) ({}) else if(true) ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - if-else-if expression",
             \\export fn entry() {
    @@ -129,7 +129,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = if(true) {} else if(true) {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - if-else-if-else statement",
             \\export fn entry() {
    @@ -138,7 +138,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    if(true) ({}) else if(true) ({}) else ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - if-else-if-else expression",
             \\export fn entry() {
    @@ -147,7 +147,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = if(true) {} else if(true) {} else {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - test statement",
             \\export fn entry() {
    @@ -156,7 +156,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    if (foo()) |_| ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - test expression",
             \\export fn entry() {
    @@ -165,7 +165,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = if (foo()) |_| {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - while statement",
             \\export fn entry() {
    @@ -174,7 +174,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    while(true) ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - while expression",
             \\export fn entry() {
    @@ -183,7 +183,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = while(true) {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - while-continue statement",
             \\export fn entry() {
    @@ -192,7 +192,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    while(true):({}) ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - while-continue expression",
             \\export fn entry() {
    @@ -201,7 +201,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = while(true):({}) {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - for statement",
             \\export fn entry() {
    @@ -210,7 +210,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    for(foo()) ({})
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("implicit semicolon - for expression",
             \\export fn entry() {
    @@ -219,7 +219,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    _ = for(foo()) {}
             \\    var bad = {};
             \\}
    -    , ".tmp_source.zig:5:5: error: invalid token: 'var'");
    +    , ".tmp_source.zig:5:5: error: expected token ';', found 'var'");
     
         cases.add("multiple function definitions",
             \\fn a() {}
    @@ -276,12 +276,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("undeclared identifier",
             \\export fn a() {
    +        \\    return
             \\    b +
    -        \\    c
    +        \\    c;
             \\}
         ,
    -            ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'",
    -            ".tmp_source.zig:3:5: error: use of undeclared identifier 'c'");
    +            ".tmp_source.zig:3:5: error: use of undeclared identifier 'b'",
    +            ".tmp_source.zig:4:5: error: use of undeclared identifier 'c'");
     
         cases.add("parameter redeclaration",
             \\fn f(a : i32, a : i32) {
    @@ -306,9 +307,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
         cases.add("variable has wrong type",
             \\export fn f() -> i32 {
             \\    const a = c"a";
    -        \\    a
    +        \\    return a;
             \\}
    -    , ".tmp_source.zig:3:5: error: expected type 'i32', found '&const u8'");
    +    , ".tmp_source.zig:3:12: error: expected type 'i32', found '&const u8'");
     
         cases.add("if condition is bool, not int",
             \\export fn f() {
    @@ -393,23 +394,23 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("missing else clause",
             \\fn f(b: bool) {
    -        \\    const x : i32 = if (b) { 1 };
    -        \\    const y = if (b) { i32(1) };
    +        \\    const x : i32 = if (b) h: { break :h 1; };
    +        \\    const y = if (b) h: { break :h i32(1); };
             \\}
             \\export fn entry() { f(true); }
    -    , ".tmp_source.zig:2:30: error: integer value 1 cannot be implicitly casted to type 'void'",
    +    , ".tmp_source.zig:2:42: error: integer value 1 cannot be implicitly casted to type 'void'",
                      ".tmp_source.zig:3:15: error: incompatible types: 'i32' and 'void'");
     
         cases.add("direct struct loop",
             \\const A = struct { a : A, };
    -        \\export fn entry() -> usize { @sizeOf(A) }
    +        \\export fn entry() -> usize { return @sizeOf(A); }
         , ".tmp_source.zig:1:11: error: struct 'A' contains itself");
     
         cases.add("indirect struct loop",
             \\const A = struct { b : B, };
             \\const B = struct { c : C, };
             \\const C = struct { a : A, };
    -        \\export fn entry() -> usize { @sizeOf(A) }
    +        \\export fn entry() -> usize { return @sizeOf(A); }
         , ".tmp_source.zig:1:11: error: struct 'A' contains itself");
     
         cases.add("invalid struct field",
    @@ -507,10 +508,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("cast unreachable",
             \\fn f() -> i32 {
    -        \\    i32(return 1)
    +        \\    return i32(return 1);
             \\}
             \\export fn entry() { _ = f(); }
    -    , ".tmp_source.zig:2:8: error: unreachable code");
    +    , ".tmp_source.zig:2:15: error: unreachable code");
     
         cases.add("invalid builtin fn",
             \\fn f() -> @bogus(foo) {
    @@ -533,7 +534,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("struct init syntax for array",
             \\const foo = []u16{.x = 1024,};
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:1:18: error: type '[]u16' does not support struct initialization syntax");
     
         cases.add("type variables must be constant",
    @@ -576,7 +577,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    }
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch");
     
         cases.add("switch expression - duplicate enumeration prong",
    @@ -596,7 +597,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    }
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         , ".tmp_source.zig:13:15: error: duplicate switch value",
           ".tmp_source.zig:10:15: note: other value is here");
     
    @@ -618,7 +619,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    }
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         , ".tmp_source.zig:13:15: error: duplicate switch value",
           ".tmp_source.zig:10:15: note: other value is here");
     
    @@ -641,20 +642,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\        0 => {},
             \\    }
             \\}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         ,
             ".tmp_source.zig:2:5: error: switch must handle all possibilities");
     
         cases.add("switch expression - duplicate or overlapping integer value",
             \\fn foo(x: u8) -> u8 {
    -        \\    switch (x) {
    +        \\    return switch (x) {
             \\        0 ... 100 => u8(0),
             \\        101 ... 200 => 1,
             \\        201, 203 ... 207 => 2,
             \\        206 ... 255 => 3,
    -        \\    }
    +        \\    };
             \\}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         ,
             ".tmp_source.zig:6:9: error: duplicate switch value",
             ".tmp_source.zig:5:14: note: previous value is here");
    @@ -666,14 +667,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    }
             \\}
             \\const y: u8 = 100;
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         ,
             ".tmp_source.zig:2:5: error: else prong required when switching on type '&u8'");
     
         cases.add("global variable initializer must be constant expression",
             \\extern fn foo() -> i32;
             \\const x = foo();
    -        \\export fn entry() -> i32 { x }
    +        \\export fn entry() -> i32 { return x; }
         , ".tmp_source.zig:2:11: error: unable to evaluate constant expression");
     
         cases.add("array concatenation with wrong type",
    @@ -681,38 +682,38 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const derp = usize(1234);
             \\const a = derp ++ "foo";
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(a)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(a)); }
         , ".tmp_source.zig:3:11: error: expected array or C string literal, found 'usize'");
     
         cases.add("non compile time array concatenation",
             \\fn f() -> []u8 {
    -        \\    s ++ "foo"
    +        \\    return s ++ "foo";
             \\}
             \\var s: [10]u8 = undefined;
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    -    , ".tmp_source.zig:2:5: error: unable to evaluate constant expression");
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
    +    , ".tmp_source.zig:2:12: error: unable to evaluate constant expression");
     
         cases.add("@cImport with bogus include",
             \\const c = @cImport(@cInclude("bogus.h"));
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(c.bogo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(c.bogo)); }
         , ".tmp_source.zig:1:11: error: C import failed",
                      ".h:1:10: note: 'bogus.h' file not found");
     
         cases.add("address of number literal",
             \\const x = 3;
             \\const y = &x;
    -        \\fn foo() -> &const i32 { y }
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    -    , ".tmp_source.zig:3:26: error: expected type '&const i32', found '&const (integer literal)'");
    +        \\fn foo() -> &const i32 { return y; }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
    +    , ".tmp_source.zig:3:33: error: expected type '&const i32', found '&const (integer literal)'");
     
         cases.add("integer overflow error",
             \\const x : u8 = 300;
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(x)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(x)); }
         , ".tmp_source.zig:1:16: error: integer value 300 cannot be implicitly casted to type 'u8'");
     
         cases.add("incompatible number literals",
             \\const x = 2 == 2.0;
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(x)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(x)); }
         , ".tmp_source.zig:1:11: error: integer value 2 cannot be implicitly casted to type '(float literal)'");
     
         cases.add("missing function call param",
    @@ -738,32 +739,32 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    const result = members[index]();
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         , ".tmp_source.zig:20:34: error: expected 1 arguments, found 0");
     
         cases.add("missing function name and param name",
             \\fn () {}
             \\fn f(i32) {}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         ,
                 ".tmp_source.zig:1:1: error: missing function name",
                 ".tmp_source.zig:2:6: error: missing parameter name");
     
         cases.add("wrong function type",
             \\const fns = []fn(){ a, b, c };
    -        \\fn a() -> i32 {0}
    -        \\fn b() -> i32 {1}
    -        \\fn c() -> i32 {2}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) }
    +        \\fn a() -> i32 {return 0;}
    +        \\fn b() -> i32 {return 1;}
    +        \\fn c() -> i32 {return 2;}
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(fns)); }
         , ".tmp_source.zig:1:21: error: expected type 'fn()', found 'fn() -> i32'");
     
         cases.add("extern function pointer mismatch",
             \\const fns = [](fn(i32)->i32){ a, b, c };
    -        \\pub fn a(x: i32) -> i32 {x + 0}
    -        \\pub fn b(x: i32) -> i32 {x + 1}
    -        \\export fn c(x: i32) -> i32 {x + 2}
    +        \\pub fn a(x: i32) -> i32 {return x + 0;}
    +        \\pub fn b(x: i32) -> i32 {return x + 1;}
    +        \\export fn c(x: i32) -> i32 {return x + 2;}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(fns)); }
         , ".tmp_source.zig:1:37: error: expected type 'fn(i32) -> i32', found 'extern fn(i32) -> i32'");
     
     
    @@ -771,14 +772,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const x : f64 = 1.0;
             \\const y : f32 = x;
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(y)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(y)); }
         , ".tmp_source.zig:2:17: error: expected type 'f32', found 'f64'");
     
     
         cases.add("colliding invalid top level functions",
             \\fn func() -> bogus {}
             \\fn func() -> bogus {}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(func)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(func)); }
         ,
                 ".tmp_source.zig:2:1: error: redefinition of 'func'",
                 ".tmp_source.zig:1:14: error: use of undeclared identifier 'bogus'");
    @@ -786,7 +787,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("bogus compile var",
             \\const x = @import("builtin").bogus;
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(x)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(x)); }
         , ".tmp_source.zig:1:29: error: no member named 'bogus' in '");
     
     
    @@ -795,11 +796,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    y: [get()]u8,
             \\};
             \\var global_var: usize = 1;
    -        \\fn get() -> usize { global_var }
    +        \\fn get() -> usize { return global_var; }
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(Foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(Foo)); }
         ,
    -            ".tmp_source.zig:5:21: error: unable to evaluate constant expression",
    +            ".tmp_source.zig:5:28: error: unable to evaluate constant expression",
                 ".tmp_source.zig:2:12: note: called from here",
                 ".tmp_source.zig:2:8: note: called from here");
     
    @@ -810,7 +811,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\};
             \\const x = Foo {.field = 1} + Foo {.field = 2};
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(x)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(x)); }
         , ".tmp_source.zig:4:28: error: invalid operands to binary expression: 'Foo' and 'Foo'");
     
     
    @@ -820,10 +821,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const int_x = u32(1) / u32(0);
             \\const float_x = f32(1.0) / f32(0.0);
             \\
    -        \\export fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) }
    -        \\export fn entry2() -> usize { @sizeOf(@typeOf(lit_float_x)) }
    -        \\export fn entry3() -> usize { @sizeOf(@typeOf(int_x)) }
    -        \\export fn entry4() -> usize { @sizeOf(@typeOf(float_x)) }
    +        \\export fn entry1() -> usize { return @sizeOf(@typeOf(lit_int_x)); }
    +        \\export fn entry2() -> usize { return @sizeOf(@typeOf(lit_float_x)); }
    +        \\export fn entry3() -> usize { return @sizeOf(@typeOf(int_x)); }
    +        \\export fn entry4() -> usize { return @sizeOf(@typeOf(float_x)); }
         ,
                 ".tmp_source.zig:1:21: error: division by zero is undefined",
                 ".tmp_source.zig:2:25: error: division by zero is undefined",
    @@ -835,14 +836,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const foo = "a
             \\b";
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:1:13: error: newline not allowed in string literal");
     
         cases.add("invalid comparison for function pointers",
             \\fn foo() {}
             \\const invalid = foo > foo;
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(invalid)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(invalid)); }
         , ".tmp_source.zig:2:21: error: operator not allowed for type 'fn()'");
     
         cases.add("generic function instance with non-constant expression",
    @@ -851,13 +852,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    return foo(a, b);
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(test1)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(test1)); }
         , ".tmp_source.zig:3:16: error: unable to evaluate constant expression");
     
         cases.add("assign null to non-nullable pointer",
             \\const a: &u8 = null;
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(a)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(a)); }
         , ".tmp_source.zig:1:16: error: expected type '&u8', found '(null)'");
     
         cases.add("indexing an array of size zero",
    @@ -870,18 +871,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
         cases.add("compile time division by zero",
             \\const y = foo(0);
             \\fn foo(x: u32) -> u32 {
    -        \\    1 / x
    +        \\    return 1 / x;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(y)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(y)); }
         ,
    -            ".tmp_source.zig:3:7: error: division by zero is undefined",
    +            ".tmp_source.zig:3:14: error: division by zero is undefined",
                 ".tmp_source.zig:1:14: note: called from here");
     
         cases.add("branch on undefined value",
             \\const x = if (undefined) true else false;
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(x)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(x)); }
         , ".tmp_source.zig:1:15: error: use of undefined value");
     
     
    @@ -891,7 +892,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    return fibbonaci(x - 1) + fibbonaci(x - 2);
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(seventh_fib_number)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(seventh_fib_number)); }
         ,
                 ".tmp_source.zig:3:21: error: evaluation exceeded 1000 backwards branches",
                 ".tmp_source.zig:3:21: note: called from here");
    @@ -899,7 +900,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
         cases.add("@embedFile with bogus file",
             \\const resource = @embedFile("bogus.txt");
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(resource)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(resource)); }
         , ".tmp_source.zig:1:29: error: unable to find '", "bogus.txt'");
     
         cases.add("non-const expression in struct literal outside function",
    @@ -909,7 +910,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const a = Foo {.x = get_it()};
             \\extern fn get_it() -> i32;
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(a)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(a)); }
         , ".tmp_source.zig:4:21: error: unable to evaluate constant expression");
     
         cases.add("non-const expression function call with struct return value outside function",
    @@ -919,11 +920,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const a = get_it();
             \\fn get_it() -> Foo {
             \\    global_side_effect = true;
    -        \\    Foo {.x = 13}
    +        \\    return Foo {.x = 13};
             \\}
             \\var global_side_effect = false;
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(a)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(a)); }
         ,
                 ".tmp_source.zig:6:24: error: unable to evaluate constant expression",
                 ".tmp_source.zig:4:17: note: called from here");
    @@ -939,21 +940,21 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("illegal comparison of types",
             \\fn bad_eql_1(a: []u8, b: []u8) -> bool {
    -        \\    a == b
    +        \\    return a == b;
             \\}
             \\const EnumWithData = union(enum) {
             \\    One: void,
             \\    Two: i32,
             \\};
             \\fn bad_eql_2(a: &const EnumWithData, b: &const EnumWithData) -> bool {
    -        \\    *a == *b
    +        \\    return *a == *b;
             \\}
             \\
    -        \\export fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) }
    -        \\export fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) }
    +        \\export fn entry1() -> usize { return @sizeOf(@typeOf(bad_eql_1)); }
    +        \\export fn entry2() -> usize { return @sizeOf(@typeOf(bad_eql_2)); }
         ,
    -            ".tmp_source.zig:2:7: error: operator not allowed for type '[]u8'",
    -            ".tmp_source.zig:9:8: error: operator not allowed for type 'EnumWithData'");
    +            ".tmp_source.zig:2:14: error: operator not allowed for type '[]u8'",
    +            ".tmp_source.zig:9:15: error: operator not allowed for type 'EnumWithData'");
     
         cases.add("non-const switch number literal",
             \\export fn foo() {
    @@ -964,7 +965,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    };
             \\}
             \\fn bar() -> i32 {
    -        \\    2
    +        \\    return 2;
             \\}
         , ".tmp_source.zig:2:15: error: unable to infer expression type");
     
    @@ -987,56 +988,56 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
         cases.add("negation overflow in function evaluation",
             \\const y = neg(-128);
             \\fn neg(x: i8) -> i8 {
    -        \\    -x
    +        \\    return -x;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(y)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(y)); }
         ,
    -            ".tmp_source.zig:3:5: error: negation caused overflow",
    +            ".tmp_source.zig:3:12: error: negation caused overflow",
                 ".tmp_source.zig:1:14: note: called from here");
     
         cases.add("add overflow in function evaluation",
             \\const y = add(65530, 10);
             \\fn add(a: u16, b: u16) -> u16 {
    -        \\    a + b
    +        \\    return a + b;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(y)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(y)); }
         ,
    -            ".tmp_source.zig:3:7: error: operation caused overflow",
    +            ".tmp_source.zig:3:14: error: operation caused overflow",
                 ".tmp_source.zig:1:14: note: called from here");
     
     
         cases.add("sub overflow in function evaluation",
             \\const y = sub(10, 20);
             \\fn sub(a: u16, b: u16) -> u16 {
    -        \\    a - b
    +        \\    return a - b;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(y)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(y)); }
         ,
    -            ".tmp_source.zig:3:7: error: operation caused overflow",
    +            ".tmp_source.zig:3:14: error: operation caused overflow",
                 ".tmp_source.zig:1:14: note: called from here");
     
         cases.add("mul overflow in function evaluation",
             \\const y = mul(300, 6000);
             \\fn mul(a: u16, b: u16) -> u16 {
    -        \\    a * b
    +        \\    return a * b;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(y)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(y)); }
         ,
    -            ".tmp_source.zig:3:7: error: operation caused overflow",
    +            ".tmp_source.zig:3:14: error: operation caused overflow",
                 ".tmp_source.zig:1:14: note: called from here");
     
         cases.add("truncate sign mismatch",
             \\fn f() -> i8 {
             \\    const x: u32 = 10;
    -        \\    @truncate(i8, x)
    +        \\    return @truncate(i8, x);
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    -    , ".tmp_source.zig:3:19: error: expected signed integer type, found 'u32'");
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
    +    , ".tmp_source.zig:3:26: error: expected signed integer type, found 'u32'");
     
         cases.add("%return in function with non error return type",
             \\export fn f() {
    @@ -1067,16 +1068,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("export function with comptime parameter",
             \\export fn foo(comptime x: i32, y: i32) -> i32{
    -        \\    x + y
    +        \\    return x + y;
             \\}
         , ".tmp_source.zig:1:15: error: comptime parameter not allowed in function with calling convention 'ccc'");
     
         cases.add("extern function with comptime parameter",
             \\extern fn foo(comptime x: i32, y: i32) -> i32;
             \\fn f() -> i32 {
    -        \\    foo(1, 2)
    +        \\    return foo(1, 2);
             \\}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         , ".tmp_source.zig:1:15: error: comptime parameter not allowed in function with calling convention 'ccc'");
     
         cases.add("convert fixed size array to slice with invalid size",
    @@ -1090,15 +1091,15 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\var a: u32 = 0;
             \\pub fn List(comptime T: type) -> type {
             \\    a += 1;
    -        \\    SmallList(T, 8)
    +        \\    return SmallList(T, 8);
             \\}
             \\
             \\pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) -> type {
    -        \\    struct {
    +        \\    return struct {
             \\        items: []T,
             \\        length: usize,
             \\        prealloc_items: [STATIC_SIZE]T,
    -        \\    }
    +        \\    };
             \\}
             \\
             \\export fn function_with_return_type_type() {
    @@ -1113,7 +1114,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\fn f(m: []const u8) {
             \\    m.copy(u8, self[0..], m);
             \\}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         , ".tmp_source.zig:3:6: error: no member named 'copy' in '[]const u8'");
     
         cases.add("wrong number of arguments for method fn call",
    @@ -1124,7 +1125,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\
             \\    foo.method(1, 2);
             \\}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(f)); }
         , ".tmp_source.zig:6:15: error: expected 2 arguments, found 3");
     
         cases.add("assign through constant pointer",
    @@ -1149,7 +1150,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\fn foo(blah: []u8) {
             \\    for (blah) { }
             \\}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:2:5: error: for loop expression missing element parameter");
     
         cases.add("misspelled type with pointer only reference",
    @@ -1182,7 +1183,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    var jd = JsonNode {.kind = JsonType.JSONArray , .jobject = JsonOA.JSONArray {jll} };
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:5:16: error: use of undeclared identifier 'JsonList'");
     
         cases.add("method call with first arg type primitive",
    @@ -1190,9 +1191,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    x: i32,
             \\
             \\    fn init(x: i32) -> Foo {
    -        \\        Foo {
    +        \\        return Foo {
             \\            .x = x,
    -        \\        }
    +        \\        };
             \\    }
             \\};
             \\
    @@ -1209,10 +1210,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    allocator: &Allocator,
             \\
             \\    pub fn init(allocator: &Allocator) -> List {
    -        \\        List {
    +        \\        return List {
             \\            .len = 0,
             \\            .allocator = allocator,
    -        \\        }
    +        \\        };
             \\    }
             \\};
             \\
    @@ -1235,10 +1236,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const TINY_QUANTUM_SIZE = 1 << TINY_QUANTUM_SHIFT;
             \\var block_aligned_stuff: usize = (4 + TINY_QUANTUM_SIZE) & ~(TINY_QUANTUM_SIZE - 1);
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(block_aligned_stuff)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(block_aligned_stuff)); }
         , ".tmp_source.zig:3:60: error: unable to perform binary not operation on type '(integer literal)'");
     
    -    cases.addCase({
    +    cases.addCase(x: {
             const tc = cases.create("multiple files with private function error",
                 \\const foo = @import("foo.zig");
                 \\
    @@ -1253,14 +1254,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
                 \\fn privateFunction() { }
             );
     
    -        tc
    +        break :x tc;
         });
     
         cases.add("container init with non-type",
             \\const zero: i32 = 0;
             \\const a = zero{1};
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(a)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(a)); }
         , ".tmp_source.zig:2:11: error: expected type, found 'i32'");
     
         cases.add("assign to constant field",
    @@ -1288,22 +1289,22 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    return 0;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(testTrickyDefer)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(testTrickyDefer)); }
         , ".tmp_source.zig:4:11: error: cannot return from defer expression");
     
         cases.add("attempt to access var args out of bounds",
             \\fn add(args: ...) -> i32 {
    -        \\    args[0] + args[1]
    +        \\    return args[0] + args[1];
             \\}
             \\
             \\fn foo() -> i32 {
    -        \\    add(i32(1234))
    +        \\    return add(i32(1234));
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         ,
    -            ".tmp_source.zig:2:19: error: index 1 outside argument list of size 1",
    -            ".tmp_source.zig:6:8: note: called from here");
    +            ".tmp_source.zig:2:26: error: index 1 outside argument list of size 1",
    +            ".tmp_source.zig:6:15: note: called from here");
     
         cases.add("pass integer literal to var args",
             \\fn add(args: ...) -> i32 {
    @@ -1315,11 +1316,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\}
             \\
             \\fn bar() -> i32 {
    -        \\    add(1, 2, 3, 4)
    +        \\    return add(1, 2, 3, 4);
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(bar)) }
    -    , ".tmp_source.zig:10:9: error: parameter of type '(integer literal)' requires comptime");
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(bar)); }
    +    , ".tmp_source.zig:10:16: error: parameter of type '(integer literal)' requires comptime");
     
         cases.add("assign too big number to u16",
             \\export fn foo() {
    @@ -1329,12 +1330,12 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("global variable alignment non power of 2",
             \\const some_data: [100]u8 align(3) = undefined;
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(some_data)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(some_data)); }
         , ".tmp_source.zig:1:32: error: alignment value 3 is not a power of 2");
     
         cases.add("function alignment non power of 2",
             \\extern fn foo() align(3);
    -        \\export fn entry() { foo() }
    +        \\export fn entry() { return foo(); }
         , ".tmp_source.zig:1:23: error: alignment value 3 is not a power of 2");
     
         cases.add("compile log",
    @@ -1369,7 +1370,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    return *x;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:8:26: error: expected type '&const u3', found '&align(1:3:6) const u3'");
     
         cases.add("referring to a struct that is invalid",
    @@ -1405,14 +1406,14 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\export fn foo() {
             \\    bar();
             \\}
    -        \\fn bar() -> i32 { 0 }
    +        \\fn bar() -> i32 { return 0; }
         , ".tmp_source.zig:2:8: error: expression value is ignored");
     
         cases.add("ignored assert-err-ok return value",
             \\export fn foo() {
             \\    %%bar();
             \\}
    -        \\fn bar() -> %i32 { 0 }
    +        \\fn bar() -> %i32 { return 0; }
         , ".tmp_source.zig:2:5: error: expression value is ignored");
     
         cases.add("ignored statement value",
    @@ -1439,11 +1440,11 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\}
         , ".tmp_source.zig:2:12: error: expression value is ignored");
     
    -    cases.add("ignored defered statement value",
    +    cases.add("ignored defered function call",
             \\export fn foo() {
             \\    defer bar();
             \\}
    -        \\fn bar() -> %i32 { 0 }
    +        \\fn bar() -> %i32 { return 0; }
         , ".tmp_source.zig:2:14: error: expression value is ignored");
     
         cases.add("dereference an array",
    @@ -1454,7 +1455,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    return (*out)[0..1];
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(pass)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(pass)); }
         , ".tmp_source.zig:4:5: error: attempt to dereference non pointer type '[10]u8'");
     
         cases.add("pass const ptr to mutable ptr fn",
    @@ -1467,10 +1468,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    return true;
             \\}
             \\
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:4:19: error: expected type '&[]const u8', found '&const []const u8'");
     
    -    cases.addCase({
    +    cases.addCase(x: {
             const tc = cases.create("export collision",
                 \\const foo = @import("foo.zig");
                 \\
    @@ -1486,13 +1487,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
                 \\pub const baz = 1234;
             );
     
    -        tc
    +        break :x tc;
         });
     
         cases.add("pass non-copyable type by value to function",
             \\const Point = struct { x: i32, y: i32, };
             \\fn foo(p: Point) { }
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:2:11: error: type 'Point' is not copyable; cannot pass by value");
     
         cases.add("implicit cast from array to mutable slice",
    @@ -1515,7 +1516,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\fn foo(e: error) -> u2 {
             \\    return u2(e);
             \\}
    -        \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) }
    +        \\export fn entry() -> usize { return @sizeOf(@typeOf(foo)); }
         , ".tmp_source.zig:4:14: error: too many error values to fit in 'u2'");
     
         cases.add("asm at compile time",
    @@ -1665,17 +1666,17 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("inner struct member shadowing outer struct member",
             \\fn A() -> type {
    -        \\    struct {
    +        \\    return struct {
             \\        b: B(),
             \\
             \\        const Self = this;
             \\
             \\        fn B() -> type {
    -        \\            struct {
    +        \\            return struct {
             \\                const Self = this;
    -        \\            }
    +        \\            };
             \\        }
    -        \\    }
    +        \\    };
             \\}
             \\comptime {
             \\    assert(A().B().Self != A().Self);
    @@ -1691,7 +1692,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\export fn foo() {
             \\    while (bar()) {}
             \\}
    -        \\fn bar() -> ?i32 { 1 }
    +        \\fn bar() -> ?i32 { return 1; }
         ,
             ".tmp_source.zig:2:15: error: expected type 'bool', found '?i32'");
     
    @@ -1699,7 +1700,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\export fn foo() {
             \\    while (bar()) {}
             \\}
    -        \\fn bar() -> %i32 { 1 }
    +        \\fn bar() -> %i32 { return 1; }
         ,
             ".tmp_source.zig:2:15: error: expected type 'bool', found '%i32'");
     
    @@ -1707,7 +1708,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\export fn foo() {
             \\    while (bar()) |x| {}
             \\}
    -        \\fn bar() -> bool { true }
    +        \\fn bar() -> bool { return true; }
         ,
             ".tmp_source.zig:2:15: error: expected nullable type, found 'bool'");
     
    @@ -1715,7 +1716,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\export fn foo() {
             \\    while (bar()) |x| {}
             \\}
    -        \\fn bar() -> %i32 { 1 }
    +        \\fn bar() -> %i32 { return 1; }
         ,
             ".tmp_source.zig:2:15: error: expected nullable type, found '%i32'");
     
    @@ -1723,7 +1724,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\export fn foo() {
             \\    while (bar()) |x| {} else |err| {}
             \\}
    -        \\fn bar() -> bool { true }
    +        \\fn bar() -> bool { return true; }
         ,
             ".tmp_source.zig:2:15: error: expected error union type, found 'bool'");
     
    @@ -1731,7 +1732,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\export fn foo() {
             \\    while (bar()) |x| {} else |err| {}
             \\}
    -        \\fn bar() -> ?i32 { 1 }
    +        \\fn bar() -> ?i32 { return 1; }
         ,
             ".tmp_source.zig:2:15: error: expected error union type, found '?i32'");
     
    @@ -1762,17 +1763,17 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("signed integer division",
             \\export fn foo(a: i32, b: i32) -> i32 {
    -        \\    a / b
    +        \\    return a / b;
             \\}
         ,
    -        ".tmp_source.zig:2:7: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExact");
    +        ".tmp_source.zig:2:14: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExact");
     
         cases.add("signed integer remainder division",
             \\export fn foo(a: i32, b: i32) -> i32 {
    -        \\    a % b
    +        \\    return a % b;
             \\}
         ,
    -        ".tmp_source.zig:2:7: error: remainder division with 'i32' and 'i32': signed integers and floats must use @rem or @mod");
    +        ".tmp_source.zig:2:14: error: remainder division with 'i32' and 'i32': signed integers and floats must use @rem or @mod");
     
         cases.add("cast negative value to unsigned integer",
             \\comptime {
    @@ -1922,17 +1923,17 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("explicit cast float literal to integer when there is a fraction component",
             \\export fn entry() -> i32 {
    -        \\    i32(12.34)
    +        \\    return i32(12.34);
             \\}
         ,
    -        ".tmp_source.zig:2:9: error: fractional component prevents float value 12.340000 from being casted to type 'i32'");
    +        ".tmp_source.zig:2:16: error: fractional component prevents float value 12.340000 from being casted to type 'i32'");
     
         cases.add("non pointer given to @ptrToInt",
             \\export fn entry(x: i32) -> usize {
    -        \\    @ptrToInt(x)
    +        \\    return @ptrToInt(x);
             \\}
         ,
    -        ".tmp_source.zig:2:15: error: expected pointer, found 'i32'");
    +        ".tmp_source.zig:2:22: error: expected pointer, found 'i32'");
     
         cases.add("@shlExact shifts out 1 bits",
             \\comptime {
    @@ -2028,7 +2029,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     
         cases.add("@alignCast expects pointer or slice",
             \\export fn entry() {
    -        \\    @alignCast(4, u32(3))
    +        \\    @alignCast(4, u32(3));
             \\}
         ,
             ".tmp_source.zig:2:22: error: expected pointer or slice, found 'u32'");
    @@ -2040,7 +2041,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\fn testImplicitlyDecreaseFnAlign(ptr: fn () align(8) -> i32, answer: i32) {
             \\    if (ptr() != answer) unreachable;
             \\}
    -        \\fn alignedSmall() align(4) -> i32 { 1234 }
    +        \\fn alignedSmall() align(4) -> i32 { return 1234; }
         ,
             ".tmp_source.zig:2:35: error: expected type 'fn() align(8) -> i32', found 'fn() align(4) -> i32'");
     
    @@ -2206,17 +2207,17 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\const Mode = @import("builtin").Mode;
             \\
             \\fn Free(comptime filename: []const u8) -> TestCase {
    -        \\    TestCase {
    +        \\    return TestCase {
             \\        .filename = filename,
             \\        .problem_type = ProblemType.Free,
    -        \\    }
    +        \\    };
             \\}
             \\
             \\fn LibC(comptime filename: []const u8) -> TestCase {
    -        \\    TestCase {
    +        \\    return TestCase {
             \\        .filename = filename,
             \\        .problem_type = ProblemType.LinkLibC,
    -        \\    }
    +        \\    };
             \\}
             \\
             \\const TestCase = struct {
    @@ -2374,9 +2375,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\pub fn MemoryPool(comptime T: type) -> type {
             \\    const free_list_t = @compileError("aoeu");
             \\
    -        \\    struct {
    +        \\    return struct {
             \\        free_list: free_list_t,
    -        \\    }
    +        \\    };
             \\}
             \\
             \\export fn entry() {
    @@ -2651,7 +2652,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    C: bool,
             \\};
             \\export fn entry() {
    -        \\    var a = Payload { .A = { 1234 } };
    +        \\    var a = Payload { .A = 1234 };
             \\}
         ,
             ".tmp_source.zig:6:29: error: extern union does not support enum tag type");
    @@ -2668,7 +2669,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    C: bool,
             \\};
             \\export fn entry() {
    -        \\    var a = Payload { .A = { 1234 } };
    +        \\    var a = Payload { .A = 1234 };
             \\}
         ,
             ".tmp_source.zig:6:29: error: packed union does not support enum tag type");
    @@ -2680,7 +2681,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
             \\    C: bool,
             \\};
             \\export fn entry() {
    -        \\    const a = Payload { .A = { 1234 } };
    +        \\    const a = Payload { .A = 1234 };
             \\    foo(a);
             \\}
             \\fn foo(a: &const Payload) {
    diff --git a/test/debug_safety.zig b/test/debug_safety.zig
    index 36f8d020c3..58ebf838b0 100644
    --- a/test/debug_safety.zig
    +++ b/test/debug_safety.zig
    @@ -19,7 +19,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    baz(bar(a));
             \\}
             \\fn bar(a: []const i32) -> i32 {
    -        \\    a[4]
    +        \\    return a[4];
             \\}
             \\fn baz(a: i32) { }
         );
    @@ -34,7 +34,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn add(a: u16, b: u16) -> u16 {
    -        \\    a + b
    +        \\    return a + b;
             \\}
         );
     
    @@ -48,7 +48,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn sub(a: u16, b: u16) -> u16 {
    -        \\    a - b
    +        \\    return a - b;
             \\}
         );
     
    @@ -62,7 +62,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn mul(a: u16, b: u16) -> u16 {
    -        \\    a * b
    +        \\    return a * b;
             \\}
         );
     
    @@ -76,7 +76,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 32767) return error.Whatever;
             \\}
             \\fn neg(a: i16) -> i16 {
    -        \\    -a
    +        \\    return -a;
             \\}
         );
     
    @@ -90,7 +90,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 32767) return error.Whatever;
             \\}
             \\fn div(a: i16, b: i16) -> i16 {
    -        \\    @divTrunc(a, b)
    +        \\    return @divTrunc(a, b);
             \\}
         );
     
    @@ -104,7 +104,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn shl(a: i16, b: u4) -> i16 {
    -        \\    @shlExact(a, b)
    +        \\    return @shlExact(a, b);
             \\}
         );
     
    @@ -118,7 +118,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn shl(a: u16, b: u4) -> u16 {
    -        \\    @shlExact(a, b)
    +        \\    return @shlExact(a, b);
             \\}
         );
     
    @@ -132,7 +132,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn shr(a: i16, b: u4) -> i16 {
    -        \\    @shrExact(a, b)
    +        \\    return @shrExact(a, b);
             \\}
         );
     
    @@ -146,7 +146,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn shr(a: u16, b: u4) -> u16 {
    -        \\    @shrExact(a, b)
    +        \\    return @shrExact(a, b);
             \\}
         );
     
    @@ -159,7 +159,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    const x = div0(999, 0);
             \\}
             \\fn div0(a: i32, b: i32) -> i32 {
    -        \\    @divTrunc(a, b)
    +        \\    return @divTrunc(a, b);
             \\}
         );
     
    @@ -173,7 +173,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn divExact(a: i32, b: i32) -> i32 {
    -        \\    @divExact(a, b)
    +        \\    return @divExact(a, b);
             \\}
         );
     
    @@ -187,7 +187,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x.len == 0) return error.Whatever;
             \\}
             \\fn widenSlice(slice: []align(1) const u8) -> []align(1) const i32 {
    -        \\    ([]align(1) const i32)(slice)
    +        \\    return ([]align(1) const i32)(slice);
             \\}
         );
     
    @@ -201,7 +201,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn shorten_cast(x: i32) -> i8 {
    -        \\    i8(x)
    +        \\    return i8(x);
             \\}
         );
     
    @@ -215,7 +215,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
             \\    if (x == 0) return error.Whatever;
             \\}
             \\fn unsigned_cast(x: i32) -> u32 {
    -        \\    u32(x)
    +        \\    return u32(x);
             \\}
         );
     
    diff --git a/test/standalone/pkg_import/pkg.zig b/test/standalone/pkg_import/pkg.zig
    index a051497fdb..52ca884937 100644
    --- a/test/standalone/pkg_import/pkg.zig
    +++ b/test/standalone/pkg_import/pkg.zig
    @@ -1 +1 @@
    -pub fn add(a: i32, b: i32) -> i32 { a + b }
    +pub fn add(a: i32, b: i32) -> i32 { return a + b; }
    diff --git a/test/tests.zig b/test/tests.zig
    index a5eb9d4db9..a184045125 100644
    --- a/test/tests.zig
    +++ b/test/tests.zig
    @@ -284,7 +284,7 @@ pub const CompareOutputContext = struct {
                         warn("Process {} terminated unexpectedly\n", full_exe_path);
                         return error.TestFailed;
                     },
    -            };
    +            }
     
     
                 if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
    @@ -615,7 +615,7 @@ pub const CompileErrorContext = struct {
                         warn("Process {} terminated unexpectedly\n", b.zig_exe);
                         return error.TestFailed;
                     },
    -            };
    +            }
     
     
                 const stdout = stdout_buf.toSliceConst();
    @@ -891,7 +891,7 @@ pub const TranslateCContext = struct {
                         warn("Compilation terminated unexpectedly\n");
                         return error.TestFailed;
                     },
    -            };
    +            }
     
                 const stdout = stdout_buf.toSliceConst();
                 const stderr = stderr_buf.toSliceConst();
    diff --git a/test/translate_c.zig b/test/translate_c.zig
    index 4f545139e7..cdf900c8b2 100644
    --- a/test/translate_c.zig
    +++ b/test/translate_c.zig
    @@ -203,13 +203,13 @@ pub fn addCases(cases: &tests.TranslateCContext) {
             \\pub extern var fn_ptr: ?extern fn();
         ,
             \\pub inline fn foo() {
    -        \\    (??fn_ptr)()
    +        \\    return (??fn_ptr)();
             \\}
         ,
             \\pub extern var fn_ptr2: ?extern fn(c_int, f32) -> u8;
         ,
             \\pub inline fn bar(arg0: c_int, arg1: f32) -> u8 {
    -        \\    (??fn_ptr2)(arg0, arg1)
    +        \\    return (??fn_ptr2)(arg0, arg1);
             \\}
         );
     
    @@ -475,10 +475,10 @@ pub fn addCases(cases: &tests.TranslateCContext) {
             \\pub export fn max(a: c_int) {
             \\    var b: c_int;
             \\    var c: c_int;
    -        \\    c = {
    +        \\    c = x: {
             \\        const _tmp = a;
             \\        b = _tmp;
    -        \\        _tmp
    +        \\        break :x _tmp;
             \\    };
             \\}
         );
    @@ -613,9 +613,9 @@ pub fn addCases(cases: &tests.TranslateCContext) {
             \\}
         ,
             \\pub export fn foo() -> c_int {
    -        \\    return {
    +        \\    return x: {
             \\        _ = 1;
    -        \\        2
    +        \\        break :x 2;
             \\    };
             \\}
         );
    @@ -645,45 +645,45 @@ pub fn addCases(cases: &tests.TranslateCContext) {
         ,
             \\pub export fn foo() {
             \\    var a: c_int = 0;
    -        \\    a += {
    +        \\    a += x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) + 1);
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a -= {
    +        \\    a -= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) - 1);
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a *= {
    +        \\    a *= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) * 1);
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a &= {
    +        \\    a &= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) & 1);
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a |= {
    +        \\    a |= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) | 1);
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a ^= {
    +        \\    a ^= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) ^ 1);
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a >>= @import("std").math.Log2Int(c_int)({
    +        \\    a >>= @import("std").math.Log2Int(c_int)(x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) >> @import("std").math.Log2Int(c_int)(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    });
    -        \\    a <<= @import("std").math.Log2Int(c_int)({
    +        \\    a <<= @import("std").math.Log2Int(c_int)(x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) << @import("std").math.Log2Int(c_int)(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    });
             \\}
         );
    @@ -703,45 +703,45 @@ pub fn addCases(cases: &tests.TranslateCContext) {
         ,
             \\pub export fn foo() {
             \\    var a: c_uint = c_uint(0);
    -        \\    a +%= {
    +        \\    a +%= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) +% c_uint(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a -%= {
    +        \\    a -%= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) -% c_uint(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a *%= {
    +        \\    a *%= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) *% c_uint(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a &= {
    +        \\    a &= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) & c_uint(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a |= {
    +        \\    a |= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) | c_uint(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a ^= {
    +        \\    a ^= x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) ^ c_uint(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    a >>= @import("std").math.Log2Int(c_uint)({
    +        \\    a >>= @import("std").math.Log2Int(c_uint)(x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) >> @import("std").math.Log2Int(c_uint)(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    });
    -        \\    a <<= @import("std").math.Log2Int(c_uint)({
    +        \\    a <<= @import("std").math.Log2Int(c_uint)(x: {
             \\        const _ref = &a;
             \\        (*_ref) = ((*_ref) << @import("std").math.Log2Int(c_uint)(1));
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    });
             \\}
         );
    @@ -778,29 +778,29 @@ pub fn addCases(cases: &tests.TranslateCContext) {
             \\    i -= 1;
             \\    u +%= 1;
             \\    u -%= 1;
    -        \\    i = {
    +        \\    i = x: {
             \\        const _ref = &i;
             \\        const _tmp = *_ref;
             \\        (*_ref) += 1;
    -        \\        _tmp
    +        \\        break :x _tmp;
             \\    };
    -        \\    i = {
    +        \\    i = x: {
             \\        const _ref = &i;
             \\        const _tmp = *_ref;
             \\        (*_ref) -= 1;
    -        \\        _tmp
    +        \\        break :x _tmp;
             \\    };
    -        \\    u = {
    +        \\    u = x: {
             \\        const _ref = &u;
             \\        const _tmp = *_ref;
             \\        (*_ref) +%= 1;
    -        \\        _tmp
    +        \\        break :x _tmp;
             \\    };
    -        \\    u = {
    +        \\    u = x: {
             \\        const _ref = &u;
             \\        const _tmp = *_ref;
             \\        (*_ref) -%= 1;
    -        \\        _tmp
    +        \\        break :x _tmp;
             \\    };
             \\}
         );
    @@ -826,25 +826,25 @@ pub fn addCases(cases: &tests.TranslateCContext) {
             \\    i -= 1;
             \\    u +%= 1;
             \\    u -%= 1;
    -        \\    i = {
    +        \\    i = x: {
             \\        const _ref = &i;
             \\        (*_ref) += 1;
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    i = {
    +        \\    i = x: {
             \\        const _ref = &i;
             \\        (*_ref) -= 1;
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    u = {
    +        \\    u = x: {
             \\        const _ref = &u;
             \\        (*_ref) +%= 1;
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
    -        \\    u = {
    +        \\    u = x: {
             \\        const _ref = &u;
             \\        (*_ref) -%= 1;
    -        \\        *_ref
    +        \\        break :x *_ref;
             \\    };
             \\}
         );
    @@ -1037,7 +1037,7 @@ pub fn addCases(cases: &tests.TranslateCContext) {
             \\pub const glClearPFN = PFNGLCLEARPROC;
         ,
             \\pub inline fn glClearUnion(arg0: GLbitfield) {
    -        \\    (??glProcs.gl.Clear)(arg0)
    +        \\    return (??glProcs.gl.Clear)(arg0);
             \\}
         ,
             \\pub const OpenGLProcs = union_OpenGLProcs;
    
    From ea805c5fe7a8e24597fcd59f064efc2a7dca3859 Mon Sep 17 00:00:00 2001
    From: Andrew Kelley 
    Date: Fri, 22 Dec 2017 02:33:39 -0500
    Subject: [PATCH 65/69] fix darwin and windows from previous commit
    
    ---
     std/os/child_process.zig | 14 ++++++++------
     std/os/darwin.zig        |  4 ++--
     std/os/windows/util.zig  |  6 +++---
     3 files changed, 13 insertions(+), 11 deletions(-)
    
    diff --git a/std/os/child_process.zig b/std/os/child_process.zig
    index e719af65a8..6e86c99056 100644
    --- a/std/os/child_process.zig
    +++ b/std/os/child_process.zig
    @@ -560,12 +560,14 @@ pub const ChildProcess = struct {
     
             // the cwd set in ChildProcess is in effect when choosing the executable path
             // to match posix semantics
    -        const app_name = if (self.cwd) |cwd| x: {
    -            const resolved = %return os.path.resolve(self.allocator, cwd, self.argv[0]);
    -            defer self.allocator.free(resolved);
    -            break :x %return cstr.addNullByte(self.allocator, resolved);
    -        } else x: {
    -            break :x %return cstr.addNullByte(self.allocator, self.argv[0]);
    +        const app_name = x: {
    +            if (self.cwd) |cwd| {
    +                const resolved = %return os.path.resolve(self.allocator, cwd, self.argv[0]);
    +                defer self.allocator.free(resolved);
    +                break :x %return cstr.addNullByte(self.allocator, resolved);
    +            } else {
    +                break :x %return cstr.addNullByte(self.allocator, self.argv[0]);
    +            }
             };
             defer self.allocator.free(app_name);
     
    diff --git a/std/os/darwin.zig b/std/os/darwin.zig
    index f4166c2151..e230826b7e 100644
    --- a/std/os/darwin.zig
    +++ b/std/os/darwin.zig
    @@ -117,11 +117,11 @@ pub fn close(fd: i32) -> usize {
     }
     
     pub fn abort() -> noreturn {
    -    return c.abort();
    +    c.abort();
     }
     
     pub fn exit(code: i32) -> noreturn {
    -    return c.exit(code);
    +    c.exit(code);
     }
     
     pub fn isatty(fd: i32) -> bool {
    diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig
    index 0964adc16b..5b2dc1efb8 100644
    --- a/std/os/windows/util.zig
    +++ b/std/os/windows/util.zig
    @@ -16,11 +16,11 @@ pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) ->
             windows.WAIT_ABANDONED => error.WaitAbandoned,
             windows.WAIT_OBJECT_0 => {},
             windows.WAIT_TIMEOUT => error.WaitTimeOut,
    -        windows.WAIT_FAILED => {
    +        windows.WAIT_FAILED => x: {
                 const err = windows.GetLastError();
    -            switch (err) {
    +            break :x switch (err) {
                     else => os.unexpectedErrorWindows(err),
    -            }
    +            };
             },
             else => error.Unexpected,
         };
    
    From 0e7fb69bea4d2b50ab352b71de87a563b57a645a Mon Sep 17 00:00:00 2001
    From: Josh Wolfe 
    Date: Fri, 22 Dec 2017 00:49:17 -0700
    Subject: [PATCH 66/69] bufPrint returns an error
    
    ---
     std/fmt/index.zig | 30 ++++++++++++++++++------------
     std/os/path.zig   |  2 +-
     2 files changed, 19 insertions(+), 13 deletions(-)
    
    diff --git a/std/fmt/index.zig b/std/fmt/index.zig
    index 53fd085488..fef968a1d5 100644
    --- a/std/fmt/index.zig
    +++ b/std/fmt/index.zig
    @@ -371,6 +371,10 @@ test "fmt.parseInt" {
         assert(%%parseInt(i32, "-10", 10) == -10);
         assert(%%parseInt(i32, "+10", 10) == 10);
         assert(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidChar);
    +    assert(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidChar);
    +    assert(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidChar);
    +    assert(%%parseInt(u8, "255", 10) == 255);
    +    assert(if (parseInt(u8, "256", 10)) |_| false else |err| err == error.Overflow);
     }
     
     pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) -> %T {
    @@ -412,14 +416,16 @@ const BufPrintContext = struct {
         remaining: []u8,
     };
     
    +error BufferTooSmall;
     fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> %void {
    +    if (context.remaining.len < bytes.len) return error.BufferTooSmall;
         mem.copy(u8, context.remaining, bytes);
         context.remaining = context.remaining[bytes.len..];
     }
     
    -pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) -> []u8 {
    +pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) -> %[]u8 {
         var context = BufPrintContext { .remaining = buf, };
    -    %%format(&context, bufPrintWrite, fmt, args);
    +    %return format(&context, bufPrintWrite, fmt, args);
         return buf[0..buf.len - context.remaining.len];
     }
     
    @@ -475,31 +481,31 @@ test "fmt.format" {
         {
             var buf1: [32]u8 = undefined;
             const value: ?i32 = 1234;
    -        const result = bufPrint(buf1[0..], "nullable: {}\n", value);
    +        const result = %%bufPrint(buf1[0..], "nullable: {}\n", value);
             assert(mem.eql(u8, result, "nullable: 1234\n"));
         }
         {
             var buf1: [32]u8 = undefined;
             const value: ?i32 = null;
    -        const result = bufPrint(buf1[0..], "nullable: {}\n", value);
    +        const result = %%bufPrint(buf1[0..], "nullable: {}\n", value);
             assert(mem.eql(u8, result, "nullable: null\n"));
         }
         {
             var buf1: [32]u8 = undefined;
             const value: %i32 = 1234;
    -        const result = bufPrint(buf1[0..], "error union: {}\n", value);
    +        const result = %%bufPrint(buf1[0..], "error union: {}\n", value);
             assert(mem.eql(u8, result, "error union: 1234\n"));
         }
         {
             var buf1: [32]u8 = undefined;
             const value: %i32 = error.InvalidChar;
    -        const result = bufPrint(buf1[0..], "error union: {}\n", value);
    +        const result = %%bufPrint(buf1[0..], "error union: {}\n", value);
             assert(mem.eql(u8, result, "error union: error.InvalidChar\n"));
         }
         {
             var buf1: [32]u8 = undefined;
             const value: u3 = 0b101;
    -        const result = bufPrint(buf1[0..], "u3: {}\n", value);
    +        const result = %%bufPrint(buf1[0..], "u3: {}\n", value);
             assert(mem.eql(u8, result, "u3: 5\n"));
         }
     
    @@ -509,28 +515,28 @@ test "fmt.format" {
             {
                 var buf1: [32]u8 = undefined;
                 const value: f32 = 12.34;
    -            const result = bufPrint(buf1[0..], "f32: {}\n", value);
    +            const result = %%bufPrint(buf1[0..], "f32: {}\n", value);
                 assert(mem.eql(u8, result, "f32: 1.23400001e1\n"));
             }
             {
                 var buf1: [32]u8 = undefined;
                 const value: f64 = -12.34e10;
    -            const result = bufPrint(buf1[0..], "f64: {}\n", value);
    +            const result = %%bufPrint(buf1[0..], "f64: {}\n", value);
                 assert(mem.eql(u8, result, "f64: -1.234e11\n"));
             }
             {
                 var buf1: [32]u8 = undefined;
    -            const result = bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);
    +            const result = %%bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);
                 assert(mem.eql(u8, result, "f64: NaN\n"));
             }
             {
                 var buf1: [32]u8 = undefined;
    -            const result = bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);
    +            const result = %%bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);
                 assert(mem.eql(u8, result, "f64: Infinity\n"));
             }
             {
                 var buf1: [32]u8 = undefined;
    -            const result = bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
    +            const result = %%bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
                 assert(mem.eql(u8, result, "f64: -Infinity\n"));
             }
         }
    diff --git a/std/os/path.zig b/std/os/path.zig
    index a42ebb3433..59e9a53027 100644
    --- a/std/os/path.zig
    +++ b/std/os/path.zig
    @@ -1012,7 +1012,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
                 defer os.close(fd);
     
                 var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
    -            const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd);
    +            const proc_path = %%fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd);
     
                 return os.readLink(allocator, proc_path);
             },
    
    From e44a11341d21a14441c5607efa04b9c1be9baf5a Mon Sep 17 00:00:00 2001
    From: Andrew Kelley 
    Date: Fri, 22 Dec 2017 13:14:07 -0500
    Subject: [PATCH 67/69] std.math: remove unnecessary inline calls and
    
    workaround windows 32 bit test failure
    See #537
    ---
     std/math/acos.zig     |  4 ++--
     std/math/acosh.zig    |  4 ++--
     std/math/asin.zig     |  4 ++--
     std/math/asinh.zig    |  4 ++--
     std/math/atan.zig     | 22 +++++++++++-----------
     std/math/atan2.zig    |  4 ++--
     std/math/atanh.zig    |  4 ++--
     std/math/cbrt.zig     |  4 ++--
     std/math/ceil.zig     |  4 ++--
     std/math/copysign.zig |  4 ++--
     std/math/cos.zig      |  4 ++--
     std/math/cosh.zig     |  4 ++--
     std/math/exp.zig      |  4 ++--
     std/math/exp2.zig     |  4 ++--
     std/math/expm1.zig    |  4 ++--
     std/math/fabs.zig     |  4 ++--
     std/math/floor.zig    |  4 ++--
     std/math/fma.zig      |  4 ++--
     std/math/frexp.zig    |  4 ++--
     std/math/hypot.zig    |  4 ++--
     std/math/ilogb.zig    |  4 ++--
     std/math/log1p.zig    |  4 ++--
     std/math/modf.zig     |  4 ++--
     std/math/round.zig    |  4 ++--
     std/math/scalbn.zig   |  4 ++--
     std/math/signbit.zig  |  4 ++--
     std/math/sin.zig      |  4 ++--
     std/math/sinh.zig     |  4 ++--
     std/math/tan.zig      |  4 ++--
     std/math/tanh.zig     |  4 ++--
     std/math/trunc.zig    |  4 ++--
     31 files changed, 71 insertions(+), 71 deletions(-)
    
    diff --git a/std/math/acos.zig b/std/math/acos.zig
    index ae7afce032..7690497da5 100644
    --- a/std/math/acos.zig
    +++ b/std/math/acos.zig
    @@ -8,8 +8,8 @@ const assert = @import("../debug.zig").assert;
     pub fn acos(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(acos32, x),
    -        f64 => @inlineCall(acos64, x),
    +        f32 => acos32(x),
    +        f64 => acos64(x),
             else => @compileError("acos not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/acosh.zig b/std/math/acosh.zig
    index 0fdf9e41e0..3b2c2c0f31 100644
    --- a/std/math/acosh.zig
    +++ b/std/math/acosh.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn acosh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(acosh32, x),
    -        f64 => @inlineCall(acosh64, x),
    +        f32 => acosh32(x),
    +        f64 => acosh64(x),
             else => @compileError("acosh not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/asin.zig b/std/math/asin.zig
    index 11aad04107..c5d24c35e5 100644
    --- a/std/math/asin.zig
    +++ b/std/math/asin.zig
    @@ -9,8 +9,8 @@ const assert = @import("../debug.zig").assert;
     pub fn asin(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(asin32, x),
    -        f64 => @inlineCall(asin64, x),
    +        f32 => asin32(x),
    +        f64 => asin64(x),
             else => @compileError("asin not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/asinh.zig b/std/math/asinh.zig
    index eda7e15861..f963dae77b 100644
    --- a/std/math/asinh.zig
    +++ b/std/math/asinh.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn asinh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(asinh32, x),
    -        f64 => @inlineCall(asinh64, x),
    +        f32 => asinh32(x),
    +        f64 => asinh64(x),
             else => @compileError("asinh not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/atan.zig b/std/math/atan.zig
    index 4527f6bf28..cf244eb762 100644
    --- a/std/math/atan.zig
    +++ b/std/math/atan.zig
    @@ -9,8 +9,8 @@ const assert = @import("../debug.zig").assert;
     pub fn atan(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(atan32, x),
    -        f64 => @inlineCall(atan64, x),
    +        f32 => atan32(x),
    +        f64 => atan64(x),
             else => @compileError("atan not implemented for " ++ @typeName(T)),
         };
     }
    @@ -99,11 +99,11 @@ fn atan32(x_: f32) -> f32 {
         const s1 = z * (aT[0] + w * (aT[2] + w * aT[4]));
         const s2 = w * (aT[1] + w * aT[3]);
     
    -    if (id == null) {
    -        return x - x * (s1 + s2);
    -    } else {
    -        const zz = atanhi[??id] - ((x * (s1 + s2) - atanlo[??id]) - x);
    +    if (id) |id_value| {
    +        const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x);
             return if (sign != 0) -zz else zz;
    +    } else {
    +        return x - x * (s1 + s2);
         }
     }
     
    @@ -198,16 +198,16 @@ fn atan64(x_: f64) -> f64 {
         const s1 = z * (aT[0] + w * (aT[2] + w * (aT[4] + w * (aT[6] + w * (aT[8] + w * aT[10])))));
         const s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * aT[9]))));
     
    -    if (id == null) {
    -        return x - x * (s1 + s2);
    -    } else {
    -        const zz = atanhi[??id] - ((x * (s1 + s2) - atanlo[??id]) - x);
    +    if (id) |id_value| {
    +        const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x);
             return if (sign != 0) -zz else zz;
    +    } else {
    +        return x - x * (s1 + s2);
         }
     }
     
     test "math.atan" {
    -    assert(atan(f32(0.2)) == atan32(0.2));
    +    assert(@bitCast(u32, atan(f32(0.2))) == @bitCast(u32, atan32(0.2)));
         assert(atan(f64(0.2)) == atan64(0.2));
     }
     
    diff --git a/std/math/atan2.zig b/std/math/atan2.zig
    index 9124952b74..b3af18cd9e 100644
    --- a/std/math/atan2.zig
    +++ b/std/math/atan2.zig
    @@ -23,8 +23,8 @@ const assert = @import("../debug.zig").assert;
     
     fn atan2(comptime T: type, x: T, y: T) -> T {
         return switch (T) {
    -        f32 => @inlineCall(atan2_32, x, y),
    -        f64 => @inlineCall(atan2_64, x, y),
    +        f32 => atan2_32(x, y),
    +        f64 => atan2_64(x, y),
             else => @compileError("atan2 not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/atanh.zig b/std/math/atanh.zig
    index 2f87efbbd3..13de90279b 100644
    --- a/std/math/atanh.zig
    +++ b/std/math/atanh.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn atanh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(atanh_32, x),
    -        f64 => @inlineCall(atanh_64, x),
    +        f32 => atanh_32(x),
    +        f64 => atanh_64(x),
             else => @compileError("atanh not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/cbrt.zig b/std/math/cbrt.zig
    index ff353655b5..a8df75dff2 100644
    --- a/std/math/cbrt.zig
    +++ b/std/math/cbrt.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn cbrt(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(cbrt32, x),
    -        f64 => @inlineCall(cbrt64, x),
    +        f32 => cbrt32(x),
    +        f64 => cbrt64(x),
             else => @compileError("cbrt not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/ceil.zig b/std/math/ceil.zig
    index a8db486f92..8e27132e25 100644
    --- a/std/math/ceil.zig
    +++ b/std/math/ceil.zig
    @@ -11,8 +11,8 @@ const assert = @import("../debug.zig").assert;
     pub fn ceil(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(ceil32, x),
    -        f64 => @inlineCall(ceil64, x),
    +        f32 => ceil32(x),
    +        f64 => ceil64(x),
             else => @compileError("ceil not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/copysign.zig b/std/math/copysign.zig
    index 12e092c9ab..05205e1a0e 100644
    --- a/std/math/copysign.zig
    +++ b/std/math/copysign.zig
    @@ -3,8 +3,8 @@ const assert = @import("../debug.zig").assert;
     
     pub fn copysign(comptime T: type, x: T, y: T) -> T {
         return switch (T) {
    -        f32 => @inlineCall(copysign32, x, y),
    -        f64 => @inlineCall(copysign64, x, y),
    +        f32 => copysign32(x, y),
    +        f64 => copysign64(x, y),
             else => @compileError("copysign not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/cos.zig b/std/math/cos.zig
    index 285a2e538b..4c3b8e1282 100644
    --- a/std/math/cos.zig
    +++ b/std/math/cos.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn cos(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(cos32, x),
    -        f64 => @inlineCall(cos64, x),
    +        f32 => cos32(x),
    +        f64 => cos64(x),
             else => @compileError("cos not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/cosh.zig b/std/math/cosh.zig
    index f7fea99bcc..6d40d71b8d 100644
    --- a/std/math/cosh.zig
    +++ b/std/math/cosh.zig
    @@ -12,8 +12,8 @@ const assert = @import("../debug.zig").assert;
     pub fn cosh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(cosh32, x),
    -        f64 => @inlineCall(cosh64, x),
    +        f32 => cosh32(x),
    +        f64 => cosh64(x),
             else => @compileError("cosh not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/exp.zig b/std/math/exp.zig
    index e51221a74a..6e591daea3 100644
    --- a/std/math/exp.zig
    +++ b/std/math/exp.zig
    @@ -9,8 +9,8 @@ const assert = @import("../debug.zig").assert;
     pub fn exp(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(exp32, x),
    -        f64 => @inlineCall(exp64, x),
    +        f32 => exp32(x),
    +        f64 => exp64(x),
             else => @compileError("exp not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/exp2.zig b/std/math/exp2.zig
    index e867aabe23..6061f32391 100644
    --- a/std/math/exp2.zig
    +++ b/std/math/exp2.zig
    @@ -9,8 +9,8 @@ const assert = @import("../debug.zig").assert;
     pub fn exp2(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(exp2_32, x),
    -        f64 => @inlineCall(exp2_64, x),
    +        f32 => exp2_32(x),
    +        f64 => exp2_64(x),
             else => @compileError("exp2 not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/expm1.zig b/std/math/expm1.zig
    index 14a69958e8..fbe1841030 100644
    --- a/std/math/expm1.zig
    +++ b/std/math/expm1.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn expm1(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(expm1_32, x),
    -        f64 => @inlineCall(expm1_64, x),
    +        f32 => expm1_32(x),
    +        f64 => expm1_64(x),
             else => @compileError("exp1m not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/fabs.zig b/std/math/fabs.zig
    index 58244f4f2e..cf1b8f1f14 100644
    --- a/std/math/fabs.zig
    +++ b/std/math/fabs.zig
    @@ -9,8 +9,8 @@ const assert = @import("../debug.zig").assert;
     pub fn fabs(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(fabs32, x),
    -        f64 => @inlineCall(fabs64, x),
    +        f32 => fabs32(x),
    +        f64 => fabs64(x),
             else => @compileError("fabs not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/floor.zig b/std/math/floor.zig
    index 875070397d..d7de45e9d0 100644
    --- a/std/math/floor.zig
    +++ b/std/math/floor.zig
    @@ -11,8 +11,8 @@ const math = @import("index.zig");
     pub fn floor(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(floor32, x),
    -        f64 => @inlineCall(floor64, x),
    +        f32 => floor32(x),
    +        f64 => floor64(x),
             else => @compileError("floor not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/fma.zig b/std/math/fma.zig
    index c870dfd293..8e5adc80b2 100644
    --- a/std/math/fma.zig
    +++ b/std/math/fma.zig
    @@ -3,8 +3,8 @@ const assert = @import("../debug.zig").assert;
     
     pub fn fma(comptime T: type, x: T, y: T, z: T) -> T {
         return switch (T) {
    -        f32 => @inlineCall(fma32, x, y, z),
    -        f64 => @inlineCall(fma64, x, y ,z),
    +        f32 => fma32(x, y, z),
    +        f64 => fma64(x, y ,z),
             else => @compileError("fma not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/frexp.zig b/std/math/frexp.zig
    index e648555e31..1a317a2c80 100644
    --- a/std/math/frexp.zig
    +++ b/std/math/frexp.zig
    @@ -19,8 +19,8 @@ pub const frexp64_result = frexp_result(f64);
     pub fn frexp(x: var) -> frexp_result(@typeOf(x)) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(frexp32, x),
    -        f64 => @inlineCall(frexp64, x),
    +        f32 => frexp32(x),
    +        f64 => frexp64(x),
             else => @compileError("frexp not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/hypot.zig b/std/math/hypot.zig
    index 68794e24fe..9b09ed53a4 100644
    --- a/std/math/hypot.zig
    +++ b/std/math/hypot.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     
     pub fn hypot(comptime T: type, x: T, y: T) -> T {
         return switch (T) {
    -        f32 => @inlineCall(hypot32, x, y),
    -        f64 => @inlineCall(hypot64, x, y),
    +        f32 => hypot32(x, y),
    +        f64 => hypot64(x, y),
             else => @compileError("hypot not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/ilogb.zig b/std/math/ilogb.zig
    index e056ceb097..41a1e2d83f 100644
    --- a/std/math/ilogb.zig
    +++ b/std/math/ilogb.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn ilogb(x: var) -> i32 {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(ilogb32, x),
    -        f64 => @inlineCall(ilogb64, x),
    +        f32 => ilogb32(x),
    +        f64 => ilogb64(x),
             else => @compileError("ilogb not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/log1p.zig b/std/math/log1p.zig
    index 433a7c6192..b369385038 100644
    --- a/std/math/log1p.zig
    +++ b/std/math/log1p.zig
    @@ -12,8 +12,8 @@ const assert = @import("../debug.zig").assert;
     pub fn log1p(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(log1p_32, x),
    -        f64 => @inlineCall(log1p_64, x),
    +        f32 => log1p_32(x),
    +        f64 => log1p_64(x),
             else => @compileError("log1p not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/modf.zig b/std/math/modf.zig
    index 5b78680c51..72730b67d7 100644
    --- a/std/math/modf.zig
    +++ b/std/math/modf.zig
    @@ -18,8 +18,8 @@ pub const modf64_result = modf_result(f64);
     pub fn modf(x: var) -> modf_result(@typeOf(x)) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(modf32, x),
    -        f64 => @inlineCall(modf64, x),
    +        f32 => modf32(x),
    +        f64 => modf64(x),
             else => @compileError("modf not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/round.zig b/std/math/round.zig
    index 8e604d1b68..1e193fb1d7 100644
    --- a/std/math/round.zig
    +++ b/std/math/round.zig
    @@ -11,8 +11,8 @@ const math = @import("index.zig");
     pub fn round(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(round32, x),
    -        f64 => @inlineCall(round64, x),
    +        f32 => round32(x),
    +        f64 => round64(x),
             else => @compileError("round not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/scalbn.zig b/std/math/scalbn.zig
    index 0c898a783c..0be6a3d47e 100644
    --- a/std/math/scalbn.zig
    +++ b/std/math/scalbn.zig
    @@ -4,8 +4,8 @@ const assert = @import("../debug.zig").assert;
     pub fn scalbn(x: var, n: i32) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(scalbn32, x, n),
    -        f64 => @inlineCall(scalbn64, x, n),
    +        f32 => scalbn32(x, n),
    +        f64 => scalbn64(x, n),
             else => @compileError("scalbn not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/signbit.zig b/std/math/signbit.zig
    index 6efbc4db54..b8ccecfa1b 100644
    --- a/std/math/signbit.zig
    +++ b/std/math/signbit.zig
    @@ -4,8 +4,8 @@ const assert = @import("../debug.zig").assert;
     pub fn signbit(x: var) -> bool {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(signbit32, x),
    -        f64 => @inlineCall(signbit64, x),
    +        f32 => signbit32(x),
    +        f64 => signbit64(x),
             else => @compileError("signbit not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/sin.zig b/std/math/sin.zig
    index 6fbbaff291..392bef1bc0 100644
    --- a/std/math/sin.zig
    +++ b/std/math/sin.zig
    @@ -11,8 +11,8 @@ const assert = @import("../debug.zig").assert;
     pub fn sin(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(sin32, x),
    -        f64 => @inlineCall(sin64, x),
    +        f32 => sin32(x),
    +        f64 => sin64(x),
             else => @compileError("sin not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/sinh.zig b/std/math/sinh.zig
    index 095dd7ea06..4c575f10ec 100644
    --- a/std/math/sinh.zig
    +++ b/std/math/sinh.zig
    @@ -12,8 +12,8 @@ const expo2 = @import("expo2.zig").expo2;
     pub fn sinh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(sinh32, x),
    -        f64 => @inlineCall(sinh64, x),
    +        f32 => sinh32(x),
    +        f64 => sinh64(x),
             else => @compileError("sinh not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/tan.zig b/std/math/tan.zig
    index 2a3c46eb6f..ff53a758b4 100644
    --- a/std/math/tan.zig
    +++ b/std/math/tan.zig
    @@ -11,8 +11,8 @@ const assert = @import("../debug.zig").assert;
     pub fn tan(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(tan32, x),
    -        f64 => @inlineCall(tan64, x),
    +        f32 => tan32(x),
    +        f64 => tan64(x),
             else => @compileError("tan not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/tanh.zig b/std/math/tanh.zig
    index c4fe8f2031..7715029361 100644
    --- a/std/math/tanh.zig
    +++ b/std/math/tanh.zig
    @@ -12,8 +12,8 @@ const expo2 = @import("expo2.zig").expo2;
     pub fn tanh(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(tanh32, x),
    -        f64 => @inlineCall(tanh64, x),
    +        f32 => tanh32(x),
    +        f64 => tanh64(x),
             else => @compileError("tanh not implemented for " ++ @typeName(T)),
         };
     }
    diff --git a/std/math/trunc.zig b/std/math/trunc.zig
    index 01cb1bb84a..81eacb30ba 100644
    --- a/std/math/trunc.zig
    +++ b/std/math/trunc.zig
    @@ -10,8 +10,8 @@ const assert = @import("../debug.zig").assert;
     pub fn trunc(x: var) -> @typeOf(x) {
         const T = @typeOf(x);
         return switch (T) {
    -        f32 => @inlineCall(trunc32, x),
    -        f64 => @inlineCall(trunc64, x),
    +        f32 => trunc32(x),
    +        f64 => trunc64(x),
             else => @compileError("trunc not implemented for " ++ @typeName(T)),
         };
     }
    
    From 760b307e8a8fcbb31fc1f2abb170ef7399aa917e Mon Sep 17 00:00:00 2001
    From: Andrew Kelley 
    Date: Fri, 22 Dec 2017 18:27:33 -0500
    Subject: [PATCH 68/69] fix endianness of sub-byte integer fields in packed
     structs
    
    closes #307
    ---
     src/codegen.cpp       | 40 +++++++++++++++++++++++++++++++---------
     test/cases/struct.zig | 25 +++++++++++++++++++++++++
     2 files changed, 56 insertions(+), 9 deletions(-)
    
    diff --git a/src/codegen.cpp b/src/codegen.cpp
    index 7546416090..0aecacb0b2 100644
    --- a/src/codegen.cpp
    +++ b/src/codegen.cpp
    @@ -1238,11 +1238,13 @@ static LLVMValueRef gen_assign_raw(CodeGen *g, LLVMValueRef ptr, TypeTableEntry
             return nullptr;
         }
     
    +    bool big_endian = g->is_big_endian;
    +
         LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, "");
     
         uint32_t bit_offset = ptr_type->data.pointer.bit_offset;
         uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int));
    -    uint32_t shift_amt = host_bit_count - bit_offset - unaligned_bit_count;
    +    uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset;
         LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false);
     
         LLVMValueRef mask_val = LLVMConstAllOnes(child_type->type_ref);
    @@ -2198,12 +2200,14 @@ static LLVMValueRef ir_render_load_ptr(CodeGen *g, IrExecutable *executable, IrI
         if (unaligned_bit_count == 0)
             return get_handle_value(g, ptr, child_type, ptr_type);
     
    +    bool big_endian = g->is_big_endian;
    +
         assert(!handle_is_ptr(child_type));
         LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, "");
     
         uint32_t bit_offset = ptr_type->data.pointer.bit_offset;
         uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int));
    -    uint32_t shift_amt = host_bit_count - bit_offset - unaligned_bit_count;
    +    uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset;
     
         LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false);
         LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, "");
    @@ -3796,17 +3800,26 @@ static LLVMValueRef pack_const_int(CodeGen *g, LLVMTypeRef big_int_type_ref, Con
             case TypeTableEntryIdStruct:
                 {
                     assert(type_entry->data.structure.layout == ContainerLayoutPacked);
    +                bool is_big_endian = g->is_big_endian; // TODO get endianness from struct type
     
                     LLVMValueRef val = LLVMConstInt(big_int_type_ref, 0, false);
    +                size_t used_bits = 0;
                     for (size_t i = 0; i < type_entry->data.structure.src_field_count; i += 1) {
                         TypeStructField *field = &type_entry->data.structure.fields[i];
                         if (field->gen_index == SIZE_MAX) {
                             continue;
                         }
                         LLVMValueRef child_val = pack_const_int(g, big_int_type_ref, &const_val->data.x_struct.fields[i]);
    -                    LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, field->packed_bits_size, false);
    -                    val = LLVMConstShl(val, shift_amt);
    -                    val = LLVMConstOr(val, child_val);
    +                    if (is_big_endian) {
    +                        LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, field->packed_bits_size, false);
    +                        val = LLVMConstShl(val, shift_amt);
    +                        val = LLVMConstOr(val, child_val);
    +                    } else {
    +                        LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, used_bits, false);
    +                        LLVMValueRef child_val_shifted = LLVMConstShl(child_val, shift_amt);
    +                        val = LLVMConstOr(val, child_val_shifted);
    +                        used_bits += field->packed_bits_size;
    +                    }
                     }
                     return val;
                 }
    @@ -3931,9 +3944,11 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) {
                                 fields[type_struct_field->gen_index] = val;
                                 make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(field_val->type, val);
                             } else {
    +                            bool is_big_endian = g->is_big_endian; // TODO get endianness from struct type
                                 LLVMTypeRef big_int_type_ref = LLVMStructGetTypeAtIndex(type_entry->type_ref,
                                         (unsigned)type_struct_field->gen_index);
                                 LLVMValueRef val = LLVMConstInt(big_int_type_ref, 0, false);
    +                            size_t used_bits = 0;
                                 for (size_t i = src_field_index; i < src_field_index_end; i += 1) {
                                     TypeStructField *it_field = &type_entry->data.structure.fields[i];
                                     if (it_field->gen_index == SIZE_MAX) {
    @@ -3941,10 +3956,17 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) {
                                     }
                                     LLVMValueRef child_val = pack_const_int(g, big_int_type_ref,
                                             &const_val->data.x_struct.fields[i]);
    -                                LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref,
    -                                        it_field->packed_bits_size, false);
    -                                val = LLVMConstShl(val, shift_amt);
    -                                val = LLVMConstOr(val, child_val);
    +                                if (is_big_endian) {
    +                                    LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref,
    +                                            it_field->packed_bits_size, false);
    +                                    val = LLVMConstShl(val, shift_amt);
    +                                    val = LLVMConstOr(val, child_val);
    +                                } else {
    +                                    LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, used_bits, false);
    +                                    LLVMValueRef child_val_shifted = LLVMConstShl(child_val, shift_amt);
    +                                    val = LLVMConstOr(val, child_val_shifted);
    +                                    used_bits += it_field->packed_bits_size;
    +                                }
                                 }
                                 fields[type_struct_field->gen_index] = val;
                             }
    diff --git a/test/cases/struct.zig b/test/cases/struct.zig
    index 28792e9a73..bdd2e5d049 100644
    --- a/test/cases/struct.zig
    +++ b/test/cases/struct.zig
    @@ -379,3 +379,28 @@ const Nibbles = packed struct {
         x: u4,
         y: u4,
     };
    +
    +const Bitfields = packed struct {
    +    f1: u16,
    +    f2: u16,
    +    f3: u8,
    +    f4: u8,
    +    f5: u4,
    +    f6: u4,
    +    f7: u8,
    +};
    +
    +test "native bit field understands endianness" {
    +    var all: u64 = 0x7765443322221111;
    +    var bytes: [8]u8 = undefined;
    +    @memcpy(&bytes[0], @ptrCast(&u8, &all), 8);
    +    var bitfields = *@ptrCast(&Bitfields, &bytes[0]);
    +
    +    assert(bitfields.f1 == 0x1111);
    +    assert(bitfields.f2 == 0x2222);
    +    assert(bitfields.f3 == 0x33);
    +    assert(bitfields.f4 == 0x44);
    +    assert(bitfields.f5 == 0x5);
    +    assert(bitfields.f6 == 0x6);
    +    assert(bitfields.f7 == 0x77);
    +}
    
    From 39c7bd24e4f768b23074b8634ac637b175b7639f Mon Sep 17 00:00:00 2001
    From: Andrew Kelley 
    Date: Sat, 23 Dec 2017 00:29:39 -0500
    Subject: [PATCH 69/69] port most of main.cpp to self hosted compiler
    
    ---
     README.md                  |  53 ++--
     build.zig                  | 145 ++++++++-
     src-self-hosted/llvm.zig   |  13 +
     src-self-hosted/main.zig   | 623 ++++++++++++++++++++++++++++++++++---
     src-self-hosted/module.zig | 295 ++++++++++++++++++
     src-self-hosted/target.zig |  51 +++
     std/buffer.zig             |   5 +
     std/c/index.zig            |   1 +
     std/heap.zig               |  15 +-
     std/os/index.zig           |  33 ++
     10 files changed, 1156 insertions(+), 78 deletions(-)
     create mode 100644 src-self-hosted/llvm.zig
     create mode 100644 src-self-hosted/module.zig
    
    diff --git a/README.md b/README.md
    index 42783fab85..987197289c 100644
    --- a/README.md
    +++ b/README.md
    @@ -119,31 +119,22 @@ libc. Create demo games using Zig.
     [![Build Status](https://travis-ci.org/zig-lang/zig.svg?branch=master)](https://travis-ci.org/zig-lang/zig)
     [![Build status](https://ci.appveyor.com/api/projects/status/4t80mk2dmucrc38i/branch/master?svg=true)](https://ci.appveyor.com/project/andrewrk/zig-d3l86/branch/master)
     
    -### Dependencies
    +### Stage 1: Build Zig from C++ Source Code
     
    -#### Build Dependencies
    -
    -These compile tools must be available on your system and are used to build
    -the Zig compiler itself:
    +#### Dependencies
     
     ##### POSIX
     
      * gcc >= 5.0.0 or clang >= 3.6.0
      * cmake >= 2.8.5
    + * LLVM, Clang, LLD libraries == 5.x, compiled with the same gcc or clang version above
     
     ##### Windows
     
      * Microsoft Visual Studio 2015
    + * LLVM, Clang, LLD libraries == 5.x, compiled with the same MSVC version above
     
    -#### Library Dependencies
    -
    -These libraries must be installed on your system, with the development files
    -available. The Zig compiler links against them. You have to use the same
    -compiler for these libraries as you do to compile Zig.
    -
    - * LLVM, Clang, and LLD libraries == 5.x
    -
    -### Debug / Development Build
    +#### Instructions
     
     If you have gcc or clang installed, you can find out what `ZIG_LIBC_LIB_DIR`,
     `ZIG_LIBC_STATIC_LIB_DIR`, and `ZIG_LIBC_INCLUDE_DIR` should be set to
    @@ -158,7 +149,7 @@ make install
     ./zig build --build-file ../build.zig test
     ```
     
    -#### MacOS
    +##### MacOS
     
     `ZIG_LIBC_LIB_DIR` and `ZIG_LIBC_STATIC_LIB_DIR` are unused.
     
    @@ -172,21 +163,35 @@ make install
     ./zig build --build-file ../build.zig test
     ```
     
    -#### Windows
    +##### Windows
     
     See https://github.com/zig-lang/zig/wiki/Building-Zig-on-Windows
     
    -### Release / Install Build
    +### Stage 2: Build Self-Hosted Zig from Zig Source Code
     
    -Once installed, `ZIG_LIBC_LIB_DIR` and `ZIG_LIBC_INCLUDE_DIR` can be overridden
    -by the `--libc-lib-dir` and `--libc-include-dir` parameters to the zig binary.
    +*Note: Stage 2 compiler is not complete. Beta users of Zig should use the
    +Stage 1 compiler for now.*
    +
    +Dependencies are the same as Stage 1, except now you have a working zig compiler.
     
     ```
    -mkdir build
    -cd build
    -cmake .. -DCMAKE_BUILD_TYPE=Release -DZIG_LIBC_LIB_DIR=/some/path -DZIG_LIBC_INCLUDE_DIR=/some/path -DZIG_LIBC_STATIC_INCLUDE_DIR=/some/path
    -make
    -sudo make install
    +bin/zig build --build-file ../build.zig --prefix $(pwd)/stage2 install
    +```
    +
    +### Stage 3: Rebuild Self-Hosted Zig Using the Self-Hosted Compiler
    +
    +This is the actual compiler binary that we will install to the system.
    +
    +#### Debug / Development Build
    +
    +```
    +./stage2/bin/zig build --build-file ../build.zig --prefix $(pwd)/stage3 install
    +```
    +
    +#### Release / Install Build
    +
    +```
    +./stage2/bin/zig build --build-file ../build.zig install -Drelease-fast
     ```
     
     ### Test Coverage
    diff --git a/build.zig b/build.zig
    index 45953cc4af..96638659be 100644
    --- a/build.zig
    +++ b/build.zig
    @@ -32,15 +32,18 @@ pub fn build(b: &Builder) {
         docs_step.dependOn(&docgen_cmd.step);
         docs_step.dependOn(&docgen_home_cmd.step);
     
    -    var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
    -    exe.setBuildMode(mode);
    -    exe.linkSystemLibrary("c");
    -    dependOnLib(exe, findLLVM(b));
    +    if (findLLVM(b)) |llvm| {
    +        var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
    +        exe.setBuildMode(mode);
    +        exe.linkSystemLibrary("c");
    +        dependOnLib(exe, llvm);
     
    -    b.default_step.dependOn(&exe.step);
    -    b.default_step.dependOn(docs_step);
    +        b.default_step.dependOn(&exe.step);
    +        b.default_step.dependOn(docs_step);
     
    -    b.installArtifact(exe);
    +        b.installArtifact(exe);
    +        installStdLib(b);
    +    }
     
     
         const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
    @@ -91,7 +94,7 @@ const LibraryDep = struct {
         includes: ArrayList([]const u8),
     };
     
    -fn findLLVM(b: &Builder) -> LibraryDep {
    +fn findLLVM(b: &Builder) -> ?LibraryDep {
         const llvm_config_exe = b.findProgram(
             [][]const u8{"llvm-config-5.0", "llvm-config"},
             [][]const u8{
    @@ -102,7 +105,8 @@ fn findLLVM(b: &Builder) -> LibraryDep {
                 "C:/Libraries/llvm-5.0.0/bin",
             }) %% |err|
         {
    -        std.debug.panic("unable to find llvm-config: {}\n", err);
    +        warn("unable to find llvm-config: {}\n", err);
    +        return null;
         };
         const libs_output = b.exec([][]const u8{llvm_config_exe, "--libs", "--system-libs"});
         const includes_output = b.exec([][]const u8{llvm_config_exe, "--includedir"});
    @@ -143,3 +147,126 @@ fn findLLVM(b: &Builder) -> LibraryDep {
         }
         return result;
     }
    +
    +pub fn installStdLib(b: &Builder) {
    +    const stdlib_files = []const []const u8 {
    +        "array_list.zig",
    +        "base64.zig",
    +        "buf_map.zig",
    +        "buf_set.zig",
    +        "buffer.zig",
    +        "build.zig",
    +        "c/darwin.zig",
    +        "c/index.zig",
    +        "c/linux.zig",
    +        "c/windows.zig",
    +        "cstr.zig",
    +        "debug.zig",
    +        "dwarf.zig",
    +        "elf.zig",
    +        "empty.zig",
    +        "endian.zig",
    +        "fmt/errol/enum3.zig",
    +        "fmt/errol/index.zig",
    +        "fmt/errol/lookup.zig",
    +        "fmt/index.zig",
    +        "hash_map.zig",
    +        "heap.zig",
    +        "index.zig",
    +        "io.zig",
    +        "linked_list.zig",
    +        "math/acos.zig",
    +        "math/acosh.zig",
    +        "math/asin.zig",
    +        "math/asinh.zig",
    +        "math/atan.zig",
    +        "math/atan2.zig",
    +        "math/atanh.zig",
    +        "math/cbrt.zig",
    +        "math/ceil.zig",
    +        "math/copysign.zig",
    +        "math/cos.zig",
    +        "math/cosh.zig",
    +        "math/exp.zig",
    +        "math/exp2.zig",
    +        "math/expm1.zig",
    +        "math/expo2.zig",
    +        "math/fabs.zig",
    +        "math/floor.zig",
    +        "math/fma.zig",
    +        "math/frexp.zig",
    +        "math/hypot.zig",
    +        "math/ilogb.zig",
    +        "math/index.zig",
    +        "math/inf.zig",
    +        "math/isfinite.zig",
    +        "math/isinf.zig",
    +        "math/isnan.zig",
    +        "math/isnormal.zig",
    +        "math/ln.zig",
    +        "math/log.zig",
    +        "math/log10.zig",
    +        "math/log1p.zig",
    +        "math/log2.zig",
    +        "math/modf.zig",
    +        "math/nan.zig",
    +        "math/pow.zig",
    +        "math/round.zig",
    +        "math/scalbn.zig",
    +        "math/signbit.zig",
    +        "math/sin.zig",
    +        "math/sinh.zig",
    +        "math/sqrt.zig",
    +        "math/tan.zig",
    +        "math/tanh.zig",
    +        "math/trunc.zig",
    +        "mem.zig",
    +        "net.zig",
    +        "os/child_process.zig",
    +        "os/darwin.zig",
    +        "os/darwin_errno.zig",
    +        "os/get_user_id.zig",
    +        "os/index.zig",
    +        "os/linux.zig",
    +        "os/linux_errno.zig",
    +        "os/linux_i386.zig",
    +        "os/linux_x86_64.zig",
    +        "os/path.zig",
    +        "os/windows/error.zig",
    +        "os/windows/index.zig",
    +        "os/windows/util.zig",
    +        "rand.zig",
    +        "sort.zig",
    +        "special/bootstrap.zig",
    +        "special/bootstrap_lib.zig",
    +        "special/build_file_template.zig",
    +        "special/build_runner.zig",
    +        "special/builtin.zig",
    +        "special/compiler_rt/aulldiv.zig",
    +        "special/compiler_rt/aullrem.zig",
    +        "special/compiler_rt/comparetf2.zig",
    +        "special/compiler_rt/fixuint.zig",
    +        "special/compiler_rt/fixunsdfdi.zig",
    +        "special/compiler_rt/fixunsdfsi.zig",
    +        "special/compiler_rt/fixunsdfti.zig",
    +        "special/compiler_rt/fixunssfdi.zig",
    +        "special/compiler_rt/fixunssfsi.zig",
    +        "special/compiler_rt/fixunssfti.zig",
    +        "special/compiler_rt/fixunstfdi.zig",
    +        "special/compiler_rt/fixunstfsi.zig",
    +        "special/compiler_rt/fixunstfti.zig",
    +        "special/compiler_rt/index.zig",
    +        "special/compiler_rt/udivmod.zig",
    +        "special/compiler_rt/udivmoddi4.zig",
    +        "special/compiler_rt/udivmodti4.zig",
    +        "special/compiler_rt/udivti3.zig",
    +        "special/compiler_rt/umodti3.zig",
    +        "special/panic.zig",
    +        "special/test_runner.zig",
    +    };
    +    for (stdlib_files) |stdlib_file| {
    +        const src_path = %%os.path.join(b.allocator, "std", stdlib_file);
    +        const dest_path = %%os.path.join(b.allocator, "lib", "zig", "std", stdlib_file);
    +        b.installFile(src_path, dest_path);
    +    }
    +}
    diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig
    new file mode 100644
    index 0000000000..6a1439291b
    --- /dev/null
    +++ b/src-self-hosted/llvm.zig
    @@ -0,0 +1,13 @@
    +const builtin = @import("builtin");
    +const c = @import("c.zig");
    +const assert = @import("std").debug.assert;
    +
    +pub const ValueRef = removeNullability(c.LLVMValueRef);
    +pub const ModuleRef = removeNullability(c.LLVMModuleRef);
    +pub const ContextRef = removeNullability(c.LLVMContextRef);
    +pub const BuilderRef = removeNullability(c.LLVMBuilderRef);
    +
    +fn removeNullability(comptime T: type) -> type {
    +    comptime assert(@typeId(T) == builtin.TypeId.Nullable);
    +    return T.Child;
    +}
    diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig
    index 6c40d2216f..6fdacda4b2 100644
    --- a/src-self-hosted/main.zig
    +++ b/src-self-hosted/main.zig
    @@ -4,71 +4,620 @@ const io = std.io;
     const os = std.os;
     const heap = std.heap;
     const warn = std.debug.warn;
    -const Tokenizer = @import("tokenizer.zig").Tokenizer;
    -const Token = @import("tokenizer.zig").Token;
    -const Parser = @import("parser.zig").Parser;
     const assert = std.debug.assert;
     const target = @import("target.zig");
    +const Target = target.Target;
    +const Module = @import("module.zig").Module;
    +const ErrColor = Module.ErrColor;
    +const Emit = Module.Emit;
    +const builtin = @import("builtin");
    +const ArrayList = std.ArrayList;
    +
    +error InvalidCommandLineArguments;
    +error ZigLibDirNotFound;
    +error ZigInstallationNotFound;
    +
    +const default_zig_cache_name = "zig-cache";
     
     pub fn main() -> %void {
         main2() %% |err| {
    -        warn("{}\n", @errorName(err));
    +        if (err != error.InvalidCommandLineArguments) {
    +            warn("{}\n", @errorName(err));
    +        }
             return err;
         };
     }
     
    -pub fn main2() -> %void {
    -    var incrementing_allocator = %return heap.IncrementingAllocator.init(10 * 1024 * 1024);
    -    defer incrementing_allocator.deinit();
    +const Cmd = enum {
    +    None,
    +    Build,
    +    Test,
    +    Version,
    +    Zen,
    +    TranslateC,
    +    Targets,
    +};
     
    -    const allocator = &incrementing_allocator.allocator;
    +fn badArgs(comptime format: []const u8, args: ...) -> error {
    +    var stderr = %return io.getStdErr();
    +    var stderr_stream_adapter = io.FileOutStream.init(&stderr);
    +    const stderr_stream = &stderr_stream_adapter.stream;
    +    %return stderr_stream.print(format ++ "\n\n", args);
    +    %return printUsage(&stderr_stream_adapter.stream);
    +    return error.InvalidCommandLineArguments;
    +}
    +
    +pub fn main2() -> %void {
    +    const allocator = std.heap.c_allocator;
     
         const args = %return os.argsAlloc(allocator);
         defer os.argsFree(allocator, args);
     
    -    target.initializeAll();
    +    var cmd = Cmd.None;
    +    var build_kind: Module.Kind = undefined;
    +    var build_mode: builtin.Mode = builtin.Mode.Debug;
    +    var color = ErrColor.Auto;
    +    var emit_file_type = Emit.Binary;
     
    -    const target_file = args[1];
    +    var strip = false;
    +    var is_static = false;
    +    var verbose_tokenize = false;
    +    var verbose_ast_tree = false;
    +    var verbose_ast_fmt = false;
    +    var verbose_link = false;
    +    var verbose_ir = false;
    +    var verbose_llvm_ir = false;
    +    var verbose_cimport = false;
    +    var mwindows = false;
    +    var mconsole = false;
    +    var rdynamic = false;
    +    var each_lib_rpath = false;
    +    var timing_info = false;
     
    -    const target_file_buf = %return io.readFileAlloc(target_file, allocator);
    -    defer allocator.free(target_file_buf);
    +    var in_file_arg: ?[]u8 = null;
    +    var out_file: ?[]u8 = null;
    +    var out_file_h: ?[]u8 = null;
    +    var out_name_arg: ?[]u8 = null;
    +    var libc_lib_dir_arg: ?[]u8 = null;
    +    var libc_static_lib_dir_arg: ?[]u8 = null;
    +    var libc_include_dir_arg: ?[]u8 = null;
    +    var msvc_lib_dir_arg: ?[]u8 = null;
    +    var kernel32_lib_dir_arg: ?[]u8 = null;
    +    var zig_install_prefix: ?[]u8 = null;
    +    var dynamic_linker_arg: ?[]u8 = null;
    +    var cache_dir_arg: ?[]const u8 = null;
    +    var target_arch: ?[]u8 = null;
    +    var target_os: ?[]u8 = null;
    +    var target_environ: ?[]u8 = null;
    +    var mmacosx_version_min: ?[]u8 = null;
    +    var mios_version_min: ?[]u8 = null;
    +    var linker_script_arg: ?[]u8 = null;
    +    var test_name_prefix_arg: ?[]u8 = null;
     
    -    var stderr_file = %return std.io.getStdErr();
    -    var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file);
    -    const out_stream = &stderr_file_out_stream.stream;
    +    var test_filters = ArrayList([]const u8).init(allocator);
    +    defer test_filters.deinit();
     
    -    warn("====input:====\n");
    +    var lib_dirs = ArrayList([]const u8).init(allocator);
    +    defer lib_dirs.deinit();
     
    -    warn("{}", target_file_buf);
    +    var clang_argv = ArrayList([]const u8).init(allocator);
    +    defer clang_argv.deinit();
     
    -    warn("====tokenization:====\n");
    -    {
    -        var tokenizer = Tokenizer.init(target_file_buf);
    -        while (true) {
    -            const token = tokenizer.next();
    -            tokenizer.dump(token);
    -            if (token.id == Token.Id.Eof) {
    -                break;
    +    var llvm_argv = ArrayList([]const u8).init(allocator);
    +    defer llvm_argv.deinit();
    +
    +    var link_libs = ArrayList([]const u8).init(allocator);
    +    defer link_libs.deinit();
    +
    +    var frameworks = ArrayList([]const u8).init(allocator);
    +    defer frameworks.deinit();
    +
    +    var objects = ArrayList([]const u8).init(allocator);
    +    defer objects.deinit();
    +
    +    var asm_files = ArrayList([]const u8).init(allocator);
    +    defer asm_files.deinit();
    +
    +    var rpath_list = ArrayList([]const u8).init(allocator);
    +    defer rpath_list.deinit();
    +
    +    var ver_major: u32 = 0;
    +    var ver_minor: u32 = 0;
    +    var ver_patch: u32 = 0;
    +
    +    var arg_i: usize = 1;
    +    while (arg_i < args.len) : (arg_i += 1) {
    +        const arg = args[arg_i];
    +
    +        if (arg.len != 0 and arg[0] == '-') {
    +            if (mem.eql(u8, arg, "--release-fast")) {
    +                build_mode = builtin.Mode.ReleaseFast;
    +            } else if (mem.eql(u8, arg, "--release-safe")) {
    +                build_mode = builtin.Mode.ReleaseSafe;
    +            } else if (mem.eql(u8, arg, "--strip")) {
    +                strip = true;
    +            } else if (mem.eql(u8, arg, "--static")) {
    +                is_static = true;
    +            } else if (mem.eql(u8, arg, "--verbose-tokenize")) {
    +                verbose_tokenize = true;
    +            } else if (mem.eql(u8, arg, "--verbose-ast-tree")) {
    +                verbose_ast_tree = true;
    +            } else if (mem.eql(u8, arg, "--verbose-ast-fmt")) {
    +                verbose_ast_fmt = true;
    +            } else if (mem.eql(u8, arg, "--verbose-link")) {
    +                verbose_link = true;
    +            } else if (mem.eql(u8, arg, "--verbose-ir")) {
    +                verbose_ir = true;
    +            } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
    +                verbose_llvm_ir = true;
    +            } else if (mem.eql(u8, arg, "--verbose-cimport")) {
    +                verbose_cimport = true;
    +            } else if (mem.eql(u8, arg, "-mwindows")) {
    +                mwindows = true;
    +            } else if (mem.eql(u8, arg, "-mconsole")) {
    +                mconsole = true;
    +            } else if (mem.eql(u8, arg, "-rdynamic")) {
    +                rdynamic = true;
    +            } else if (mem.eql(u8, arg, "--each-lib-rpath")) {
    +                each_lib_rpath = true;
    +            } else if (mem.eql(u8, arg, "--enable-timing-info")) {
    +                timing_info = true;
    +            } else if (mem.eql(u8, arg, "--test-cmd-bin")) {
    +                @panic("TODO --test-cmd-bin");
    +            } else if (arg[1] == 'L' and arg.len > 2) {
    +                // alias for --library-path
    +                %return lib_dirs.append(arg[1..]);
    +            } else if (mem.eql(u8, arg, "--pkg-begin")) {
    +                @panic("TODO --pkg-begin");
    +            } else if (mem.eql(u8, arg, "--pkg-end")) {
    +                @panic("TODO --pkg-end");
    +            } else if (arg_i + 1 >= args.len) {
    +                return badArgs("expected another argument after {}", arg);
    +            } else {
    +                arg_i += 1;
    +                if (mem.eql(u8, arg, "--output")) {
    +                    out_file = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--output-h")) {
    +                    out_file_h = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--color")) {
    +                    if (mem.eql(u8, args[arg_i], "auto")) {
    +                        color = ErrColor.Auto;
    +                    } else if (mem.eql(u8, args[arg_i], "on")) {
    +                        color = ErrColor.On;
    +                    } else if (mem.eql(u8, args[arg_i], "off")) {
    +                        color = ErrColor.Off;
    +                    } else {
    +                        return badArgs("--color options are 'auto', 'on', or 'off'");
    +                    }
    +                } else if (mem.eql(u8, arg, "--emit")) {
    +                    if (mem.eql(u8, args[arg_i], "asm")) {
    +                        emit_file_type = Emit.Assembly;
    +                    } else if (mem.eql(u8, args[arg_i], "bin")) {
    +                        emit_file_type = Emit.Binary;
    +                    } else if (mem.eql(u8, args[arg_i], "llvm-ir")) {
    +                        emit_file_type = Emit.LlvmIr;
    +                    } else {
    +                        return badArgs("--emit options are 'asm', 'bin', or 'llvm-ir'");
    +                    }
    +                } else if (mem.eql(u8, arg, "--name")) {
    +                    out_name_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--libc-lib-dir")) {
    +                    libc_lib_dir_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--libc-static-lib-dir")) {
    +                    libc_static_lib_dir_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--libc-include-dir")) {
    +                    libc_include_dir_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--msvc-lib-dir")) {
    +                    msvc_lib_dir_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--kernel32-lib-dir")) {
    +                    kernel32_lib_dir_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--zig-install-prefix")) {
    +                    zig_install_prefix = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--dynamic-linker")) {
    +                    dynamic_linker_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "-isystem")) {
    +                    %return clang_argv.append("-isystem");
    +                    %return clang_argv.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "-dirafter")) {
    +                    %return clang_argv.append("-dirafter");
    +                    %return clang_argv.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "-mllvm")) {
    +                    %return clang_argv.append("-mllvm");
    +                    %return clang_argv.append(args[arg_i]);
    +
    +                    %return llvm_argv.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--library-path") or mem.eql(u8, arg, "-L")) {
    +                    %return lib_dirs.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--library")) {
    +                    %return link_libs.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--object")) {
    +                    %return objects.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--assembly")) {
    +                    %return asm_files.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--cache-dir")) {
    +                    cache_dir_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--target-arch")) {
    +                    target_arch = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--target-os")) {
    +                    target_os = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--target-environ")) {
    +                    target_environ = args[arg_i];
    +                } else if (mem.eql(u8, arg, "-mmacosx-version-min")) {
    +                    mmacosx_version_min = args[arg_i];
    +                } else if (mem.eql(u8, arg, "-mios-version-min")) {
    +                    mios_version_min = args[arg_i];
    +                } else if (mem.eql(u8, arg, "-framework")) {
    +                    %return frameworks.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--linker-script")) {
    +                    linker_script_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "-rpath")) {
    +                    %return rpath_list.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--test-filter")) {
    +                    %return test_filters.append(args[arg_i]);
    +                } else if (mem.eql(u8, arg, "--test-name-prefix")) {
    +                    test_name_prefix_arg = args[arg_i];
    +                } else if (mem.eql(u8, arg, "--ver-major")) {
    +                    ver_major = %return std.fmt.parseUnsigned(u32, args[arg_i], 10);
    +                } else if (mem.eql(u8, arg, "--ver-minor")) {
    +                    ver_minor = %return std.fmt.parseUnsigned(u32, args[arg_i], 10);
    +                } else if (mem.eql(u8, arg, "--ver-patch")) {
    +                    ver_patch = %return std.fmt.parseUnsigned(u32, args[arg_i], 10);
    +                } else if (mem.eql(u8, arg, "--test-cmd")) {
    +                    @panic("TODO --test-cmd");
    +                } else {
    +                    return badArgs("invalid argument: {}", arg);
    +                }
                 }
    +        } else if (cmd == Cmd.None) {
    +            if (mem.eql(u8, arg, "build-obj")) {
    +                cmd = Cmd.Build;
    +                build_kind = Module.Kind.Obj;
    +            } else if (mem.eql(u8, arg, "build-exe")) {
    +                cmd = Cmd.Build;
    +                build_kind = Module.Kind.Exe;
    +            } else if (mem.eql(u8, arg, "build-lib")) {
    +                cmd = Cmd.Build;
    +                build_kind = Module.Kind.Lib;
    +            } else if (mem.eql(u8, arg, "version")) {
    +                cmd = Cmd.Version;
    +            } else if (mem.eql(u8, arg, "zen")) {
    +                cmd = Cmd.Zen;
    +            } else if (mem.eql(u8, arg, "translate-c")) {
    +                cmd = Cmd.TranslateC;
    +            } else if (mem.eql(u8, arg, "test")) {
    +                cmd = Cmd.Test;
    +                build_kind = Module.Kind.Exe;
    +            } else {
    +                return badArgs("unrecognized command: {}", arg);
    +            }
    +        } else switch (cmd) {
    +            Cmd.Build, Cmd.TranslateC, Cmd.Test => {
    +                if (in_file_arg == null) {
    +                    in_file_arg = arg;
    +                } else {
    +                    return badArgs("unexpected extra parameter: {}", arg);
    +                }
    +            },
    +            Cmd.Version, Cmd.Zen, Cmd.Targets => {
    +                return badArgs("unexpected extra parameter: {}", arg);
    +            },
    +            Cmd.None => unreachable,
             }
         }
     
    -    warn("====parse:====\n");
    +    target.initializeAll();
     
    -    var tokenizer = Tokenizer.init(target_file_buf);
    -    var parser = Parser.init(&tokenizer, allocator, target_file);
    -    defer parser.deinit();
    +    // TODO
    +//    ZigTarget alloc_target;
    +//    ZigTarget *target;
    +//    if (!target_arch && !target_os && !target_environ) {
    +//        target = nullptr;
    +//    } else {
    +//        target = &alloc_target;
    +//        get_unknown_target(target);
    +//        if (target_arch) {
    +//            if (parse_target_arch(target_arch, &target->arch)) {
    +//                fprintf(stderr, "invalid --target-arch argument\n");
    +//                return usage(arg0);
    +//            }
    +//        }
    +//        if (target_os) {
    +//            if (parse_target_os(target_os, &target->os)) {
    +//                fprintf(stderr, "invalid --target-os argument\n");
    +//                return usage(arg0);
    +//            }
    +//        }
    +//        if (target_environ) {
    +//            if (parse_target_environ(target_environ, &target->env_type)) {
    +//                fprintf(stderr, "invalid --target-environ argument\n");
    +//                return usage(arg0);
    +//            }
    +//        }
    +//    }
     
    -    const root_node = %return parser.parse();
    -    defer parser.freeAst(root_node);
    +    switch (cmd) {
    +        Cmd.None => return badArgs("expected command"),
    +        Cmd.Zen => return printZen(),
    +        Cmd.Build, Cmd.Test, Cmd.TranslateC => {
    +            if (cmd == Cmd.Build and in_file_arg == null and objects.len == 0 and asm_files.len == 0) {
    +                return badArgs("expected source file argument or at least one --object or --assembly argument");
    +            } else if ((cmd == Cmd.TranslateC or cmd == Cmd.Test) and in_file_arg == null) {
    +                return badArgs("expected source file argument");
    +            } else if (cmd == Cmd.Build and build_kind == Module.Kind.Obj and objects.len != 0) {
    +                return badArgs("When building an object file, --object arguments are invalid");
    +            }
     
    -    %return parser.renderAst(out_stream, root_node);
    +            const root_name = switch (cmd) {
    +                Cmd.Build, Cmd.TranslateC => x: {
    +                    if (out_name_arg) |out_name| {
    +                        break :x out_name;
    +                    } else if (in_file_arg) |in_file_path| {
    +                        const basename = os.path.basename(in_file_path);
    +                        var it = mem.split(basename, ".");
    +                        break :x it.next() ?? return badArgs("file name cannot be empty");
    +                    } else {
    +                        return badArgs("--name [name] not provided and unable to infer");
    +                    }
    +                },
    +                Cmd.Test => "test",
    +                else => unreachable,
    +            };
     
    -    warn("====fmt:====\n");
    -    %return parser.renderSource(out_stream, root_node);
    +            const zig_root_source_file = if (cmd == Cmd.TranslateC) null else in_file_arg;
    +
    +            const chosen_cache_dir = cache_dir_arg ?? default_zig_cache_name;
    +            const full_cache_dir = %return os.path.resolve(allocator, ".", chosen_cache_dir);
    +            defer allocator.free(full_cache_dir);
    +
    +            const zig_lib_dir = %return resolveZigLibDir(allocator, zig_install_prefix);
    +            %defer allocator.free(zig_lib_dir);
    +
    +            const module = %return Module.create(allocator, root_name, zig_root_source_file,
    +                Target.Native, build_kind, build_mode, zig_lib_dir, full_cache_dir);
    +            defer module.destroy();
    +
    +            module.version_major = ver_major;
    +            module.version_minor = ver_minor;
    +            module.version_patch = ver_patch;
    +
    +            module.is_test = cmd == Cmd.Test;
    +            if (linker_script_arg) |linker_script| {
    +                module.linker_script = linker_script;
    +            }
    +            module.each_lib_rpath = each_lib_rpath;
    +            module.clang_argv = clang_argv.toSliceConst();
    +            module.llvm_argv = llvm_argv.toSliceConst();
    +            module.strip = strip;
    +            module.is_static = is_static;
    +
    +            if (libc_lib_dir_arg) |libc_lib_dir| {
    +                module.libc_lib_dir = libc_lib_dir;
    +            }
    +            if (libc_static_lib_dir_arg) |libc_static_lib_dir| {
    +                module.libc_static_lib_dir = libc_static_lib_dir;
    +            }
    +            if (libc_include_dir_arg) |libc_include_dir| {
    +                module.libc_include_dir = libc_include_dir;
    +            }
    +            if (msvc_lib_dir_arg) |msvc_lib_dir| {
    +                module.msvc_lib_dir = msvc_lib_dir;
    +            }
    +            if (kernel32_lib_dir_arg) |kernel32_lib_dir| {
    +                module.kernel32_lib_dir = kernel32_lib_dir;
    +            }
    +            if (dynamic_linker_arg) |dynamic_linker| {
    +                module.dynamic_linker = dynamic_linker;
    +            }
    +            module.verbose_tokenize = verbose_tokenize;
    +            module.verbose_ast_tree = verbose_ast_tree;
    +            module.verbose_ast_fmt = verbose_ast_fmt;
    +            module.verbose_link = verbose_link;
    +            module.verbose_ir = verbose_ir;
    +            module.verbose_llvm_ir = verbose_llvm_ir;
    +            module.verbose_cimport = verbose_cimport;
    +
    +            module.err_color = color;
    +
    +            module.lib_dirs = lib_dirs.toSliceConst();
    +            module.darwin_frameworks = frameworks.toSliceConst();
    +            module.rpath_list = rpath_list.toSliceConst();
    +
    +            for (link_libs.toSliceConst()) |name| {
    +                _ = %return module.addLinkLib(name, true);
    +            }
    +
    +            module.windows_subsystem_windows = mwindows;
    +            module.windows_subsystem_console = mconsole;
    +            module.linker_rdynamic = rdynamic;
    +
    +            if (mmacosx_version_min != null and mios_version_min != null) {
    +                return badArgs("-mmacosx-version-min and -mios-version-min options not allowed together");
    +            }
    +
    +            if (mmacosx_version_min) |ver| {
    +                module.darwin_version_min = Module.DarwinVersionMin { .MacOS = ver };
    +            } else if (mios_version_min) |ver| {
    +                module.darwin_version_min = Module.DarwinVersionMin { .Ios = ver };
    +            }
    +
    +            module.test_filters = test_filters.toSliceConst();
    +            module.test_name_prefix = test_name_prefix_arg;
    +            module.out_h_path = out_file_h;
    +
    +            // TODO
    +            //add_package(g, cur_pkg, g->root_package);
    +
    +            switch (cmd) {
    +                Cmd.Build => {
    +                    module.emit_file_type = emit_file_type;
    +
    +                    module.link_objects = objects.toSliceConst();
    +                    module.assembly_files = asm_files.toSliceConst();
    +
    +                    %return module.build();
    +                    %return module.link(out_file);
    +                },
    +                Cmd.TranslateC => @panic("TODO translate-c"),
    +                Cmd.Test => @panic("TODO test cmd"),
    +                else => unreachable,
    +            }
    +        },
    +        Cmd.Version => @panic("TODO zig version"),
    +        Cmd.Targets => @panic("TODO zig targets"),
    +    }
     }
     
    -test "import other tests" {
    -    _ = @import("parser.zig");
    -    _ = @import("tokenizer.zig");
    +fn printUsage(stream: &io.OutStream) -> %void {
    +    %return stream.write(
    +        \\Usage: zig [command] [options]
    +        \\
    +        \\Commands:
    +        \\  build                        build project from build.zig
    +        \\  build-exe [source]           create executable from source or object files
    +        \\  build-lib [source]           create library from source or object files
    +        \\  build-obj [source]           create object from source or assembly
    +        \\  translate-c [source]         convert c code to zig code
    +        \\  targets                      list available compilation targets
    +        \\  test [source]                create and run a test build
    +        \\  version                      print version number and exit
    +        \\  zen                          print zen of zig and exit
    +        \\Compile Options:
    +        \\  --assembly [source]          add assembly file to build
    +        \\  --cache-dir [path]           override the cache directory
    +        \\  --color [auto|off|on]        enable or disable colored error messages
    +        \\  --emit [filetype]            emit a specific file format as compilation output
    +        \\  --enable-timing-info         print timing diagnostics
    +        \\  --libc-include-dir [path]    directory where libc stdlib.h resides
    +        \\  --name [name]                override output name
    +        \\  --output [file]              override destination path
    +        \\  --output-h [file]            override generated header file path
    +        \\  --pkg-begin [name] [path]    make package available to import and push current pkg
    +        \\  --pkg-end                    pop current pkg
    +        \\  --release-fast               build with optimizations on and safety off
    +        \\  --release-safe               build with optimizations on and safety on
    +        \\  --static                     output will be statically linked
    +        \\  --strip                      exclude debug symbols
    +        \\  --target-arch [name]         specify target architecture
    +        \\  --target-environ [name]      specify target environment
    +        \\  --target-os [name]           specify target operating system
    +        \\  --verbose-tokenize           enable compiler debug info: tokenization
    +        \\  --verbose-ast-tree           enable compiler debug info: parsing into an AST (treeview)
    +        \\  --verbose-ast-fmt            enable compiler debug info: parsing into an AST (render source)
    +        \\  --verbose-cimport            enable compiler debug info: C imports
    +        \\  --verbose-ir                 enable compiler debug info: Zig IR
    +        \\  --verbose-llvm-ir            enable compiler debug info: LLVM IR
    +        \\  --verbose-link               enable compiler debug info: linking
    +        \\  --zig-install-prefix [path]  override directory where zig thinks it is installed
    +        \\  -dirafter [dir]              same as -isystem but do it last
    +        \\  -isystem [dir]               add additional search path for other .h files
    +        \\  -mllvm [arg]                 additional arguments to forward to LLVM's option processing
    +        \\Link Options:
    +        \\  --ar-path [path]             set the path to ar
    +        \\  --dynamic-linker [path]      set the path to ld.so
    +        \\  --each-lib-rpath             add rpath for each used dynamic library
    +        \\  --libc-lib-dir [path]        directory where libc crt1.o resides
    +        \\  --libc-static-lib-dir [path] directory where libc crtbegin.o resides
    +        \\  --msvc-lib-dir [path]        (windows) directory where vcruntime.lib resides
    +        \\  --kernel32-lib-dir [path]    (windows) directory where kernel32.lib resides
    +        \\  --library [lib]              link against lib
    +        \\  --library-path [dir]         add a directory to the library search path
    +        \\  --linker-script [path]       use a custom linker script
    +        \\  --object [obj]               add object file to build
    +        \\  -L[dir]                      alias for --library-path
    +        \\  -rdynamic                    add all symbols to the dynamic symbol table
    +        \\  -rpath [path]                add directory to the runtime library search path
    +        \\  -mconsole                    (windows) --subsystem console to the linker
    +        \\  -mwindows                    (windows) --subsystem windows to the linker
    +        \\  -framework [name]            (darwin) link against framework
    +        \\  -mios-version-min [ver]      (darwin) set iOS deployment target
    +        \\  -mmacosx-version-min [ver]   (darwin) set Mac OS X deployment target
    +        \\  --ver-major [ver]            dynamic library semver major version
    +        \\  --ver-minor [ver]            dynamic library semver minor version
    +        \\  --ver-patch [ver]            dynamic library semver patch version
    +        \\Test Options:
    +        \\  --test-filter [text]         skip tests that do not match filter
    +        \\  --test-name-prefix [text]    add prefix to all tests
    +        \\  --test-cmd [arg]             specify test execution command one arg at a time
    +        \\  --test-cmd-bin               appends test binary path to test cmd args
    +        \\
    +    );
    +}
    +
    +fn printZen() -> %void {
    +    var stdout_file = %return io.getStdErr();
    +    %return stdout_file.write(
    +        \\
    +        \\ * Communicate intent precisely.
    +        \\ * Edge cases matter.
    +        \\ * Favor reading code over writing code.
    +        \\ * Only one obvious way to do things.
    +        \\ * Runtime crashes are better than bugs.
    +        \\ * Compile errors are better than runtime crashes.
    +        \\ * Incremental improvements.
    +        \\ * Avoid local maximums.
    +        \\ * Reduce the amount one must remember.
    +        \\ * Minimize energy spent on coding style.
    +        \\ * Together we serve end users.
    +        \\
    +        \\
    +    );
    +}
    +
    +/// Caller must free result
    +fn resolveZigLibDir(allocator: &mem.Allocator, zig_install_prefix_arg: ?[]const u8) -> %[]u8 {
    +    if (zig_install_prefix_arg) |zig_install_prefix| {
    +        return testZigInstallPrefix(allocator, zig_install_prefix) %% |err| {
    +            warn("No Zig installation found at prefix {}: {}\n", zig_install_prefix_arg, @errorName(err));
    +            return error.ZigInstallationNotFound;
    +        };
    +    } else {
    +        return findZigLibDir(allocator) %% |err| {
    +            warn("Unable to find zig lib directory: {}.\nReinstall Zig or use --zig-install-prefix.\n",
    +                @errorName(err));
    +            return error.ZigLibDirNotFound;
    +        };
    +    }
    +}
    +
    +/// Caller must free result
    +fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) -> %[]u8 {
    +    const test_zig_dir = %return os.path.join(allocator, test_path, "lib", "zig");
    +    %defer allocator.free(test_zig_dir);
    +
    +    const test_index_file = %return os.path.join(allocator, test_zig_dir, "std", "index.zig");
    +    defer allocator.free(test_index_file);
    +
    +    var file = %return io.File.openRead(test_index_file, allocator);
    +    file.close();
    +
    +    return test_zig_dir;
    +}
    +
    +/// Caller must free result
    +fn findZigLibDir(allocator: &mem.Allocator) -> %[]u8 {
    +    const self_exe_path = %return os.selfExeDirPath(allocator);
    +    defer allocator.free(self_exe_path);
    +
    +    var cur_path: []const u8 = self_exe_path;
    +    while (true) {
    +        const test_dir = os.path.dirname(cur_path);
    +
    +        if (mem.eql(u8, test_dir, cur_path)) {
    +            break;
    +        }
    +
    +        return testZigInstallPrefix(allocator, test_dir) %% |err| {
    +            cur_path = test_dir;
    +            continue;
    +        };
    +    }
    +
    +    // TODO look in hard coded installation path from configuration
    +    //if (ZIG_INSTALL_PREFIX != nullptr) {
    +    //    if (test_zig_install_prefix(buf_create_from_str(ZIG_INSTALL_PREFIX), out_path)) {
    +    //        return 0;
    +    //    }
    +    //}
    +
    +    return error.FileNotFound;
     }
    diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig
    new file mode 100644
    index 0000000000..700ccf0176
    --- /dev/null
    +++ b/src-self-hosted/module.zig
    @@ -0,0 +1,295 @@
    +const std = @import("std");
    +const os = std.os;
    +const io = std.io;
    +const mem = std.mem;
    +const Buffer = std.Buffer;
    +const llvm = @import("llvm.zig");
    +const c = @import("c.zig");
    +const builtin = @import("builtin");
    +const Target = @import("target.zig").Target;
    +const warn = std.debug.warn;
    +const Tokenizer = @import("tokenizer.zig").Tokenizer;
    +const Token = @import("tokenizer.zig").Token;
    +const Parser = @import("parser.zig").Parser;
    +const ArrayList = std.ArrayList;
    +
    +pub const Module = struct {
    +    allocator: &mem.Allocator,
    +    name: Buffer,
    +    root_src_path: ?[]const u8,
    +    module: llvm.ModuleRef,
    +    context: llvm.ContextRef,
    +    builder: llvm.BuilderRef,
    +    target: Target,
    +    build_mode: builtin.Mode,
    +    zig_lib_dir: []const u8,
    +
    +    version_major: u32,
    +    version_minor: u32,
    +    version_patch: u32,
    +
    +    linker_script: ?[]const u8,
    +    cache_dir: []const u8,
    +    libc_lib_dir: ?[]const u8,
    +    libc_static_lib_dir: ?[]const u8,
    +    libc_include_dir: ?[]const u8,
    +    msvc_lib_dir: ?[]const u8,
    +    kernel32_lib_dir: ?[]const u8,
    +    dynamic_linker: ?[]const u8,
    +    out_h_path: ?[]const u8,
    +
    +    is_test: bool,
    +    each_lib_rpath: bool,
    +    strip: bool,
    +    is_static: bool,
    +    linker_rdynamic: bool,
    +
    +    clang_argv: []const []const u8,
    +    llvm_argv: []const []const u8,
    +    lib_dirs: []const []const u8,
    +    rpath_list: []const []const u8,
    +    assembly_files: []const []const u8,
    +    link_objects: []const []const u8,
    +
    +    windows_subsystem_windows: bool,
    +    windows_subsystem_console: bool,
    +
    +    link_libs_list: ArrayList(&LinkLib),
    +    libc_link_lib: ?&LinkLib,
    +
    +    err_color: ErrColor,
    +
    +    verbose_tokenize: bool,
    +    verbose_ast_tree: bool,
    +    verbose_ast_fmt: bool,
    +    verbose_cimport: bool,
    +    verbose_ir: bool,
    +    verbose_llvm_ir: bool,
    +    verbose_link: bool,
    +
    +    darwin_frameworks: []const []const u8,
    +    darwin_version_min: DarwinVersionMin,
    +
    +    test_filters: []const []const u8,
    +    test_name_prefix: ?[]const u8,
    +
    +    emit_file_type: Emit,
    +
    +    kind: Kind,
    +
    +    pub const DarwinVersionMin = union(enum) {
    +        None,
    +        MacOS: []const u8,
    +        Ios: []const u8,
    +    };
    +
    +    pub const Kind = enum {
    +        Exe,
    +        Lib,
    +        Obj,
    +    };
    +
    +    pub const ErrColor = enum {
    +        Auto,
    +        Off,
    +        On,
    +    };
    +
    +    pub const LinkLib = struct {
    +        name: []const u8,
    +        path: ?[]const u8,
    +        /// the list of symbols we depend on from this lib
    +        symbols: ArrayList([]u8),
    +        provided_explicitly: bool,
    +    };
    +
    +    pub const Emit = enum {
    +        Binary,
    +        Assembly,
    +        LlvmIr,
    +    };
    +
    +    pub fn create(allocator: &mem.Allocator, name: []const u8, root_src_path: ?[]const u8, target: &const Target,
    +        kind: Kind, build_mode: builtin.Mode, zig_lib_dir: []const u8, cache_dir: []const u8) -> %&Module
    +    {
    +        var name_buffer = %return Buffer.init(allocator, name);
    +        %defer name_buffer.deinit();
    +
    +        const context = c.LLVMContextCreate() ?? return error.OutOfMemory;
    +        %defer c.LLVMContextDispose(context);
    +
    +        const module = c.LLVMModuleCreateWithNameInContext(name_buffer.ptr(), context) ?? return error.OutOfMemory;
    +        %defer c.LLVMDisposeModule(module);
    +
    +        const builder = c.LLVMCreateBuilderInContext(context) ?? return error.OutOfMemory;
    +        %defer c.LLVMDisposeBuilder(builder);
    +
    +        const module_ptr = %return allocator.create(Module);
    +        %defer allocator.destroy(module_ptr);
    +
    +        *module_ptr = Module {
    +            .allocator = allocator,
    +            .name = name_buffer,
    +            .root_src_path = root_src_path,
    +            .module = module,
    +            .context = context,
    +            .builder = builder,
    +            .target = *target,
    +            .kind = kind,
    +            .build_mode = build_mode,
    +            .zig_lib_dir = zig_lib_dir,
    +            .cache_dir = cache_dir,
    +
    +            .version_major = 0,
    +            .version_minor = 0,
    +            .version_patch = 0,
    +
    +            .verbose_tokenize = false,
    +            .verbose_ast_tree = false,
    +            .verbose_ast_fmt = false,
    +            .verbose_cimport = false,
    +            .verbose_ir = false,
    +            .verbose_llvm_ir = false,
    +            .verbose_link = false,
    +
    +            .linker_script = null,
    +            .libc_lib_dir = null,
    +            .libc_static_lib_dir = null,
    +            .libc_include_dir = null,
    +            .msvc_lib_dir = null,
    +            .kernel32_lib_dir = null,
    +            .dynamic_linker = null,
    +            .out_h_path = null,
    +            .is_test = false,
    +            .each_lib_rpath = false,
    +            .strip = false,
    +            .is_static = false,
    +            .linker_rdynamic = false,
    +            .clang_argv = [][]const u8{},
    +            .llvm_argv = [][]const u8{},
    +            .lib_dirs = [][]const u8{},
    +            .rpath_list = [][]const u8{},
    +            .assembly_files = [][]const u8{},
    +            .link_objects = [][]const u8{},
    +            .windows_subsystem_windows = false,
    +            .windows_subsystem_console = false,
    +            .link_libs_list = ArrayList(&LinkLib).init(allocator),
    +            .libc_link_lib = null,
    +            .err_color = ErrColor.Auto,
    +            .darwin_frameworks = [][]const u8{},
    +            .darwin_version_min = DarwinVersionMin.None,
    +            .test_filters = [][]const u8{},
    +            .test_name_prefix = null,
    +            .emit_file_type = Emit.Binary,
    +        };
    +        return module_ptr;
    +    }
    +
    +    fn dump(self: &Module) {
    +        c.LLVMDumpModule(self.module);
    +    }
    +
    +    pub fn destroy(self: &Module) {
    +        c.LLVMDisposeBuilder(self.builder);
    +        c.LLVMDisposeModule(self.module);
    +        c.LLVMContextDispose(self.context);
    +        self.name.deinit();
    +
    +        self.allocator.destroy(self);
    +    }
    +
    +    pub fn build(self: &Module) -> %void {
    +        const root_src_path = self.root_src_path ?? @panic("TODO handle null root src path");
    +        const root_src_real_path = os.path.real(self.allocator, root_src_path) %% |err| {
    +            %return printError("unable to open '{}': {}", root_src_path, err);
    +            return err;
    +        };
    +        %defer self.allocator.free(root_src_real_path);
    +
    +        const source_code = io.readFileAlloc(root_src_real_path, self.allocator) %% |err| {
    +            %return printError("unable to open '{}': {}", root_src_real_path, err);
    +            return err;
    +        };
    +        %defer self.allocator.free(source_code);
    +
    +        warn("====input:====\n");
    +
    +        warn("{}", source_code);
    +
    +        warn("====tokenization:====\n");
    +        {
    +            var tokenizer = Tokenizer.init(source_code);
    +            while (true) {
    +                const token = tokenizer.next();
    +                tokenizer.dump(token);
    +                if (token.id == Token.Id.Eof) {
    +                    break;
    +                }
    +            }
    +        }
    +
    +        warn("====parse:====\n");
    +
    +        var tokenizer = Tokenizer.init(source_code);
    +        var parser = Parser.init(&tokenizer, self.allocator, root_src_real_path);
    +        defer parser.deinit();
    +
    +        const root_node = %return parser.parse();
    +        defer parser.freeAst(root_node);
    +
    +        var stderr_file = %return std.io.getStdErr();
    +        var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file);
    +        const out_stream = &stderr_file_out_stream.stream;
    +        %return parser.renderAst(out_stream, root_node);
    +
    +        warn("====fmt:====\n");
    +        %return parser.renderSource(out_stream, root_node);
    +
    +        warn("====ir:====\n");
    +        warn("TODO\n\n");
    +
    +        warn("====llvm ir:====\n");
    +        self.dump();
    +        
    +    }
    +
    +    pub fn link(self: &Module, out_file: ?[]const u8) -> %void {
    +        warn("TODO link");
    +    }
    +
    +    pub fn addLinkLib(self: &Module, name: []const u8, provided_explicitly: bool) -> %&LinkLib {
    +        const is_libc = mem.eql(u8, name, "c");
    +
    +        if (is_libc) {
    +            if (self.libc_link_lib) |libc_link_lib| {
    +                return libc_link_lib;
    +            }
    +        }
    +
    +        for (self.link_libs_list.toSliceConst()) |existing_lib| {
    +            if (mem.eql(u8, name, existing_lib.name)) {
    +                return existing_lib;
    +            }
    +        }
    +
    +        const link_lib = %return self.allocator.create(LinkLib);
    +        *link_lib = LinkLib {
    +            .name = name,
    +            .path = null,
    +            .provided_explicitly = provided_explicitly,
    +            .symbols = ArrayList([]u8).init(self.allocator),
    +        };
    +        %return self.link_libs_list.append(link_lib);
    +        if (is_libc) {
    +            self.libc_link_lib = link_lib;
    +        }
    +        return link_lib;
    +    }
    +};
    +
    +fn printError(comptime format: []const u8, args: ...) -> %void {
    +    var stderr_file = %return std.io.getStdErr();
    +    var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file);
    +    const out_stream = &stderr_file_out_stream.stream;
    +    %return out_stream.print(format, args);
    +}
    diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig
    index 90e2132ac5..0d077690dd 100644
    --- a/src-self-hosted/target.zig
    +++ b/src-self-hosted/target.zig
    @@ -1,5 +1,56 @@
    +const builtin = @import("builtin");
     const c = @import("c.zig");
     
    +pub const CrossTarget = struct {
    +    arch: builtin.Arch,
    +    os: builtin.Os,
    +    environ: builtin.Environ,
    +};
    +
    +pub const Target = union(enum) {
    +    Native,
    +    Cross: CrossTarget,
    +
    +    pub fn oFileExt(self: &const Target) -> []const u8 {
    +        const environ = switch (*self) {
    +            Target.Native => builtin.environ,
    +            Target.Cross => |t| t.environ,
    +        };
    +        return switch (environ) {
    +            builtin.Environ.msvc => ".obj",
    +            else => ".o",
    +        };
    +    }
    +
    +    pub fn exeFileExt(self: &const Target) -> []const u8 {
    +        return switch (self.getOs()) {
    +            builtin.Os.windows => ".exe",
    +            else => "",
    +        };
    +    }
    +
    +    pub fn getOs(self: &const Target) -> builtin.Os {
    +        return switch (*self) {
    +            Target.Native => builtin.os,
    +            Target.Cross => |t| t.os,
    +        };
    +    }
    +
    +    pub fn isDarwin(self: &const Target) -> bool {
    +        return switch (self.getOs()) {
    +            builtin.Os.darwin, builtin.Os.ios, builtin.Os.macosx => true,
    +            else => false,
    +        };
    +    }
    +
    +    pub fn isWindows(self: &const Target) -> bool {
    +        return switch (self.getOs()) {
    +            builtin.Os.windows => true,
    +            else => false,
    +        };
    +    }
    +};
    +
     pub fn initializeAll() {
         c.LLVMInitializeAllTargets();
         c.LLVMInitializeAllTargetInfos();
    diff --git a/std/buffer.zig b/std/buffer.zig
    index 267d9b6353..4f5d281f48 100644
    --- a/std/buffer.zig
    +++ b/std/buffer.zig
    @@ -139,6 +139,11 @@ pub const Buffer = struct {
             %return self.resize(m.len);
             mem.copy(u8, self.list.toSlice(), m);
         }
    +
    +    /// For passing to C functions.
    +    pub fn ptr(self: &const Buffer) -> &u8 {
    +        return self.list.items.ptr;
    +    }
     };
     
     test "simple Buffer" {
    diff --git a/std/c/index.zig b/std/c/index.zig
    index 2ac867ee71..e04b990633 100644
    --- a/std/c/index.zig
    +++ b/std/c/index.zig
    @@ -48,3 +48,4 @@ pub extern "c" fn setregid(rgid: c_uint, egid: c_uint) -> c_int;
     pub extern "c" fn malloc(usize) -> ?&c_void;
     pub extern "c" fn realloc(&c_void, usize) -> ?&c_void;
     pub extern "c" fn free(&c_void);
    +pub extern "c" fn posix_memalign(memptr: &&c_void, alignment: usize, size: usize) -> c_int;
    diff --git a/std/heap.zig b/std/heap.zig
    index 605eddb40b..ec447c1aa8 100644
    --- a/std/heap.zig
    +++ b/std/heap.zig
    @@ -10,7 +10,8 @@ const Allocator = mem.Allocator;
     
     error OutOfMemory;
     
    -pub var c_allocator = Allocator {
    +pub const c_allocator = &c_allocator_state;
    +var c_allocator_state = Allocator {
         .allocFn = cAlloc,
         .reallocFn = cRealloc,
         .freeFn = cFree,
    @@ -24,15 +25,13 @@ fn cAlloc(self: &Allocator, n: usize, alignment: u29) -> %[]u8 {
     }
     
     fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 {
    -    if (new_size <= old_mem.len) {
    +    const old_ptr = @ptrCast(&c_void, old_mem.ptr);
    +    if (c.realloc(old_ptr, new_size)) |buf| {
    +        return @ptrCast(&u8, buf)[0..new_size];
    +    } else if (new_size <= old_mem.len) {
             return old_mem[0..new_size];
         } else {
    -        const old_ptr = @ptrCast(&c_void, old_mem.ptr);
    -        if (c.realloc(old_ptr, usize(new_size))) |buf| {
    -            return @ptrCast(&u8, buf)[0..new_size];
    -        } else {
    -            return error.OutOfMemory;
    -        }
    +        return error.OutOfMemory;
         }
     }
     
    diff --git a/std/os/index.zig b/std/os/index.zig
    index 09109c3242..8e79eda40b 100644
    --- a/std/os/index.zig
    +++ b/std/os/index.zig
    @@ -1543,6 +1543,39 @@ pub fn openSelfExe() -> %io.File {
         }
     }
     
    +/// Get the directory path that contains the current executable.
    +/// Caller owns returned memory.
    +pub fn selfExeDirPath(allocator: &mem.Allocator) -> %[]u8 {
    +    switch (builtin.os) {
    +        Os.linux => {
    +            // If the currently executing binary has been deleted,
    +            // the file path looks something like `/a/b/c/exe (deleted)`
    +            // This path cannot be opened, but it's valid for determining the directory
    +            // the executable was in when it was run.
    +            const full_exe_path = %return readLink(allocator, "/proc/self/exe");
    +            %defer allocator.free(full_exe_path);
    +            const dir = path.dirname(full_exe_path);
    +            return allocator.shrink(u8, full_exe_path, dir.len);
    +        },
    +        Os.windows => {
    +            @panic("TODO windows std.os.selfExeDirPath");
    +            //buf_resize(out_path, 256);
    +            //for (;;) {
    +            //    DWORD copied_amt = GetModuleFileName(nullptr, buf_ptr(out_path), buf_len(out_path));
    +            //    if (copied_amt <= 0) {
    +            //        return ErrorFileNotFound;
    +            //    }
    +            //    if (copied_amt < buf_len(out_path)) {
    +            //        buf_resize(out_path, copied_amt);
    +            //        return 0;
    +            //    }
    +            //    buf_resize(out_path, buf_len(out_path) * 2);
    +            //}
    +        },
    +        else => @compileError("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)),
    +    }
    +}
    +
     pub fn isTty(handle: FileHandle) -> bool {
         if (is_windows) {
             return windows_util.windowsIsTty(handle);