diff --git a/README.md b/README.md index 2a1ab6bcbf..7598c623c3 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 == 6.x, compiled with the same gcc or clang version above ##### Windows * Microsoft Visual Studio 2015 + * LLVM, Clang, LLD libraries == 6.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 == 6.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 1c1d4f832e..96638659be 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(); @@ -25,14 +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"); + 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"); @@ -53,6 +64,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)); @@ -60,3 +75,198 @@ 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 llvm_config_exe = b.findProgram( + [][]const u8{"llvm-config-5.0", "llvm-config"}, + [][]const u8{ + "/usr/local/opt/llvm@5/bin", + "/mingw64/bin", + "/c/msys64/mingw64/bin", + "c:/msys64/mingw64/bin", + "C:/Libraries/llvm-5.0.0/bin", + }) %% |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"}); + const libdir_output = b.exec([][]const u8{llvm_config_exe, "--libdir"}); + + 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; +} + +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/doc/docgen.zig b/doc/docgen.zig index 81febb074e..d481baf4b3 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -42,14 +42,14 @@ 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| { 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 e5b896410d..162c8b3e03 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -136,6 +136,7 @@
  • @divFloor
  • @divTrunc
  • @embedFile
  • +
  • @export
  • @tagName
  • @EnumTagType
  • @errorName
  • @@ -3020,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
    @@ -4368,6 +4368,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

    @@ -5815,13 +5820,15 @@ 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 ";" +GlobalVarDecl = option("export") VariableDeclaration ";" -VariableDeclaration = option("comptime") ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") "=" Expression +LocalVarDecl = option("comptime") VariableDeclaration + +VariableDeclaration = ("var" | "const") Symbol option(":" TypeExpr) option("align" "(" Expression ")") option("section" "(" Expression ")") "=" Expression ContainerMember = (ContainerField | FnDef | GlobalVarDecl) @@ -5831,21 +5838,17 @@ UseDecl = "use" Expression ";" ExternDecl = "extern" option(String) (FnProto | VariableDeclaration) ";" -FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("->" TypeExpr) +FnProto = option("coldcc" | "nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) -VisibleMod = "pub" | "export" - -FnDef = option("inline" | "extern") FnProto Block +FnDef = option("inline" | "export") FnProto Block ParamDeclList = "(" list(ParamDecl, ",") ")" ParamDecl = option("noalias" | "comptime") option(Symbol ":") (TypeExpr | "...") -Block = "{" many(Statement) option(Expression) "}" +Block = option(Symbol ":") "{" many(Statement) "}" -Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" - -Label = Symbol ":" +Statement = LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" TypeExpr = PrefixOpExpression | "var" @@ -5885,13 +5888,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 @@ -5901,7 +5904,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 @@ -5949,15 +5952,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 | (option("extern") 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/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/ast.zig b/src-self-hosted/ast.zig new file mode 100644 index 0000000000..5fd836b950 --- /dev/null +++ b/src-self-hosted/ast.zig @@ -0,0 +1,273 @@ +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, + Block, + InfixOp, + PrefixOp, + 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.Block => @fieldParentPtr(NodeBlock, "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), + }; + } + + 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.Block => allocator.destroy(@fieldParentPtr(NodeBlock, "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)), + }; + } +}; + +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 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 NodeInfixOp = struct { + base: Node, + op_token: Token, + lhs: &Node, + op: InfixOp, + rhs: &Node, + + const InfixOp = enum { + EqualEqual, + BangEqual, + }; + + pub fn iterate(self: &NodeInfixOp, index: usize) -> ?&Node { + var i = index; + + 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; + } +}; + +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/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/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 71180b2001..6fdacda4b2 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1,208 +1,476 @@ +const std = @import("std"); +const mem = std.mem; +const io = std.io; +const os = std.os; +const heap = std.heap; +const warn = std.debug.warn; +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 io = @import("std").io; -const os = @import("std").os; -const heap = @import("std").heap; +const ArrayList = std.ArrayList; -// TODO: sync up CLI with c++ code -// TODO: concurrency +error InvalidCommandLineArguments; +error ZigLibDirNotFound; +error ZigInstallationNotFound; -error InvalidArgument; -error MissingArg0; - -var arg0: []u8 = undefined; - -var stderr_file: io.File = undefined; -const stderr = &stderr_file.out_stream; +const default_zig_cache_name = "zig-cache"; 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; + main2() %% |err| { + if (err != error.InvalidCommandLineArguments) { + warn("{}\n", @errorName(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") %% {}; - return err; }; - defer incrementing_allocator.deinit(); +} - const allocator = &incrementing_allocator.allocator; - - arg0 = %return (args_it.next(allocator) ?? error.MissingArg0); - defer allocator.free(arg0); +const Cmd = enum { + None, + Build, + Test, + Version, + Zen, + TranslateC, + Targets, +}; + +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); + + 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; - var build_mode = builtin.Mode.Debug; var strip = false; var is_static = false; - var verbose = 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; - while (args_it.next()) |arg_or_err| { - const arg = %return arg_or_err; + 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; - if (arg[0] == '-') { - if (strcmp(arg, "--release-fast") == 0) { + var test_filters = ArrayList([]const u8).init(allocator); + defer test_filters.deinit(); + + var lib_dirs = ArrayList([]const u8).init(allocator); + defer lib_dirs.deinit(); + + var clang_argv = ArrayList([]const u8).init(allocator); + defer clang_argv.deinit(); + + 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 (strcmp(arg, "--release-safe") == 0) { + } else if (mem.eql(u8, arg, "--release-safe")) { build_mode = builtin.Mode.ReleaseSafe; - } else if (strcmp(arg, "--strip") == 0) { + } else if (mem.eql(u8, arg, "--strip")) { strip = true; - } else if (strcmp(arg, "--static") == 0) { + } else if (mem.eql(u8, arg, "--static")) { is_static = true; - } else if (strcmp(arg, "--verbose") == 0) { - verbose = true; - } else if (strcmp(arg, "--verbose-link") == 0) { + } 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 (strcmp(arg, "--verbose-ir") == 0) { + } else if (mem.eql(u8, arg, "--verbose-ir")) { verbose_ir = true; - } else if (strcmp(arg, "-mwindows") == 0) { + } 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 (strcmp(arg, "-mconsole") == 0) { + } else if (mem.eql(u8, arg, "-mconsole")) { mconsole = true; - } else if (strcmp(arg, "-municode") == 0) { - municode = true; - } else if (strcmp(arg, "-rdynamic") == 0) { + } else if (mem.eql(u8, arg, "-rdynamic")) { rdynamic = true; - } else if (strcmp(arg, "--each-lib-rpath") == 0) { + } else if (mem.eql(u8, arg, "--each-lib-rpath")) { each_lib_rpath = true; - } else if (strcmp(arg, "--enable-timing-info") == 0) { + } else if (mem.eql(u8, arg, "--enable-timing-info")) { timing_info = true; - } else if (strcmp(arg, "--test-cmd-bin") == 0) { - test_exec_args.append(nullptr); - } else if (arg[1] == 'L' && arg[2] != 0) { + } 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 - 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); + %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 { - 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; + 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 { - fprintf(stderr, "--color options are 'auto', 'on', or 'off'\n"); - return usage(arg0); + return badArgs("--color options are 'auto', 'on', or 'off'"); } - } 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]); + } 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]); - 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]); + %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 { - fprintf(stderr, "Invalid argument: %s\n", arg); - return usage(arg0); + 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, } } + + target.initializeAll(); + + // 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); +// } +// } +// } + + 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"); + } + + 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, + }; + + 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"), + } } -fn printUsage(outstream: &io.OutStream) -> %void { - %return outstream.print("Usage: {} [command] [options]\n", arg0); - %return outstream.write( +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 @@ -217,6 +485,7 @@ fn printUsage(outstream: &io.OutStream) -> %void { \\ --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 @@ -231,9 +500,13 @@ fn printUsage(outstream: &io.OutStream) -> %void { \\ --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 + \\ --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 @@ -255,7 +528,6 @@ fn printUsage(outstream: &io.OutStream) -> %void { \\ -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 @@ -271,17 +543,81 @@ fn printUsage(outstream: &io.OutStream) -> %void { ); } -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. - \\ -; +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/parser.zig b/src-self-hosted/parser.zig new file mode 100644 index 0000000000..c997536ce2 --- /dev/null +++ b/src-self-hosted/parser.zig @@ -0,0 +1,1203 @@ +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; +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 +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, + 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. + 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{}, + .cleanup_root_node = null, + }; + } + + pub fn deinit(self: &Parser) { + assert(self.cleanup_root_node == null); + 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, + ExpectOperand, + Operand: &ast.Node, + AfterOperand, + InfixOp: &ast.NodeInfixOp, + PrefixOp: &ast.NodePrefixOp, + AddrOfModifiers: &ast.NodePrefixOp.AddrOfInfo, + 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 { + const result = self.parseInner() %% |err| x: { + if (self.cleanup_root_node) |root_node| { + self.freeAst(root_node); + } + break :x 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 = 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); + break :x root_node; + }; + assert(self.cleanup_root_node == null); + self.cleanup_root_node = 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 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; + } + + 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| { + // 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 => { + %return stack.append(State { .PrefixOp = %return self.createPrefixOp(token, + ast.NodePrefixOp.PrefixOp.Return) }); + %return stack.append(State.ExpectOperand); + continue; + }, + Token.Id.Ampersand => { + 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.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) %% 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_info.align_expr} }); + continue; + }, + Token.Id.Keyword_const => { + 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 => { + 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); + continue; + }, + } + }, + + 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 { .Expression = 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; + }, + + // These are data, not control flow. + State.InfixOp => unreachable, + State.PrefixOp => unreachable, + State.Operand => unreachable, + } + @import("std").debug.panic("{}", @tagName(state)); + //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 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 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.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; + } + + 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 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 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 { + 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; + return put_back_token; + } else { + return 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, + VarDecl: &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 { .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.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.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.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); + %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.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) { + .allocator = self.allocator, + .items = typed_slice, + .len = 0, + }; + } + + fn deinitUtilityArrayList(self: &Parser, list: var) { + self.utility_bytes = ([]align(utility_bytes_align) u8)(list.items); + } + +}; + +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 = 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)); + 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); + break :x 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( + \\var foo: c_int align(1); + \\ + ); + + 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; + \\} + \\ + ); + + 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; + \\ + ); + + testCanonical( + \\fn f1(a: bool, b: bool) -> bool { + \\ a != b; + \\ return a == b; + \\} + \\ + ); +} diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig new file mode 100644 index 0000000000..0d077690dd --- /dev/null +++ b/src-self-hosted/target.zig @@ -0,0 +1,60 @@ +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(); + c.LLVMInitializeAllTargetMCs(); + c.LLVMInitializeAllAsmPrinters(); + c.LLVMInitializeAllAsmParsers(); +} diff --git a/src-self-hosted/tokenizer.zig b/src-self-hosted/tokenizer.zig new file mode 100644 index 0000000000..570d41fa7e --- /dev/null +++ b/src-self-hosted/tokenizer.zig @@ -0,0 +1,509 @@ +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, + Bang, + Equal, + EqualEqual, + BangEqual, + 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, + Equal, + Bang, + 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; + }, + '=' => { + state = State.Equal; + }, + '!' => { + state = State.Bang; + }, + '(' => { + 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.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; + 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]; + } +}; + + diff --git a/src/all_types.hpp b/src/all_types.hpp index 086ad7db49..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; @@ -37,6 +36,7 @@ struct IrBasicBlock; struct ScopeDecls; struct ZigWindowsSDK; struct Tld; +struct TldExport; struct IrGotoItem { AstNode *source_node; @@ -53,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; @@ -272,7 +271,6 @@ enum ReturnKnowledge { enum VisibMod { VisibModPrivate, VisibModPub, - VisibModExport, }; enum GlobalLinkageId { @@ -313,11 +311,8 @@ 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; }; struct TldFn { @@ -389,8 +384,6 @@ enum NodeType { NodeTypeSwitchExpr, NodeTypeSwitchProng, NodeTypeSwitchRange, - NodeTypeLabel, - NodeTypeGoto, NodeTypeCompTime, NodeTypeBreak, NodeTypeContinue, @@ -425,6 +418,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; @@ -432,6 +426,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 { @@ -452,8 +448,8 @@ struct AstNodeParamDecl { }; struct AstNodeBlock { + Buf *name; ZigList statements; - bool last_statement_is_result_expression; }; enum ReturnKind { @@ -480,15 +476,18 @@ 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; 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 { @@ -659,6 +658,7 @@ struct AstNodeTestExpr { }; struct AstNodeWhileExpr { + Buf *name; AstNode *condition; Buf *var_symbol; bool var_is_ptr; @@ -670,6 +670,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 @@ -701,11 +702,6 @@ struct AstNodeLabel { Buf *name; }; -struct AstNodeGoto { - Buf *name; - bool is_inline; -}; - struct AstNodeCompTime { AstNode *expr; }; @@ -833,11 +829,14 @@ struct AstNodeBoolLiteral { }; struct AstNodeBreakExpr { + Buf *name; AstNode *expr; // may be null }; struct AstNodeContinueExpr { + Buf *name; }; + struct AstNodeUnreachableExpr { }; @@ -883,7 +882,6 @@ struct AstNode { AstNodeSwitchProng switch_prong; AstNodeSwitchRange switch_range; AstNodeLabel label; - AstNodeGoto goto_expr; AstNodeCompTime comptime_expr; AstNodeAsmExpr asm_expr; AstNodeFieldAccessExpr field_access_expr; @@ -1177,6 +1175,11 @@ enum FnInline { FnInlineNever, }; +struct FnExport { + Buf name; + GlobalLinkageId linkage; +}; + struct FnTableEntry { LLVMValueRef llvm_value; const char *llvm_name; @@ -1204,12 +1207,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 +1260,6 @@ enum BuiltinFnId { BuiltinFnIdSetFloatMode, BuiltinFnIdTypeName, BuiltinFnIdCanImplicitCast, - BuiltinFnIdSetGlobalSection, - BuiltinFnIdSetGlobalLinkage, BuiltinFnIdPanic, BuiltinFnIdPtrCast, BuiltinFnIdBitCast, @@ -1270,6 +1270,7 @@ enum BuiltinFnId { BuiltinFnIdFieldParentPtr, BuiltinFnIdOffsetOf, BuiltinFnIdInlineCall, + BuiltinFnIdNoInlineCall, BuiltinFnIdTypeId, BuiltinFnIdShlExact, BuiltinFnIdShrExact, @@ -1278,6 +1279,7 @@ enum BuiltinFnId { BuiltinFnIdOpaqueType, BuiltinFnIdSetAlignStack, BuiltinFnIdArgType, + BuiltinFnIdExport, }; struct BuiltinFnEntry { @@ -1424,7 +1426,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; @@ -1439,7 +1441,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; @@ -1639,12 +1641,6 @@ struct ErrorTableEntry { ConstExprValue *cached_error_name_val; }; -struct LabelTableEntry { - AstNode *decl_node; - IrBasicBlock *bb; - bool used; -}; - enum ScopeId { ScopeIdDecls, ScopeIdBlock, @@ -1688,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; @@ -1734,6 +1735,7 @@ struct ScopeCImport { struct ScopeLoop { Scope base; + Buf *name; IrBasicBlock *break_block; IrBasicBlock *continue_block; IrInstruction *is_comptime; @@ -1885,8 +1887,6 @@ enum IrInstructionId { IrInstructionIdCheckStatementIsVoid, IrInstructionIdTypeName, IrInstructionIdCanImplicitCast, - IrInstructionIdSetGlobalSection, - IrInstructionIdSetGlobalLinkage, IrInstructionIdDeclRef, IrInstructionIdPanic, IrInstructionIdTagName, @@ -1900,6 +1900,7 @@ enum IrInstructionId { IrInstructionIdOpaqueType, IrInstructionIdSetAlignStack, IrInstructionIdArgType, + IrInstructionIdExport, }; struct IrInstruction { @@ -2102,7 +2103,7 @@ struct IrInstructionCall { IrInstruction **args; bool is_comptime; LLVMValueRef tmp_ptr; - bool is_inline; + FnInline fn_inline; }; struct IrInstructionConst { @@ -2625,20 +2626,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; @@ -2727,6 +2714,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 96f0eb44db..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; } @@ -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; } @@ -429,7 +435,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); @@ -1062,7 +1068,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_proto->is_export; fn_type_id->cc = extern_abi ? CallingConventionC : CallingConventionUnspecified; } else { fn_type_id->cc = fn_proto->cc; @@ -1093,6 +1099,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; @@ -1130,6 +1168,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: @@ -2227,7 +2274,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; @@ -2244,12 +2291,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; @@ -2474,7 +2519,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; @@ -2482,7 +2527,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; } @@ -2492,9 +2536,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 : @@ -2550,6 +2592,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; @@ -2561,6 +2631,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) { @@ -2574,7 +2649,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); } @@ -2582,6 +2657,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; @@ -2596,15 +2680,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")) @@ -2615,7 +2696,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, '_'); @@ -2642,17 +2723,23 @@ 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); + 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); - if (tld->visib_mod == VisibModExport) { - auto entry = g->exported_symbol_names.put_unique(tld->name, tld); + auto entry = g->exported_symbol_names.put_unique(tld->name, tld->source_node); if (entry) { - Tld *other_tld = entry->value; + 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_tld->source_node, buf_sprintf("other symbol is here")); + add_error_note(g, msg, other_source_node, buf_sprintf("other symbol here")); } } @@ -2731,7 +2818,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) { @@ -2836,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: @@ -2987,8 +3071,8 @@ 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; + bool is_export = var_decl->is_export; TypeTableEntry *explicit_type = nullptr; if (var_decl->type) { @@ -2996,9 +3080,7 @@ 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")); - } + assert(!is_export || !is_extern); VarLinkage linkage; if (is_export) { @@ -3009,7 +3091,6 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { linkage = VarLinkageInternal; } - IrInstruction *init_value = nullptr; // TODO more validation for types that can't be used for export/extern variables @@ -3058,6 +3139,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); } @@ -3319,7 +3409,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 +3421,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]; @@ -3726,8 +3816,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; @@ -3735,28 +3827,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; } - } } @@ -3820,12 +3891,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; } @@ -3888,7 +3961,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 +3968,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(); } @@ -5438,3 +5518,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 e6100c692c..6224e64dd5 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -180,5 +180,9 @@ 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); + +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/ast_render.cpp b/src/ast_render.cpp index 4f4dc1decd..f0912285f0 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(); } @@ -112,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 ""; @@ -212,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: @@ -388,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: @@ -411,8 +409,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); @@ -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) { @@ -456,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; @@ -464,19 +476,9 @@ 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 && - node->data.block.last_statement_is_result_expression)) { - fprintf(ar->f, ";"); - } + fprintf(ar->f, ";"); fprintf(ar->f, "\n"); } ar->indent -= ar->indent_size; @@ -501,6 +503,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); @@ -526,6 +531,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); @@ -584,12 +599,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 +635,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 +644,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) { @@ -800,6 +819,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); @@ -929,11 +951,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 "); @@ -942,6 +959,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); @@ -967,6 +987,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/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/codegen.cpp b/src/codegen.cpp index f84636502d..0aecacb0b2 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) { @@ -387,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)); @@ -414,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); @@ -441,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) { @@ -561,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, @@ -839,7 +867,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 +1016,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); } @@ -1210,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); @@ -2170,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, ""); @@ -2316,12 +2348,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]; @@ -2684,6 +2726,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; @@ -2975,6 +3020,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[] = { @@ -3473,8 +3527,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: @@ -3485,6 +3537,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); @@ -3747,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; } @@ -3882,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) { @@ -3892,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; } @@ -3946,8 +4017,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 +4029,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); @@ -4635,6 +4705,7 @@ static const uint8_t int_sizes_in_bits[] = { 7, 8, 16, + 29, 32, 64, 128, @@ -4955,8 +5026,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); @@ -4972,6 +5041,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); @@ -4980,6 +5050,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", 3); } static const char *bool_to_str(bool b) { @@ -5298,18 +5369,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) { @@ -5417,6 +5489,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)) @@ -5426,20 +5519,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); @@ -5666,7 +5745,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 facd7087f0..a0f5a9be47 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; } @@ -928,13 +924,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 +944,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; } @@ -1025,7 +1021,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; @@ -1191,6 +1187,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; } @@ -1203,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; @@ -2157,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) { @@ -2394,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; @@ -2977,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; } @@ -3104,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: @@ -3262,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: @@ -3522,33 +3511,14 @@ 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); + 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; @@ -3562,44 +3532,18 @@ 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); - 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) { @@ -3614,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) { @@ -4526,39 +4462,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); @@ -4672,6 +4575,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 +4596,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: { @@ -4780,6 +4685,25 @@ 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: + { + 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; + + 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); + } } zig_unreachable(); } @@ -4804,7 +4728,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) { @@ -4895,13 +4819,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) @@ -5078,7 +5007,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 @@ -5097,13 +5026,16 @@ 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; - 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) { @@ -6015,22 +5947,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); @@ -6038,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); @@ -6045,19 +5986,38 @@ 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; 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 (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")); + 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; + 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; } @@ -6098,14 +6058,24 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast ScopeLoop *loop_scope; 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 (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 { + 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; + 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; } @@ -6347,7 +6317,10 @@ 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: + case NodeTypeTestDecl: zig_unreachable(); case NodeTypeBlock: return ir_lval_wrap(irb, scope, ir_gen_block(irb, scope, node), lval); @@ -6407,8 +6380,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: @@ -6429,14 +6400,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(); } @@ -6451,70 +6414,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; @@ -6551,11 +6450,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; } @@ -7468,6 +7362,41 @@ 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 || + 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 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; @@ -7497,33 +7426,53 @@ 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) { - prev_inst = cur_inst; - } else if (cur_type->id == TypeTableEntryIdUnreachable) { - continue; - } else if (prev_type->id == TypeTableEntryIdPureError) { + } + + if (prev_type->id == TypeTableEntryIdUnreachable) { prev_inst = cur_inst; continue; - } else if (prev_type->id == TypeTableEntryIdNullLit) { + } + + if (cur_type->id == TypeTableEntryIdUnreachable) { + continue; + } + + if (prev_type->id == TypeTableEntryIdPureError) { prev_inst = cur_inst; continue; - } else if (cur_type->id == TypeTableEntryIdPureError) { + } + + if (prev_type->id == TypeTableEntryIdNullLit) { + prev_inst = cur_inst; + continue; + } + + 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) { @@ -7531,36 +7480,51 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod prev_inst = cur_inst; } continue; - } else 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; } - } else if (prev_type->id == TypeTableEntryIdErrorUnion && + continue; + } + + 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)) { @@ -7569,7 +7533,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)) { @@ -7577,20 +7543,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, @@ -7598,7 +7570,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, @@ -7607,17 +7581,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); @@ -7664,8 +7661,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); + } } } @@ -7767,9 +7765,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; } @@ -8203,6 +8199,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; } } @@ -8246,13 +8243,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; } @@ -8370,6 +8369,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) { @@ -8436,14 +8492,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 +8885,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); @@ -8907,6 +8975,38 @@ 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 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); @@ -9334,6 +9434,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) { @@ -9398,7 +9502,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); @@ -9407,7 +9512,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) { @@ -10265,6 +10370,170 @@ 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: + case CallingConventionNaked: + case CallingConventionCold: + case CallingConventionStdcall: + add_fn_export(ira->codegen, fn_entry, symbol_name, global_linkage_id, cc == CallingConventionC); + 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) { @@ -10442,7 +10711,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; @@ -10701,7 +10970,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); } } @@ -10725,7 +10994,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); @@ -10784,7 +11053,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); @@ -10823,13 +11092,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))); @@ -10839,7 +11108,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))); @@ -12183,102 +12452,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) { @@ -12999,6 +13172,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); @@ -13865,6 +14048,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}; @@ -15735,8 +15921,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, @@ -15932,10 +16122,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: @@ -16078,6 +16264,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(); } @@ -16185,12 +16373,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 223a012456..f5aba2a45d 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, @@ -895,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 " : ""; @@ -987,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); @@ -1263,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; @@ -1302,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..d069a23c5f 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 | (option("extern") 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); @@ -740,9 +725,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; @@ -751,27 +748,25 @@ 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); 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; } - 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; @@ -791,13 +786,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; @@ -1483,7 +1471,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); @@ -1493,8 +1481,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; @@ -1534,38 +1529,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 +1554,8 @@ 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_export = is_export; node->data.variable_declaration.is_const = is_const; node->data.variable_declaration.visib_mod = visib_mod; @@ -1600,6 +1578,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); @@ -1612,6 +1598,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 */ @@ -1638,35 +1668,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); @@ -1726,36 +1774,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); @@ -2082,35 +2147,6 @@ static AstNode *ast_parse_expression(ParseContext *pc, size_t *token_index, bool return nullptr; } -/* -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: @@ -2135,7 +2171,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; @@ -2143,27 +2178,54 @@ static bool statement_terminates_without_semicolon(AstNode *node) { } /* -Block = "{" many(Statement) option(Expression) "}" -Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | 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) { + 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_variable_declaration_expr(pc, token_index, false, VisibModPrivate); + 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); if (!statement_node) @@ -2171,47 +2233,28 @@ 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 == NodeTypeLabel || - 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(); } /* -FnProto = option("coldcc" | "nakedcc" | "stdcallcc") "fn" option(Symbol) ParamDeclList option("align" "(" 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); @@ -2224,6 +2267,21 @@ 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) { + is_extern = true; + *token_index += 1; + 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; *token_index += 1; @@ -2238,6 +2296,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); @@ -2259,6 +2318,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); @@ -2270,35 +2337,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) { @@ -2344,7 +2411,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); @@ -2447,9 +2514,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; } @@ -2460,7 +2524,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); @@ -2553,7 +2617,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 (;;) { @@ -2580,9 +2644,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; } @@ -2605,7 +2666,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); @@ -2669,6 +2730,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 +2758,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 @@ -2799,12 +2862,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/tokenizer.cpp b/src/tokenizer.cpp index 77d74c52ee..9bcc79ede7 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -134,6 +134,7 @@ static const struct ZigKeyword zig_keywords[] = { {"packed", TokenIdKeywordPacked}, {"pub", TokenIdKeywordPub}, {"return", TokenIdKeywordReturn}, + {"section", TokenIdKeywordSection}, {"stdcallcc", TokenIdKeywordStdcallCC}, {"struct", TokenIdKeywordStruct}, {"switch", TokenIdKeywordSwitch}, @@ -1533,6 +1534,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..58a20adc1a 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -47,6 +47,7 @@ enum TokenId { TokenIdFloatLiteral, TokenIdIntLiteral, TokenIdKeywordAlign, + TokenIdKeywordSection, TokenIdKeywordAnd, TokenIdKeywordAsm, TokenIdKeywordBreak, diff --git a/src/translate_c.cpp b/src/translate_c.cpp index 0139774f02..407115b104 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -73,7 +73,7 @@ struct Context { ImportTableEntry *import; ZigList *errors; VisibMod visib_mod; - VisibMod export_visib_mod; + bool want_export; AstNode *root; HashMap decl_table; HashMap macro_table; @@ -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); @@ -173,6 +171,28 @@ 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; + 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); @@ -257,18 +277,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; @@ -378,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; @@ -1149,13 +1156,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); @@ -1172,9 +1181,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; } @@ -1279,6 +1288,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; @@ -1287,9 +1299,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: @@ -1329,14 +1339,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); @@ -1378,11 +1390,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; @@ -1403,13 +1415,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); @@ -1436,11 +1450,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; } @@ -1735,13 +1749,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); @@ -1767,9 +1783,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; } @@ -1790,12 +1805,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); @@ -1814,11 +1831,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; } @@ -2374,145 +2390,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; @@ -2590,8 +2467,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; } @@ -2691,12 +2567,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; @@ -3246,7 +3124,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; + 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) { @@ -3865,14 +3744,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) { @@ -3883,16 +3754,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); @@ -4003,6 +3864,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) { @@ -4030,7 +3895,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: @@ -4047,55 +3912,131 @@ 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) { + 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 (@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: 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); @@ -4108,7 +4049,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; } @@ -4189,10 +4130,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->export_visib_mod = VisibModPub; + c->want_export = false; } else { c->visib_mod = VisibModPub; - c->export_visib_mod = VisibModExport; + c->want_export = true; } c->decl_table.init(8); c->macro_table.init(8); diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 5623002197..bd3eadbee9 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 2f742566f4..f335a8955a 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/array_list.zig b/std/array_list.zig index 65b7e023e2..04db4dd280 100644 --- a/std/array_list.zig +++ b/std/array_list.zig @@ -3,42 +3,46 @@ const assert = debug.assert; const mem = @import("mem.zig"); const Allocator = mem.Allocator; -pub fn ArrayList(comptime T: type) -> type{ - struct { +pub fn ArrayList(comptime T: type) -> type { + return AlignedArrayList(T, @alignOf(T)); +} + +pub fn AlignedArrayList(comptime T: type, comptime A: u29) -> type{ + return 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{}, + return Self { + .items = []align(A) T{}, .len = 0, .allocator = allocator, - } + }; } pub fn deinit(l: &Self) { 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,7 +101,13 @@ 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(); + } + }; } test "basic ArrayList test" { 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/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/buffer.zig b/std/buffer.zig index 96abaeb762..4f5d281f48 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. @@ -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); @@ -117,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 { @@ -136,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/build.zig b/std/build.zig index 9bdc4b3076..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,70 @@ 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) + ; + } + } + + 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)) { + 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, self.fmt("{}{}", name, exe_extension)); + 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, self.fmt("{}{}", name, exe_extension)); + 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"); + }, } } }; @@ -755,6 +811,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, @@ -844,7 +901,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), @@ -865,6 +922,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, }; @@ -879,7 +937,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), @@ -888,6 +946,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, @@ -1018,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) { @@ -1035,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) { @@ -1069,11 +1126,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 +1282,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 +1297,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); @@ -1544,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, @@ -1555,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) { @@ -1862,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/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/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 a2bea9eddd..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 } } } @@ -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; @@ -113,6 +111,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 +126,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 +144,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; @@ -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 => { @@ -233,6 +233,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 +334,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) { @@ -347,7 +357,7 @@ const Die = struct { FormValue.String => |value| value, FormValue.StrPtr => |offset| getString(st, offset), else => error.InvalidDebugInfo, - } + }; } }; @@ -393,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, @@ -411,7 +421,7 @@ const LineNumberProgram = struct { .prev_is_stmt = undefined, .prev_basic_block = undefined, .prev_end_sequence = undefined, - } + }; } pub fn checkLineMatch(self: &LineNumberProgram) -> %?LineInfo { @@ -420,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 { @@ -484,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 { @@ -524,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), @@ -535,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); @@ -552,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) }, @@ -562,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 { @@ -842,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; @@ -862,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; } }; @@ -900,25 +898,51 @@ 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 { 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 { @@ -965,40 +989,62 @@ 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 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; -var some_mem: [100 * 1024]u8 = undefined; -var some_mem_index: usize = 0; +/// 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, -error OutOfMemory; - -fn globalAlloc(self: &mem.Allocator, n: usize, alignment: usize) -> %[]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); - const adjusted_index = some_mem_index + march_forward_bytes; - const end_index = adjusted_index + n; - if (end_index > some_mem.len) { - return error.OutOfMemory; + 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, + }, + }; } - const result = some_mem[adjusted_index .. end_index]; - some_mem_index = end_index; - return result; -} -fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]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); + 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 globalFree(self: &mem.Allocator, memory: []u8) { } + 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); + } +}; diff --git a/std/elf.zig b/std/elf.zig index f7be236d15..60b0119894 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,29 +243,27 @@ 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; + section_loop: 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| { 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 section; + if (null_byte == 0) return elf_section; } - - next_section: } 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/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..fef968a1d5 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"); @@ -372,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 { @@ -413,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]; } @@ -476,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")); } @@ -510,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/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 d0bf8ab871..ec447c1aa8 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -10,30 +10,28 @@ 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, }; -fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 { - if (c.malloc(usize(n))) |buf| { +fn cAlloc(self: &Allocator, n: usize, alignment: u29) -> %[]u8 { + 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: usize) -> %[]u8 { - if (new_size <= old_mem.len) { - old_mem[0..new_size] +fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { + 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| { - @ptrCast(&u8, buf)[0..new_size] - } else { - error.OutOfMemory - } + return error.OutOfMemory; } } @@ -106,7 +104,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 +119,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/index.zig b/std/index.zig index 8033eee142..323eee203e 100644 --- a/std/index.zig +++ b/std/index.zig @@ -1,7 +1,9 @@ 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; +pub const BufferOutStream = @import("buffer.zig").BufferOutStream; pub const HashMap = @import("hash_map.zig").HashMap; pub const LinkedList = @import("linked_list.zig").LinkedList; @@ -26,12 +28,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/io.zig b/std/io.zig index c86ebed326..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); @@ -481,6 +478,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 @@ -493,6 +498,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 { @@ -619,3 +638,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); + } +}; + 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..7690497da5 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) { - f32 => @inlineCall(acos32, x), - f64 => @inlineCall(acos64, x), + return switch (T) { + f32 => acos32(x), + f64 => 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..3b2c2c0f31 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) { - f32 => @inlineCall(acosh32, x), - f64 => @inlineCall(acosh64, x), + return switch (T) { + f32 => acosh32(x), + f64 => 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..c5d24c35e5 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) { - f32 => @inlineCall(asin32, x), - f64 => @inlineCall(asin64, x), + return switch (T) { + f32 => asin32(x), + f64 => 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..f963dae77b 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) { - f32 => @inlineCall(asinh32, x), - f64 => @inlineCall(asinh64, x), + return switch (T) { + f32 => asinh32(x), + f64 => 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..cf244eb762 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) { - f32 => @inlineCall(atan32, x), - f64 => @inlineCall(atan64, x), + return switch (T) { + f32 => atan32(x), + f64 => atan64(x), else => @compileError("atan not implemented for " ++ @typeName(T)), - } + }; } fn atan32(x_: f32) -> f32 { @@ -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) { - x - x * (s1 + s2) + if (id) |id_value| { + const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x); + return if (sign != 0) -zz else zz; } else { - const zz = atanhi[??id] - ((x * (s1 + s2) - atanlo[??id]) - x); - if (sign != 0) -zz else zz + 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) { - x - x * (s1 + s2) + if (id) |id_value| { + const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x); + return if (sign != 0) -zz else zz; } else { - const zz = atanhi[??id] - ((x * (s1 + s2) - atanlo[??id]) - x); - if (sign != 0) -zz else zz + 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 0e566837ce..b3af18cd9e 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) { - f32 => @inlineCall(atan2_32, x, y), - f64 => @inlineCall(atan2_64, x, y), + return switch (T) { + f32 => atan2_32(x, y), + f64 => 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..13de90279b 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) { - f32 => @inlineCall(atanh_32, x), - f64 => @inlineCall(atanh_64, x), + return switch (T) { + f32 => atanh_32(x), + f64 => 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..a8df75dff2 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) { - f32 => @inlineCall(cbrt32, x), - f64 => @inlineCall(cbrt64, x), + return switch (T) { + f32 => cbrt32(x), + f64 => 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..8e27132e25 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) { - f32 => @inlineCall(ceil32, x), - f64 => @inlineCall(ceil64, x), + return switch (T) { + f32 => ceil32(x), + f64 => 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..05205e1a0e 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) { - f32 => @inlineCall(copysign32, x, y), - f64 => @inlineCall(copysign64, x, y), + return switch (T) { + f32 => copysign32(x, y), + f64 => 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..4c3b8e1282 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) { - f32 => @inlineCall(cos32, x), - f64 => @inlineCall(cos64, x), + return switch (T) { + f32 => cos32(x), + f64 => 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..6d40d71b8d 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) { - f32 => @inlineCall(cosh32, x), - f64 => @inlineCall(cosh64, x), + return switch (T) { + f32 => cosh32(x), + f64 => 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..6e591daea3 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) { - f32 => @inlineCall(exp32, x), - f64 => @inlineCall(exp64, x), + return switch (T) { + f32 => exp32(x), + f64 => 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..6061f32391 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) { - f32 => @inlineCall(exp2_32, x), - f64 => @inlineCall(exp2_64, x), + return switch (T) { + f32 => exp2_32(x), + f64 => 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..fbe1841030 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) { - f32 => @inlineCall(expm1_32, x), - f64 => @inlineCall(expm1_64, x), + return switch (T) { + f32 => expm1_32(x), + f64 => 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..cf1b8f1f14 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) { - f32 => @inlineCall(fabs32, x), - f64 => @inlineCall(fabs64, x), + return switch (T) { + f32 => fabs32(x), + f64 => 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..d7de45e9d0 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) { - f32 => @inlineCall(floor32, x), - f64 => @inlineCall(floor64, x), + return switch (T) { + f32 => floor32(x), + f64 => 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..8e5adc80b2 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) { - f32 => @inlineCall(fma32, x, y, z), - f64 => @inlineCall(fma64, x, y ,z), + return switch (T) { + f32 => fma32(x, y, z), + f64 => 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..1a317a2c80 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) { - f32 => @inlineCall(frexp32, x), - f64 => @inlineCall(frexp64, x), + return switch (T) { + f32 => frexp32(x), + f64 => 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..9b09ed53a4 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) { - f32 => @inlineCall(hypot32, x, y), - f64 => @inlineCall(hypot64, x, y), + return switch (T) { + f32 => hypot32(x, y), + f64 => 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..41a1e2d83f 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) { - f32 => @inlineCall(ilogb32, x), - f64 => @inlineCall(ilogb64, x), + return switch (T) { + f32 => ilogb32(x), + f64 => 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 8a583fb8e9..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. @@ -174,14 +174,8 @@ 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 + return if (x < y) x else y; } test "math.min" { @@ -189,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" { @@ -199,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) { @@ -221,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. @@ -273,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" { @@ -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/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..b369385038 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) { - f32 => @inlineCall(log1p_32, x), - f64 => @inlineCall(log1p_64, x), + return switch (T) { + f32 => log1p_32(x), + f64 => 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..72730b67d7 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) { - f32 => @inlineCall(modf32, x), - f64 => @inlineCall(modf64, x), + return switch (T) { + f32 => modf32(x), + f64 => 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..1e193fb1d7 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) { - f32 => @inlineCall(round32, x), - f64 => @inlineCall(round64, x), + return switch (T) { + f32 => round32(x), + f64 => 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..0be6a3d47e 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) { - f32 => @inlineCall(scalbn32, x, n), - f64 => @inlineCall(scalbn64, x, n), + return switch (T) { + f32 => scalbn32(x, n), + f64 => 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..b8ccecfa1b 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) { - f32 => @inlineCall(signbit32, x), - f64 => @inlineCall(signbit64, x), + return switch (T) { + f32 => signbit32(x), + f64 => 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..392bef1bc0 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) { - f32 => @inlineCall(sin32, x), - f64 => @inlineCall(sin64, x), + return switch (T) { + f32 => sin32(x), + f64 => 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..4c575f10ec 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) { - f32 => @inlineCall(sinh32, x), - f64 => @inlineCall(sinh64, x), + return switch (T) { + f32 => sinh32(x), + f64 => 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 bd1bb1fca4..263e616617 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)), } } @@ -42,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; } @@ -90,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 @@ -131,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); @@ -223,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" { @@ -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/math/tan.zig b/std/math/tan.zig index 6ac30fa667..ff53a758b4 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) { - f32 => @inlineCall(tan32, x), - f64 => @inlineCall(tan64, x), + return switch (T) { + f32 => tan32(x), + f64 => 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..7715029361 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) { - f32 => @inlineCall(tanh32, x), - f64 => @inlineCall(tanh64, x), + return switch (T) { + f32 => tanh32(x), + f64 => 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..81eacb30ba 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) { - f32 => @inlineCall(trunc32, x), - f64 => @inlineCall(trunc64, x), + return switch (T) { + f32 => trunc32(x), + f64 => 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 815e122812..7438eba70a 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -3,26 +3,31 @@ const assert = debug.assert; 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 - /// 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. + /// The returned newly allocated memory is undefined. + 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 + /// + /// 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` 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 +35,52 @@ 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); + // This loop should get optimized out in ReleaseFast mode + for (byte_slice) |*byte| { + *byte = undefined; + } + 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 old_byte_slice = ([]u8)(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, 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)); } /// 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 +88,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 ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } fn free(self: &Allocator, memory: var) { @@ -79,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. @@ -95,17 +166,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. @@ -276,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" { @@ -433,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; } @@ -446,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; } @@ -456,3 +532,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/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 75a2dcf24d..6e86c99056 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); @@ -116,7 +115,7 @@ pub const ChildProcess = struct { return self.spawnWindows(); } else { return self.spawnPosix(); - }; + } } pub fn spawnAndWait(self: &ChildProcess) -> %Term { @@ -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(); @@ -210,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)}; } }); @@ -261,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 @@ -280,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 { @@ -305,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(); } @@ -411,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); } @@ -503,30 +542,32 @@ 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 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]) + 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); @@ -589,6 +630,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 +653,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(); @@ -701,7 +743,7 @@ fn makePipe() -> %[2]i32 { return switch (err) { posix.EMFILE, posix.ENFILE => error.SystemResources, else => os.unexpectedErrorPosix(err), - } + }; } return fds; } @@ -760,10 +802,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..e230826b7e 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() + c.abort(); } pub fn exit(code: i32) -> noreturn { - c.exit(code) + 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 361750aedc..8e79eda40b 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), - } + }; } } @@ -902,40 +901,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 +988,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, - }; } }; @@ -1422,6 +1423,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"}); @@ -1494,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); diff --git a/std/os/linux.zig b/std/os/linux.zig index 5951f9d7bc..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 { @@ -651,92 +651,70 @@ 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)) + 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; @@ -771,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; @@ -782,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 { @@ -803,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_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/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..59e9a53027 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); @@ -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); }, diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index b3fc095d43..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, }; @@ -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 d02d685e07..a36a5e1747 100644 --- a/std/sort.zig +++ b/std/sort.zig @@ -1,75 +1,966 @@ -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; +const builtin = @import("builtin"); -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 +1004,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 +1012,19 @@ 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" { + 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"}, @@ -164,7 +1060,12 @@ test "testSort" { } } -test "testSortDesc" { +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}}, @@ -182,3 +1083,74 @@ test "testSortDesc" { assert(mem.eql(i32, slice, case[1])); } } + +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); + + assert(mem.eql(i32, arr, []i32{ 1, 2, 3, 4, 5 })); +} + +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; + 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); + } + } +} + +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; +} diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index 924e537ddd..177e245400 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -5,20 +5,20 @@ 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 { + const strong_linkage = builtin.GlobalLinkage.Strong; + if (builtin.link_libc) { + @export("main", main, strong_linkage); + } else if (builtin.os == builtin.Os.windows) { + @export("WinMainCRTStartup", WinMainCRTStartup, strong_linkage); + } else { + @export("_start", _start, strong_linkage); } +} +nakedcc fn _start() -> noreturn { switch (builtin.arch) { builtin.Arch.x86_64 => { argc_ptr = asm("lea (%%rsp), %[argc]": [argc] "=r" (-> &usize)); @@ -28,17 +28,14 @@ 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 { - 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); } @@ -58,17 +55,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/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 51e6646574..e6c09863ca 100644 --- a/std/special/builtin.zig +++ b/std/special/builtin.zig @@ -35,25 +35,26 @@ 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; +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"); -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); @@ -83,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; @@ -91,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; @@ -114,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/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..b88c35019b 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); @@ -40,35 +38,25 @@ export 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 - } - }; -} - -// 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 @@ -78,8 +66,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); @@ -89,57 +76,27 @@ export 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 - } - }; + ; } -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 b9f93a601a..5e8c91c8d5 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; + + @export("__letf2", @import("comparetf2.zig").__letf2, linkage); + @export("__getf2", @import("comparetf2.zig").__getf2, linkage); + + if (!is_test) { + // only create these aliases when not testing + @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); + } + + @export("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); + + @export("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); + @export("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); + @export("__fixunssfti", @import("fixunssfti.zig").__fixunssfti, linkage); + + @export("__fixunsdfsi", @import("fixunsdfsi.zig").__fixunsdfsi, linkage); + @export("__fixunsdfdi", @import("fixunsdfdi.zig").__fixunsdfdi, linkage); + @export("__fixunsdfti", @import("fixunsdfti.zig").__fixunsdfti, linkage); + + @export("__fixunstfsi", @import("fixunstfsi.zig").__fixunstfsi, linkage); + @export("__fixunstfdi", @import("fixunstfdi.zig").__fixunstfdi, linkage); + @export("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage); + + @export("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage); + @export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage); + + @export("__udivti3", @import("udivti3.zig").__udivti3, linkage); + @export("__umodti3", @import("umodti3.zig").__umodti3, linkage); + + @export("__udivsi3", __udivsi3, linkage); + @export("__udivdi3", __udivdi3, linkage); + @export("__umoddi3", __umoddi3, linkage); + @export("__udivmodsi4", __udivmodsi4, linkage); + + if (isArmArch()) { + @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) { + @export("_chkstk", _chkstk, strong_linkage); + @export("__chkstk_ms", __chkstk_ms, 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) { + @export("__chkstk", __chkstk, strong_linkage); + @export("___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 { @@ -115,156 +148,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))); @@ -272,19 +273,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 @@ -480,4 +470,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/behavior.zig b/test/behavior.zig index bdd428074b..96a323a6c8 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -7,6 +7,8 @@ comptime { _ = @import("cases/bitcast.zig"); _ = @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"); @@ -18,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/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/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/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 => {}, + } + } +} 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 ec900511eb..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 }; } @@ -344,3 +344,36 @@ fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) { MultipleChoice2.Unspecified5 => 9, }); } + +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); +} + +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"); +} 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 e10e7acaca..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" { @@ -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/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/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/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 9f4f064f6b..e456ca529a 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -12,8 +12,11 @@ test "empty function with comments" { emptyFunctionWithComments(); } -export fn disabledExternFn() { - @setGlobalLinkage(disabledExternFn, builtin.GlobalLinkage.Internal); +comptime { + @export("disabledExternFn", disabledExternFn, builtin.GlobalLinkage.Internal); +} + +extern fn disabledExternFn() { } test "call disabled extern fn" { @@ -107,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; @@ -132,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" { @@ -164,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); } @@ -187,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() {} @@ -277,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(); } @@ -307,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" { @@ -376,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; } @@ -480,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 @@ -504,7 +507,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); } 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/slice.zig b/test/cases/slice.zig index 1498f948ea..c47de5b09e 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,21 @@ 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]; +} + +test "implicitly cast array of size 0 to slice" { + var msg = []u8 {}; + assertLenIsZero(msg); +} + +fn assertLenIsZero(msg: []const u8) { + assert(msg.len == 0); +} diff --git a/test/cases/struct.zig b/test/cases/struct.zig index 1a9a03c71a..bdd2e5d049 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 { @@ -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); +} 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/union.zig b/test/cases/union.zig index 1db9a1bef1..90d869289f 100644 --- a/test/cases/union.zig +++ b/test/cases/union.zig @@ -190,3 +190,33 @@ 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, }; + +test "implicit cast union to its tag type" { + var x: Value2 = Letter2.B; + assert(x == Letter2.B); + giveMeLetterB(x); +} +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); +} + 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 33833cecfa..e61fe1f9d2 100644 --- a/test/cases/while.zig +++ b/test/cases/while.zig @@ -118,80 +118,95 @@ 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); } -fn returnNull() -> ?i32 { null } -fn returnMaybe(x: i32) -> ?i32 { x } +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 { 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 1a7b8b51dd..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,6 +442,88 @@ pub fn addCases(cases: &tests.CompareOutputContext) { tc.addSourceFile("foo.txt", "1234\nabcd\n"); - tc + break :x tc; + }); + + cases.addCase(x: { + 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", + }); + + break :x tc; + }); + + cases.addCase(x: { + 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", + }); + + break :x tc; }); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5d13ed8d48..60e5c3614d 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,37 @@ 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: label 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)); + \\ + \\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() { \\ {} @@ -8,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() { @@ -17,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() { @@ -26,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() { @@ -35,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() { @@ -53,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() { @@ -62,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() { @@ -71,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() { @@ -80,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() { @@ -89,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() { @@ -98,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() { @@ -107,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() { @@ -116,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() { @@ -125,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() { @@ -134,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() { @@ -143,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() { @@ -152,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() { @@ -161,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() { @@ -170,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() { @@ -179,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() { @@ -188,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() {} @@ -245,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) { @@ -275,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() { @@ -362,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", @@ -476,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) { @@ -502,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", @@ -545,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", @@ -565,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"); @@ -587,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"); @@ -610,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"); @@ -635,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", @@ -650,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", @@ -707,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'"); @@ -740,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'"); @@ -755,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 '"); @@ -764,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"); @@ -779,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'"); @@ -789,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", @@ -804,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", @@ -820,33 +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("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; \\ - \\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", @@ -859,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"); @@ -880,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"); @@ -888,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", @@ -898,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", @@ -908,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"); @@ -928,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() { @@ -953,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"); @@ -976,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() { @@ -1056,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", @@ -1079,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() { @@ -1102,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", @@ -1113,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", @@ -1138,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", @@ -1171,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", @@ -1179,9 +1191,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ x: i32, \\ \\ fn init(x: i32) -> Foo { - \\ Foo { + \\ return Foo { \\ .x = x, - \\ } + \\ }; \\ } \\}; \\ @@ -1198,10 +1210,10 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\ allocator: &Allocator, \\ \\ pub fn init(allocator: &Allocator) -> List { - \\ List { + \\ return List { \\ .len = 0, \\ .allocator = allocator, - \\ } + \\ }; \\ } \\}; \\ @@ -1224,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"); \\ @@ -1242,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", @@ -1277,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 { @@ -1304,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() { @@ -1318,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", @@ -1358,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", @@ -1394,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", @@ -1428,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", @@ -1443,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", @@ -1456,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"); \\ @@ -1468,20 +1480,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} , "foo.zig:1:8: error: exported symbol collision: 'bar'", - ".tmp_source.zig:3:8: note: other symbol is here"); + ".tmp_source.zig:3:8: note: other symbol here"); tc.addSourceFile("foo.zig", \\export fn bar() {} \\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", @@ -1504,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", @@ -1611,23 +1623,29 @@ 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"); + cases.add("setting a section on an extern variable", + \\extern var foo: i32 section(".text2"); + \\export fn entry() -> i32 { + \\ return foo; \\} , - ".tmp_source.zig:3:5: error: cannot set section of external variable 'foo'", - ".tmp_source.zig:1:8: note: declared here"); + ".tmp_source.zig:1:29: error: cannot set section of external variable 'foo'"); - cases.add("@setGlobalSection extern fn", - \\extern fn foo(); - \\comptime { - \\ @setGlobalSection(foo, ".text2"); + cases.add("setting a section on a local variable", + \\export fn entry() -> i32 { + \\ var foo: i32 section(".text2") = 1234; + \\ return foo; \\} , - ".tmp_source.zig:3:5: error: cannot set section of external function 'foo'", - ".tmp_source.zig:1:8: note: declared here"); + ".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", \\export fn foo() -> &i32 { @@ -1648,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); @@ -1674,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'"); @@ -1682,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'"); @@ -1690,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'"); @@ -1698,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'"); @@ -1706,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'"); @@ -1714,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'"); @@ -1745,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 { @@ -1838,16 +1856,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 }; \\ @@ -1915,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 { @@ -2021,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'"); @@ -2033,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'"); @@ -2120,12 +2128,13 @@ pub fn addCases(cases: &tests.CompileErrorContext) { , ".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)); + cases.add("wrong types given to @export", + \\extern fn entry() { } + \\comptime { + \\ @export("entry", entry, u32(1234)); \\} , - ".tmp_source.zig:2:33: error: expected type 'GlobalLinkage', found 'u32'"); + ".tmp_source.zig:3:32: error: expected type 'GlobalLinkage', found 'u32'"); cases.add("struct with invalid field", \\const std = @import("std"); @@ -2198,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 { @@ -2366,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() { @@ -2643,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"); @@ -2660,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"); @@ -2672,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) { @@ -2684,4 +2693,47 @@ 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"); + + 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'"); } 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 e74afa1755..a184045125 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; @@ -269,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())) { @@ -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); @@ -599,7 +615,7 @@ pub const CompileErrorContext = struct { warn("Process {} terminated unexpectedly\n", b.zig_exe); return error.TestFailed; }, - }; + } const stdout = stdout_buf.toSliceConst(); @@ -875,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 d4974109da..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); \\} ); @@ -325,12 +325,12 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return a; \\} , - \\export 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; \\} - \\export 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; \\} , - \\export 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; \\} , - \\export 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; \\} , - \\export 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; \\} , - \\export 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); \\} - \\export 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); \\} , - \\export 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; \\} , - \\export 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; \\} , - \\export 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,13 +472,13 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ c = b = a; \\} , - \\export fn max(a: c_int) { + \\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; \\ }; \\} ); @@ -493,7 +493,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return i; \\} , - \\export 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() {} - \\export 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, \\}; - \\export 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) { \\ ;;;;; \\} , - \\export 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; - \\export 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; \\} , - \\export 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; \\} , - \\export 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); \\} , - \\export 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; \\} , - \\export fn foo() -> ?&c_int { + \\pub export fn foo() -> ?&c_int { \\ return null; \\} ); @@ -612,10 +612,10 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return 1, 2; \\} , - \\export fn foo() -> c_int { - \\ return { + \\pub export fn foo() -> c_int { + \\ return x: { \\ _ = 1; - \\ 2 + \\ break :x 2; \\ }; \\} ); @@ -625,7 +625,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ return (1 << 2) >> 1; \\} , - \\export 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,47 +643,47 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a <<= (a <<= 1); \\} , - \\export fn foo() { + \\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; \\ }); \\} ); @@ -701,47 +701,47 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ a <<= (a <<= 1); \\} , - \\export fn foo() { + \\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; \\ }); \\} ); @@ -771,36 +771,36 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ u = u--; \\} , - \\export fn foo() { + \\pub export fn foo() { \\ var i: c_int = 0; \\ var u: c_uint = c_uint(0); \\ i += 1; \\ 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; \\ }; \\} ); @@ -819,32 +819,32 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ u = --u; \\} , - \\export fn foo() { + \\pub export fn foo() { \\ var i: c_int = 0; \\ var u: c_uint = c_uint(0); \\ i += 1; \\ 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; \\ }; \\} ); @@ -862,7 +862,7 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ while (b != 0); \\} , - \\export 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(); \\} , - \\export fn foo() {} - \\export fn baz() {} - \\export fn bar() { + \\pub export fn foo() {} + \\pub export fn baz() {} + \\pub export fn bar() { \\ var f: ?extern fn() = foo; \\ (??f)(); \\ (??f)(); @@ -901,8 +901,8 @@ pub fn addCases(cases: &tests.TranslateCContext) { \\ *x = 1; \\} , - \\export fn foo(x: ?&c_int) { - \\ (*(??x)) = 1; + \\pub export fn foo(x: ?&c_int) { + \\ (*??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; \\} ); @@ -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); @@ -1079,50 +1037,12 @@ 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; ); - 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; @@ -1188,4 +1108,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 = 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); + ); }