| author | |
| committer | |
| log | b85ef2300fa72f5f4c73b8eb9e14f0218ada592d |
| tree | daee8ab81eaefb5433f6ba3750656ba769a311a4 |
| parent | 75080e351af8be45722bca50c1d5fcd503304d77 |
| parent | 175adc0bd738c2e3a55bb71c6a53dcc920c203ba |
87 files changed, 9017 insertions(+), 2483 deletions(-)
CMakeLists.txt+22-7| ... | ... | @@ -89,6 +89,7 @@ set(ZIG_TARGET_MCPU "baseline" CACHE STRING "-mcpu parameter to output binaries |
| 89 | 89 | set(ZIG_EXECUTABLE "" CACHE STRING "(when cross compiling) path to already-built zig binary") |
| 90 | 90 | set(ZIG_SINGLE_THREADED off CACHE BOOL "limit the zig compiler to use only 1 thread") |
| 91 | 91 | set(ZIG_OMIT_STAGE2 off CACHE BOOL "omit the stage2 backend from stage1") |
| 92 | set(ZIG_ENABLE_LOGGING off CACHE BOOL "enable logging") | |
| 92 | 93 | |
| 93 | 94 | if("${ZIG_TARGET_TRIPLE}" STREQUAL "native") |
| 94 | 95 | set(ZIG_USE_LLVM_CONFIG ON CACHE BOOL "use llvm-config to find LLVM libraries") |
| ... | ... | @@ -564,7 +565,14 @@ set(ZIG_STAGE2_SOURCES |
| 564 | 565 | "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" |
| 565 | 566 | "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" |
| 566 | 567 | "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" |
| 568 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" | |
| 569 | "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig" | |
| 570 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" | |
| 571 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" | |
| 567 | 572 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" |
| 573 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Zld.zig" | |
| 574 | "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" | |
| 575 | "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig" | |
| 568 | 576 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" |
| 569 | 577 | "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" |
| 570 | 578 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" |
| ... | ... | @@ -600,6 +608,12 @@ else() |
| 600 | 608 | set(ZIG_OMIT_STAGE2_BOOL "false") |
| 601 | 609 | endif() |
| 602 | 610 | |
| 611 | if(ZIG_ENABLE_LOGGING) | |
| 612 | set(ZIG_ENABLE_LOGGING_BOOL "true") | |
| 613 | else() | |
| 614 | set(ZIG_ENABLE_LOGGING_BOOL "false") | |
| 615 | endif() | |
| 616 | ||
| 603 | 617 | configure_file ( |
| 604 | 618 | "${CMAKE_SOURCE_DIR}/src/stage1/config.h.in" |
| 605 | 619 | "${ZIG_CONFIG_H_OUT}" |
| ... | ... | @@ -728,12 +742,14 @@ if(MSVC OR MINGW) |
| 728 | 742 | target_link_libraries(zigstage1 LINK_PUBLIC version) |
| 729 | 743 | endif() |
| 730 | 744 | |
| 731 | add_executable(zig0 ${ZIG0_SOURCES}) | |
| 732 | set_target_properties(zig0 PROPERTIES | |
| 733 | COMPILE_FLAGS ${EXE_CFLAGS} | |
| 734 | LINK_FLAGS ${EXE_LDFLAGS} | |
| 735 | ) | |
| 736 | target_link_libraries(zig0 zigstage1) | |
| 745 | if("${ZIG_EXECUTABLE}" STREQUAL "") | |
| 746 | add_executable(zig0 ${ZIG0_SOURCES}) | |
| 747 | set_target_properties(zig0 PROPERTIES | |
| 748 | COMPILE_FLAGS ${EXE_CFLAGS} | |
| 749 | LINK_FLAGS ${EXE_LDFLAGS} | |
| 750 | ) | |
| 751 | target_link_libraries(zig0 zigstage1) | |
| 752 | endif() | |
| 737 | 753 | |
| 738 | 754 | if(MSVC) |
| 739 | 755 | set(ZIG1_OBJECT "${CMAKE_BINARY_DIR}/zig1.obj") |
| ... | ... | @@ -782,7 +798,6 @@ if("${ZIG_EXECUTABLE}" STREQUAL "") |
| 782 | 798 | else() |
| 783 | 799 | add_custom_command( |
| 784 | 800 | OUTPUT "${ZIG1_OBJECT}" |
| 785 | BYPRODUCTS "${ZIG1_OBJECT}" | |
| 786 | 801 | COMMAND "${ZIG_EXECUTABLE}" "build-obj" ${BUILD_ZIG1_ARGS} |
| 787 | 802 | DEPENDS ${ZIG_STAGE2_SOURCES} |
| 788 | 803 | COMMENT STATUS "Building self-hosted component ${ZIG1_OBJECT}" |
ci/azure/macos_arm64_script created+132| ... | ... | @@ -0,0 +1,132 @@ |
| 1 | #!/bin/sh | |
| 2 | ||
| 3 | set -x | |
| 4 | set -e | |
| 5 | ||
| 6 | brew install s3cmd ninja gnu-tar | |
| 7 | ||
| 8 | ZIGDIR="$(pwd)" | |
| 9 | ARCH="aarch64" | |
| 10 | # {product}-{os}{sdk_version}-{arch}-{llvm_version}-{cmake_build_type} | |
| 11 | CACHE_HOST_BASENAME="llvm-macos10.15-x86_64-11.0.1-release" | |
| 12 | CACHE_ARM64_BASENAME="llvm-macos11.0-arm64-11.0.1-release" | |
| 13 | PREFIX_HOST="$HOME/$CACHE_HOST_BASENAME" | |
| 14 | PREFIX_ARM64="$HOME/$CACHE_ARM64_BASENAME" | |
| 15 | JOBS="-j2" | |
| 16 | ||
| 17 | rm -rf $PREFIX | |
| 18 | cd $HOME | |
| 19 | wget -nv "https://ziglang.org/deps/$CACHE_HOST_BASENAME.tar.xz" | |
| 20 | wget -nv "https://ziglang.org/deps/$CACHE_ARM64_BASENAME.tar.xz" | |
| 21 | ||
| 22 | gtar xf "$CACHE_HOST_BASENAME.tar.xz" | |
| 23 | gtar xf "$CACHE_ARM64_BASENAME.tar.xz" | |
| 24 | ||
| 25 | cd $ZIGDIR | |
| 26 | ||
| 27 | # Make the `zig version` number consistent. | |
| 28 | # This will affect the cmake command below. | |
| 29 | git config core.abbrev 9 | |
| 30 | git fetch --unshallow || true | |
| 31 | git fetch --tags | |
| 32 | ||
| 33 | # Select xcode: latest version found on vmImage macOS-10.15 . | |
| 34 | DEVELOPER_DIR=/Applications/Xcode_12.4.app | |
| 35 | ||
| 36 | export ZIG_LOCAL_CACHE_DIR="$ZIGDIR/zig-cache" | |
| 37 | export ZIG_GLOBAL_CACHE_DIR="$ZIGDIR/zig-cache" | |
| 38 | ||
| 39 | # Build zig for host and use `Debug` type to make builds a little faster. | |
| 40 | ||
| 41 | cd $ZIGDIR | |
| 42 | mkdir build.host | |
| 43 | cd build.host | |
| 44 | cmake -G "Ninja" .. \ | |
| 45 | -DCMAKE_INSTALL_PREFIX="$(pwd)/release" \ | |
| 46 | -DCMAKE_PREFIX_PATH="$PREFIX_HOST" \ | |
| 47 | -DCMAKE_BUILD_TYPE="Debug" \ | |
| 48 | -DZIG_STATIC="OFF" | |
| 49 | ||
| 50 | # Build but do not install. | |
| 51 | ninja $JOBS | |
| 52 | ||
| 53 | ZIG_EXE="$ZIGDIR/build.host/zig" | |
| 54 | ||
| 55 | # Build zig for arm64 target. | |
| 56 | # - use `Release` type for published tarballs | |
| 57 | # - ad-hoc codesign with linker | |
| 58 | # - note: apple quarantine of downloads (eg. via safari) still apply | |
| 59 | ||
| 60 | cd $ZIGDIR | |
| 61 | mkdir build.arm64 | |
| 62 | cd build.arm64 | |
| 63 | cmake -G "Ninja" .. \ | |
| 64 | -DCMAKE_INSTALL_PREFIX="$(pwd)/release" \ | |
| 65 | -DCMAKE_PREFIX_PATH="$PREFIX_ARM64" \ | |
| 66 | -DCMAKE_BUILD_TYPE="Release" \ | |
| 67 | -DCMAKE_CROSSCOMPILING="True" \ | |
| 68 | -DCMAKE_SYSTEM_NAME="Darwin" \ | |
| 69 | -DCMAKE_C_FLAGS="-arch arm64" \ | |
| 70 | -DCMAKE_CXX_FLAGS="-arch arm64" \ | |
| 71 | -DCMAKE_EXE_LINKER_FLAGS="-lz -Xlinker -adhoc_codesign" \ | |
| 72 | -DZIG_USE_LLVM_CONFIG="OFF" \ | |
| 73 | -DZIG_EXECUTABLE="$ZIG_EXE" \ | |
| 74 | -DZIG_TARGET_TRIPLE="${ARCH}-macos" \ | |
| 75 | -DZIG_STATIC="OFF" | |
| 76 | ||
| 77 | ninja $JOBS install | |
| 78 | ||
| 79 | # Disable test because binary is foreign arch. | |
| 80 | #release/bin/zig build test | |
| 81 | ||
| 82 | if [ "${BUILD_REASON}" != "PullRequest" ]; then | |
| 83 | mv ../LICENSE release/ | |
| 84 | ||
| 85 | # We do not run test suite but still need langref. | |
| 86 | mkdir -p release/docs | |
| 87 | $ZIG_EXE run ../doc/docgen.zig -- $ZIG_EXE ../doc/langref.html.in release/docs/langref.html | |
| 88 | ||
| 89 | # Produce the experimental std lib documentation. | |
| 90 | mkdir -p release/docs/std | |
| 91 | $ZIG_EXE test ../lib/std/std.zig \ | |
| 92 | --override-lib-dir ../lib \ | |
| 93 | -femit-docs=release/docs/std \ | |
| 94 | -fno-emit-bin | |
| 95 | ||
| 96 | # Remove the unnecessary bin dir in $prefix/bin/zig | |
| 97 | mv release/bin/zig release/ | |
| 98 | rmdir release/bin | |
| 99 | ||
| 100 | # Remove the unnecessary zig dir in $prefix/lib/zig/std/std.zig | |
| 101 | mv release/lib/zig release/lib2 | |
| 102 | rmdir release/lib | |
| 103 | mv release/lib2 release/lib | |
| 104 | ||
| 105 | VERSION=$($ZIG_EXE version) | |
| 106 | DIRNAME="zig-macos-$ARCH-$VERSION" | |
| 107 | TARBALL="$DIRNAME.tar.xz" | |
| 108 | gtar cJf "$TARBALL" release/ --owner=root --sort=name --transform="s,^release,${DIRNAME}," | |
| 109 | ln "$TARBALL" "$BUILD_ARTIFACTSTAGINGDIRECTORY/." | |
| 110 | ||
| 111 | mv "$DOWNLOADSECUREFILE_SECUREFILEPATH" "$HOME/.s3cfg" | |
| 112 | s3cmd put -P --add-header="cache-control: public, max-age=31536000, immutable" "$TARBALL" s3://ziglang.org/builds/ | |
| 113 | ||
| 114 | SHASUM=$(shasum -a 256 $TARBALL | cut '-d ' -f1) | |
| 115 | BYTESIZE=$(wc -c < $TARBALL) | |
| 116 | ||
| 117 | JSONFILE="macos-$GITBRANCH.json" | |
| 118 | touch $JSONFILE | |
| 119 | echo "{\"tarball\": \"$TARBALL\"," >>$JSONFILE | |
| 120 | echo "\"shasum\": \"$SHASUM\"," >>$JSONFILE | |
| 121 | echo "\"size\": \"$BYTESIZE\"}" >>$JSONFILE | |
| 122 | ||
| 123 | s3cmd put -P --add-header="Cache-Control: max-age=0, must-revalidate" "$JSONFILE" "s3://ziglang.org/builds/$JSONFILE" | |
| 124 | s3cmd put -P "$JSONFILE" "s3://ziglang.org/builds/$ARCH-macos-$VERSION.json" | |
| 125 | ||
| 126 | # `set -x` causes these variables to be mangled. | |
| 127 | # See https://developercommunity.visualstudio.com/content/problem/375679/pipeline-variable-incorrectly-inserts-single-quote.html | |
| 128 | set +x | |
| 129 | echo "##vso[task.setvariable variable=tarball;isOutput=true]$TARBALL" | |
| 130 | echo "##vso[task.setvariable variable=shasum;isOutput=true]$SHASUM" | |
| 131 | echo "##vso[task.setvariable variable=bytesize;isOutput=true]$BYTESIZE" | |
| 132 | fi |
ci/azure/pipelines.yml+14-1| ... | ... | @@ -12,6 +12,19 @@ jobs: |
| 12 | 12 | - script: ci/azure/macos_script |
| 13 | 13 | name: main |
| 14 | 14 | displayName: 'Build and test' |
| 15 | - job: BuildMacOS_arm64 | |
| 16 | pool: | |
| 17 | vmImage: 'macOS-10.15' | |
| 18 | ||
| 19 | timeoutInMinutes: 60 | |
| 20 | ||
| 21 | steps: | |
| 22 | - task: DownloadSecureFile@1 | |
| 23 | inputs: | |
| 24 | secureFile: s3cfg | |
| 25 | - script: ci/azure/macos_arm64_script | |
| 26 | name: main | |
| 27 | displayName: 'Build and cross-compile' | |
| 15 | 28 | - job: BuildLinux |
| 16 | 29 | pool: |
| 17 | 30 | vmImage: 'ubuntu-18.04' |
| ... | ... | @@ -31,7 +44,7 @@ jobs: |
| 31 | 44 | timeoutInMinutes: 360 |
| 32 | 45 | steps: |
| 33 | 46 | - powershell: | |
| 34 | (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2021-01-05/msys2-base-x86_64-20210105.sfx.exe", "sfx.exe") | |
| 47 | (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2021-02-28/msys2-base-x86_64-20210228.sfx.exe", "sfx.exe") | |
| 35 | 48 | .\sfx.exe -y -o\ |
| 36 | 49 | del sfx.exe |
| 37 | 50 | displayName: Download/Extract/Install MSYS2 |
ci/azure/windows_msvc_install+1-1| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | set -x |
| 4 | 4 | set -e |
| 5 | 5 | |
| 6 | pacman -Su --needed --noconfirm | |
| 6 | pacman -Suy --needed --noconfirm | |
| 7 | 7 | pacman -S --needed --noconfirm wget p7zip python3-pip tar xz |
| 8 | 8 | |
| 9 | 9 | pip install s3cmd |
doc/docgen.zig+18-5| ... | ... | @@ -4,6 +4,7 @@ const io = std.io; |
| 4 | 4 | const fs = std.fs; |
| 5 | 5 | const process = std.process; |
| 6 | 6 | const ChildProcess = std.ChildProcess; |
| 7 | const Progress = std.Progress; | |
| 7 | 8 | const print = std.debug.print; |
| 8 | 9 | const mem = std.mem; |
| 9 | 10 | const testing = std.testing; |
| ... | ... | @@ -234,7 +235,7 @@ fn parseError(tokenizer: *Tokenizer, token: Token, comptime fmt: []const u8, arg |
| 234 | 235 | } |
| 235 | 236 | } |
| 236 | 237 | { |
| 237 | const caret_count = token.end - token.start; | |
| 238 | const caret_count = std.math.min(token.end, loc.line_end) - token.start; | |
| 238 | 239 | var i: usize = 0; |
| 239 | 240 | while (i < caret_count) : (i += 1) { |
| 240 | 241 | print("~", .{}); |
| ... | ... | @@ -1012,6 +1013,9 @@ fn tokenizeAndPrint(docgen_tokenizer: *Tokenizer, out: anytype, source_token: To |
| 1012 | 1013 | |
| 1013 | 1014 | fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: anytype, zig_exe: []const u8, do_code_tests: bool) !void { |
| 1014 | 1015 | var code_progress_index: usize = 0; |
| 1016 | var progress = Progress{}; | |
| 1017 | const root_node = try progress.start("Generating docgen examples", toc.nodes.len); | |
| 1018 | defer root_node.end(); | |
| 1015 | 1019 | |
| 1016 | 1020 | var env_map = try process.getEnvMap(allocator); |
| 1017 | 1021 | try env_map.set("ZIG_DEBUG_COLOR", "1"); |
| ... | ... | @@ -1058,8 +1062,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1058 | 1062 | try tokenizeAndPrint(tokenizer, out, content_tok); |
| 1059 | 1063 | }, |
| 1060 | 1064 | .Code => |code| { |
| 1061 | code_progress_index += 1; | |
| 1062 | print("docgen example code {}/{}...", .{ code_progress_index, tokenizer.code_node_count }); | |
| 1065 | root_node.completeOne(); | |
| 1063 | 1066 | |
| 1064 | 1067 | const raw_source = tokenizer.buffer[code.source_token.start..code.source_token.end]; |
| 1065 | 1068 | const trimmed_raw_source = mem.trim(u8, raw_source, " \n"); |
| ... | ... | @@ -1071,7 +1074,6 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1071 | 1074 | try out.writeAll("</pre>"); |
| 1072 | 1075 | |
| 1073 | 1076 | if (!do_code_tests) { |
| 1074 | print("SKIP\n", .{}); | |
| 1075 | 1077 | continue; |
| 1076 | 1078 | } |
| 1077 | 1079 | |
| ... | ... | @@ -1133,12 +1135,14 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1133 | 1135 | switch (result.term) { |
| 1134 | 1136 | .Exited => |exit_code| { |
| 1135 | 1137 | if (exit_code == 0) { |
| 1138 | progress.log("", .{}); | |
| 1136 | 1139 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 1137 | 1140 | dumpArgs(build_args.items); |
| 1138 | 1141 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); |
| 1139 | 1142 | } |
| 1140 | 1143 | }, |
| 1141 | 1144 | else => { |
| 1145 | progress.log("", .{}); | |
| 1142 | 1146 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 1143 | 1147 | dumpArgs(build_args.items); |
| 1144 | 1148 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); |
| ... | ... | @@ -1187,6 +1191,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1187 | 1191 | switch (result.term) { |
| 1188 | 1192 | .Exited => |exit_code| { |
| 1189 | 1193 | if (exit_code == 0) { |
| 1194 | progress.log("", .{}); | |
| 1190 | 1195 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 1191 | 1196 | dumpArgs(run_args); |
| 1192 | 1197 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); |
| ... | ... | @@ -1266,18 +1271,21 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1266 | 1271 | switch (result.term) { |
| 1267 | 1272 | .Exited => |exit_code| { |
| 1268 | 1273 | if (exit_code == 0) { |
| 1274 | progress.log("", .{}); | |
| 1269 | 1275 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 1270 | 1276 | dumpArgs(test_args.items); |
| 1271 | 1277 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); |
| 1272 | 1278 | } |
| 1273 | 1279 | }, |
| 1274 | 1280 | else => { |
| 1281 | progress.log("", .{}); | |
| 1275 | 1282 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 1276 | 1283 | dumpArgs(test_args.items); |
| 1277 | 1284 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); |
| 1278 | 1285 | }, |
| 1279 | 1286 | } |
| 1280 | 1287 | if (mem.indexOf(u8, result.stderr, error_match) == null) { |
| 1288 | progress.log("", .{}); | |
| 1281 | 1289 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); |
| 1282 | 1290 | return parseError(tokenizer, code.source_token, "example did not have expected compile error", .{}); |
| 1283 | 1291 | } |
| ... | ... | @@ -1321,18 +1329,21 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1321 | 1329 | switch (result.term) { |
| 1322 | 1330 | .Exited => |exit_code| { |
| 1323 | 1331 | if (exit_code == 0) { |
| 1332 | progress.log("", .{}); | |
| 1324 | 1333 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 1325 | 1334 | dumpArgs(test_args.items); |
| 1326 | 1335 | return parseError(tokenizer, code.source_token, "example test incorrectly succeeded", .{}); |
| 1327 | 1336 | } |
| 1328 | 1337 | }, |
| 1329 | 1338 | else => { |
| 1339 | progress.log("", .{}); | |
| 1330 | 1340 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 1331 | 1341 | dumpArgs(test_args.items); |
| 1332 | 1342 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); |
| 1333 | 1343 | }, |
| 1334 | 1344 | } |
| 1335 | 1345 | if (mem.indexOf(u8, result.stderr, error_match) == null) { |
| 1346 | progress.log("", .{}); | |
| 1336 | 1347 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); |
| 1337 | 1348 | return parseError(tokenizer, code.source_token, "example did not have expected runtime safety error message", .{}); |
| 1338 | 1349 | } |
| ... | ... | @@ -1400,18 +1411,21 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1400 | 1411 | switch (result.term) { |
| 1401 | 1412 | .Exited => |exit_code| { |
| 1402 | 1413 | if (exit_code == 0) { |
| 1414 | progress.log("", .{}); | |
| 1403 | 1415 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 1404 | 1416 | dumpArgs(build_args.items); |
| 1405 | 1417 | return parseError(tokenizer, code.source_token, "example build incorrectly succeeded", .{}); |
| 1406 | 1418 | } |
| 1407 | 1419 | }, |
| 1408 | 1420 | else => { |
| 1421 | progress.log("", .{}); | |
| 1409 | 1422 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 1410 | 1423 | dumpArgs(build_args.items); |
| 1411 | 1424 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); |
| 1412 | 1425 | }, |
| 1413 | 1426 | } |
| 1414 | 1427 | if (mem.indexOf(u8, result.stderr, error_match) == null) { |
| 1428 | progress.log("", .{}); | |
| 1415 | 1429 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); |
| 1416 | 1430 | return parseError(tokenizer, code.source_token, "example did not have expected compile error message", .{}); |
| 1417 | 1431 | } |
| ... | ... | @@ -1461,7 +1475,6 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any |
| 1461 | 1475 | try out.print("\n{s}{s}</code></pre>\n", .{ escaped_stderr, escaped_stdout }); |
| 1462 | 1476 | }, |
| 1463 | 1477 | } |
| 1464 | print("OK\n", .{}); | |
| 1465 | 1478 | }, |
| 1466 | 1479 | } |
| 1467 | 1480 | } |
doc/langref.html.in+3-3| ... | ... | @@ -9952,9 +9952,9 @@ export fn decode_base_64( |
| 9952 | 9952 | ) usize { |
| 9953 | 9953 | const src = source_ptr[0..source_len]; |
| 9954 | 9954 | const dest = dest_ptr[0..dest_len]; |
| 9955 | const base64_decoder = base64.standard_decoder_unsafe; | |
| 9956 | const decoded_size = base64_decoder.calcSize(src); | |
| 9957 | base64_decoder.decode(dest[0..decoded_size], src); | |
| 9955 | const base64_decoder = base64.standard.Decoder; | |
| 9956 | const decoded_size = base64_decoder.calcSizeForSlice(src) catch unreachable; | |
| 9957 | base64_decoder.decode(dest[0..decoded_size], src) catch unreachable; | |
| 9958 | 9958 | return decoded_size; |
| 9959 | 9959 | } |
| 9960 | 9960 | {#code_end#} |
lib/std/array_hash_map.zig+13-13| ... | ... | @@ -687,8 +687,9 @@ pub fn ArrayHashMapUnmanaged( |
| 687 | 687 | |
| 688 | 688 | /// Removes the last inserted `Entry` in the hash map and returns it. |
| 689 | 689 | pub fn pop(self: *Self) Entry { |
| 690 | const top = self.entries.pop(); | |
| 690 | const top = self.entries.items[self.entries.items.len - 1]; | |
| 691 | 691 | _ = self.removeWithHash(top.key, top.hash, .index_only); |
| 692 | self.entries.items.len -= 1; | |
| 692 | 693 | return top; |
| 693 | 694 | } |
| 694 | 695 | |
| ... | ... | @@ -1258,19 +1259,18 @@ test "pop" { |
| 1258 | 1259 | var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); |
| 1259 | 1260 | defer map.deinit(); |
| 1260 | 1261 | |
| 1261 | testing.expect((try map.fetchPut(1, 11)) == null); | |
| 1262 | testing.expect((try map.fetchPut(2, 22)) == null); | |
| 1263 | testing.expect((try map.fetchPut(3, 33)) == null); | |
| 1264 | testing.expect((try map.fetchPut(4, 44)) == null); | |
| 1262 | // Insert just enough entries so that the map expands. Afterwards, | |
| 1263 | // pop all entries out of the map. | |
| 1265 | 1264 | |
| 1266 | const pop1 = map.pop(); | |
| 1267 | testing.expect(pop1.key == 4 and pop1.value == 44); | |
| 1268 | const pop2 = map.pop(); | |
| 1269 | testing.expect(pop2.key == 3 and pop2.value == 33); | |
| 1270 | const pop3 = map.pop(); | |
| 1271 | testing.expect(pop3.key == 2 and pop3.value == 22); | |
| 1272 | const pop4 = map.pop(); | |
| 1273 | testing.expect(pop4.key == 1 and pop4.value == 11); | |
| 1265 | var i: i32 = 0; | |
| 1266 | while (i < 9) : (i += 1) { | |
| 1267 | testing.expect((try map.fetchPut(i, i)) == null); | |
| 1268 | } | |
| 1269 | ||
| 1270 | while (i > 0) : (i -= 1) { | |
| 1271 | const pop = map.pop(); | |
| 1272 | testing.expect(pop.key == i - 1 and pop.value == i - 1); | |
| 1273 | } | |
| 1274 | 1274 | } |
| 1275 | 1275 | |
| 1276 | 1276 | test "reIndex" { |
lib/std/base64.zig+322-324| ... | ... | @@ -8,454 +8,452 @@ const assert = std.debug.assert; |
| 8 | 8 | const testing = std.testing; |
| 9 | 9 | const mem = std.mem; |
| 10 | 10 | |
| 11 | pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
| 12 | pub const standard_pad_char = '='; | |
| 13 | pub const standard_encoder = Base64Encoder.init(standard_alphabet_chars, standard_pad_char); | |
| 11 | pub const Error = error{ | |
| 12 | InvalidCharacter, | |
| 13 | InvalidPadding, | |
| 14 | NoSpaceLeft, | |
| 15 | }; | |
| 16 | ||
| 17 | /// Base64 codecs | |
| 18 | pub const Codecs = struct { | |
| 19 | alphabet_chars: [64]u8, | |
| 20 | pad_char: ?u8, | |
| 21 | decoderWithIgnore: fn (ignore: []const u8) Base64DecoderWithIgnore, | |
| 22 | Encoder: Base64Encoder, | |
| 23 | Decoder: Base64Decoder, | |
| 24 | }; | |
| 25 | ||
| 26 | pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".*; | |
| 27 | fn standardBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { | |
| 28 | return Base64DecoderWithIgnore.init(standard_alphabet_chars, '=', ignore); | |
| 29 | } | |
| 30 | ||
| 31 | /// Standard Base64 codecs, with padding | |
| 32 | pub const standard = Codecs{ | |
| 33 | .alphabet_chars = standard_alphabet_chars, | |
| 34 | .pad_char = '=', | |
| 35 | .decoderWithIgnore = standardBase64DecoderWithIgnore, | |
| 36 | .Encoder = Base64Encoder.init(standard_alphabet_chars, '='), | |
| 37 | .Decoder = Base64Decoder.init(standard_alphabet_chars, '='), | |
| 38 | }; | |
| 39 | ||
| 40 | /// Standard Base64 codecs, without padding | |
| 41 | pub const standard_no_pad = Codecs{ | |
| 42 | .alphabet_chars = standard_alphabet_chars, | |
| 43 | .pad_char = null, | |
| 44 | .decoderWithIgnore = standardBase64DecoderWithIgnore, | |
| 45 | .Encoder = Base64Encoder.init(standard_alphabet_chars, null), | |
| 46 | .Decoder = Base64Decoder.init(standard_alphabet_chars, null), | |
| 47 | }; | |
| 48 | ||
| 49 | pub const url_safe_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; | |
| 50 | fn urlSafeBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { | |
| 51 | return Base64DecoderWithIgnore.init(url_safe_alphabet_chars, null, ignore); | |
| 52 | } | |
| 53 | ||
| 54 | /// URL-safe Base64 codecs, with padding | |
| 55 | pub const url_safe = Codecs{ | |
| 56 | .alphabet_chars = url_safe_alphabet_chars, | |
| 57 | .pad_char = '=', | |
| 58 | .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, | |
| 59 | .Encoder = Base64Encoder.init(url_safe_alphabet_chars, '='), | |
| 60 | .Decoder = Base64Decoder.init(url_safe_alphabet_chars, '='), | |
| 61 | }; | |
| 62 | ||
| 63 | /// URL-safe Base64 codecs, without padding | |
| 64 | pub const url_safe_no_pad = Codecs{ | |
| 65 | .alphabet_chars = url_safe_alphabet_chars, | |
| 66 | .pad_char = null, | |
| 67 | .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, | |
| 68 | .Encoder = Base64Encoder.init(url_safe_alphabet_chars, null), | |
| 69 | .Decoder = Base64Decoder.init(url_safe_alphabet_chars, null), | |
| 70 | }; | |
| 71 | ||
| 72 | // Backwards compatibility | |
| 73 | ||
| 74 | /// Deprecated - Use `standard.pad_char` | |
| 75 | pub const standard_pad_char = standard.pad_char; | |
| 76 | /// Deprecated - Use `standard.Encoder` | |
| 77 | pub const standard_encoder = standard.Encoder; | |
| 78 | /// Deprecated - Use `standard.Decoder` | |
| 79 | pub const standard_decoder = standard.Decoder; | |
| 14 | 80 | |
| 15 | 81 | pub const Base64Encoder = struct { |
| 16 | alphabet_chars: []const u8, | |
| 17 | pad_char: u8, | |
| 82 | alphabet_chars: [64]u8, | |
| 83 | pad_char: ?u8, | |
| 18 | 84 | |
| 19 | /// a bunch of assertions, then simply pass the data right through. | |
| 20 | pub fn init(alphabet_chars: []const u8, pad_char: u8) Base64Encoder { | |
| 85 | /// A bunch of assertions, then simply pass the data right through. | |
| 86 | pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Encoder { | |
| 21 | 87 | assert(alphabet_chars.len == 64); |
| 22 | 88 | var char_in_alphabet = [_]bool{false} ** 256; |
| 23 | 89 | for (alphabet_chars) |c| { |
| 24 | 90 | assert(!char_in_alphabet[c]); |
| 25 | assert(c != pad_char); | |
| 91 | assert(pad_char == null or c != pad_char.?); | |
| 26 | 92 | char_in_alphabet[c] = true; |
| 27 | 93 | } |
| 28 | ||
| 29 | 94 | return Base64Encoder{ |
| 30 | 95 | .alphabet_chars = alphabet_chars, |
| 31 | 96 | .pad_char = pad_char, |
| 32 | 97 | }; |
| 33 | 98 | } |
| 34 | 99 | |
| 35 | /// ceil(source_len * 4/3) | |
| 36 | pub fn calcSize(source_len: usize) usize { | |
| 37 | return @divTrunc(source_len + 2, 3) * 4; | |
| 100 | /// Compute the encoded length | |
| 101 | pub fn calcSize(encoder: *const Base64Encoder, source_len: usize) usize { | |
| 102 | if (encoder.pad_char != null) { | |
| 103 | return @divTrunc(source_len + 2, 3) * 4; | |
| 104 | } else { | |
| 105 | const leftover = source_len % 3; | |
| 106 | return @divTrunc(source_len, 3) * 4 + @divTrunc(leftover * 4 + 2, 3); | |
| 107 | } | |
| 38 | 108 | } |
| 39 | 109 | |
| 40 | /// dest.len must be what you get from ::calcSize. | |
| 110 | /// dest.len must at least be what you get from ::calcSize. | |
| 41 | 111 | pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) []const u8 { |
| 42 | assert(dest.len >= Base64Encoder.calcSize(source.len)); | |
| 43 | ||
| 44 | var i: usize = 0; | |
| 45 | var out_index: usize = 0; | |
| 46 | while (i + 2 < source.len) : (i += 3) { | |
| 47 | dest[out_index] = encoder.alphabet_chars[(source[i] >> 2) & 0x3f]; | |
| 48 | out_index += 1; | |
| 49 | ||
| 50 | dest[out_index] = encoder.alphabet_chars[((source[i] & 0x3) << 4) | ((source[i + 1] & 0xf0) >> 4)]; | |
| 51 | out_index += 1; | |
| 52 | ||
| 53 | dest[out_index] = encoder.alphabet_chars[((source[i + 1] & 0xf) << 2) | ((source[i + 2] & 0xc0) >> 6)]; | |
| 54 | out_index += 1; | |
| 55 | ||
| 56 | dest[out_index] = encoder.alphabet_chars[source[i + 2] & 0x3f]; | |
| 57 | out_index += 1; | |
| 112 | const out_len = encoder.calcSize(source.len); | |
| 113 | assert(dest.len >= out_len); | |
| 114 | ||
| 115 | const nibbles = source.len / 3; | |
| 116 | const leftover = source.len - 3 * nibbles; | |
| 117 | ||
| 118 | var acc: u12 = 0; | |
| 119 | var acc_len: u4 = 0; | |
| 120 | var out_idx: usize = 0; | |
| 121 | for (source) |v| { | |
| 122 | acc = (acc << 8) + v; | |
| 123 | acc_len += 8; | |
| 124 | while (acc_len >= 6) { | |
| 125 | acc_len -= 6; | |
| 126 | dest[out_idx] = encoder.alphabet_chars[@truncate(u6, (acc >> acc_len))]; | |
| 127 | out_idx += 1; | |
| 128 | } | |
| 58 | 129 | } |
| 59 | ||
| 60 | if (i < source.len) { | |
| 61 | dest[out_index] = encoder.alphabet_chars[(source[i] >> 2) & 0x3f]; | |
| 62 | out_index += 1; | |
| 63 | ||
| 64 | if (i + 1 == source.len) { | |
| 65 | dest[out_index] = encoder.alphabet_chars[(source[i] & 0x3) << 4]; | |
| 66 | out_index += 1; | |
| 67 | ||
| 68 | dest[out_index] = encoder.pad_char; | |
| 69 | out_index += 1; | |
| 70 | } else { | |
| 71 | dest[out_index] = encoder.alphabet_chars[((source[i] & 0x3) << 4) | ((source[i + 1] & 0xf0) >> 4)]; | |
| 72 | out_index += 1; | |
| 73 | ||
| 74 | dest[out_index] = encoder.alphabet_chars[(source[i + 1] & 0xf) << 2]; | |
| 75 | out_index += 1; | |
| 130 | if (acc_len > 0) { | |
| 131 | dest[out_idx] = encoder.alphabet_chars[@truncate(u6, (acc << 6 - acc_len))]; | |
| 132 | out_idx += 1; | |
| 133 | } | |
| 134 | if (encoder.pad_char) |pad_char| { | |
| 135 | for (dest[out_idx..]) |*pad| { | |
| 136 | pad.* = pad_char; | |
| 76 | 137 | } |
| 77 | ||
| 78 | dest[out_index] = encoder.pad_char; | |
| 79 | out_index += 1; | |
| 80 | 138 | } |
| 81 | return dest[0..out_index]; | |
| 139 | return dest[0..out_len]; | |
| 82 | 140 | } |
| 83 | 141 | }; |
| 84 | 142 | |
| 85 | pub const standard_decoder = Base64Decoder.init(standard_alphabet_chars, standard_pad_char); | |
| 86 | ||
| 87 | 143 | pub const Base64Decoder = struct { |
| 144 | const invalid_char: u8 = 0xff; | |
| 145 | ||
| 88 | 146 | /// e.g. 'A' => 0. |
| 89 | /// undefined for any value not in the 64 alphabet chars. | |
| 147 | /// `invalid_char` for any value not in the 64 alphabet chars. | |
| 90 | 148 | char_to_index: [256]u8, |
| 149 | pad_char: ?u8, | |
| 91 | 150 | |
| 92 | /// true only for the 64 chars in the alphabet, not the pad char. | |
| 93 | char_in_alphabet: [256]bool, | |
| 94 | pad_char: u8, | |
| 95 | ||
| 96 | pub fn init(alphabet_chars: []const u8, pad_char: u8) Base64Decoder { | |
| 97 | assert(alphabet_chars.len == 64); | |
| 98 | ||
| 151 | pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder { | |
| 99 | 152 | var result = Base64Decoder{ |
| 100 | .char_to_index = undefined, | |
| 101 | .char_in_alphabet = [_]bool{false} ** 256, | |
| 153 | .char_to_index = [_]u8{invalid_char} ** 256, | |
| 102 | 154 | .pad_char = pad_char, |
| 103 | 155 | }; |
| 104 | 156 | |
| 157 | var char_in_alphabet = [_]bool{false} ** 256; | |
| 105 | 158 | for (alphabet_chars) |c, i| { |
| 106 | assert(!result.char_in_alphabet[c]); | |
| 107 | assert(c != pad_char); | |
| 159 | assert(!char_in_alphabet[c]); | |
| 160 | assert(pad_char == null or c != pad_char.?); | |
| 108 | 161 | |
| 109 | 162 | result.char_to_index[c] = @intCast(u8, i); |
| 110 | result.char_in_alphabet[c] = true; | |
| 163 | char_in_alphabet[c] = true; | |
| 111 | 164 | } |
| 165 | return result; | |
| 166 | } | |
| 112 | 167 | |
| 168 | /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding. | |
| 169 | /// `InvalidPadding` is returned if the input length is not valid. | |
| 170 | pub fn calcSizeUpperBound(decoder: *const Base64Decoder, source_len: usize) Error!usize { | |
| 171 | var result = source_len / 4 * 3; | |
| 172 | const leftover = source_len % 4; | |
| 173 | if (decoder.pad_char != null) { | |
| 174 | if (leftover % 4 != 0) return error.InvalidPadding; | |
| 175 | } else { | |
| 176 | if (leftover % 4 == 1) return error.InvalidPadding; | |
| 177 | result += leftover * 3 / 4; | |
| 178 | } | |
| 113 | 179 | return result; |
| 114 | 180 | } |
| 115 | 181 | |
| 116 | /// If the encoded buffer is detected to be invalid, returns error.InvalidPadding. | |
| 117 | pub fn calcSize(decoder: *const Base64Decoder, source: []const u8) !usize { | |
| 118 | if (source.len % 4 != 0) return error.InvalidPadding; | |
| 119 | return calcDecodedSizeExactUnsafe(source, decoder.pad_char); | |
| 182 | /// Return the exact decoded size for a slice. | |
| 183 | /// `InvalidPadding` is returned if the input length is not valid. | |
| 184 | pub fn calcSizeForSlice(decoder: *const Base64Decoder, source: []const u8) Error!usize { | |
| 185 | const source_len = source.len; | |
| 186 | var result = try decoder.calcSizeUpperBound(source_len); | |
| 187 | if (decoder.pad_char) |pad_char| { | |
| 188 | if (source_len >= 1 and source[source_len - 1] == pad_char) result -= 1; | |
| 189 | if (source_len >= 2 and source[source_len - 2] == pad_char) result -= 1; | |
| 190 | } | |
| 191 | return result; | |
| 120 | 192 | } |
| 121 | 193 | |
| 122 | 194 | /// dest.len must be what you get from ::calcSize. |
| 123 | 195 | /// invalid characters result in error.InvalidCharacter. |
| 124 | 196 | /// invalid padding results in error.InvalidPadding. |
| 125 | pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) !void { | |
| 126 | assert(dest.len == (decoder.calcSize(source) catch unreachable)); | |
| 127 | assert(source.len % 4 == 0); | |
| 128 | ||
| 129 | var src_cursor: usize = 0; | |
| 130 | var dest_cursor: usize = 0; | |
| 131 | ||
| 132 | while (src_cursor < source.len) : (src_cursor += 4) { | |
| 133 | if (!decoder.char_in_alphabet[source[src_cursor + 0]]) return error.InvalidCharacter; | |
| 134 | if (!decoder.char_in_alphabet[source[src_cursor + 1]]) return error.InvalidCharacter; | |
| 135 | if (src_cursor < source.len - 4 or source[src_cursor + 3] != decoder.pad_char) { | |
| 136 | // common case | |
| 137 | if (!decoder.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter; | |
| 138 | if (!decoder.char_in_alphabet[source[src_cursor + 3]]) return error.InvalidCharacter; | |
| 139 | dest[dest_cursor + 0] = decoder.char_to_index[source[src_cursor + 0]] << 2 | decoder.char_to_index[source[src_cursor + 1]] >> 4; | |
| 140 | dest[dest_cursor + 1] = decoder.char_to_index[source[src_cursor + 1]] << 4 | decoder.char_to_index[source[src_cursor + 2]] >> 2; | |
| 141 | dest[dest_cursor + 2] = decoder.char_to_index[source[src_cursor + 2]] << 6 | decoder.char_to_index[source[src_cursor + 3]]; | |
| 142 | dest_cursor += 3; | |
| 143 | } else if (source[src_cursor + 2] != decoder.pad_char) { | |
| 144 | // one pad char | |
| 145 | if (!decoder.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter; | |
| 146 | dest[dest_cursor + 0] = decoder.char_to_index[source[src_cursor + 0]] << 2 | decoder.char_to_index[source[src_cursor + 1]] >> 4; | |
| 147 | dest[dest_cursor + 1] = decoder.char_to_index[source[src_cursor + 1]] << 4 | decoder.char_to_index[source[src_cursor + 2]] >> 2; | |
| 148 | if (decoder.char_to_index[source[src_cursor + 2]] << 6 != 0) return error.InvalidPadding; | |
| 149 | dest_cursor += 2; | |
| 150 | } else { | |
| 151 | // two pad chars | |
| 152 | dest[dest_cursor + 0] = decoder.char_to_index[source[src_cursor + 0]] << 2 | decoder.char_to_index[source[src_cursor + 1]] >> 4; | |
| 153 | if (decoder.char_to_index[source[src_cursor + 1]] << 4 != 0) return error.InvalidPadding; | |
| 154 | dest_cursor += 1; | |
| 197 | pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void { | |
| 198 | if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding; | |
| 199 | var acc: u12 = 0; | |
| 200 | var acc_len: u4 = 0; | |
| 201 | var dest_idx: usize = 0; | |
| 202 | var leftover_idx: ?usize = null; | |
| 203 | for (source) |c, src_idx| { | |
| 204 | const d = decoder.char_to_index[c]; | |
| 205 | if (d == invalid_char) { | |
| 206 | if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; | |
| 207 | leftover_idx = src_idx; | |
| 208 | break; | |
| 209 | } | |
| 210 | acc = (acc << 6) + d; | |
| 211 | acc_len += 6; | |
| 212 | if (acc_len >= 8) { | |
| 213 | acc_len -= 8; | |
| 214 | dest[dest_idx] = @truncate(u8, acc >> acc_len); | |
| 215 | dest_idx += 1; | |
| 155 | 216 | } |
| 156 | 217 | } |
| 157 | ||
| 158 | assert(src_cursor == source.len); | |
| 159 | assert(dest_cursor == dest.len); | |
| 218 | if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { | |
| 219 | return error.InvalidPadding; | |
| 220 | } | |
| 221 | if (leftover_idx == null) return; | |
| 222 | var leftover = source[leftover_idx.?..]; | |
| 223 | if (decoder.pad_char) |pad_char| { | |
| 224 | const padding_len = acc_len / 2; | |
| 225 | var padding_chars: usize = 0; | |
| 226 | var i: usize = 0; | |
| 227 | for (leftover) |c| { | |
| 228 | if (c != pad_char) { | |
| 229 | return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; | |
| 230 | } | |
| 231 | padding_chars += 1; | |
| 232 | } | |
| 233 | if (padding_chars != padding_len) return error.InvalidPadding; | |
| 234 | } | |
| 160 | 235 | } |
| 161 | 236 | }; |
| 162 | 237 | |
| 163 | 238 | pub const Base64DecoderWithIgnore = struct { |
| 164 | 239 | decoder: Base64Decoder, |
| 165 | 240 | char_is_ignored: [256]bool, |
| 166 | pub fn init(alphabet_chars: []const u8, pad_char: u8, ignore_chars: []const u8) Base64DecoderWithIgnore { | |
| 241 | ||
| 242 | pub fn init(alphabet_chars: [64]u8, pad_char: ?u8, ignore_chars: []const u8) Base64DecoderWithIgnore { | |
| 167 | 243 | var result = Base64DecoderWithIgnore{ |
| 168 | 244 | .decoder = Base64Decoder.init(alphabet_chars, pad_char), |
| 169 | 245 | .char_is_ignored = [_]bool{false} ** 256, |
| 170 | 246 | }; |
| 171 | ||
| 172 | 247 | for (ignore_chars) |c| { |
| 173 | assert(!result.decoder.char_in_alphabet[c]); | |
| 248 | assert(result.decoder.char_to_index[c] == Base64Decoder.invalid_char); | |
| 174 | 249 | assert(!result.char_is_ignored[c]); |
| 175 | 250 | assert(result.decoder.pad_char != c); |
| 176 | 251 | result.char_is_ignored[c] = true; |
| 177 | 252 | } |
| 178 | ||
| 179 | 253 | return result; |
| 180 | 254 | } |
| 181 | 255 | |
| 182 | /// If no characters end up being ignored or padding, this will be the exact decoded size. | |
| 183 | pub fn calcSizeUpperBound(encoded_len: usize) usize { | |
| 184 | return @divTrunc(encoded_len, 4) * 3; | |
| 256 | /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding | |
| 257 | /// `InvalidPadding` is returned if the input length is not valid. | |
| 258 | pub fn calcSizeUpperBound(decoder_with_ignore: *const Base64DecoderWithIgnore, source_len: usize) Error!usize { | |
| 259 | var result = source_len / 4 * 3; | |
| 260 | if (decoder_with_ignore.decoder.pad_char == null) { | |
| 261 | const leftover = source_len % 4; | |
| 262 | result += leftover * 3 / 4; | |
| 263 | } | |
| 264 | return result; | |
| 185 | 265 | } |
| 186 | 266 | |
| 187 | 267 | /// Invalid characters that are not ignored result in error.InvalidCharacter. |
| 188 | 268 | /// Invalid padding results in error.InvalidPadding. |
| 189 | /// Decoding more data than can fit in dest results in error.OutputTooSmall. See also ::calcSizeUpperBound. | |
| 269 | /// Decoding more data than can fit in dest results in error.NoSpaceLeft. See also ::calcSizeUpperBound. | |
| 190 | 270 | /// Returns the number of bytes written to dest. |
| 191 | pub fn decode(decoder_with_ignore: *const Base64DecoderWithIgnore, dest: []u8, source: []const u8) !usize { | |
| 271 | pub fn decode(decoder_with_ignore: *const Base64DecoderWithIgnore, dest: []u8, source: []const u8) Error!usize { | |
| 192 | 272 | const decoder = &decoder_with_ignore.decoder; |
| 193 | ||
| 194 | var src_cursor: usize = 0; | |
| 195 | var dest_cursor: usize = 0; | |
| 196 | ||
| 197 | while (true) { | |
| 198 | // get the next 4 chars, if available | |
| 199 | var next_4_chars: [4]u8 = undefined; | |
| 200 | var available_chars: usize = 0; | |
| 201 | var pad_char_count: usize = 0; | |
| 202 | while (available_chars < 4 and src_cursor < source.len) { | |
| 203 | var c = source[src_cursor]; | |
| 204 | src_cursor += 1; | |
| 205 | ||
| 206 | if (decoder.char_in_alphabet[c]) { | |
| 207 | // normal char | |
| 208 | next_4_chars[available_chars] = c; | |
| 209 | available_chars += 1; | |
| 210 | } else if (decoder_with_ignore.char_is_ignored[c]) { | |
| 211 | // we're told to skip this one | |
| 212 | continue; | |
| 213 | } else if (c == decoder.pad_char) { | |
| 214 | // the padding has begun. count the pad chars. | |
| 215 | pad_char_count += 1; | |
| 216 | while (src_cursor < source.len) { | |
| 217 | c = source[src_cursor]; | |
| 218 | src_cursor += 1; | |
| 219 | if (c == decoder.pad_char) { | |
| 220 | pad_char_count += 1; | |
| 221 | if (pad_char_count > 2) return error.InvalidCharacter; | |
| 222 | } else if (decoder_with_ignore.char_is_ignored[c]) { | |
| 223 | // we can even ignore chars during the padding | |
| 224 | continue; | |
| 225 | } else return error.InvalidCharacter; | |
| 226 | } | |
| 227 | break; | |
| 228 | } else return error.InvalidCharacter; | |
| 273 | var acc: u12 = 0; | |
| 274 | var acc_len: u4 = 0; | |
| 275 | var dest_idx: usize = 0; | |
| 276 | var leftover_idx: ?usize = null; | |
| 277 | for (source) |c, src_idx| { | |
| 278 | if (decoder_with_ignore.char_is_ignored[c]) continue; | |
| 279 | const d = decoder.char_to_index[c]; | |
| 280 | if (d == Base64Decoder.invalid_char) { | |
| 281 | if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; | |
| 282 | leftover_idx = src_idx; | |
| 283 | break; | |
| 229 | 284 | } |
| 230 | ||
| 231 | switch (available_chars) { | |
| 232 | 4 => { | |
| 233 | // common case | |
| 234 | if (dest_cursor + 3 > dest.len) return error.OutputTooSmall; | |
| 235 | assert(pad_char_count == 0); | |
| 236 | dest[dest_cursor + 0] = decoder.char_to_index[next_4_chars[0]] << 2 | decoder.char_to_index[next_4_chars[1]] >> 4; | |
| 237 | dest[dest_cursor + 1] = decoder.char_to_index[next_4_chars[1]] << 4 | decoder.char_to_index[next_4_chars[2]] >> 2; | |
| 238 | dest[dest_cursor + 2] = decoder.char_to_index[next_4_chars[2]] << 6 | decoder.char_to_index[next_4_chars[3]]; | |
| 239 | dest_cursor += 3; | |
| 240 | continue; | |
| 241 | }, | |
| 242 | 3 => { | |
| 243 | if (dest_cursor + 2 > dest.len) return error.OutputTooSmall; | |
| 244 | if (pad_char_count != 1) return error.InvalidPadding; | |
| 245 | dest[dest_cursor + 0] = decoder.char_to_index[next_4_chars[0]] << 2 | decoder.char_to_index[next_4_chars[1]] >> 4; | |
| 246 | dest[dest_cursor + 1] = decoder.char_to_index[next_4_chars[1]] << 4 | decoder.char_to_index[next_4_chars[2]] >> 2; | |
| 247 | if (decoder.char_to_index[next_4_chars[2]] << 6 != 0) return error.InvalidPadding; | |
| 248 | dest_cursor += 2; | |
| 249 | break; | |
| 250 | }, | |
| 251 | 2 => { | |
| 252 | if (dest_cursor + 1 > dest.len) return error.OutputTooSmall; | |
| 253 | if (pad_char_count != 2) return error.InvalidPadding; | |
| 254 | dest[dest_cursor + 0] = decoder.char_to_index[next_4_chars[0]] << 2 | decoder.char_to_index[next_4_chars[1]] >> 4; | |
| 255 | if (decoder.char_to_index[next_4_chars[1]] << 4 != 0) return error.InvalidPadding; | |
| 256 | dest_cursor += 1; | |
| 257 | break; | |
| 258 | }, | |
| 259 | 1 => { | |
| 260 | return error.InvalidPadding; | |
| 261 | }, | |
| 262 | 0 => { | |
| 263 | if (pad_char_count != 0) return error.InvalidPadding; | |
| 264 | break; | |
| 265 | }, | |
| 266 | else => unreachable, | |
| 285 | acc = (acc << 6) + d; | |
| 286 | acc_len += 6; | |
| 287 | if (acc_len >= 8) { | |
| 288 | if (dest_idx == dest.len) return error.NoSpaceLeft; | |
| 289 | acc_len -= 8; | |
| 290 | dest[dest_idx] = @truncate(u8, acc >> acc_len); | |
| 291 | dest_idx += 1; | |
| 267 | 292 | } |
| 268 | 293 | } |
| 269 | ||
| 270 | assert(src_cursor == source.len); | |
| 271 | ||
| 272 | return dest_cursor; | |
| 273 | } | |
| 274 | }; | |
| 275 | ||
| 276 | pub const standard_decoder_unsafe = Base64DecoderUnsafe.init(standard_alphabet_chars, standard_pad_char); | |
| 277 | ||
| 278 | pub const Base64DecoderUnsafe = struct { | |
| 279 | /// e.g. 'A' => 0. | |
| 280 | /// undefined for any value not in the 64 alphabet chars. | |
| 281 | char_to_index: [256]u8, | |
| 282 | pad_char: u8, | |
| 283 | ||
| 284 | pub fn init(alphabet_chars: []const u8, pad_char: u8) Base64DecoderUnsafe { | |
| 285 | assert(alphabet_chars.len == 64); | |
| 286 | var result = Base64DecoderUnsafe{ | |
| 287 | .char_to_index = undefined, | |
| 288 | .pad_char = pad_char, | |
| 289 | }; | |
| 290 | for (alphabet_chars) |c, i| { | |
| 291 | assert(c != pad_char); | |
| 292 | result.char_to_index[c] = @intCast(u8, i); | |
| 294 | if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { | |
| 295 | return error.InvalidPadding; | |
| 293 | 296 | } |
| 294 | return result; | |
| 295 | } | |
| 296 | ||
| 297 | /// The source buffer must be valid. | |
| 298 | pub fn calcSize(decoder: *const Base64DecoderUnsafe, source: []const u8) usize { | |
| 299 | return calcDecodedSizeExactUnsafe(source, decoder.pad_char); | |
| 300 | } | |
| 301 | ||
| 302 | /// dest.len must be what you get from ::calcDecodedSizeExactUnsafe. | |
| 303 | /// invalid characters or padding will result in undefined values. | |
| 304 | pub fn decode(decoder: *const Base64DecoderUnsafe, dest: []u8, source: []const u8) void { | |
| 305 | assert(dest.len == decoder.calcSize(source)); | |
| 306 | ||
| 307 | var src_index: usize = 0; | |
| 308 | var dest_index: usize = 0; | |
| 309 | var in_buf_len: usize = source.len; | |
| 310 | ||
| 311 | while (in_buf_len > 0 and source[in_buf_len - 1] == decoder.pad_char) { | |
| 312 | in_buf_len -= 1; | |
| 297 | const padding_len = acc_len / 2; | |
| 298 | if (leftover_idx == null) { | |
| 299 | if (decoder.pad_char != null and padding_len != 0) return error.InvalidPadding; | |
| 300 | return dest_idx; | |
| 313 | 301 | } |
| 314 | ||
| 315 | while (in_buf_len > 4) { | |
| 316 | dest[dest_index] = decoder.char_to_index[source[src_index + 0]] << 2 | decoder.char_to_index[source[src_index + 1]] >> 4; | |
| 317 | dest_index += 1; | |
| 318 | ||
| 319 | dest[dest_index] = decoder.char_to_index[source[src_index + 1]] << 4 | decoder.char_to_index[source[src_index + 2]] >> 2; | |
| 320 | dest_index += 1; | |
| 321 | ||
| 322 | dest[dest_index] = decoder.char_to_index[source[src_index + 2]] << 6 | decoder.char_to_index[source[src_index + 3]]; | |
| 323 | dest_index += 1; | |
| 324 | ||
| 325 | src_index += 4; | |
| 326 | in_buf_len -= 4; | |
| 327 | } | |
| 328 | ||
| 329 | if (in_buf_len > 1) { | |
| 330 | dest[dest_index] = decoder.char_to_index[source[src_index + 0]] << 2 | decoder.char_to_index[source[src_index + 1]] >> 4; | |
| 331 | dest_index += 1; | |
| 332 | } | |
| 333 | if (in_buf_len > 2) { | |
| 334 | dest[dest_index] = decoder.char_to_index[source[src_index + 1]] << 4 | decoder.char_to_index[source[src_index + 2]] >> 2; | |
| 335 | dest_index += 1; | |
| 336 | } | |
| 337 | if (in_buf_len > 3) { | |
| 338 | dest[dest_index] = decoder.char_to_index[source[src_index + 2]] << 6 | decoder.char_to_index[source[src_index + 3]]; | |
| 339 | dest_index += 1; | |
| 302 | var leftover = source[leftover_idx.?..]; | |
| 303 | if (decoder.pad_char) |pad_char| { | |
| 304 | var padding_chars: usize = 0; | |
| 305 | var i: usize = 0; | |
| 306 | for (leftover) |c| { | |
| 307 | if (decoder_with_ignore.char_is_ignored[c]) continue; | |
| 308 | if (c != pad_char) { | |
| 309 | return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; | |
| 310 | } | |
| 311 | padding_chars += 1; | |
| 312 | } | |
| 313 | if (padding_chars != padding_len) return error.InvalidPadding; | |
| 340 | 314 | } |
| 315 | return dest_idx; | |
| 341 | 316 | } |
| 342 | 317 | }; |
| 343 | 318 | |
| 344 | fn calcDecodedSizeExactUnsafe(source: []const u8, pad_char: u8) usize { | |
| 345 | if (source.len == 0) return 0; | |
| 346 | var result = @divExact(source.len, 4) * 3; | |
| 347 | if (source[source.len - 1] == pad_char) { | |
| 348 | result -= 1; | |
| 349 | if (source[source.len - 2] == pad_char) { | |
| 350 | result -= 1; | |
| 351 | } | |
| 352 | } | |
| 353 | return result; | |
| 354 | } | |
| 355 | ||
| 356 | 319 | test "base64" { |
| 357 | 320 | @setEvalBranchQuota(8000); |
| 358 | 321 | testBase64() catch unreachable; |
| 359 | comptime (testBase64() catch unreachable); | |
| 322 | comptime testAllApis(standard, "comptime", "Y29tcHRpbWU=") catch unreachable; | |
| 323 | } | |
| 324 | ||
| 325 | test "base64 url_safe_no_pad" { | |
| 326 | @setEvalBranchQuota(8000); | |
| 327 | testBase64UrlSafeNoPad() catch unreachable; | |
| 328 | comptime testAllApis(url_safe_no_pad, "comptime", "Y29tcHRpbWU") catch unreachable; | |
| 360 | 329 | } |
| 361 | 330 | |
| 362 | 331 | fn testBase64() !void { |
| 363 | try testAllApis("", ""); | |
| 364 | try testAllApis("f", "Zg=="); | |
| 365 | try testAllApis("fo", "Zm8="); | |
| 366 | try testAllApis("foo", "Zm9v"); | |
| 367 | try testAllApis("foob", "Zm9vYg=="); | |
| 368 | try testAllApis("fooba", "Zm9vYmE="); | |
| 369 | try testAllApis("foobar", "Zm9vYmFy"); | |
| 370 | ||
| 371 | try testDecodeIgnoreSpace("", " "); | |
| 372 | try testDecodeIgnoreSpace("f", "Z g= ="); | |
| 373 | try testDecodeIgnoreSpace("fo", " Zm8="); | |
| 374 | try testDecodeIgnoreSpace("foo", "Zm9v "); | |
| 375 | try testDecodeIgnoreSpace("foob", "Zm9vYg = = "); | |
| 376 | try testDecodeIgnoreSpace("fooba", "Zm9v YmE="); | |
| 377 | try testDecodeIgnoreSpace("foobar", " Z m 9 v Y m F y "); | |
| 332 | const codecs = standard; | |
| 333 | ||
| 334 | try testAllApis(codecs, "", ""); | |
| 335 | try testAllApis(codecs, "f", "Zg=="); | |
| 336 | try testAllApis(codecs, "fo", "Zm8="); | |
| 337 | try testAllApis(codecs, "foo", "Zm9v"); | |
| 338 | try testAllApis(codecs, "foob", "Zm9vYg=="); | |
| 339 | try testAllApis(codecs, "fooba", "Zm9vYmE="); | |
| 340 | try testAllApis(codecs, "foobar", "Zm9vYmFy"); | |
| 341 | ||
| 342 | try testDecodeIgnoreSpace(codecs, "", " "); | |
| 343 | try testDecodeIgnoreSpace(codecs, "f", "Z g= ="); | |
| 344 | try testDecodeIgnoreSpace(codecs, "fo", " Zm8="); | |
| 345 | try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); | |
| 346 | try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg = = "); | |
| 347 | try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE="); | |
| 348 | try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); | |
| 349 | ||
| 350 | // test getting some api errors | |
| 351 | try testError(codecs, "A", error.InvalidPadding); | |
| 352 | try testError(codecs, "AA", error.InvalidPadding); | |
| 353 | try testError(codecs, "AAA", error.InvalidPadding); | |
| 354 | try testError(codecs, "A..A", error.InvalidCharacter); | |
| 355 | try testError(codecs, "AA=A", error.InvalidPadding); | |
| 356 | try testError(codecs, "AA/=", error.InvalidPadding); | |
| 357 | try testError(codecs, "A/==", error.InvalidPadding); | |
| 358 | try testError(codecs, "A===", error.InvalidPadding); | |
| 359 | try testError(codecs, "====", error.InvalidPadding); | |
| 360 | ||
| 361 | try testNoSpaceLeftError(codecs, "AA=="); | |
| 362 | try testNoSpaceLeftError(codecs, "AAA="); | |
| 363 | try testNoSpaceLeftError(codecs, "AAAA"); | |
| 364 | try testNoSpaceLeftError(codecs, "AAAAAA=="); | |
| 365 | } | |
| 366 | ||
| 367 | fn testBase64UrlSafeNoPad() !void { | |
| 368 | const codecs = url_safe_no_pad; | |
| 369 | ||
| 370 | try testAllApis(codecs, "", ""); | |
| 371 | try testAllApis(codecs, "f", "Zg"); | |
| 372 | try testAllApis(codecs, "fo", "Zm8"); | |
| 373 | try testAllApis(codecs, "foo", "Zm9v"); | |
| 374 | try testAllApis(codecs, "foob", "Zm9vYg"); | |
| 375 | try testAllApis(codecs, "fooba", "Zm9vYmE"); | |
| 376 | try testAllApis(codecs, "foobar", "Zm9vYmFy"); | |
| 377 | ||
| 378 | try testDecodeIgnoreSpace(codecs, "", " "); | |
| 379 | try testDecodeIgnoreSpace(codecs, "f", "Z g "); | |
| 380 | try testDecodeIgnoreSpace(codecs, "fo", " Zm8"); | |
| 381 | try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); | |
| 382 | try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg "); | |
| 383 | try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE"); | |
| 384 | try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); | |
| 378 | 385 | |
| 379 | 386 | // test getting some api errors |
| 380 | try testError("A", error.InvalidPadding); | |
| 381 | try testError("AA", error.InvalidPadding); | |
| 382 | try testError("AAA", error.InvalidPadding); | |
| 383 | try testError("A..A", error.InvalidCharacter); | |
| 384 | try testError("AA=A", error.InvalidCharacter); | |
| 385 | try testError("AA/=", error.InvalidPadding); | |
| 386 | try testError("A/==", error.InvalidPadding); | |
| 387 | try testError("A===", error.InvalidCharacter); | |
| 388 | try testError("====", error.InvalidCharacter); | |
| 389 | ||
| 390 | try testOutputTooSmallError("AA=="); | |
| 391 | try testOutputTooSmallError("AAA="); | |
| 392 | try testOutputTooSmallError("AAAA"); | |
| 393 | try testOutputTooSmallError("AAAAAA=="); | |
| 387 | try testError(codecs, "A", error.InvalidPadding); | |
| 388 | try testError(codecs, "AAA=", error.InvalidCharacter); | |
| 389 | try testError(codecs, "A..A", error.InvalidCharacter); | |
| 390 | try testError(codecs, "AA=A", error.InvalidCharacter); | |
| 391 | try testError(codecs, "AA/=", error.InvalidCharacter); | |
| 392 | try testError(codecs, "A/==", error.InvalidCharacter); | |
| 393 | try testError(codecs, "A===", error.InvalidCharacter); | |
| 394 | try testError(codecs, "====", error.InvalidCharacter); | |
| 395 | ||
| 396 | try testNoSpaceLeftError(codecs, "AA"); | |
| 397 | try testNoSpaceLeftError(codecs, "AAA"); | |
| 398 | try testNoSpaceLeftError(codecs, "AAAA"); | |
| 399 | try testNoSpaceLeftError(codecs, "AAAAAA"); | |
| 394 | 400 | } |
| 395 | 401 | |
| 396 | fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) !void { | |
| 402 | fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void { | |
| 397 | 403 | // Base64Encoder |
| 398 | 404 | { |
| 399 | 405 | var buffer: [0x100]u8 = undefined; |
| 400 | const encoded = standard_encoder.encode(&buffer, expected_decoded); | |
| 406 | const encoded = codecs.Encoder.encode(&buffer, expected_decoded); | |
| 401 | 407 | testing.expectEqualSlices(u8, expected_encoded, encoded); |
| 402 | 408 | } |
| 403 | 409 | |
| 404 | 410 | // Base64Decoder |
| 405 | 411 | { |
| 406 | 412 | var buffer: [0x100]u8 = undefined; |
| 407 | var decoded = buffer[0..try standard_decoder.calcSize(expected_encoded)]; | |
| 408 | try standard_decoder.decode(decoded, expected_encoded); | |
| 413 | var decoded = buffer[0..try codecs.Decoder.calcSizeForSlice(expected_encoded)]; | |
| 414 | try codecs.Decoder.decode(decoded, expected_encoded); | |
| 409 | 415 | testing.expectEqualSlices(u8, expected_decoded, decoded); |
| 410 | 416 | } |
| 411 | 417 | |
| 412 | 418 | // Base64DecoderWithIgnore |
| 413 | 419 | { |
| 414 | const standard_decoder_ignore_nothing = Base64DecoderWithIgnore.init(standard_alphabet_chars, standard_pad_char, ""); | |
| 420 | const decoder_ignore_nothing = codecs.decoderWithIgnore(""); | |
| 415 | 421 | var buffer: [0x100]u8 = undefined; |
| 416 | var decoded = buffer[0..Base64DecoderWithIgnore.calcSizeUpperBound(expected_encoded.len)]; | |
| 417 | var written = try standard_decoder_ignore_nothing.decode(decoded, expected_encoded); | |
| 422 | var decoded = buffer[0..try decoder_ignore_nothing.calcSizeUpperBound(expected_encoded.len)]; | |
| 423 | var written = try decoder_ignore_nothing.decode(decoded, expected_encoded); | |
| 418 | 424 | testing.expect(written <= decoded.len); |
| 419 | 425 | testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); |
| 420 | 426 | } |
| 421 | ||
| 422 | // Base64DecoderUnsafe | |
| 423 | { | |
| 424 | var buffer: [0x100]u8 = undefined; | |
| 425 | var decoded = buffer[0..standard_decoder_unsafe.calcSize(expected_encoded)]; | |
| 426 | standard_decoder_unsafe.decode(decoded, expected_encoded); | |
| 427 | testing.expectEqualSlices(u8, expected_decoded, decoded); | |
| 428 | } | |
| 429 | 427 | } |
| 430 | 428 | |
| 431 | fn testDecodeIgnoreSpace(expected_decoded: []const u8, encoded: []const u8) !void { | |
| 432 | const standard_decoder_ignore_space = Base64DecoderWithIgnore.init(standard_alphabet_chars, standard_pad_char, " "); | |
| 429 | fn testDecodeIgnoreSpace(codecs: Codecs, expected_decoded: []const u8, encoded: []const u8) !void { | |
| 430 | const decoder_ignore_space = codecs.decoderWithIgnore(" "); | |
| 433 | 431 | var buffer: [0x100]u8 = undefined; |
| 434 | var decoded = buffer[0..Base64DecoderWithIgnore.calcSizeUpperBound(encoded.len)]; | |
| 435 | var written = try standard_decoder_ignore_space.decode(decoded, encoded); | |
| 432 | var decoded = buffer[0..try decoder_ignore_space.calcSizeUpperBound(encoded.len)]; | |
| 433 | var written = try decoder_ignore_space.decode(decoded, encoded); | |
| 436 | 434 | testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); |
| 437 | 435 | } |
| 438 | 436 | |
| 439 | fn testError(encoded: []const u8, expected_err: anyerror) !void { | |
| 440 | const standard_decoder_ignore_space = Base64DecoderWithIgnore.init(standard_alphabet_chars, standard_pad_char, " "); | |
| 437 | fn testError(codecs: Codecs, encoded: []const u8, expected_err: anyerror) !void { | |
| 438 | const decoder_ignore_space = codecs.decoderWithIgnore(" "); | |
| 441 | 439 | var buffer: [0x100]u8 = undefined; |
| 442 | if (standard_decoder.calcSize(encoded)) |decoded_size| { | |
| 440 | if (codecs.Decoder.calcSizeForSlice(encoded)) |decoded_size| { | |
| 443 | 441 | var decoded = buffer[0..decoded_size]; |
| 444 | if (standard_decoder.decode(decoded, encoded)) |_| { | |
| 442 | if (codecs.Decoder.decode(decoded, encoded)) |_| { | |
| 445 | 443 | return error.ExpectedError; |
| 446 | 444 | } else |err| if (err != expected_err) return err; |
| 447 | 445 | } else |err| if (err != expected_err) return err; |
| 448 | 446 | |
| 449 | if (standard_decoder_ignore_space.decode(buffer[0..], encoded)) |_| { | |
| 447 | if (decoder_ignore_space.decode(buffer[0..], encoded)) |_| { | |
| 450 | 448 | return error.ExpectedError; |
| 451 | 449 | } else |err| if (err != expected_err) return err; |
| 452 | 450 | } |
| 453 | 451 | |
| 454 | fn testOutputTooSmallError(encoded: []const u8) !void { | |
| 455 | const standard_decoder_ignore_space = Base64DecoderWithIgnore.init(standard_alphabet_chars, standard_pad_char, " "); | |
| 452 | fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { | |
| 453 | const decoder_ignore_space = codecs.decoderWithIgnore(" "); | |
| 456 | 454 | var buffer: [0x100]u8 = undefined; |
| 457 | var decoded = buffer[0 .. calcDecodedSizeExactUnsafe(encoded, standard_pad_char) - 1]; | |
| 458 | if (standard_decoder_ignore_space.decode(decoded, encoded)) |_| { | |
| 455 | var decoded = buffer[0 .. (try codecs.Decoder.calcSizeForSlice(encoded)) - 1]; | |
| 456 | if (decoder_ignore_space.decode(decoded, encoded)) |_| { | |
| 459 | 457 | return error.ExpectedError; |
| 460 | } else |err| if (err != error.OutputTooSmall) return err; | |
| 458 | } else |err| if (err != error.NoSpaceLeft) return err; | |
| 461 | 459 | } |
lib/std/bit_set.zig+21-7| ... | ... | @@ -176,7 +176,7 @@ pub fn IntegerBitSet(comptime size: u16) type { |
| 176 | 176 | /// The default options (.{}) will iterate indices of set bits in |
| 177 | 177 | /// ascending order. Modifications to the underlying bit set may |
| 178 | 178 | /// or may not be observed by the iterator. |
| 179 | pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options.direction) { | |
| 179 | pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { | |
| 180 | 180 | return .{ |
| 181 | 181 | .bits_remain = switch (options.kind) { |
| 182 | 182 | .set => self.mask, |
| ... | ... | @@ -185,7 +185,11 @@ pub fn IntegerBitSet(comptime size: u16) type { |
| 185 | 185 | }; |
| 186 | 186 | } |
| 187 | 187 | |
| 188 | fn Iterator(comptime direction: IteratorOptions.Direction) type { | |
| 188 | pub fn Iterator(comptime options: IteratorOptions) type { | |
| 189 | return SingleWordIterator(options.direction); | |
| 190 | } | |
| 191 | ||
| 192 | fn SingleWordIterator(comptime direction: IteratorOptions.Direction) type { | |
| 189 | 193 | return struct { |
| 190 | 194 | const IterSelf = @This(); |
| 191 | 195 | // all bits which have not yet been iterated over |
| ... | ... | @@ -425,8 +429,12 @@ pub fn ArrayBitSet(comptime MaskIntType: type, comptime size: usize) type { |
| 425 | 429 | /// The default options (.{}) will iterate indices of set bits in |
| 426 | 430 | /// ascending order. Modifications to the underlying bit set may |
| 427 | 431 | /// or may not be observed by the iterator. |
| 428 | pub fn iterator(self: *const Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { | |
| 429 | return BitSetIterator(MaskInt, options).init(&self.masks, last_item_mask); | |
| 432 | pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { | |
| 433 | return Iterator(options).init(&self.masks, last_item_mask); | |
| 434 | } | |
| 435 | ||
| 436 | pub fn Iterator(comptime options: IteratorOptions) type { | |
| 437 | return BitSetIterator(MaskInt, options); | |
| 430 | 438 | } |
| 431 | 439 | |
| 432 | 440 | fn maskBit(index: usize) MaskInt { |
| ... | ... | @@ -700,11 +708,15 @@ pub const DynamicBitSetUnmanaged = struct { |
| 700 | 708 | /// ascending order. Modifications to the underlying bit set may |
| 701 | 709 | /// or may not be observed by the iterator. Resizing the underlying |
| 702 | 710 | /// bit set invalidates the iterator. |
| 703 | pub fn iterator(self: *const Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { | |
| 711 | pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { | |
| 704 | 712 | const num_masks = numMasks(self.bit_length); |
| 705 | 713 | const padding_bits = num_masks * @bitSizeOf(MaskInt) - self.bit_length; |
| 706 | 714 | const last_item_mask = (~@as(MaskInt, 0)) >> @intCast(ShiftInt, padding_bits); |
| 707 | return BitSetIterator(MaskInt, options).init(self.masks[0..num_masks], last_item_mask); | |
| 715 | return Iterator(options).init(self.masks[0..num_masks], last_item_mask); | |
| 716 | } | |
| 717 | ||
| 718 | pub fn Iterator(comptime options: IteratorOptions) type { | |
| 719 | return BitSetIterator(MaskInt, options); | |
| 708 | 720 | } |
| 709 | 721 | |
| 710 | 722 | fn maskBit(index: usize) MaskInt { |
| ... | ... | @@ -858,9 +870,11 @@ pub const DynamicBitSet = struct { |
| 858 | 870 | /// ascending order. Modifications to the underlying bit set may |
| 859 | 871 | /// or may not be observed by the iterator. Resizing the underlying |
| 860 | 872 | /// bit set invalidates the iterator. |
| 861 | pub fn iterator(self: *Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { | |
| 873 | pub fn iterator(self: *Self, comptime options: IteratorOptions) Iterator(options) { | |
| 862 | 874 | return self.unmanaged.iterator(options); |
| 863 | 875 | } |
| 876 | ||
| 877 | pub const Iterator = DynamicBitSetUnmanaged.Iterator; | |
| 864 | 878 | }; |
| 865 | 879 | |
| 866 | 880 | /// Options for configuring an iterator over a bit set |
lib/std/build.zig+7-16| ... | ... | @@ -51,7 +51,7 @@ pub const Builder = struct { |
| 51 | 51 | default_step: *Step, |
| 52 | 52 | env_map: *BufMap, |
| 53 | 53 | top_level_steps: ArrayList(*TopLevelStep), |
| 54 | install_prefix: ?[]const u8, | |
| 54 | install_prefix: []const u8, | |
| 55 | 55 | dest_dir: ?[]const u8, |
| 56 | 56 | lib_dir: []const u8, |
| 57 | 57 | exe_dir: []const u8, |
| ... | ... | @@ -156,7 +156,7 @@ pub const Builder = struct { |
| 156 | 156 | .default_step = undefined, |
| 157 | 157 | .env_map = env_map, |
| 158 | 158 | .search_prefixes = ArrayList([]const u8).init(allocator), |
| 159 | .install_prefix = null, | |
| 159 | .install_prefix = undefined, | |
| 160 | 160 | .lib_dir = undefined, |
| 161 | 161 | .exe_dir = undefined, |
| 162 | 162 | .h_dir = undefined, |
| ... | ... | @@ -190,22 +190,13 @@ pub const Builder = struct { |
| 190 | 190 | } |
| 191 | 191 | |
| 192 | 192 | /// This function is intended to be called by std/special/build_runner.zig, not a build.zig file. |
| 193 | pub fn setInstallPrefix(self: *Builder, optional_prefix: ?[]const u8) void { | |
| 194 | self.install_prefix = optional_prefix; | |
| 195 | } | |
| 196 | ||
| 197 | /// This function is intended to be called by std/special/build_runner.zig, not a build.zig file. | |
| 198 | pub fn resolveInstallPrefix(self: *Builder) void { | |
| 193 | pub fn resolveInstallPrefix(self: *Builder, install_prefix: ?[]const u8) void { | |
| 199 | 194 | if (self.dest_dir) |dest_dir| { |
| 200 | const install_prefix = self.install_prefix orelse "/usr"; | |
| 201 | self.install_path = fs.path.join(self.allocator, &[_][]const u8{ dest_dir, install_prefix }) catch unreachable; | |
| 195 | self.install_prefix = install_prefix orelse "/usr"; | |
| 196 | self.install_path = fs.path.join(self.allocator, &[_][]const u8{ dest_dir, self.install_prefix }) catch unreachable; | |
| 202 | 197 | } else { |
| 203 | const install_prefix = self.install_prefix orelse blk: { | |
| 204 | const p = self.cache_root; | |
| 205 | self.install_prefix = p; | |
| 206 | break :blk p; | |
| 207 | }; | |
| 208 | self.install_path = install_prefix; | |
| 198 | self.install_prefix = install_prefix orelse self.cache_root; | |
| 199 | self.install_path = self.install_prefix; | |
| 209 | 200 | } |
| 210 | 201 | self.lib_dir = fs.path.join(self.allocator, &[_][]const u8{ self.install_path, "lib" }) catch unreachable; |
| 211 | 202 | self.exe_dir = fs.path.join(self.allocator, &[_][]const u8{ self.install_path, "bin" }) catch unreachable; |
lib/std/c.zig+3-3| ... | ... | @@ -295,9 +295,9 @@ pub extern "c" fn kevent( |
| 295 | 295 | ) c_int; |
| 296 | 296 | |
| 297 | 297 | pub extern "c" fn getaddrinfo( |
| 298 | noalias node: [*:0]const u8, | |
| 299 | noalias service: [*:0]const u8, | |
| 300 | noalias hints: *const addrinfo, | |
| 298 | noalias node: ?[*:0]const u8, | |
| 299 | noalias service: ?[*:0]const u8, | |
| 300 | noalias hints: ?*const addrinfo, | |
| 301 | 301 | noalias res: **addrinfo, |
| 302 | 302 | ) EAI; |
| 303 | 303 |
lib/std/c/builtins.zig+7-1| ... | ... | @@ -140,7 +140,7 @@ pub fn __builtin_object_size(ptr: ?*const c_void, ty: c_int) callconv(.Inline) u |
| 140 | 140 | // If it is not possible to determine which objects ptr points to at compile time, |
| 141 | 141 | // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 |
| 142 | 142 | // for type 2 or 3. |
| 143 | if (ty == 0 or ty == 1) return @bitCast(usize, -@as(c_long, 1)); | |
| 143 | if (ty == 0 or ty == 1) return @bitCast(usize, -@as(isize, 1)); | |
| 144 | 144 | if (ty == 2 or ty == 3) return 0; |
| 145 | 145 | unreachable; |
| 146 | 146 | } |
| ... | ... | @@ -188,3 +188,9 @@ pub fn __builtin_memcpy( |
| 188 | 188 | pub fn __builtin_expect(expr: c_long, c: c_long) callconv(.Inline) c_long { |
| 189 | 189 | return expr; |
| 190 | 190 | } |
| 191 | ||
| 192 | // __builtin_alloca_with_align is not currently implemented. | |
| 193 | // It is used in a run-translated-c test and a test-translate-c test to ensure that non-implemented | |
| 194 | // builtins are correctly demoted. If you implement __builtin_alloca_with_align, please update the | |
| 195 | // run-translated-c test and the test-translate-c test to use a different non-implemented builtin. | |
| 196 | // pub fn __builtin_alloca_with_align(size: usize, alignment: usize) callconv(.Inline) *c_void {} |
lib/std/crypto.zig+17-3| ... | ... | @@ -24,8 +24,12 @@ pub const aead = struct { |
| 24 | 24 | pub const Gimli = @import("crypto/gimli.zig").Aead; |
| 25 | 25 | |
| 26 | 26 | pub const chacha_poly = struct { |
| 27 | pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305; | |
| 28 | pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305; | |
| 27 | pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305; | |
| 28 | pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305; | |
| 29 | pub const ChaCha8Poly1305 = @import("crypto/chacha20.zig").ChaCha8Poly1305; | |
| 30 | pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChaCha20Poly1305; | |
| 31 | pub const XChaCha12Poly1305 = @import("crypto/chacha20.zig").XChaCha12Poly1305; | |
| 32 | pub const XChaCha8Poly1305 = @import("crypto/chacha20.zig").XChaCha8Poly1305; | |
| 29 | 33 | }; |
| 30 | 34 | |
| 31 | 35 | pub const isap = @import("crypto/isap.zig"); |
| ... | ... | @@ -119,8 +123,14 @@ pub const sign = struct { |
| 119 | 123 | pub const stream = struct { |
| 120 | 124 | pub const chacha = struct { |
| 121 | 125 | pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF; |
| 126 | pub const ChaCha12IETF = @import("crypto/chacha20.zig").ChaCha12IETF; | |
| 127 | pub const ChaCha8IETF = @import("crypto/chacha20.zig").ChaCha8IETF; | |
| 122 | 128 | pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce; |
| 129 | pub const ChaCha12With64BitNonce = @import("crypto/chacha20.zig").ChaCha12With64BitNonce; | |
| 130 | pub const ChaCha8With64BitNonce = @import("crypto/chacha20.zig").ChaCha8With64BitNonce; | |
| 123 | 131 | pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF; |
| 132 | pub const XChaCha12IETF = @import("crypto/chacha20.zig").XChaCha12IETF; | |
| 133 | pub const XChaCha8IETF = @import("crypto/chacha20.zig").XChaCha8IETF; | |
| 124 | 134 | }; |
| 125 | 135 | |
| 126 | 136 | pub const salsa = struct { |
| ... | ... | @@ -144,6 +154,8 @@ pub const random = &@import("crypto/tlcsprng.zig").interface; |
| 144 | 154 | |
| 145 | 155 | const std = @import("std.zig"); |
| 146 | 156 | |
| 157 | pub const Error = @import("crypto/error.zig").Error; | |
| 158 | ||
| 147 | 159 | test "crypto" { |
| 148 | 160 | const please_windows_dont_oom = std.Target.current.os.tag == .windows; |
| 149 | 161 | if (please_windows_dont_oom) return error.SkipZigTest; |
| ... | ... | @@ -151,7 +163,9 @@ test "crypto" { |
| 151 | 163 | inline for (std.meta.declarations(@This())) |decl| { |
| 152 | 164 | switch (decl.data) { |
| 153 | 165 | .Type => |t| { |
| 154 | std.testing.refAllDecls(t); | |
| 166 | if (@typeInfo(t) != .ErrorSet) { | |
| 167 | std.testing.refAllDecls(t); | |
| 168 | } | |
| 155 | 169 | }, |
| 156 | 170 | .Var => |v| { |
| 157 | 171 | _ = v; |
lib/std/crypto/25519/curve25519.zig+7-6| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | const std = @import("std"); |
| 7 | const Error = std.crypto.Error; | |
| 7 | 8 | |
| 8 | 9 | /// Group operations over Curve25519. |
| 9 | 10 | pub const Curve25519 = struct { |
| ... | ... | @@ -28,12 +29,12 @@ pub const Curve25519 = struct { |
| 28 | 29 | pub const basePoint = Curve25519{ .x = Fe.curve25519BasePoint }; |
| 29 | 30 | |
| 30 | 31 | /// Check that the encoding of a Curve25519 point is canonical. |
| 31 | pub fn rejectNonCanonical(s: [32]u8) !void { | |
| 32 | pub fn rejectNonCanonical(s: [32]u8) Error!void { | |
| 32 | 33 | return Fe.rejectNonCanonical(s, false); |
| 33 | 34 | } |
| 34 | 35 | |
| 35 | 36 | /// Reject the neutral element. |
| 36 | pub fn rejectIdentity(p: Curve25519) !void { | |
| 37 | pub fn rejectIdentity(p: Curve25519) Error!void { | |
| 37 | 38 | if (p.x.isZero()) { |
| 38 | 39 | return error.IdentityElement; |
| 39 | 40 | } |
| ... | ... | @@ -44,7 +45,7 @@ pub const Curve25519 = struct { |
| 44 | 45 | return p.dbl().dbl().dbl(); |
| 45 | 46 | } |
| 46 | 47 | |
| 47 | fn ladder(p: Curve25519, s: [32]u8, comptime bits: usize) !Curve25519 { | |
| 48 | fn ladder(p: Curve25519, s: [32]u8, comptime bits: usize) Error!Curve25519 { | |
| 48 | 49 | var x1 = p.x; |
| 49 | 50 | var x2 = Fe.one; |
| 50 | 51 | var z2 = Fe.zero; |
| ... | ... | @@ -85,7 +86,7 @@ pub const Curve25519 = struct { |
| 85 | 86 | /// way to use Curve25519 for a DH operation. |
| 86 | 87 | /// Return error.IdentityElement if the resulting point is |
| 87 | 88 | /// the identity element. |
| 88 | pub fn clampedMul(p: Curve25519, s: [32]u8) !Curve25519 { | |
| 89 | pub fn clampedMul(p: Curve25519, s: [32]u8) Error!Curve25519 { | |
| 89 | 90 | var t: [32]u8 = s; |
| 90 | 91 | scalar.clamp(&t); |
| 91 | 92 | return try ladder(p, t, 255); |
| ... | ... | @@ -95,14 +96,14 @@ pub const Curve25519 = struct { |
| 95 | 96 | /// Return error.IdentityElement if the resulting point is |
| 96 | 97 | /// the identity element or error.WeakPublicKey if the public |
| 97 | 98 | /// key is a low-order point. |
| 98 | pub fn mul(p: Curve25519, s: [32]u8) !Curve25519 { | |
| 99 | pub fn mul(p: Curve25519, s: [32]u8) Error!Curve25519 { | |
| 99 | 100 | const cofactor = [_]u8{8} ++ [_]u8{0} ** 31; |
| 100 | 101 | _ = ladder(p, cofactor, 4) catch |_| return error.WeakPublicKey; |
| 101 | 102 | return try ladder(p, s, 256); |
| 102 | 103 | } |
| 103 | 104 | |
| 104 | 105 | /// Compute the Curve25519 equivalent to an Edwards25519 point. |
| 105 | pub fn fromEdwards25519(p: std.crypto.ecc.Edwards25519) !Curve25519 { | |
| 106 | pub fn fromEdwards25519(p: std.crypto.ecc.Edwards25519) Error!Curve25519 { | |
| 106 | 107 | try p.clearCofactor().rejectIdentity(); |
| 107 | 108 | const one = std.crypto.ecc.Edwards25519.Fe.one; |
| 108 | 109 | const x = one.add(p.y).mul(one.sub(p.y).invert()); // xMont=(1+yEd)/(1-yEd) |
lib/std/crypto/25519/ed25519.zig+12-11| ... | ... | @@ -8,7 +8,8 @@ const crypto = std.crypto; |
| 8 | 8 | const debug = std.debug; |
| 9 | 9 | const fmt = std.fmt; |
| 10 | 10 | const mem = std.mem; |
| 11 | const Sha512 = std.crypto.hash.sha2.Sha512; | |
| 11 | const Sha512 = crypto.hash.sha2.Sha512; | |
| 12 | const Error = crypto.Error; | |
| 12 | 13 | |
| 13 | 14 | /// Ed25519 (EdDSA) signatures. |
| 14 | 15 | pub const Ed25519 = struct { |
| ... | ... | @@ -40,7 +41,7 @@ pub const Ed25519 = struct { |
| 40 | 41 | /// |
| 41 | 42 | /// For this reason, an EdDSA secret key is commonly called a seed, |
| 42 | 43 | /// from which the actual secret is derived. |
| 43 | pub fn create(seed: ?[seed_length]u8) !KeyPair { | |
| 44 | pub fn create(seed: ?[seed_length]u8) Error!KeyPair { | |
| 44 | 45 | const ss = seed orelse ss: { |
| 45 | 46 | var random_seed: [seed_length]u8 = undefined; |
| 46 | 47 | crypto.random.bytes(&random_seed); |
| ... | ... | @@ -71,7 +72,7 @@ pub const Ed25519 = struct { |
| 71 | 72 | /// Sign a message using a key pair, and optional random noise. |
| 72 | 73 | /// Having noise creates non-standard, non-deterministic signatures, |
| 73 | 74 | /// but has been proven to increase resilience against fault attacks. |
| 74 | pub fn sign(msg: []const u8, key_pair: KeyPair, noise: ?[noise_length]u8) ![signature_length]u8 { | |
| 75 | pub fn sign(msg: []const u8, key_pair: KeyPair, noise: ?[noise_length]u8) Error![signature_length]u8 { | |
| 75 | 76 | const seed = key_pair.secret_key[0..seed_length]; |
| 76 | 77 | const public_key = key_pair.secret_key[seed_length..]; |
| 77 | 78 | if (!mem.eql(u8, public_key, &key_pair.public_key)) { |
| ... | ... | @@ -111,8 +112,8 @@ pub const Ed25519 = struct { |
| 111 | 112 | } |
| 112 | 113 | |
| 113 | 114 | /// Verify an Ed25519 signature given a message and a public key. |
| 114 | /// Returns error.InvalidSignature is the signature verification failed. | |
| 115 | pub fn verify(sig: [signature_length]u8, msg: []const u8, public_key: [public_length]u8) !void { | |
| 115 | /// Returns error.SignatureVerificationFailed is the signature verification failed. | |
| 116 | pub fn verify(sig: [signature_length]u8, msg: []const u8, public_key: [public_length]u8) Error!void { | |
| 116 | 117 | const r = sig[0..32]; |
| 117 | 118 | const s = sig[32..64]; |
| 118 | 119 | try Curve.scalar.rejectNonCanonical(s.*); |
| ... | ... | @@ -133,7 +134,7 @@ pub const Ed25519 = struct { |
| 133 | 134 | const ah = try a.neg().mulPublic(hram); |
| 134 | 135 | const sb_ah = (try Curve.basePoint.mulPublic(s.*)).add(ah); |
| 135 | 136 | if (expected_r.sub(sb_ah).clearCofactor().rejectIdentity()) |_| { |
| 136 | return error.InvalidSignature; | |
| 137 | return error.SignatureVerificationFailed; | |
| 137 | 138 | } else |_| {} |
| 138 | 139 | } |
| 139 | 140 | |
| ... | ... | @@ -145,7 +146,7 @@ pub const Ed25519 = struct { |
| 145 | 146 | }; |
| 146 | 147 | |
| 147 | 148 | /// Verify several signatures in a single operation, much faster than verifying signatures one-by-one |
| 148 | pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) !void { | |
| 149 | pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) Error!void { | |
| 149 | 150 | var r_batch: [count][32]u8 = undefined; |
| 150 | 151 | var s_batch: [count][32]u8 = undefined; |
| 151 | 152 | var a_batch: [count]Curve = undefined; |
| ... | ... | @@ -200,7 +201,7 @@ pub const Ed25519 = struct { |
| 200 | 201 | |
| 201 | 202 | const zsb = try Curve.basePoint.mulPublic(zs_sum); |
| 202 | 203 | if (zr.add(zah).sub(zsb).rejectIdentity()) |_| { |
| 203 | return error.InvalidSignature; | |
| 204 | return error.SignatureVerificationFailed; | |
| 204 | 205 | } else |_| {} |
| 205 | 206 | } |
| 206 | 207 | }; |
| ... | ... | @@ -223,7 +224,7 @@ test "ed25519 signature" { |
| 223 | 224 | var buf: [128]u8 = undefined; |
| 224 | 225 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&sig)}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808"); |
| 225 | 226 | try Ed25519.verify(sig, "test", key_pair.public_key); |
| 226 | std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", key_pair.public_key)); | |
| 227 | std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verify(sig, "TEST", key_pair.public_key)); | |
| 227 | 228 | } |
| 228 | 229 | |
| 229 | 230 | test "ed25519 batch verification" { |
| ... | ... | @@ -251,7 +252,7 @@ test "ed25519 batch verification" { |
| 251 | 252 | try Ed25519.verifyBatch(2, signature_batch); |
| 252 | 253 | |
| 253 | 254 | signature_batch[1].sig = sig1; |
| 254 | std.testing.expectError(error.InvalidSignature, Ed25519.verifyBatch(signature_batch.len, signature_batch)); | |
| 255 | std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(signature_batch.len, signature_batch)); | |
| 255 | 256 | } |
| 256 | 257 | } |
| 257 | 258 | |
| ... | ... | @@ -316,7 +317,7 @@ test "ed25519 test vectors" { |
| 316 | 317 | .msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41", |
| 317 | 318 | .public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43", |
| 318 | 319 | .sig_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f", |
| 319 | .expected = error.InvalidSignature, // 8 - non-canonical R | |
| 320 | .expected = error.SignatureVerificationFailed, // 8 - non-canonical R | |
| 320 | 321 | }, |
| 321 | 322 | Vec{ |
| 322 | 323 | .msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41", |
lib/std/crypto/25519/edwards25519.zig+11-10| ... | ... | @@ -7,6 +7,7 @@ const std = @import("std"); |
| 7 | 7 | const debug = std.debug; |
| 8 | 8 | const fmt = std.fmt; |
| 9 | 9 | const mem = std.mem; |
| 10 | const Error = std.crypto.Error; | |
| 10 | 11 | |
| 11 | 12 | /// Group operations over Edwards25519. |
| 12 | 13 | pub const Edwards25519 = struct { |
| ... | ... | @@ -25,7 +26,7 @@ pub const Edwards25519 = struct { |
| 25 | 26 | is_base: bool = false, |
| 26 | 27 | |
| 27 | 28 | /// Decode an Edwards25519 point from its compressed (Y+sign) coordinates. |
| 28 | pub fn fromBytes(s: [encoded_length]u8) !Edwards25519 { | |
| 29 | pub fn fromBytes(s: [encoded_length]u8) Error!Edwards25519 { | |
| 29 | 30 | const z = Fe.one; |
| 30 | 31 | const y = Fe.fromBytes(s); |
| 31 | 32 | var u = y.sq(); |
| ... | ... | @@ -55,7 +56,7 @@ pub const Edwards25519 = struct { |
| 55 | 56 | } |
| 56 | 57 | |
| 57 | 58 | /// Check that the encoding of a point is canonical. |
| 58 | pub fn rejectNonCanonical(s: [32]u8) !void { | |
| 59 | pub fn rejectNonCanonical(s: [32]u8) Error!void { | |
| 59 | 60 | return Fe.rejectNonCanonical(s, true); |
| 60 | 61 | } |
| 61 | 62 | |
| ... | ... | @@ -80,7 +81,7 @@ pub const Edwards25519 = struct { |
| 80 | 81 | const identityElement = Edwards25519{ .x = Fe.zero, .y = Fe.one, .z = Fe.one, .t = Fe.zero }; |
| 81 | 82 | |
| 82 | 83 | /// Reject the neutral element. |
| 83 | pub fn rejectIdentity(p: Edwards25519) !void { | |
| 84 | pub fn rejectIdentity(p: Edwards25519) Error!void { | |
| 84 | 85 | if (p.x.isZero()) { |
| 85 | 86 | return error.IdentityElement; |
| 86 | 87 | } |
| ... | ... | @@ -176,7 +177,7 @@ pub const Edwards25519 = struct { |
| 176 | 177 | // Based on real-world benchmarks, we only use this for multi-scalar multiplication. |
| 177 | 178 | // NAF could be useful to half the size of precomputation tables, but we intentionally |
| 178 | 179 | // avoid these to keep the standard library lightweight. |
| 179 | fn pcMul(pc: [9]Edwards25519, s: [32]u8, comptime vartime: bool) !Edwards25519 { | |
| 180 | fn pcMul(pc: [9]Edwards25519, s: [32]u8, comptime vartime: bool) Error!Edwards25519 { | |
| 180 | 181 | std.debug.assert(vartime); |
| 181 | 182 | const e = nonAdjacentForm(s); |
| 182 | 183 | var q = Edwards25519.identityElement; |
| ... | ... | @@ -196,7 +197,7 @@ pub const Edwards25519 = struct { |
| 196 | 197 | } |
| 197 | 198 | |
| 198 | 199 | // Scalar multiplication with a 4-bit window and the first 15 multiples. |
| 199 | fn pcMul16(pc: [16]Edwards25519, s: [32]u8, comptime vartime: bool) !Edwards25519 { | |
| 200 | fn pcMul16(pc: [16]Edwards25519, s: [32]u8, comptime vartime: bool) Error!Edwards25519 { | |
| 200 | 201 | var q = Edwards25519.identityElement; |
| 201 | 202 | var pos: usize = 252; |
| 202 | 203 | while (true) : (pos -= 4) { |
| ... | ... | @@ -234,7 +235,7 @@ pub const Edwards25519 = struct { |
| 234 | 235 | /// Multiply an Edwards25519 point by a scalar without clamping it. |
| 235 | 236 | /// Return error.WeakPublicKey if the resulting point is |
| 236 | 237 | /// the identity element. |
| 237 | pub fn mul(p: Edwards25519, s: [32]u8) !Edwards25519 { | |
| 238 | pub fn mul(p: Edwards25519, s: [32]u8) Error!Edwards25519 { | |
| 238 | 239 | const pc = if (p.is_base) basePointPc else pc: { |
| 239 | 240 | const xpc = precompute(p, 15); |
| 240 | 241 | xpc[4].rejectIdentity() catch |_| return error.WeakPublicKey; |
| ... | ... | @@ -245,7 +246,7 @@ pub const Edwards25519 = struct { |
| 245 | 246 | |
| 246 | 247 | /// Multiply an Edwards25519 point by a *PUBLIC* scalar *IN VARIABLE TIME* |
| 247 | 248 | /// This can be used for signature verification. |
| 248 | pub fn mulPublic(p: Edwards25519, s: [32]u8) !Edwards25519 { | |
| 249 | pub fn mulPublic(p: Edwards25519, s: [32]u8) Error!Edwards25519 { | |
| 249 | 250 | if (p.is_base) { |
| 250 | 251 | return pcMul16(basePointPc, s, true); |
| 251 | 252 | } else { |
| ... | ... | @@ -257,7 +258,7 @@ pub const Edwards25519 = struct { |
| 257 | 258 | |
| 258 | 259 | /// Multiscalar multiplication *IN VARIABLE TIME* for public data |
| 259 | 260 | /// Computes ps0*ss0 + ps1*ss1 + ps2*ss2... faster than doing many of these operations individually |
| 260 | pub fn mulMulti(comptime count: usize, ps: [count]Edwards25519, ss: [count][32]u8) !Edwards25519 { | |
| 261 | pub fn mulMulti(comptime count: usize, ps: [count]Edwards25519, ss: [count][32]u8) Error!Edwards25519 { | |
| 261 | 262 | var pcs: [count][9]Edwards25519 = undefined; |
| 262 | 263 | for (ps) |p, i| { |
| 263 | 264 | if (p.is_base) { |
| ... | ... | @@ -296,14 +297,14 @@ pub const Edwards25519 = struct { |
| 296 | 297 | /// This is strongly recommended for DH operations. |
| 297 | 298 | /// Return error.WeakPublicKey if the resulting point is |
| 298 | 299 | /// the identity element. |
| 299 | pub fn clampedMul(p: Edwards25519, s: [32]u8) !Edwards25519 { | |
| 300 | pub fn clampedMul(p: Edwards25519, s: [32]u8) Error!Edwards25519 { | |
| 300 | 301 | var t: [32]u8 = s; |
| 301 | 302 | scalar.clamp(&t); |
| 302 | 303 | return mul(p, t); |
| 303 | 304 | } |
| 304 | 305 | |
| 305 | 306 | // montgomery -- recover y = sqrt(x^3 + A*x^2 + x) |
| 306 | fn xmontToYmont(x: Fe) !Fe { | |
| 307 | fn xmontToYmont(x: Fe) Error!Fe { | |
| 307 | 308 | var x2 = x.sq(); |
| 308 | 309 | const x3 = x.mul(x2); |
| 309 | 310 | x2 = x2.mul32(Fe.edwards25519a_32); |
lib/std/crypto/25519/field.zig+3-2| ... | ... | @@ -6,6 +6,7 @@ |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const readIntLittle = std.mem.readIntLittle; |
| 8 | 8 | const writeIntLittle = std.mem.writeIntLittle; |
| 9 | const Error = std.crypto.Error; | |
| 9 | 10 | |
| 10 | 11 | pub const Fe = struct { |
| 11 | 12 | limbs: [5]u64, |
| ... | ... | @@ -112,7 +113,7 @@ pub const Fe = struct { |
| 112 | 113 | } |
| 113 | 114 | |
| 114 | 115 | /// Reject non-canonical encodings of an element, possibly ignoring the top bit |
| 115 | pub fn rejectNonCanonical(s: [32]u8, comptime ignore_extra_bit: bool) !void { | |
| 116 | pub fn rejectNonCanonical(s: [32]u8, comptime ignore_extra_bit: bool) Error!void { | |
| 116 | 117 | var c: u16 = (s[31] & 0x7f) ^ 0x7f; |
| 117 | 118 | comptime var i = 30; |
| 118 | 119 | inline while (i > 0) : (i -= 1) { |
| ... | ... | @@ -412,7 +413,7 @@ pub const Fe = struct { |
| 412 | 413 | } |
| 413 | 414 | |
| 414 | 415 | /// Compute the square root of `x2`, returning `error.NotSquare` if `x2` was not a square |
| 415 | pub fn sqrt(x2: Fe) !Fe { | |
| 416 | pub fn sqrt(x2: Fe) Error!Fe { | |
| 416 | 417 | var x2_copy = x2; |
| 417 | 418 | const x = x2.uncheckedSqrt(); |
| 418 | 419 | const check = x.sq().sub(x2_copy); |
lib/std/crypto/25519/ristretto255.zig+5-4| ... | ... | @@ -5,6 +5,7 @@ |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const fmt = std.fmt; |
| 8 | const Error = std.crypto.Error; | |
| 8 | 9 | |
| 9 | 10 | /// Group operations over Edwards25519. |
| 10 | 11 | pub const Ristretto255 = struct { |
| ... | ... | @@ -34,7 +35,7 @@ pub const Ristretto255 = struct { |
| 34 | 35 | return .{ .ratio_is_square = @boolToInt(has_m_root) | @boolToInt(has_p_root), .root = x.abs() }; |
| 35 | 36 | } |
| 36 | 37 | |
| 37 | fn rejectNonCanonical(s: [encoded_length]u8) !void { | |
| 38 | fn rejectNonCanonical(s: [encoded_length]u8) Error!void { | |
| 38 | 39 | if ((s[0] & 1) != 0) { |
| 39 | 40 | return error.NonCanonical; |
| 40 | 41 | } |
| ... | ... | @@ -42,7 +43,7 @@ pub const Ristretto255 = struct { |
| 42 | 43 | } |
| 43 | 44 | |
| 44 | 45 | /// Reject the neutral element. |
| 45 | pub fn rejectIdentity(p: Ristretto255) callconv(.Inline) !void { | |
| 46 | pub fn rejectIdentity(p: Ristretto255) callconv(.Inline) Error!void { | |
| 46 | 47 | return p.p.rejectIdentity(); |
| 47 | 48 | } |
| 48 | 49 | |
| ... | ... | @@ -50,7 +51,7 @@ pub const Ristretto255 = struct { |
| 50 | 51 | pub const basePoint = Ristretto255{ .p = Curve.basePoint }; |
| 51 | 52 | |
| 52 | 53 | /// Decode a Ristretto255 representative. |
| 53 | pub fn fromBytes(s: [encoded_length]u8) !Ristretto255 { | |
| 54 | pub fn fromBytes(s: [encoded_length]u8) Error!Ristretto255 { | |
| 54 | 55 | try rejectNonCanonical(s); |
| 55 | 56 | const s_ = Fe.fromBytes(s); |
| 56 | 57 | const ss = s_.sq(); // s^2 |
| ... | ... | @@ -153,7 +154,7 @@ pub const Ristretto255 = struct { |
| 153 | 154 | /// Multiply a Ristretto255 element with a scalar. |
| 154 | 155 | /// Return error.WeakPublicKey if the resulting element is |
| 155 | 156 | /// the identity element. |
| 156 | pub fn mul(p: Ristretto255, s: [encoded_length]u8) callconv(.Inline) !Ristretto255 { | |
| 157 | pub fn mul(p: Ristretto255, s: [encoded_length]u8) callconv(.Inline) Error!Ristretto255 { | |
| 157 | 158 | return Ristretto255{ .p = try p.p.mul(s) }; |
| 158 | 159 | } |
| 159 | 160 |
lib/std/crypto/25519/scalar.zig+2-1| ... | ... | @@ -5,6 +5,7 @@ |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const mem = std.mem; |
| 8 | const Error = std.crypto.Error; | |
| 8 | 9 | |
| 9 | 10 | /// 2^252 + 27742317777372353535851937790883648493 |
| 10 | 11 | pub const field_size = [32]u8{ |
| ... | ... | @@ -18,7 +19,7 @@ pub const CompressedScalar = [32]u8; |
| 18 | 19 | pub const zero = [_]u8{0} ** 32; |
| 19 | 20 | |
| 20 | 21 | /// Reject a scalar whose encoding is not canonical. |
| 21 | pub fn rejectNonCanonical(s: [32]u8) !void { | |
| 22 | pub fn rejectNonCanonical(s: [32]u8) Error!void { | |
| 22 | 23 | var c: u8 = 0; |
| 23 | 24 | var n: u8 = 1; |
| 24 | 25 | var i: usize = 31; |
lib/std/crypto/25519/x25519.zig+6-5| ... | ... | @@ -9,6 +9,7 @@ const mem = std.mem; |
| 9 | 9 | const fmt = std.fmt; |
| 10 | 10 | |
| 11 | 11 | const Sha512 = crypto.hash.sha2.Sha512; |
| 12 | const Error = crypto.Error; | |
| 12 | 13 | |
| 13 | 14 | /// X25519 DH function. |
| 14 | 15 | pub const X25519 = struct { |
| ... | ... | @@ -31,7 +32,7 @@ pub const X25519 = struct { |
| 31 | 32 | secret_key: [secret_length]u8, |
| 32 | 33 | |
| 33 | 34 | /// Create a new key pair using an optional seed. |
| 34 | pub fn create(seed: ?[seed_length]u8) !KeyPair { | |
| 35 | pub fn create(seed: ?[seed_length]u8) Error!KeyPair { | |
| 35 | 36 | const sk = seed orelse sk: { |
| 36 | 37 | var random_seed: [seed_length]u8 = undefined; |
| 37 | 38 | crypto.random.bytes(&random_seed); |
| ... | ... | @@ -44,7 +45,7 @@ pub const X25519 = struct { |
| 44 | 45 | } |
| 45 | 46 | |
| 46 | 47 | /// Create a key pair from an Ed25519 key pair |
| 47 | pub fn fromEd25519(ed25519_key_pair: crypto.sign.Ed25519.KeyPair) !KeyPair { | |
| 48 | pub fn fromEd25519(ed25519_key_pair: crypto.sign.Ed25519.KeyPair) Error!KeyPair { | |
| 48 | 49 | const seed = ed25519_key_pair.secret_key[0..32]; |
| 49 | 50 | var az: [Sha512.digest_length]u8 = undefined; |
| 50 | 51 | Sha512.hash(seed, &az, .{}); |
| ... | ... | @@ -59,13 +60,13 @@ pub const X25519 = struct { |
| 59 | 60 | }; |
| 60 | 61 | |
| 61 | 62 | /// Compute the public key for a given private key. |
| 62 | pub fn recoverPublicKey(secret_key: [secret_length]u8) ![public_length]u8 { | |
| 63 | pub fn recoverPublicKey(secret_key: [secret_length]u8) Error![public_length]u8 { | |
| 63 | 64 | const q = try Curve.basePoint.clampedMul(secret_key); |
| 64 | 65 | return q.toBytes(); |
| 65 | 66 | } |
| 66 | 67 | |
| 67 | 68 | /// Compute the X25519 equivalent to an Ed25519 public eky. |
| 68 | pub fn publicKeyFromEd25519(ed25519_public_key: [crypto.sign.Ed25519.public_length]u8) ![public_length]u8 { | |
| 69 | pub fn publicKeyFromEd25519(ed25519_public_key: [crypto.sign.Ed25519.public_length]u8) Error![public_length]u8 { | |
| 69 | 70 | const pk_ed = try crypto.ecc.Edwards25519.fromBytes(ed25519_public_key); |
| 70 | 71 | const pk = try Curve.fromEdwards25519(pk_ed); |
| 71 | 72 | return pk.toBytes(); |
| ... | ... | @@ -74,7 +75,7 @@ pub const X25519 = struct { |
| 74 | 75 | /// Compute the scalar product of a public key and a secret scalar. |
| 75 | 76 | /// Note that the output should not be used as a shared secret without |
| 76 | 77 | /// hashing it first. |
| 77 | pub fn scalarmult(secret_key: [secret_length]u8, public_key: [public_length]u8) ![shared_length]u8 { | |
| 78 | pub fn scalarmult(secret_key: [secret_length]u8, public_key: [public_length]u8) Error![shared_length]u8 { | |
| 78 | 79 | const q = try Curve.fromBytes(public_key).clampedMul(secret_key); |
| 79 | 80 | return q.toBytes(); |
| 80 | 81 | } |
lib/std/crypto/aegis.zig+3-2| ... | ... | @@ -8,6 +8,7 @@ const std = @import("std"); |
| 8 | 8 | const mem = std.mem; |
| 9 | 9 | const assert = std.debug.assert; |
| 10 | 10 | const AesBlock = std.crypto.core.aes.Block; |
| 11 | const Error = std.crypto.Error; | |
| 11 | 12 | |
| 12 | 13 | const State128L = struct { |
| 13 | 14 | blocks: [8]AesBlock, |
| ... | ... | @@ -136,7 +137,7 @@ pub const Aegis128L = struct { |
| 136 | 137 | /// ad: Associated Data |
| 137 | 138 | /// npub: public nonce |
| 138 | 139 | /// k: private key |
| 139 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { | |
| 140 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) Error!void { | |
| 140 | 141 | assert(c.len == m.len); |
| 141 | 142 | var state = State128L.init(key, npub); |
| 142 | 143 | var src: [32]u8 align(16) = undefined; |
| ... | ... | @@ -298,7 +299,7 @@ pub const Aegis256 = struct { |
| 298 | 299 | /// ad: Associated Data |
| 299 | 300 | /// npub: public nonce |
| 300 | 301 | /// k: private key |
| 301 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { | |
| 302 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) Error!void { | |
| 302 | 303 | assert(c.len == m.len); |
| 303 | 304 | var state = State256.init(key, npub); |
| 304 | 305 | var src: [16]u8 align(16) = undefined; |
lib/std/crypto/aes_gcm.zig+2-1| ... | ... | @@ -12,6 +12,7 @@ const debug = std.debug; |
| 12 | 12 | const Ghash = std.crypto.onetimeauth.Ghash; |
| 13 | 13 | const mem = std.mem; |
| 14 | 14 | const modes = crypto.core.modes; |
| 15 | const Error = crypto.Error; | |
| 15 | 16 | |
| 16 | 17 | pub const Aes128Gcm = AesGcm(crypto.core.aes.Aes128); |
| 17 | 18 | pub const Aes256Gcm = AesGcm(crypto.core.aes.Aes256); |
| ... | ... | @@ -59,7 +60,7 @@ fn AesGcm(comptime Aes: anytype) type { |
| 59 | 60 | } |
| 60 | 61 | } |
| 61 | 62 | |
| 62 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { | |
| 63 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) Error!void { | |
| 63 | 64 | assert(c.len == m.len); |
| 64 | 65 | |
| 65 | 66 | const aes = Aes.initEnc(key); |
lib/std/crypto/aes_ocb.zig+2-1| ... | ... | @@ -10,6 +10,7 @@ const aes = crypto.core.aes; |
| 10 | 10 | const assert = std.debug.assert; |
| 11 | 11 | const math = std.math; |
| 12 | 12 | const mem = std.mem; |
| 13 | const Error = crypto.Error; | |
| 13 | 14 | |
| 14 | 15 | pub const Aes128Ocb = AesOcb(aes.Aes128); |
| 15 | 16 | pub const Aes256Ocb = AesOcb(aes.Aes256); |
| ... | ... | @@ -178,7 +179,7 @@ fn AesOcb(comptime Aes: anytype) type { |
| 178 | 179 | /// ad: Associated Data |
| 179 | 180 | /// npub: public nonce |
| 180 | 181 | /// k: secret key |
| 181 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { | |
| 182 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) Error!void { | |
| 182 | 183 | assert(c.len == m.len); |
| 183 | 184 | |
| 184 | 185 | const aes_enc_ctx = Aes.initEnc(key); |
lib/std/crypto/bcrypt.zig+8-14| ... | ... | @@ -11,7 +11,8 @@ const math = std.math; |
| 11 | 11 | const mem = std.mem; |
| 12 | 12 | const debug = std.debug; |
| 13 | 13 | const testing = std.testing; |
| 14 | const utils = std.crypto.utils; | |
| 14 | const utils = crypto.utils; | |
| 15 | const Error = crypto.Error; | |
| 15 | 16 | |
| 16 | 17 | const salt_length: usize = 16; |
| 17 | 18 | const salt_str_length: usize = 22; |
| ... | ... | @@ -21,13 +22,6 @@ const ct_length: usize = 24; |
| 21 | 22 | /// Length (in bytes) of a password hash |
| 22 | 23 | pub const hash_length: usize = 60; |
| 23 | 24 | |
| 24 | pub const BcryptError = error{ | |
| 25 | /// The hashed password cannot be decoded. | |
| 26 | InvalidEncoding, | |
| 27 | /// The hash is not valid for the given password. | |
| 28 | InvalidPassword, | |
| 29 | }; | |
| 30 | ||
| 31 | 25 | const State = struct { |
| 32 | 26 | sboxes: [4][256]u32 = [4][256]u32{ |
| 33 | 27 | .{ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a }, |
| ... | ... | @@ -185,7 +179,7 @@ const Codec = struct { |
| 185 | 179 | debug.assert(j == b64.len); |
| 186 | 180 | } |
| 187 | 181 | |
| 188 | fn decode(bin: []u8, b64: []const u8) BcryptError!void { | |
| 182 | fn decode(bin: []u8, b64: []const u8) Error!void { | |
| 189 | 183 | var i: usize = 0; |
| 190 | 184 | var j: usize = 0; |
| 191 | 185 | while (j < bin.len) { |
| ... | ... | @@ -210,7 +204,7 @@ const Codec = struct { |
| 210 | 204 | } |
| 211 | 205 | }; |
| 212 | 206 | |
| 213 | fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8) BcryptError![hash_length]u8 { | |
| 207 | fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8) Error![hash_length]u8 { | |
| 214 | 208 | var state = State{}; |
| 215 | 209 | var password_buf: [73]u8 = undefined; |
| 216 | 210 | const trimmed_len = math.min(password.len, password_buf.len - 1); |
| ... | ... | @@ -258,14 +252,14 @@ fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8) |
| 258 | 252 | /// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes. |
| 259 | 253 | /// If this is an issue for your application, hash the password first using a function such as SHA-512, |
| 260 | 254 | /// and then use the resulting hash as the password parameter for bcrypt. |
| 261 | pub fn strHash(password: []const u8, rounds_log: u6) ![hash_length]u8 { | |
| 255 | pub fn strHash(password: []const u8, rounds_log: u6) Error![hash_length]u8 { | |
| 262 | 256 | var salt: [salt_length]u8 = undefined; |
| 263 | 257 | crypto.random.bytes(&salt); |
| 264 | 258 | return strHashInternal(password, rounds_log, salt); |
| 265 | 259 | } |
| 266 | 260 | |
| 267 | 261 | /// Verify that a previously computed hash is valid for a given password. |
| 268 | pub fn strVerify(h: [hash_length]u8, password: []const u8) BcryptError!void { | |
| 262 | pub fn strVerify(h: [hash_length]u8, password: []const u8) Error!void { | |
| 269 | 263 | if (!mem.eql(u8, "$2", h[0..2])) return error.InvalidEncoding; |
| 270 | 264 | if (h[3] != '$' or h[6] != '$') return error.InvalidEncoding; |
| 271 | 265 | const rounds_log_str = h[4..][0..2]; |
| ... | ... | @@ -275,7 +269,7 @@ pub fn strVerify(h: [hash_length]u8, password: []const u8) BcryptError!void { |
| 275 | 269 | const rounds_log = fmt.parseInt(u6, rounds_log_str[0..], 10) catch return error.InvalidEncoding; |
| 276 | 270 | const wanted_s = try strHashInternal(password, rounds_log, salt); |
| 277 | 271 | if (!mem.eql(u8, wanted_s[0..], h[0..])) { |
| 278 | return error.InvalidPassword; | |
| 272 | return error.PasswordVerificationFailed; | |
| 279 | 273 | } |
| 280 | 274 | } |
| 281 | 275 | |
| ... | ... | @@ -292,7 +286,7 @@ test "bcrypt codec" { |
| 292 | 286 | test "bcrypt" { |
| 293 | 287 | const s = try strHash("password", 5); |
| 294 | 288 | try strVerify(s, "password"); |
| 295 | testing.expectError(error.InvalidPassword, strVerify(s, "invalid password")); | |
| 289 | testing.expectError(error.PasswordVerificationFailed, strVerify(s, "invalid password")); | |
| 296 | 290 | |
| 297 | 291 | const long_s = try strHash("password" ** 100, 5); |
| 298 | 292 | try strVerify(long_s, "password" ** 100); |
lib/std/crypto/benchmark.zig+1| ... | ... | @@ -202,6 +202,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime |
| 202 | 202 | const aeads = [_]Crypto{ |
| 203 | 203 | Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" }, |
| 204 | 204 | Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" }, |
| 205 | Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" }, | |
| 205 | 206 | Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" }, |
| 206 | 207 | Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" }, |
| 207 | 208 | Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" }, |
lib/std/crypto/chacha20.zig+599-571| ... | ... | @@ -13,287 +13,359 @@ const testing = std.testing; |
| 13 | 13 | const maxInt = math.maxInt; |
| 14 | 14 | const Vector = std.meta.Vector; |
| 15 | 15 | const Poly1305 = std.crypto.onetimeauth.Poly1305; |
| 16 | const Error = std.crypto.Error; | |
| 17 | ||
| 18 | /// IETF-variant of the ChaCha20 stream cipher, as designed for TLS. | |
| 19 | pub const ChaCha20IETF = ChaChaIETF(20); | |
| 20 | ||
| 21 | /// IETF-variant of the ChaCha20 stream cipher, reduced to 12 rounds. | |
| 22 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 23 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 24 | pub const ChaCha12IETF = ChaChaIETF(12); | |
| 25 | ||
| 26 | /// IETF-variant of the ChaCha20 stream cipher, reduced to 8 rounds. | |
| 27 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 28 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 29 | pub const ChaCha8IETF = ChaChaIETF(8); | |
| 30 | ||
| 31 | /// Original ChaCha20 stream cipher. | |
| 32 | pub const ChaCha20With64BitNonce = ChaChaWith64BitNonce(20); | |
| 33 | ||
| 34 | /// Original ChaCha20 stream cipher, reduced to 12 rounds. | |
| 35 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 36 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 37 | pub const ChaCha12With64BitNonce = ChaChaWith64BitNonce(12); | |
| 38 | ||
| 39 | /// Original ChaCha20 stream cipher, reduced to 8 rounds. | |
| 40 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 41 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 42 | pub const ChaCha8With64BitNonce = ChaChaWith64BitNonce(8); | |
| 43 | ||
| 44 | /// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher | |
| 45 | pub const XChaCha20IETF = XChaChaIETF(20); | |
| 46 | ||
| 47 | /// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 12 rounds | |
| 48 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 49 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 50 | pub const XChaCha12IETF = XChaChaIETF(12); | |
| 51 | ||
| 52 | /// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 8 rounds | |
| 53 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 54 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 55 | pub const XChaCha8IETF = XChaChaIETF(8); | |
| 56 | ||
| 57 | /// ChaCha20-Poly1305 authenticated cipher, as designed for TLS | |
| 58 | pub const ChaCha20Poly1305 = ChaChaPoly1305(20); | |
| 59 | ||
| 60 | /// ChaCha20-Poly1305 authenticated cipher, reduced to 12 rounds | |
| 61 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 62 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 63 | pub const ChaCha12Poly1305 = ChaChaPoly1305(12); | |
| 64 | ||
| 65 | /// ChaCha20-Poly1305 authenticated cipher, reduced to 8 rounds | |
| 66 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 67 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 68 | pub const ChaCha8Poly1305 = ChaChaPoly1305(8); | |
| 69 | ||
| 70 | /// XChaCha20-Poly1305 authenticated cipher | |
| 71 | pub const XChaCha20Poly1305 = XChaChaPoly1305(20); | |
| 72 | ||
| 73 | /// XChaCha20-Poly1305 authenticated cipher | |
| 74 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 75 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 76 | pub const XChaCha12Poly1305 = XChaChaPoly1305(12); | |
| 77 | ||
| 78 | /// XChaCha20-Poly1305 authenticated cipher | |
| 79 | /// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. | |
| 80 | /// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. | |
| 81 | pub const XChaCha8Poly1305 = XChaChaPoly1305(8); | |
| 16 | 82 | |
| 17 | 83 | // Vectorized implementation of the core function |
| 18 | const ChaCha20VecImpl = struct { | |
| 19 | const Lane = Vector(4, u32); | |
| 20 | const BlockVec = [4]Lane; | |
| 21 | ||
| 22 | fn initContext(key: [8]u32, d: [4]u32) BlockVec { | |
| 23 | const c = "expand 32-byte k"; | |
| 24 | const constant_le = comptime Lane{ | |
| 25 | mem.readIntLittle(u32, c[0..4]), | |
| 26 | mem.readIntLittle(u32, c[4..8]), | |
| 27 | mem.readIntLittle(u32, c[8..12]), | |
| 28 | mem.readIntLittle(u32, c[12..16]), | |
| 29 | }; | |
| 30 | return BlockVec{ | |
| 31 | constant_le, | |
| 32 | Lane{ key[0], key[1], key[2], key[3] }, | |
| 33 | Lane{ key[4], key[5], key[6], key[7] }, | |
| 34 | Lane{ d[0], d[1], d[2], d[3] }, | |
| 35 | }; | |
| 36 | } | |
| 84 | fn ChaChaVecImpl(comptime rounds_nb: usize) type { | |
| 85 | return struct { | |
| 86 | const Lane = Vector(4, u32); | |
| 87 | const BlockVec = [4]Lane; | |
| 88 | ||
| 89 | fn initContext(key: [8]u32, d: [4]u32) BlockVec { | |
| 90 | const c = "expand 32-byte k"; | |
| 91 | const constant_le = comptime Lane{ | |
| 92 | mem.readIntLittle(u32, c[0..4]), | |
| 93 | mem.readIntLittle(u32, c[4..8]), | |
| 94 | mem.readIntLittle(u32, c[8..12]), | |
| 95 | mem.readIntLittle(u32, c[12..16]), | |
| 96 | }; | |
| 97 | return BlockVec{ | |
| 98 | constant_le, | |
| 99 | Lane{ key[0], key[1], key[2], key[3] }, | |
| 100 | Lane{ key[4], key[5], key[6], key[7] }, | |
| 101 | Lane{ d[0], d[1], d[2], d[3] }, | |
| 102 | }; | |
| 103 | } | |
| 37 | 104 | |
| 38 | fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { | |
| 39 | x.* = input; | |
| 40 | ||
| 41 | var r: usize = 0; | |
| 42 | while (r < 20) : (r += 2) { | |
| 43 | x[0] +%= x[1]; | |
| 44 | x[3] ^= x[0]; | |
| 45 | x[3] = math.rotl(Lane, x[3], 16); | |
| 46 | ||
| 47 | x[2] +%= x[3]; | |
| 48 | x[1] ^= x[2]; | |
| 49 | x[1] = math.rotl(Lane, x[1], 12); | |
| 50 | ||
| 51 | x[0] +%= x[1]; | |
| 52 | x[3] ^= x[0]; | |
| 53 | x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 }); | |
| 54 | x[3] = math.rotl(Lane, x[3], 8); | |
| 55 | ||
| 56 | x[2] +%= x[3]; | |
| 57 | x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); | |
| 58 | x[1] ^= x[2]; | |
| 59 | x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 }); | |
| 60 | x[1] = math.rotl(Lane, x[1], 7); | |
| 61 | ||
| 62 | x[0] +%= x[1]; | |
| 63 | x[3] ^= x[0]; | |
| 64 | x[3] = math.rotl(Lane, x[3], 16); | |
| 65 | ||
| 66 | x[2] +%= x[3]; | |
| 67 | x[1] ^= x[2]; | |
| 68 | x[1] = math.rotl(Lane, x[1], 12); | |
| 69 | ||
| 70 | x[0] +%= x[1]; | |
| 71 | x[3] ^= x[0]; | |
| 72 | x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 }); | |
| 73 | x[3] = math.rotl(Lane, x[3], 8); | |
| 74 | ||
| 75 | x[2] +%= x[3]; | |
| 76 | x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); | |
| 77 | x[1] ^= x[2]; | |
| 78 | x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 }); | |
| 79 | x[1] = math.rotl(Lane, x[1], 7); | |
| 105 | fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { | |
| 106 | x.* = input; | |
| 107 | ||
| 108 | var r: usize = 0; | |
| 109 | while (r < rounds_nb) : (r += 2) { | |
| 110 | x[0] +%= x[1]; | |
| 111 | x[3] ^= x[0]; | |
| 112 | x[3] = math.rotl(Lane, x[3], 16); | |
| 113 | ||
| 114 | x[2] +%= x[3]; | |
| 115 | x[1] ^= x[2]; | |
| 116 | x[1] = math.rotl(Lane, x[1], 12); | |
| 117 | ||
| 118 | x[0] +%= x[1]; | |
| 119 | x[3] ^= x[0]; | |
| 120 | x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 }); | |
| 121 | x[3] = math.rotl(Lane, x[3], 8); | |
| 122 | ||
| 123 | x[2] +%= x[3]; | |
| 124 | x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); | |
| 125 | x[1] ^= x[2]; | |
| 126 | x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 }); | |
| 127 | x[1] = math.rotl(Lane, x[1], 7); | |
| 128 | ||
| 129 | x[0] +%= x[1]; | |
| 130 | x[3] ^= x[0]; | |
| 131 | x[3] = math.rotl(Lane, x[3], 16); | |
| 132 | ||
| 133 | x[2] +%= x[3]; | |
| 134 | x[1] ^= x[2]; | |
| 135 | x[1] = math.rotl(Lane, x[1], 12); | |
| 136 | ||
| 137 | x[0] +%= x[1]; | |
| 138 | x[3] ^= x[0]; | |
| 139 | x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 }); | |
| 140 | x[3] = math.rotl(Lane, x[3], 8); | |
| 141 | ||
| 142 | x[2] +%= x[3]; | |
| 143 | x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); | |
| 144 | x[1] ^= x[2]; | |
| 145 | x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 }); | |
| 146 | x[1] = math.rotl(Lane, x[1], 7); | |
| 147 | } | |
| 80 | 148 | } |
| 81 | } | |
| 82 | 149 | |
| 83 | fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { | |
| 84 | var i: usize = 0; | |
| 85 | while (i < 4) : (i += 1) { | |
| 86 | mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]); | |
| 87 | mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]); | |
| 88 | mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]); | |
| 89 | mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]); | |
| 150 | fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { | |
| 151 | var i: usize = 0; | |
| 152 | while (i < 4) : (i += 1) { | |
| 153 | mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]); | |
| 154 | mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]); | |
| 155 | mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]); | |
| 156 | mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]); | |
| 157 | } | |
| 90 | 158 | } |
| 91 | } | |
| 92 | 159 | |
| 93 | fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { | |
| 94 | x[0] +%= ctx[0]; | |
| 95 | x[1] +%= ctx[1]; | |
| 96 | x[2] +%= ctx[2]; | |
| 97 | x[3] +%= ctx[3]; | |
| 98 | } | |
| 160 | fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { | |
| 161 | x[0] +%= ctx[0]; | |
| 162 | x[1] +%= ctx[1]; | |
| 163 | x[2] +%= ctx[2]; | |
| 164 | x[3] +%= ctx[3]; | |
| 165 | } | |
| 99 | 166 | |
| 100 | fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { | |
| 101 | var ctx = initContext(key, counter); | |
| 102 | var x: BlockVec = undefined; | |
| 103 | var buf: [64]u8 = undefined; | |
| 104 | var i: usize = 0; | |
| 105 | while (i + 64 <= in.len) : (i += 64) { | |
| 106 | chacha20Core(x[0..], ctx); | |
| 107 | contextFeedback(&x, ctx); | |
| 108 | hashToBytes(buf[0..], x); | |
| 109 | ||
| 110 | var xout = out[i..]; | |
| 111 | const xin = in[i..]; | |
| 112 | var j: usize = 0; | |
| 113 | while (j < 64) : (j += 1) { | |
| 114 | xout[j] = xin[j]; | |
| 115 | } | |
| 116 | j = 0; | |
| 117 | while (j < 64) : (j += 1) { | |
| 118 | xout[j] ^= buf[j]; | |
| 167 | fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { | |
| 168 | var ctx = initContext(key, counter); | |
| 169 | var x: BlockVec = undefined; | |
| 170 | var buf: [64]u8 = undefined; | |
| 171 | var i: usize = 0; | |
| 172 | while (i + 64 <= in.len) : (i += 64) { | |
| 173 | chacha20Core(x[0..], ctx); | |
| 174 | contextFeedback(&x, ctx); | |
| 175 | hashToBytes(buf[0..], x); | |
| 176 | ||
| 177 | var xout = out[i..]; | |
| 178 | const xin = in[i..]; | |
| 179 | var j: usize = 0; | |
| 180 | while (j < 64) : (j += 1) { | |
| 181 | xout[j] = xin[j]; | |
| 182 | } | |
| 183 | j = 0; | |
| 184 | while (j < 64) : (j += 1) { | |
| 185 | xout[j] ^= buf[j]; | |
| 186 | } | |
| 187 | ctx[3][0] += 1; | |
| 119 | 188 | } |
| 120 | ctx[3][0] += 1; | |
| 121 | } | |
| 122 | if (i < in.len) { | |
| 123 | chacha20Core(x[0..], ctx); | |
| 124 | contextFeedback(&x, ctx); | |
| 125 | hashToBytes(buf[0..], x); | |
| 126 | ||
| 127 | var xout = out[i..]; | |
| 128 | const xin = in[i..]; | |
| 129 | var j: usize = 0; | |
| 130 | while (j < in.len % 64) : (j += 1) { | |
| 131 | xout[j] = xin[j] ^ buf[j]; | |
| 189 | if (i < in.len) { | |
| 190 | chacha20Core(x[0..], ctx); | |
| 191 | contextFeedback(&x, ctx); | |
| 192 | hashToBytes(buf[0..], x); | |
| 193 | ||
| 194 | var xout = out[i..]; | |
| 195 | const xin = in[i..]; | |
| 196 | var j: usize = 0; | |
| 197 | while (j < in.len % 64) : (j += 1) { | |
| 198 | xout[j] = xin[j] ^ buf[j]; | |
| 199 | } | |
| 132 | 200 | } |
| 133 | 201 | } |
| 134 | } | |
| 135 | 202 | |
| 136 | fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { | |
| 137 | var c: [4]u32 = undefined; | |
| 138 | for (c) |_, i| { | |
| 139 | c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); | |
| 203 | fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { | |
| 204 | var c: [4]u32 = undefined; | |
| 205 | for (c) |_, i| { | |
| 206 | c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); | |
| 207 | } | |
| 208 | const ctx = initContext(keyToWords(key), c); | |
| 209 | var x: BlockVec = undefined; | |
| 210 | chacha20Core(x[0..], ctx); | |
| 211 | var out: [32]u8 = undefined; | |
| 212 | mem.writeIntLittle(u32, out[0..4], x[0][0]); | |
| 213 | mem.writeIntLittle(u32, out[4..8], x[0][1]); | |
| 214 | mem.writeIntLittle(u32, out[8..12], x[0][2]); | |
| 215 | mem.writeIntLittle(u32, out[12..16], x[0][3]); | |
| 216 | mem.writeIntLittle(u32, out[16..20], x[3][0]); | |
| 217 | mem.writeIntLittle(u32, out[20..24], x[3][1]); | |
| 218 | mem.writeIntLittle(u32, out[24..28], x[3][2]); | |
| 219 | mem.writeIntLittle(u32, out[28..32], x[3][3]); | |
| 220 | return out; | |
| 140 | 221 | } |
| 141 | const ctx = initContext(keyToWords(key), c); | |
| 142 | var x: BlockVec = undefined; | |
| 143 | chacha20Core(x[0..], ctx); | |
| 144 | var out: [32]u8 = undefined; | |
| 145 | mem.writeIntLittle(u32, out[0..4], x[0][0]); | |
| 146 | mem.writeIntLittle(u32, out[4..8], x[0][1]); | |
| 147 | mem.writeIntLittle(u32, out[8..12], x[0][2]); | |
| 148 | mem.writeIntLittle(u32, out[12..16], x[0][3]); | |
| 149 | mem.writeIntLittle(u32, out[16..20], x[3][0]); | |
| 150 | mem.writeIntLittle(u32, out[20..24], x[3][1]); | |
| 151 | mem.writeIntLittle(u32, out[24..28], x[3][2]); | |
| 152 | mem.writeIntLittle(u32, out[28..32], x[3][3]); | |
| 153 | return out; | |
| 154 | } | |
| 155 | }; | |
| 222 | }; | |
| 223 | } | |
| 156 | 224 | |
| 157 | 225 | // Non-vectorized implementation of the core function |
| 158 | const ChaCha20NonVecImpl = struct { | |
| 159 | const BlockVec = [16]u32; | |
| 160 | ||
| 161 | fn initContext(key: [8]u32, d: [4]u32) BlockVec { | |
| 162 | const c = "expand 32-byte k"; | |
| 163 | const constant_le = comptime [4]u32{ | |
| 164 | mem.readIntLittle(u32, c[0..4]), | |
| 165 | mem.readIntLittle(u32, c[4..8]), | |
| 166 | mem.readIntLittle(u32, c[8..12]), | |
| 167 | mem.readIntLittle(u32, c[12..16]), | |
| 168 | }; | |
| 169 | return BlockVec{ | |
| 170 | constant_le[0], constant_le[1], constant_le[2], constant_le[3], | |
| 171 | key[0], key[1], key[2], key[3], | |
| 172 | key[4], key[5], key[6], key[7], | |
| 173 | d[0], d[1], d[2], d[3], | |
| 174 | }; | |
| 175 | } | |
| 176 | ||
| 177 | const QuarterRound = struct { | |
| 178 | a: usize, | |
| 179 | b: usize, | |
| 180 | c: usize, | |
| 181 | d: usize, | |
| 182 | }; | |
| 226 | fn ChaChaNonVecImpl(comptime rounds_nb: usize) type { | |
| 227 | return struct { | |
| 228 | const BlockVec = [16]u32; | |
| 229 | ||
| 230 | fn initContext(key: [8]u32, d: [4]u32) BlockVec { | |
| 231 | const c = "expand 32-byte k"; | |
| 232 | const constant_le = comptime [4]u32{ | |
| 233 | mem.readIntLittle(u32, c[0..4]), | |
| 234 | mem.readIntLittle(u32, c[4..8]), | |
| 235 | mem.readIntLittle(u32, c[8..12]), | |
| 236 | mem.readIntLittle(u32, c[12..16]), | |
| 237 | }; | |
| 238 | return BlockVec{ | |
| 239 | constant_le[0], constant_le[1], constant_le[2], constant_le[3], | |
| 240 | key[0], key[1], key[2], key[3], | |
| 241 | key[4], key[5], key[6], key[7], | |
| 242 | d[0], d[1], d[2], d[3], | |
| 243 | }; | |
| 244 | } | |
| 183 | 245 | |
| 184 | fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { | |
| 185 | return QuarterRound{ | |
| 186 | .a = a, | |
| 187 | .b = b, | |
| 188 | .c = c, | |
| 189 | .d = d, | |
| 246 | const QuarterRound = struct { | |
| 247 | a: usize, | |
| 248 | b: usize, | |
| 249 | c: usize, | |
| 250 | d: usize, | |
| 190 | 251 | }; |
| 191 | } | |
| 192 | 252 | |
| 193 | fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { | |
| 194 | x.* = input; | |
| 195 | ||
| 196 | const rounds = comptime [_]QuarterRound{ | |
| 197 | Rp(0, 4, 8, 12), | |
| 198 | Rp(1, 5, 9, 13), | |
| 199 | Rp(2, 6, 10, 14), | |
| 200 | Rp(3, 7, 11, 15), | |
| 201 | Rp(0, 5, 10, 15), | |
| 202 | Rp(1, 6, 11, 12), | |
| 203 | Rp(2, 7, 8, 13), | |
| 204 | Rp(3, 4, 9, 14), | |
| 205 | }; | |
| 253 | fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { | |
| 254 | return QuarterRound{ | |
| 255 | .a = a, | |
| 256 | .b = b, | |
| 257 | .c = c, | |
| 258 | .d = d, | |
| 259 | }; | |
| 260 | } | |
| 206 | 261 | |
| 207 | comptime var j: usize = 0; | |
| 208 | inline while (j < 20) : (j += 2) { | |
| 209 | inline for (rounds) |r| { | |
| 210 | x[r.a] +%= x[r.b]; | |
| 211 | x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16)); | |
| 212 | x[r.c] +%= x[r.d]; | |
| 213 | x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12)); | |
| 214 | x[r.a] +%= x[r.b]; | |
| 215 | x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8)); | |
| 216 | x[r.c] +%= x[r.d]; | |
| 217 | x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); | |
| 262 | fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { | |
| 263 | x.* = input; | |
| 264 | ||
| 265 | const rounds = comptime [_]QuarterRound{ | |
| 266 | Rp(0, 4, 8, 12), | |
| 267 | Rp(1, 5, 9, 13), | |
| 268 | Rp(2, 6, 10, 14), | |
| 269 | Rp(3, 7, 11, 15), | |
| 270 | Rp(0, 5, 10, 15), | |
| 271 | Rp(1, 6, 11, 12), | |
| 272 | Rp(2, 7, 8, 13), | |
| 273 | Rp(3, 4, 9, 14), | |
| 274 | }; | |
| 275 | ||
| 276 | comptime var j: usize = 0; | |
| 277 | inline while (j < rounds_nb) : (j += 2) { | |
| 278 | inline for (rounds) |r| { | |
| 279 | x[r.a] +%= x[r.b]; | |
| 280 | x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16)); | |
| 281 | x[r.c] +%= x[r.d]; | |
| 282 | x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12)); | |
| 283 | x[r.a] +%= x[r.b]; | |
| 284 | x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8)); | |
| 285 | x[r.c] +%= x[r.d]; | |
| 286 | x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); | |
| 287 | } | |
| 218 | 288 | } |
| 219 | 289 | } |
| 220 | } | |
| 221 | 290 | |
| 222 | fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { | |
| 223 | var i: usize = 0; | |
| 224 | while (i < 4) : (i += 1) { | |
| 225 | mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]); | |
| 226 | mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]); | |
| 227 | mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]); | |
| 228 | mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]); | |
| 291 | fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { | |
| 292 | var i: usize = 0; | |
| 293 | while (i < 4) : (i += 1) { | |
| 294 | mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]); | |
| 295 | mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]); | |
| 296 | mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]); | |
| 297 | mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]); | |
| 298 | } | |
| 229 | 299 | } |
| 230 | } | |
| 231 | 300 | |
| 232 | fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { | |
| 233 | var i: usize = 0; | |
| 234 | while (i < 16) : (i += 1) { | |
| 235 | x[i] +%= ctx[i]; | |
| 301 | fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { | |
| 302 | var i: usize = 0; | |
| 303 | while (i < 16) : (i += 1) { | |
| 304 | x[i] +%= ctx[i]; | |
| 305 | } | |
| 236 | 306 | } |
| 237 | } | |
| 238 | 307 | |
| 239 | fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { | |
| 240 | var ctx = initContext(key, counter); | |
| 241 | var x: BlockVec = undefined; | |
| 242 | var buf: [64]u8 = undefined; | |
| 243 | var i: usize = 0; | |
| 244 | while (i + 64 <= in.len) : (i += 64) { | |
| 245 | chacha20Core(x[0..], ctx); | |
| 246 | contextFeedback(&x, ctx); | |
| 247 | hashToBytes(buf[0..], x); | |
| 248 | ||
| 249 | var xout = out[i..]; | |
| 250 | const xin = in[i..]; | |
| 251 | var j: usize = 0; | |
| 252 | while (j < 64) : (j += 1) { | |
| 253 | xout[j] = xin[j]; | |
| 254 | } | |
| 255 | j = 0; | |
| 256 | while (j < 64) : (j += 1) { | |
| 257 | xout[j] ^= buf[j]; | |
| 308 | fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { | |
| 309 | var ctx = initContext(key, counter); | |
| 310 | var x: BlockVec = undefined; | |
| 311 | var buf: [64]u8 = undefined; | |
| 312 | var i: usize = 0; | |
| 313 | while (i + 64 <= in.len) : (i += 64) { | |
| 314 | chacha20Core(x[0..], ctx); | |
| 315 | contextFeedback(&x, ctx); | |
| 316 | hashToBytes(buf[0..], x); | |
| 317 | ||
| 318 | var xout = out[i..]; | |
| 319 | const xin = in[i..]; | |
| 320 | var j: usize = 0; | |
| 321 | while (j < 64) : (j += 1) { | |
| 322 | xout[j] = xin[j]; | |
| 323 | } | |
| 324 | j = 0; | |
| 325 | while (j < 64) : (j += 1) { | |
| 326 | xout[j] ^= buf[j]; | |
| 327 | } | |
| 328 | ctx[12] += 1; | |
| 258 | 329 | } |
| 259 | ctx[12] += 1; | |
| 260 | } | |
| 261 | if (i < in.len) { | |
| 262 | chacha20Core(x[0..], ctx); | |
| 263 | contextFeedback(&x, ctx); | |
| 264 | hashToBytes(buf[0..], x); | |
| 265 | ||
| 266 | var xout = out[i..]; | |
| 267 | const xin = in[i..]; | |
| 268 | var j: usize = 0; | |
| 269 | while (j < in.len % 64) : (j += 1) { | |
| 270 | xout[j] = xin[j] ^ buf[j]; | |
| 330 | if (i < in.len) { | |
| 331 | chacha20Core(x[0..], ctx); | |
| 332 | contextFeedback(&x, ctx); | |
| 333 | hashToBytes(buf[0..], x); | |
| 334 | ||
| 335 | var xout = out[i..]; | |
| 336 | const xin = in[i..]; | |
| 337 | var j: usize = 0; | |
| 338 | while (j < in.len % 64) : (j += 1) { | |
| 339 | xout[j] = xin[j] ^ buf[j]; | |
| 340 | } | |
| 271 | 341 | } |
| 272 | 342 | } |
| 273 | } | |
| 274 | 343 | |
| 275 | fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { | |
| 276 | var c: [4]u32 = undefined; | |
| 277 | for (c) |_, i| { | |
| 278 | c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); | |
| 344 | fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { | |
| 345 | var c: [4]u32 = undefined; | |
| 346 | for (c) |_, i| { | |
| 347 | c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); | |
| 348 | } | |
| 349 | const ctx = initContext(keyToWords(key), c); | |
| 350 | var x: BlockVec = undefined; | |
| 351 | chacha20Core(x[0..], ctx); | |
| 352 | var out: [32]u8 = undefined; | |
| 353 | mem.writeIntLittle(u32, out[0..4], x[0]); | |
| 354 | mem.writeIntLittle(u32, out[4..8], x[1]); | |
| 355 | mem.writeIntLittle(u32, out[8..12], x[2]); | |
| 356 | mem.writeIntLittle(u32, out[12..16], x[3]); | |
| 357 | mem.writeIntLittle(u32, out[16..20], x[12]); | |
| 358 | mem.writeIntLittle(u32, out[20..24], x[13]); | |
| 359 | mem.writeIntLittle(u32, out[24..28], x[14]); | |
| 360 | mem.writeIntLittle(u32, out[28..32], x[15]); | |
| 361 | return out; | |
| 279 | 362 | } |
| 280 | const ctx = initContext(keyToWords(key), c); | |
| 281 | var x: BlockVec = undefined; | |
| 282 | chacha20Core(x[0..], ctx); | |
| 283 | var out: [32]u8 = undefined; | |
| 284 | mem.writeIntLittle(u32, out[0..4], x[0]); | |
| 285 | mem.writeIntLittle(u32, out[4..8], x[1]); | |
| 286 | mem.writeIntLittle(u32, out[8..12], x[2]); | |
| 287 | mem.writeIntLittle(u32, out[12..16], x[3]); | |
| 288 | mem.writeIntLittle(u32, out[16..20], x[12]); | |
| 289 | mem.writeIntLittle(u32, out[20..24], x[13]); | |
| 290 | mem.writeIntLittle(u32, out[24..28], x[14]); | |
| 291 | mem.writeIntLittle(u32, out[28..32], x[15]); | |
| 292 | return out; | |
| 293 | } | |
| 294 | }; | |
| 363 | }; | |
| 364 | } | |
| 295 | 365 | |
| 296 | const ChaCha20Impl = if (std.Target.current.cpu.arch == .x86_64) ChaCha20VecImpl else ChaCha20NonVecImpl; | |
| 366 | fn ChaChaImpl(comptime rounds_nb: usize) type { | |
| 367 | return if (std.Target.current.cpu.arch == .x86_64) ChaChaVecImpl(rounds_nb) else ChaChaNonVecImpl(rounds_nb); | |
| 368 | } | |
| 297 | 369 | |
| 298 | 370 | fn keyToWords(key: [32]u8) [8]u32 { |
| 299 | 371 | var k: [8]u32 = undefined; |
| ... | ... | @@ -304,68 +376,239 @@ fn keyToWords(key: [32]u8) [8]u32 { |
| 304 | 376 | return k; |
| 305 | 377 | } |
| 306 | 378 | |
| 307 | /// ChaCha20 avoids the possibility of timing attacks, as there are no branches | |
| 308 | /// on secret key data. | |
| 309 | /// | |
| 310 | /// in and out should be the same length. | |
| 311 | /// counter should generally be 0 or 1 | |
| 312 | /// | |
| 313 | /// ChaCha20 is self-reversing. To decrypt just run the cipher with the same | |
| 314 | /// counter, nonce, and key. | |
| 315 | pub const ChaCha20IETF = struct { | |
| 316 | pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [12]u8) void { | |
| 317 | assert(in.len == out.len); | |
| 318 | assert((in.len >> 6) + counter <= maxInt(u32)); | |
| 319 | ||
| 320 | var c: [4]u32 = undefined; | |
| 321 | c[0] = counter; | |
| 322 | c[1] = mem.readIntLittle(u32, nonce[0..4]); | |
| 323 | c[2] = mem.readIntLittle(u32, nonce[4..8]); | |
| 324 | c[3] = mem.readIntLittle(u32, nonce[8..12]); | |
| 325 | ChaCha20Impl.chacha20Xor(out, in, keyToWords(key), c); | |
| 326 | } | |
| 327 | }; | |
| 328 | ||
| 329 | /// This is the original ChaCha20 before RFC 7539, which recommends using the | |
| 330 | /// orgininal version on applications such as disk or file encryption that might | |
| 331 | /// exceed the 256 GiB limit of the 96-bit nonce version. | |
| 332 | pub const ChaCha20With64BitNonce = struct { | |
| 333 | pub fn xor(out: []u8, in: []const u8, counter: u64, key: [32]u8, nonce: [8]u8) void { | |
| 334 | assert(in.len == out.len); | |
| 335 | assert(counter +% (in.len >> 6) >= counter); | |
| 336 | ||
| 337 | var cursor: usize = 0; | |
| 338 | const k = keyToWords(key); | |
| 339 | var c: [4]u32 = undefined; | |
| 340 | c[0] = @truncate(u32, counter); | |
| 341 | c[1] = @truncate(u32, counter >> 32); | |
| 342 | c[2] = mem.readIntLittle(u32, nonce[0..4]); | |
| 343 | c[3] = mem.readIntLittle(u32, nonce[4..8]); | |
| 344 | ||
| 345 | const block_length = (1 << 6); | |
| 346 | // The full block size is greater than the address space on a 32bit machine | |
| 347 | const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize); | |
| 348 | ||
| 349 | // first partial big block | |
| 350 | if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) { | |
| 351 | ChaCha20Impl.chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c); | |
| 352 | cursor = big_block - cursor; | |
| 353 | c[1] += 1; | |
| 354 | if (comptime @sizeOf(usize) > 4) { | |
| 355 | // A big block is giant: 256 GiB, but we can avoid this limitation | |
| 356 | var remaining_blocks: u32 = @intCast(u32, (in.len / big_block)); | |
| 357 | var i: u32 = 0; | |
| 358 | while (remaining_blocks > 0) : (remaining_blocks -= 1) { | |
| 359 | ChaCha20Impl.chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c); | |
| 360 | c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this. | |
| 361 | cursor += big_block; | |
| 379 | fn extend(key: [32]u8, nonce: [24]u8, comptime rounds_nb: usize) struct { key: [32]u8, nonce: [12]u8 } { | |
| 380 | var subnonce: [12]u8 = undefined; | |
| 381 | mem.set(u8, subnonce[0..4], 0); | |
| 382 | mem.copy(u8, subnonce[4..], nonce[16..24]); | |
| 383 | return .{ | |
| 384 | .key = ChaChaImpl(rounds_nb).hchacha20(nonce[0..16].*, key), | |
| 385 | .nonce = subnonce, | |
| 386 | }; | |
| 387 | } | |
| 388 | ||
| 389 | fn ChaChaIETF(comptime rounds_nb: usize) type { | |
| 390 | return struct { | |
| 391 | /// Nonce length in bytes. | |
| 392 | pub const nonce_length = 12; | |
| 393 | /// Key length in bytes. | |
| 394 | pub const key_length = 32; | |
| 395 | ||
| 396 | /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`. | |
| 397 | /// WARNING: This function doesn't provide authenticated encryption. | |
| 398 | /// Using the AEAD or one of the `box` versions is usually preferred. | |
| 399 | pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void { | |
| 400 | assert(in.len == out.len); | |
| 401 | assert(in.len / 64 <= (1 << 32 - 1) - counter); | |
| 402 | ||
| 403 | var d: [4]u32 = undefined; | |
| 404 | d[0] = counter; | |
| 405 | d[1] = mem.readIntLittle(u32, nonce[0..4]); | |
| 406 | d[2] = mem.readIntLittle(u32, nonce[4..8]); | |
| 407 | d[3] = mem.readIntLittle(u32, nonce[8..12]); | |
| 408 | ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d); | |
| 409 | } | |
| 410 | }; | |
| 411 | } | |
| 412 | ||
| 413 | fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type { | |
| 414 | return struct { | |
| 415 | /// Nonce length in bytes. | |
| 416 | pub const nonce_length = 8; | |
| 417 | /// Key length in bytes. | |
| 418 | pub const key_length = 32; | |
| 419 | ||
| 420 | /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`. | |
| 421 | /// WARNING: This function doesn't provide authenticated encryption. | |
| 422 | /// Using the AEAD or one of the `box` versions is usually preferred. | |
| 423 | pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void { | |
| 424 | assert(in.len == out.len); | |
| 425 | assert(in.len / 64 <= (1 << 64 - 1) - counter); | |
| 426 | ||
| 427 | var cursor: usize = 0; | |
| 428 | const k = keyToWords(key); | |
| 429 | var c: [4]u32 = undefined; | |
| 430 | c[0] = @truncate(u32, counter); | |
| 431 | c[1] = @truncate(u32, counter >> 32); | |
| 432 | c[2] = mem.readIntLittle(u32, nonce[0..4]); | |
| 433 | c[3] = mem.readIntLittle(u32, nonce[4..8]); | |
| 434 | ||
| 435 | const block_length = (1 << 6); | |
| 436 | // The full block size is greater than the address space on a 32bit machine | |
| 437 | const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize); | |
| 438 | ||
| 439 | // first partial big block | |
| 440 | if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) { | |
| 441 | ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c); | |
| 442 | cursor = big_block - cursor; | |
| 443 | c[1] += 1; | |
| 444 | if (comptime @sizeOf(usize) > 4) { | |
| 445 | // A big block is giant: 256 GiB, but we can avoid this limitation | |
| 446 | var remaining_blocks: u32 = @intCast(u32, (in.len / big_block)); | |
| 447 | var i: u32 = 0; | |
| 448 | while (remaining_blocks > 0) : (remaining_blocks -= 1) { | |
| 449 | ChaChaImpl(rounds_nb).chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c); | |
| 450 | c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this. | |
| 451 | cursor += big_block; | |
| 452 | } | |
| 362 | 453 | } |
| 363 | 454 | } |
| 455 | ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..], in[cursor..], k, c); | |
| 456 | } | |
| 457 | }; | |
| 458 | } | |
| 459 | ||
| 460 | fn XChaChaIETF(comptime rounds_nb: usize) type { | |
| 461 | return struct { | |
| 462 | /// Nonce length in bytes. | |
| 463 | pub const nonce_length = 24; | |
| 464 | /// Key length in bytes. | |
| 465 | pub const key_length = 32; | |
| 466 | ||
| 467 | /// Add the output of the XChaCha20 stream cipher to `in` and stores the result into `out`. | |
| 468 | /// WARNING: This function doesn't provide authenticated encryption. | |
| 469 | /// Using the AEAD or one of the `box` versions is usually preferred. | |
| 470 | pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void { | |
| 471 | const extended = extend(key, nonce, rounds_nb); | |
| 472 | ChaChaIETF(rounds_nb).xor(out, in, counter, extended.key, extended.nonce); | |
| 473 | } | |
| 474 | }; | |
| 475 | } | |
| 476 | ||
| 477 | fn ChaChaPoly1305(comptime rounds_nb: usize) type { | |
| 478 | return struct { | |
| 479 | pub const tag_length = 16; | |
| 480 | pub const nonce_length = 12; | |
| 481 | pub const key_length = 32; | |
| 482 | ||
| 483 | /// c: ciphertext: output buffer should be of size m.len | |
| 484 | /// tag: authentication tag: output MAC | |
| 485 | /// m: message | |
| 486 | /// ad: Associated Data | |
| 487 | /// npub: public nonce | |
| 488 | /// k: private key | |
| 489 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { | |
| 490 | assert(c.len == m.len); | |
| 491 | ||
| 492 | var polyKey = [_]u8{0} ** 32; | |
| 493 | ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub); | |
| 494 | ||
| 495 | ChaChaIETF(rounds_nb).xor(c[0..m.len], m, 1, k, npub); | |
| 496 | ||
| 497 | var mac = Poly1305.init(polyKey[0..]); | |
| 498 | mac.update(ad); | |
| 499 | if (ad.len % 16 != 0) { | |
| 500 | const zeros = [_]u8{0} ** 16; | |
| 501 | const padding = 16 - (ad.len % 16); | |
| 502 | mac.update(zeros[0..padding]); | |
| 503 | } | |
| 504 | mac.update(c[0..m.len]); | |
| 505 | if (m.len % 16 != 0) { | |
| 506 | const zeros = [_]u8{0} ** 16; | |
| 507 | const padding = 16 - (m.len % 16); | |
| 508 | mac.update(zeros[0..padding]); | |
| 509 | } | |
| 510 | var lens: [16]u8 = undefined; | |
| 511 | mem.writeIntLittle(u64, lens[0..8], ad.len); | |
| 512 | mem.writeIntLittle(u64, lens[8..16], m.len); | |
| 513 | mac.update(lens[0..]); | |
| 514 | mac.final(tag); | |
| 515 | } | |
| 516 | ||
| 517 | /// m: message: output buffer should be of size c.len | |
| 518 | /// c: ciphertext | |
| 519 | /// tag: authentication tag | |
| 520 | /// ad: Associated Data | |
| 521 | /// npub: public nonce | |
| 522 | /// k: private key | |
| 523 | /// NOTE: the check of the authentication tag is currently not done in constant time | |
| 524 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { | |
| 525 | assert(c.len == m.len); | |
| 526 | ||
| 527 | var polyKey = [_]u8{0} ** 32; | |
| 528 | ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub); | |
| 529 | ||
| 530 | var mac = Poly1305.init(polyKey[0..]); | |
| 531 | ||
| 532 | mac.update(ad); | |
| 533 | if (ad.len % 16 != 0) { | |
| 534 | const zeros = [_]u8{0} ** 16; | |
| 535 | const padding = 16 - (ad.len % 16); | |
| 536 | mac.update(zeros[0..padding]); | |
| 537 | } | |
| 538 | mac.update(c); | |
| 539 | if (c.len % 16 != 0) { | |
| 540 | const zeros = [_]u8{0} ** 16; | |
| 541 | const padding = 16 - (c.len % 16); | |
| 542 | mac.update(zeros[0..padding]); | |
| 543 | } | |
| 544 | var lens: [16]u8 = undefined; | |
| 545 | mem.writeIntLittle(u64, lens[0..8], ad.len); | |
| 546 | mem.writeIntLittle(u64, lens[8..16], c.len); | |
| 547 | mac.update(lens[0..]); | |
| 548 | var computedTag: [16]u8 = undefined; | |
| 549 | mac.final(computedTag[0..]); | |
| 550 | ||
| 551 | var acc: u8 = 0; | |
| 552 | for (computedTag) |_, i| { | |
| 553 | acc |= computedTag[i] ^ tag[i]; | |
| 554 | } | |
| 555 | if (acc != 0) { | |
| 556 | return error.AuthenticationFailed; | |
| 557 | } | |
| 558 | ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub); | |
| 559 | } | |
| 560 | }; | |
| 561 | } | |
| 562 | ||
| 563 | fn XChaChaPoly1305(comptime rounds_nb: usize) type { | |
| 564 | return struct { | |
| 565 | pub const tag_length = 16; | |
| 566 | pub const nonce_length = 24; | |
| 567 | pub const key_length = 32; | |
| 568 | ||
| 569 | /// c: ciphertext: output buffer should be of size m.len | |
| 570 | /// tag: authentication tag: output MAC | |
| 571 | /// m: message | |
| 572 | /// ad: Associated Data | |
| 573 | /// npub: public nonce | |
| 574 | /// k: private key | |
| 575 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { | |
| 576 | const extended = extend(k, npub, rounds_nb); | |
| 577 | return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key); | |
| 364 | 578 | } |
| 365 | 579 | |
| 366 | ChaCha20Impl.chacha20Xor(out[cursor..], in[cursor..], k, c); | |
| 580 | /// m: message: output buffer should be of size c.len | |
| 581 | /// c: ciphertext | |
| 582 | /// tag: authentication tag | |
| 583 | /// ad: Associated Data | |
| 584 | /// npub: public nonce | |
| 585 | /// k: private key | |
| 586 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { | |
| 587 | const extended = extend(k, npub, rounds_nb); | |
| 588 | return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key); | |
| 589 | } | |
| 590 | }; | |
| 591 | } | |
| 592 | ||
| 593 | test "chacha20 AEAD API" { | |
| 594 | const aeads = [_]type{ ChaCha20Poly1305, XChaCha20Poly1305 }; | |
| 595 | const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; | |
| 596 | const ad = "Additional data"; | |
| 597 | ||
| 598 | inline for (aeads) |aead| { | |
| 599 | const key = [_]u8{69} ** aead.key_length; | |
| 600 | const nonce = [_]u8{42} ** aead.nonce_length; | |
| 601 | var c: [m.len]u8 = undefined; | |
| 602 | var tag: [aead.tag_length]u8 = undefined; | |
| 603 | var out: [m.len]u8 = undefined; | |
| 604 | ||
| 605 | aead.encrypt(c[0..], tag[0..], m, ad, nonce, key); | |
| 606 | try aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key); | |
| 607 | testing.expectEqualSlices(u8, out[0..], m); | |
| 608 | c[0] += 1; | |
| 609 | testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key)); | |
| 367 | 610 | } |
| 368 | }; | |
| 611 | } | |
| 369 | 612 | |
| 370 | 613 | // https://tools.ietf.org/html/rfc7539#section-2.4.2 |
| 371 | 614 | test "crypto.chacha20 test vector sunscreen" { |
| ... | ... | @@ -386,7 +629,7 @@ test "crypto.chacha20 test vector sunscreen" { |
| 386 | 629 | 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, |
| 387 | 630 | 0x87, 0x4d, |
| 388 | 631 | }; |
| 389 | const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; | |
| 632 | const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; | |
| 390 | 633 | var result: [114]u8 = undefined; |
| 391 | 634 | const key = [_]u8{ |
| 392 | 635 | 0, 1, 2, 3, 4, 5, 6, 7, |
| ... | ... | @@ -400,13 +643,12 @@ test "crypto.chacha20 test vector sunscreen" { |
| 400 | 643 | 0, 0, 0, 0, |
| 401 | 644 | }; |
| 402 | 645 | |
| 403 | ChaCha20IETF.xor(result[0..], input[0..], 1, key, nonce); | |
| 646 | ChaCha20IETF.xor(result[0..], m[0..], 1, key, nonce); | |
| 404 | 647 | testing.expectEqualSlices(u8, &expected_result, &result); |
| 405 | 648 | |
| 406 | // Chacha20 is self-reversing. | |
| 407 | var plaintext: [114]u8 = undefined; | |
| 408 | ChaCha20IETF.xor(plaintext[0..], result[0..], 1, key, nonce); | |
| 409 | testing.expect(mem.order(u8, input, &plaintext) == .eq); | |
| 649 | var m2: [114]u8 = undefined; | |
| 650 | ChaCha20IETF.xor(m2[0..], result[0..], 1, key, nonce); | |
| 651 | testing.expect(mem.order(u8, m, &m2) == .eq); | |
| 410 | 652 | } |
| 411 | 653 | |
| 412 | 654 | // https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 |
| ... | ... | @@ -421,7 +663,7 @@ test "crypto.chacha20 test vector 1" { |
| 421 | 663 | 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, |
| 422 | 664 | 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86, |
| 423 | 665 | }; |
| 424 | const input = [_]u8{ | |
| 666 | const m = [_]u8{ | |
| 425 | 667 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 426 | 668 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 427 | 669 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| ... | ... | @@ -440,7 +682,7 @@ test "crypto.chacha20 test vector 1" { |
| 440 | 682 | }; |
| 441 | 683 | const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; |
| 442 | 684 | |
| 443 | ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); | |
| 685 | ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); | |
| 444 | 686 | testing.expectEqualSlices(u8, &expected_result, &result); |
| 445 | 687 | } |
| 446 | 688 | |
| ... | ... | @@ -455,7 +697,7 @@ test "crypto.chacha20 test vector 2" { |
| 455 | 697 | 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81, |
| 456 | 698 | 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63, |
| 457 | 699 | }; |
| 458 | const input = [_]u8{ | |
| 700 | const m = [_]u8{ | |
| 459 | 701 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 460 | 702 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 461 | 703 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| ... | ... | @@ -474,7 +716,7 @@ test "crypto.chacha20 test vector 2" { |
| 474 | 716 | }; |
| 475 | 717 | const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; |
| 476 | 718 | |
| 477 | ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); | |
| 719 | ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); | |
| 478 | 720 | testing.expectEqualSlices(u8, &expected_result, &result); |
| 479 | 721 | } |
| 480 | 722 | |
| ... | ... | @@ -489,7 +731,7 @@ test "crypto.chacha20 test vector 3" { |
| 489 | 731 | 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e, |
| 490 | 732 | 0x44, 0x5f, 0x41, 0xe3, |
| 491 | 733 | }; |
| 492 | const input = [_]u8{ | |
| 734 | const m = [_]u8{ | |
| 493 | 735 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 494 | 736 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 495 | 737 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| ... | ... | @@ -508,7 +750,7 @@ test "crypto.chacha20 test vector 3" { |
| 508 | 750 | }; |
| 509 | 751 | const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 1 }; |
| 510 | 752 | |
| 511 | ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); | |
| 753 | ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); | |
| 512 | 754 | testing.expectEqualSlices(u8, &expected_result, &result); |
| 513 | 755 | } |
| 514 | 756 | |
| ... | ... | @@ -523,7 +765,7 @@ test "crypto.chacha20 test vector 4" { |
| 523 | 765 | 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d, |
| 524 | 766 | 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b, |
| 525 | 767 | }; |
| 526 | const input = [_]u8{ | |
| 768 | const m = [_]u8{ | |
| 527 | 769 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 528 | 770 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 529 | 771 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| ... | ... | @@ -542,7 +784,7 @@ test "crypto.chacha20 test vector 4" { |
| 542 | 784 | }; |
| 543 | 785 | const nonce = [_]u8{ 1, 0, 0, 0, 0, 0, 0, 0 }; |
| 544 | 786 | |
| 545 | ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); | |
| 787 | ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); | |
| 546 | 788 | testing.expectEqualSlices(u8, &expected_result, &result); |
| 547 | 789 | } |
| 548 | 790 | |
| ... | ... | @@ -584,7 +826,7 @@ test "crypto.chacha20 test vector 5" { |
| 584 | 826 | 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a, |
| 585 | 827 | 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9, |
| 586 | 828 | }; |
| 587 | const input = [_]u8{ | |
| 829 | const m = [_]u8{ | |
| 588 | 830 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 589 | 831 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 590 | 832 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| ... | ... | @@ -614,147 +856,14 @@ test "crypto.chacha20 test vector 5" { |
| 614 | 856 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 615 | 857 | }; |
| 616 | 858 | |
| 617 | ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); | |
| 859 | ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); | |
| 618 | 860 | testing.expectEqualSlices(u8, &expected_result, &result); |
| 619 | 861 | } |
| 620 | 862 | |
| 621 | pub const chacha20poly1305_tag_length = 16; | |
| 622 | ||
| 623 | fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { | |
| 624 | assert(ciphertext.len == plaintext.len); | |
| 625 | ||
| 626 | // derive poly1305 key | |
| 627 | var polyKey = [_]u8{0} ** 32; | |
| 628 | ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce); | |
| 629 | ||
| 630 | // encrypt plaintext | |
| 631 | ChaCha20IETF.xor(ciphertext[0..plaintext.len], plaintext, 1, key, nonce); | |
| 632 | ||
| 633 | // construct mac | |
| 634 | var mac = Poly1305.init(polyKey[0..]); | |
| 635 | mac.update(data); | |
| 636 | if (data.len % 16 != 0) { | |
| 637 | const zeros = [_]u8{0} ** 16; | |
| 638 | const padding = 16 - (data.len % 16); | |
| 639 | mac.update(zeros[0..padding]); | |
| 640 | } | |
| 641 | mac.update(ciphertext[0..plaintext.len]); | |
| 642 | if (plaintext.len % 16 != 0) { | |
| 643 | const zeros = [_]u8{0} ** 16; | |
| 644 | const padding = 16 - (plaintext.len % 16); | |
| 645 | mac.update(zeros[0..padding]); | |
| 646 | } | |
| 647 | var lens: [16]u8 = undefined; | |
| 648 | mem.writeIntLittle(u64, lens[0..8], data.len); | |
| 649 | mem.writeIntLittle(u64, lens[8..16], plaintext.len); | |
| 650 | mac.update(lens[0..]); | |
| 651 | mac.final(tag); | |
| 652 | } | |
| 653 | ||
| 654 | fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { | |
| 655 | return chacha20poly1305SealDetached(ciphertextAndTag[0..plaintext.len], ciphertextAndTag[plaintext.len..][0..chacha20poly1305_tag_length], plaintext, data, key, nonce); | |
| 656 | } | |
| 657 | ||
| 658 | /// Verifies and decrypts an authenticated message produced by chacha20poly1305SealDetached. | |
| 659 | fn chacha20poly1305OpenDetached(dst: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { | |
| 660 | // split ciphertext and tag | |
| 661 | assert(dst.len == ciphertext.len); | |
| 662 | ||
| 663 | // derive poly1305 key | |
| 664 | var polyKey = [_]u8{0} ** 32; | |
| 665 | ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce); | |
| 666 | ||
| 667 | // construct mac | |
| 668 | var mac = Poly1305.init(polyKey[0..]); | |
| 669 | ||
| 670 | mac.update(data); | |
| 671 | if (data.len % 16 != 0) { | |
| 672 | const zeros = [_]u8{0} ** 16; | |
| 673 | const padding = 16 - (data.len % 16); | |
| 674 | mac.update(zeros[0..padding]); | |
| 675 | } | |
| 676 | mac.update(ciphertext); | |
| 677 | if (ciphertext.len % 16 != 0) { | |
| 678 | const zeros = [_]u8{0} ** 16; | |
| 679 | const padding = 16 - (ciphertext.len % 16); | |
| 680 | mac.update(zeros[0..padding]); | |
| 681 | } | |
| 682 | var lens: [16]u8 = undefined; | |
| 683 | mem.writeIntLittle(u64, lens[0..8], data.len); | |
| 684 | mem.writeIntLittle(u64, lens[8..16], ciphertext.len); | |
| 685 | mac.update(lens[0..]); | |
| 686 | var computedTag: [16]u8 = undefined; | |
| 687 | mac.final(computedTag[0..]); | |
| 688 | ||
| 689 | // verify mac in constant time | |
| 690 | // TODO: we can't currently guarantee that this will run in constant time. | |
| 691 | // See https://github.com/ziglang/zig/issues/1776 | |
| 692 | var acc: u8 = 0; | |
| 693 | for (computedTag) |_, i| { | |
| 694 | acc |= computedTag[i] ^ tag[i]; | |
| 695 | } | |
| 696 | if (acc != 0) { | |
| 697 | return error.AuthenticationFailed; | |
| 698 | } | |
| 699 | ||
| 700 | // decrypt ciphertext | |
| 701 | ChaCha20IETF.xor(dst[0..ciphertext.len], ciphertext, 1, key, nonce); | |
| 702 | } | |
| 703 | ||
| 704 | /// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal. | |
| 705 | fn chacha20poly1305Open(dst: []u8, ciphertextAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { | |
| 706 | if (ciphertextAndTag.len < chacha20poly1305_tag_length) { | |
| 707 | return error.InvalidMessage; | |
| 708 | } | |
| 709 | const ciphertextLen = ciphertextAndTag.len - chacha20poly1305_tag_length; | |
| 710 | return try chacha20poly1305OpenDetached(dst, ciphertextAndTag[0..ciphertextLen], ciphertextAndTag[ciphertextLen..][0..chacha20poly1305_tag_length], data, key, nonce); | |
| 711 | } | |
| 712 | ||
| 713 | fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [12]u8 } { | |
| 714 | var subnonce: [12]u8 = undefined; | |
| 715 | mem.set(u8, subnonce[0..4], 0); | |
| 716 | mem.copy(u8, subnonce[4..], nonce[16..24]); | |
| 717 | return .{ | |
| 718 | .key = ChaCha20Impl.hchacha20(nonce[0..16].*, key), | |
| 719 | .nonce = subnonce, | |
| 720 | }; | |
| 721 | } | |
| 722 | ||
| 723 | pub const XChaCha20IETF = struct { | |
| 724 | pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [24]u8) void { | |
| 725 | const extended = extend(key, nonce); | |
| 726 | ChaCha20IETF.xor(out, in, counter, extended.key, extended.nonce); | |
| 727 | } | |
| 728 | }; | |
| 729 | ||
| 730 | pub const xchacha20poly1305_tag_length = 16; | |
| 731 | ||
| 732 | fn xchacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { | |
| 733 | const extended = extend(key, nonce); | |
| 734 | return chacha20poly1305SealDetached(ciphertext, tag, plaintext, data, extended.key, extended.nonce); | |
| 735 | } | |
| 736 | ||
| 737 | fn xchacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { | |
| 738 | const extended = extend(key, nonce); | |
| 739 | return chacha20poly1305Seal(ciphertextAndTag, plaintext, data, extended.key, extended.nonce); | |
| 740 | } | |
| 741 | ||
| 742 | /// Verifies and decrypts an authenticated message produced by xchacha20poly1305SealDetached. | |
| 743 | fn xchacha20poly1305OpenDetached(plaintext: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void { | |
| 744 | const extended = extend(key, nonce); | |
| 745 | return try chacha20poly1305OpenDetached(plaintext, ciphertext, tag, data, extended.key, extended.nonce); | |
| 746 | } | |
| 747 | ||
| 748 | /// Verifies and decrypts an authenticated message produced by xchacha20poly1305Seal. | |
| 749 | fn xchacha20poly1305Open(ciphertextAndTag: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void { | |
| 750 | const extended = extend(key, nonce); | |
| 751 | return try chacha20poly1305Open(ciphertextAndTag, msgAndTag, data, extended.key, extended.nonce); | |
| 752 | } | |
| 753 | ||
| 754 | 863 | test "seal" { |
| 755 | 864 | { |
| 756 | const plaintext = ""; | |
| 757 | const data = ""; | |
| 865 | const m = ""; | |
| 866 | const ad = ""; | |
| 758 | 867 | const key = [_]u8{ |
| 759 | 868 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, |
| 760 | 869 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, |
| ... | ... | @@ -763,11 +872,11 @@ test "seal" { |
| 763 | 872 | const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 }; |
| 764 | 873 | |
| 765 | 874 | var out: [exp_out.len]u8 = undefined; |
| 766 | chacha20poly1305Seal(out[0..], plaintext, data, key, nonce); | |
| 875 | ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m, ad, nonce, key); | |
| 767 | 876 | testing.expectEqualSlices(u8, exp_out[0..], out[0..]); |
| 768 | 877 | } |
| 769 | 878 | { |
| 770 | const plaintext = [_]u8{ | |
| 879 | const m = [_]u8{ | |
| 771 | 880 | 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, |
| 772 | 881 | 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, |
| 773 | 882 | 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, |
| ... | ... | @@ -777,7 +886,7 @@ test "seal" { |
| 777 | 886 | 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, |
| 778 | 887 | 0x74, 0x2e, |
| 779 | 888 | }; |
| 780 | const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; | |
| 889 | const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; | |
| 781 | 890 | const key = [_]u8{ |
| 782 | 891 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, |
| 783 | 892 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, |
| ... | ... | @@ -796,15 +905,15 @@ test "seal" { |
| 796 | 905 | }; |
| 797 | 906 | |
| 798 | 907 | var out: [exp_out.len]u8 = undefined; |
| 799 | chacha20poly1305Seal(out[0..], plaintext[0..], data[0..], key, nonce); | |
| 908 | ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m[0..], ad[0..], nonce, key); | |
| 800 | 909 | testing.expectEqualSlices(u8, exp_out[0..], out[0..]); |
| 801 | 910 | } |
| 802 | 911 | } |
| 803 | 912 | |
| 804 | 913 | test "open" { |
| 805 | 914 | { |
| 806 | const ciphertext = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 }; | |
| 807 | const data = ""; | |
| 915 | const c = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 }; | |
| 916 | const ad = ""; | |
| 808 | 917 | const key = [_]u8{ |
| 809 | 918 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, |
| 810 | 919 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, |
| ... | ... | @@ -813,11 +922,11 @@ test "open" { |
| 813 | 922 | const exp_out = ""; |
| 814 | 923 | |
| 815 | 924 | var out: [exp_out.len]u8 = undefined; |
| 816 | try chacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce); | |
| 925 | try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key); | |
| 817 | 926 | testing.expectEqualSlices(u8, exp_out[0..], out[0..]); |
| 818 | 927 | } |
| 819 | 928 | { |
| 820 | const ciphertext = [_]u8{ | |
| 929 | const c = [_]u8{ | |
| 821 | 930 | 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, |
| 822 | 931 | 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, |
| 823 | 932 | 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, |
| ... | ... | @@ -828,7 +937,7 @@ test "open" { |
| 828 | 937 | 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, |
| 829 | 938 | 0x6, 0x91, |
| 830 | 939 | }; |
| 831 | const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; | |
| 940 | const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; | |
| 832 | 941 | const key = [_]u8{ |
| 833 | 942 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, |
| 834 | 943 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, |
| ... | ... | @@ -846,126 +955,45 @@ test "open" { |
| 846 | 955 | }; |
| 847 | 956 | |
| 848 | 957 | var out: [exp_out.len]u8 = undefined; |
| 849 | try chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, nonce); | |
| 958 | try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key); | |
| 850 | 959 | testing.expectEqualSlices(u8, exp_out[0..], out[0..]); |
| 851 | 960 | |
| 852 | 961 | // corrupting the ciphertext, data, key, or nonce should cause a failure |
| 853 | var bad_ciphertext = ciphertext; | |
| 854 | bad_ciphertext[0] ^= 1; | |
| 855 | testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], bad_ciphertext[0..], data[0..], key, nonce)); | |
| 856 | var bad_data = data; | |
| 857 | bad_data[0] ^= 1; | |
| 858 | testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], bad_data[0..], key, nonce)); | |
| 962 | var bad_c = c; | |
| 963 | bad_c[0] ^= 1; | |
| 964 | testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], bad_c[0..out.len], bad_c[out.len..].*, ad[0..], nonce, key)); | |
| 965 | var bad_ad = ad; | |
| 966 | bad_ad[0] ^= 1; | |
| 967 | testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, bad_ad[0..], nonce, key)); | |
| 859 | 968 | var bad_key = key; |
| 860 | 969 | bad_key[0] ^= 1; |
| 861 | testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], bad_key, nonce)); | |
| 970 | testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], nonce, bad_key)); | |
| 862 | 971 | var bad_nonce = nonce; |
| 863 | 972 | bad_nonce[0] ^= 1; |
| 864 | testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, bad_nonce)); | |
| 865 | ||
| 866 | // a short ciphertext should result in a different error | |
| 867 | testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce)); | |
| 973 | testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], bad_nonce, key)); | |
| 868 | 974 | } |
| 869 | 975 | } |
| 870 | 976 | |
| 871 | 977 | test "crypto.xchacha20" { |
| 872 | 978 | const key = [_]u8{69} ** 32; |
| 873 | 979 | const nonce = [_]u8{42} ** 24; |
| 874 | const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; | |
| 980 | const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; | |
| 875 | 981 | { |
| 876 | var ciphertext: [input.len]u8 = undefined; | |
| 877 | XChaCha20IETF.xor(ciphertext[0..], input[0..], 0, key, nonce); | |
| 878 | var buf: [2 * ciphertext.len]u8 = undefined; | |
| 879 | testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); | |
| 982 | var c: [m.len]u8 = undefined; | |
| 983 | XChaCha20IETF.xor(c[0..], m[0..], 0, key, nonce); | |
| 984 | var buf: [2 * c.len]u8 = undefined; | |
| 985 | testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); | |
| 880 | 986 | } |
| 881 | 987 | { |
| 882 | const data = "Additional data"; | |
| 883 | var ciphertext: [input.len + xchacha20poly1305_tag_length]u8 = undefined; | |
| 884 | xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce); | |
| 885 | var out: [input.len]u8 = undefined; | |
| 886 | try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce); | |
| 887 | var buf: [2 * ciphertext.len]u8 = undefined; | |
| 888 | testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); | |
| 889 | testing.expectEqualSlices(u8, out[0..], input); | |
| 890 | ciphertext[0] += 1; | |
| 891 | testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce)); | |
| 892 | } | |
| 893 | } | |
| 894 | ||
| 895 | pub const Chacha20Poly1305 = struct { | |
| 896 | pub const tag_length = 16; | |
| 897 | pub const nonce_length = 12; | |
| 898 | pub const key_length = 32; | |
| 899 | ||
| 900 | /// c: ciphertext: output buffer should be of size m.len | |
| 901 | /// tag: authentication tag: output MAC | |
| 902 | /// m: message | |
| 903 | /// ad: Associated Data | |
| 904 | /// npub: public nonce | |
| 905 | /// k: private key | |
| 906 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { | |
| 907 | assert(c.len == m.len); | |
| 908 | return chacha20poly1305SealDetached(c, tag, m, ad, k, npub); | |
| 909 | } | |
| 910 | ||
| 911 | /// m: message: output buffer should be of size c.len | |
| 912 | /// c: ciphertext | |
| 913 | /// tag: authentication tag | |
| 914 | /// ad: Associated Data | |
| 915 | /// npub: public nonce | |
| 916 | /// k: private key | |
| 917 | /// NOTE: the check of the authentication tag is currently not done in constant time | |
| 918 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { | |
| 919 | assert(c.len == m.len); | |
| 920 | return try chacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub); | |
| 921 | } | |
| 922 | }; | |
| 923 | ||
| 924 | pub const XChacha20Poly1305 = struct { | |
| 925 | pub const tag_length = 16; | |
| 926 | pub const nonce_length = 24; | |
| 927 | pub const key_length = 32; | |
| 928 | ||
| 929 | /// c: ciphertext: output buffer should be of size m.len | |
| 930 | /// tag: authentication tag: output MAC | |
| 931 | /// m: message | |
| 932 | /// ad: Associated Data | |
| 933 | /// npub: public nonce | |
| 934 | /// k: private key | |
| 935 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { | |
| 936 | assert(c.len == m.len); | |
| 937 | return xchacha20poly1305SealDetached(c, tag, m, ad, k, npub); | |
| 938 | } | |
| 939 | ||
| 940 | /// m: message: output buffer should be of size c.len | |
| 941 | /// c: ciphertext | |
| 942 | /// tag: authentication tag | |
| 943 | /// ad: Associated Data | |
| 944 | /// npub: public nonce | |
| 945 | /// k: private key | |
| 946 | /// NOTE: the check of the authentication tag is currently not done in constant time | |
| 947 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { | |
| 948 | assert(c.len == m.len); | |
| 949 | return try xchacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub); | |
| 950 | } | |
| 951 | }; | |
| 952 | ||
| 953 | test "chacha20 AEAD API" { | |
| 954 | const aeads = [_]type{ Chacha20Poly1305, XChacha20Poly1305 }; | |
| 955 | const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; | |
| 956 | const data = "Additional data"; | |
| 957 | ||
| 958 | inline for (aeads) |aead| { | |
| 959 | const key = [_]u8{69} ** aead.key_length; | |
| 960 | const nonce = [_]u8{42} ** aead.nonce_length; | |
| 961 | var ciphertext: [input.len]u8 = undefined; | |
| 962 | var tag: [aead.tag_length]u8 = undefined; | |
| 963 | var out: [input.len]u8 = undefined; | |
| 964 | ||
| 965 | aead.encrypt(ciphertext[0..], tag[0..], input, data, nonce, key); | |
| 966 | try aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key); | |
| 967 | testing.expectEqualSlices(u8, out[0..], input); | |
| 968 | ciphertext[0] += 1; | |
| 969 | testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key)); | |
| 988 | const ad = "Additional data"; | |
| 989 | var c: [m.len + XChaCha20Poly1305.tag_length]u8 = undefined; | |
| 990 | XChaCha20Poly1305.encrypt(c[0..m.len], c[m.len..], m, ad, nonce, key); | |
| 991 | var out: [m.len]u8 = undefined; | |
| 992 | try XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key); | |
| 993 | var buf: [2 * c.len]u8 = undefined; | |
| 994 | testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); | |
| 995 | testing.expectEqualSlices(u8, out[0..], m); | |
| 996 | c[0] += 1; | |
| 997 | testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key)); | |
| 970 | 998 | } |
| 971 | 999 | } |
lib/std/crypto/error.zig created+34| ... | ... | @@ -0,0 +1,34 @@ |
| 1 | pub const Error = error{ | |
| 2 | /// MAC verification failed - The tag doesn't verify for the given ciphertext and secret key | |
| 3 | AuthenticationFailed, | |
| 4 | ||
| 5 | /// The requested output length is too long for the chosen algorithm | |
| 6 | OutputTooLong, | |
| 7 | ||
| 8 | /// Finite field operation returned the identity element | |
| 9 | IdentityElement, | |
| 10 | ||
| 11 | /// Encoded input cannot be decoded | |
| 12 | InvalidEncoding, | |
| 13 | ||
| 14 | /// The signature does't verify for the given message and public key | |
| 15 | SignatureVerificationFailed, | |
| 16 | ||
| 17 | /// Both a public and secret key have been provided, but they are incompatible | |
| 18 | KeyMismatch, | |
| 19 | ||
| 20 | /// Encoded input is not in canonical form | |
| 21 | NonCanonical, | |
| 22 | ||
| 23 | /// Square root has no solutions | |
| 24 | NotSquare, | |
| 25 | ||
| 26 | /// Verification string doesn't match the provided password and parameters | |
| 27 | PasswordVerificationFailed, | |
| 28 | ||
| 29 | /// Parameters would be insecure to use | |
| 30 | WeakParameters, | |
| 31 | ||
| 32 | /// Public key would be insecure to use | |
| 33 | WeakPublicKey, | |
| 34 | }; |
lib/std/crypto/gimli.zig+3-2| ... | ... | @@ -20,6 +20,7 @@ const assert = std.debug.assert; |
| 20 | 20 | const testing = std.testing; |
| 21 | 21 | const htest = @import("test.zig"); |
| 22 | 22 | const Vector = std.meta.Vector; |
| 23 | const Error = std.crypto.Error; | |
| 23 | 24 | |
| 24 | 25 | pub const State = struct { |
| 25 | 26 | pub const BLOCKBYTES = 48; |
| ... | ... | @@ -392,7 +393,7 @@ pub const Aead = struct { |
| 392 | 393 | /// npub: public nonce |
| 393 | 394 | /// k: private key |
| 394 | 395 | /// NOTE: the check of the authentication tag is currently not done in constant time |
| 395 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { | |
| 396 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { | |
| 396 | 397 | assert(c.len == m.len); |
| 397 | 398 | |
| 398 | 399 | var state = Aead.init(ad, npub, k); |
| ... | ... | @@ -429,7 +430,7 @@ pub const Aead = struct { |
| 429 | 430 | // TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776 |
| 430 | 431 | if (!mem.eql(u8, buf[0..State.RATE], &tag)) { |
| 431 | 432 | @memset(m.ptr, undefined, m.len); |
| 432 | return error.InvalidMessage; | |
| 433 | return error.AuthenticationFailed; | |
| 433 | 434 | } |
| 434 | 435 | } |
| 435 | 436 | }; |
lib/std/crypto/isap.zig+2-1| ... | ... | @@ -3,6 +3,7 @@ const debug = std.debug; |
| 3 | 3 | const mem = std.mem; |
| 4 | 4 | const math = std.math; |
| 5 | 5 | const testing = std.testing; |
| 6 | const Error = std.crypto.Error; | |
| 6 | 7 | |
| 7 | 8 | /// ISAPv2 is an authenticated encryption system hardened against side channels and fault attacks. |
| 8 | 9 | /// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/spec-doc-rnd2/isap-spec-round2.pdf |
| ... | ... | @@ -217,7 +218,7 @@ pub const IsapA128A = struct { |
| 217 | 218 | tag.* = mac(c, ad, npub, key); |
| 218 | 219 | } |
| 219 | 220 | |
| 220 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { | |
| 221 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) Error!void { | |
| 221 | 222 | var computed_tag = mac(c, ad, npub, key); |
| 222 | 223 | var acc: u8 = 0; |
| 223 | 224 | for (computed_tag) |_, j| { |
lib/std/crypto/pbkdf2.zig+70-80| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | const std = @import("std"); |
| 8 | 8 | const mem = std.mem; |
| 9 | 9 | const maxInt = std.math.maxInt; |
| 10 | const Error = std.crypto.Error; | |
| 10 | 11 | |
| 11 | 12 | // RFC 2898 Section 5.2 |
| 12 | 13 | // |
| ... | ... | @@ -19,36 +20,28 @@ const maxInt = std.math.maxInt; |
| 19 | 20 | // pseudorandom function. See Appendix B.1 for further discussion.) |
| 20 | 21 | // PBKDF2 is recommended for new applications. |
| 21 | 22 | // |
| 22 | // PBKDF2 (P, S, c, dkLen) | |
| 23 | // PBKDF2 (P, S, c, dk_len) | |
| 23 | 24 | // |
| 24 | // Options: PRF underlying pseudorandom function (hLen | |
| 25 | // Options: PRF underlying pseudorandom function (h_len | |
| 25 | 26 | // denotes the length in octets of the |
| 26 | 27 | // pseudorandom function output) |
| 27 | 28 | // |
| 28 | 29 | // Input: P password, an octet string |
| 29 | 30 | // S salt, an octet string |
| 30 | 31 | // c iteration count, a positive integer |
| 31 | // dkLen intended length in octets of the derived | |
| 32 | // dk_len intended length in octets of the derived | |
| 32 | 33 | // key, a positive integer, at most |
| 33 | // (2^32 - 1) * hLen | |
| 34 | // (2^32 - 1) * h_len | |
| 34 | 35 | // |
| 35 | // Output: DK derived key, a dkLen-octet string | |
| 36 | // Output: DK derived key, a dk_len-octet string | |
| 36 | 37 | |
| 37 | 38 | // Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. |
| 38 | 39 | |
| 39 | pub const Pbkdf2Error = error{ | |
| 40 | /// At least one round is required | |
| 41 | TooFewRounds, | |
| 42 | ||
| 43 | /// Maximum length of the derived key is `maxInt(u32) * Prf.mac_length` | |
| 44 | DerivedKeyTooLong, | |
| 45 | }; | |
| 46 | ||
| 47 | 40 | /// Apply PBKDF2 to generate a key from a password. |
| 48 | 41 | /// |
| 49 | 42 | /// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. |
| 50 | 43 | /// |
| 51 | /// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. | |
| 44 | /// dk: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. | |
| 52 | 45 | /// May be uninitialized. All bytes will be overwritten. |
| 53 | 46 | /// Maximum size is `maxInt(u32) * Hash.digest_length` |
| 54 | 47 | /// It is a programming error to pass buffer longer than the maximum size. |
| ... | ... | @@ -59,43 +52,38 @@ pub const Pbkdf2Error = error{ |
| 59 | 52 | /// |
| 60 | 53 | /// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000. |
| 61 | 54 | /// Larger iteration counts improve security by increasing the time required to compute |
| 62 | /// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. | |
| 55 | /// the dk. It is common to tune this parameter to achieve approximately 100ms. | |
| 63 | 56 | /// |
| 64 | 57 | /// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`. |
| 65 | pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Pbkdf2Error!void { | |
| 66 | if (rounds < 1) return error.TooFewRounds; | |
| 58 | pub fn pbkdf2(dk: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Error!void { | |
| 59 | if (rounds < 1) return error.WeakParameters; | |
| 67 | 60 | |
| 68 | const dkLen = derivedKey.len; | |
| 69 | const hLen = Prf.mac_length; | |
| 70 | comptime std.debug.assert(hLen >= 1); | |
| 61 | const dk_len = dk.len; | |
| 62 | const h_len = Prf.mac_length; | |
| 63 | comptime std.debug.assert(h_len >= 1); | |
| 71 | 64 | |
| 72 | 65 | // FromSpec: |
| 73 | 66 | // |
| 74 | // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and | |
| 67 | // 1. If dk_len > maxInt(u32) * h_len, output "derived key too long" and | |
| 75 | 68 | // stop. |
| 76 | 69 | // |
| 77 | if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) { | |
| 78 | // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds | |
| 79 | return error.DerivedKeyTooLong; | |
| 70 | if (dk_len / h_len >= maxInt(u32)) { | |
| 71 | // Counter starts at 1 and is 32 bit, so if we have to return more blocks, we would overflow | |
| 72 | return error.OutputTooLong; | |
| 80 | 73 | } |
| 81 | 74 | |
| 82 | 75 | // FromSpec: |
| 83 | 76 | // |
| 84 | // 2. Let l be the number of hLen-long blocks of bytes in the derived key, | |
| 77 | // 2. Let l be the number of h_len-long blocks of bytes in the derived key, | |
| 85 | 78 | // rounding up, and let r be the number of bytes in the last |
| 86 | 79 | // block |
| 87 | 80 | // |
| 88 | 81 | |
| 89 | // l will not overflow, proof: | |
| 90 | // let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen` | |
| 91 | // then `L^-1(l, hLen) = l*hLen - hLen + 1` | |
| 92 | // 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen | |
| 93 | // 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1 | |
| 94 | // 3) -hLen <= -1 // multiply by -1 | |
| 95 | // 4) hLen >= 1 | |
| 96 | const r_ = dkLen % hLen; | |
| 97 | const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen | |
| 98 | const r = if (r_ == 0) hLen else r_; | |
| 82 | const blocks_count = @intCast(u32, std.math.divCeil(usize, dk_len, h_len) catch unreachable); | |
| 83 | var r = dk_len % h_len; | |
| 84 | if (r == 0) { | |
| 85 | r = h_len; | |
| 86 | } | |
| 99 | 87 | |
| 100 | 88 | // FromSpec: |
| 101 | 89 | // |
| ... | ... | @@ -125,37 +113,38 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: |
| 125 | 113 | // Here, INT (i) is a four-octet encoding of the integer i, most |
| 126 | 114 | // significant octet first. |
| 127 | 115 | // |
| 128 | // 4. Concatenate the blocks and extract the first dkLen octets to | |
| 116 | // 4. Concatenate the blocks and extract the first dk_len octets to | |
| 129 | 117 | // produce a derived key DK: |
| 130 | 118 | // |
| 131 | 119 | // DK = T_1 || T_2 || ... || T_l<0..r-1> |
| 132 | var block: u32 = 0; // Spec limits to u32 | |
| 133 | while (block < l) : (block += 1) { | |
| 134 | var prevBlock: [hLen]u8 = undefined; | |
| 135 | var newBlock: [hLen]u8 = undefined; | |
| 120 | ||
| 121 | var block: u32 = 0; | |
| 122 | while (block < blocks_count) : (block += 1) { | |
| 123 | var prev_block: [h_len]u8 = undefined; | |
| 124 | var new_block: [h_len]u8 = undefined; | |
| 136 | 125 | |
| 137 | 126 | // U_1 = PRF (P, S || INT (i)) |
| 138 | const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 | |
| 127 | const block_index = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 | |
| 139 | 128 | var ctx = Prf.init(password); |
| 140 | 129 | ctx.update(salt); |
| 141 | ctx.update(blockIndex[0..]); | |
| 142 | ctx.final(prevBlock[0..]); | |
| 130 | ctx.update(block_index[0..]); | |
| 131 | ctx.final(prev_block[0..]); | |
| 143 | 132 | |
| 144 | 133 | // Choose portion of DK to write into (T_n) and initialize |
| 145 | const offset = block * hLen; | |
| 146 | const blockLen = if (block != l - 1) hLen else r; | |
| 147 | const dkBlock: []u8 = derivedKey[offset..][0..blockLen]; | |
| 148 | mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); | |
| 134 | const offset = block * h_len; | |
| 135 | const block_len = if (block != blocks_count - 1) h_len else r; | |
| 136 | const dk_block: []u8 = dk[offset..][0..block_len]; | |
| 137 | mem.copy(u8, dk_block, prev_block[0..dk_block.len]); | |
| 149 | 138 | |
| 150 | 139 | var i: u32 = 1; |
| 151 | 140 | while (i < rounds) : (i += 1) { |
| 152 | 141 | // U_c = PRF (P, U_{c-1}) |
| 153 | Prf.create(&newBlock, prevBlock[0..], password); | |
| 154 | mem.copy(u8, prevBlock[0..], newBlock[0..]); | |
| 142 | Prf.create(&new_block, prev_block[0..], password); | |
| 143 | mem.copy(u8, prev_block[0..], new_block[0..]); | |
| 155 | 144 | |
| 156 | 145 | // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c |
| 157 | for (dkBlock) |_, j| { | |
| 158 | dkBlock[j] ^= newBlock[j]; | |
| 146 | for (dk_block) |_, j| { | |
| 147 | dk_block[j] ^= new_block[j]; | |
| 159 | 148 | } |
| 160 | 149 | } |
| 161 | 150 | } |
| ... | ... | @@ -165,49 +154,50 @@ const htest = @import("test.zig"); |
| 165 | 154 | const HmacSha1 = std.crypto.auth.hmac.HmacSha1; |
| 166 | 155 | |
| 167 | 156 | // RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors |
| 157 | ||
| 168 | 158 | test "RFC 6070 one iteration" { |
| 169 | 159 | const p = "password"; |
| 170 | 160 | const s = "salt"; |
| 171 | 161 | const c = 1; |
| 172 | const dkLen = 20; | |
| 162 | const dk_len = 20; | |
| 173 | 163 | |
| 174 | var derivedKey: [dkLen]u8 = undefined; | |
| 164 | var dk: [dk_len]u8 = undefined; | |
| 175 | 165 | |
| 176 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | |
| 166 | try pbkdf2(&dk, p, s, c, HmacSha1); | |
| 177 | 167 | |
| 178 | 168 | const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; |
| 179 | 169 | |
| 180 | htest.assertEqual(expected, derivedKey[0..]); | |
| 170 | htest.assertEqual(expected, dk[0..]); | |
| 181 | 171 | } |
| 182 | 172 | |
| 183 | 173 | test "RFC 6070 two iterations" { |
| 184 | 174 | const p = "password"; |
| 185 | 175 | const s = "salt"; |
| 186 | 176 | const c = 2; |
| 187 | const dkLen = 20; | |
| 177 | const dk_len = 20; | |
| 188 | 178 | |
| 189 | var derivedKey: [dkLen]u8 = undefined; | |
| 179 | var dk: [dk_len]u8 = undefined; | |
| 190 | 180 | |
| 191 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | |
| 181 | try pbkdf2(&dk, p, s, c, HmacSha1); | |
| 192 | 182 | |
| 193 | 183 | const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; |
| 194 | 184 | |
| 195 | htest.assertEqual(expected, derivedKey[0..]); | |
| 185 | htest.assertEqual(expected, dk[0..]); | |
| 196 | 186 | } |
| 197 | 187 | |
| 198 | 188 | test "RFC 6070 4096 iterations" { |
| 199 | 189 | const p = "password"; |
| 200 | 190 | const s = "salt"; |
| 201 | 191 | const c = 4096; |
| 202 | const dkLen = 20; | |
| 192 | const dk_len = 20; | |
| 203 | 193 | |
| 204 | var derivedKey: [dkLen]u8 = undefined; | |
| 194 | var dk: [dk_len]u8 = undefined; | |
| 205 | 195 | |
| 206 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | |
| 196 | try pbkdf2(&dk, p, s, c, HmacSha1); | |
| 207 | 197 | |
| 208 | 198 | const expected = "4b007901b765489abead49d926f721d065a429c1"; |
| 209 | 199 | |
| 210 | htest.assertEqual(expected, derivedKey[0..]); | |
| 200 | htest.assertEqual(expected, dk[0..]); | |
| 211 | 201 | } |
| 212 | 202 | |
| 213 | 203 | test "RFC 6070 16,777,216 iterations" { |
| ... | ... | @@ -219,48 +209,48 @@ test "RFC 6070 16,777,216 iterations" { |
| 219 | 209 | const p = "password"; |
| 220 | 210 | const s = "salt"; |
| 221 | 211 | const c = 16777216; |
| 222 | const dkLen = 20; | |
| 212 | const dk_len = 20; | |
| 223 | 213 | |
| 224 | var derivedKey = [_]u8{0} ** dkLen; | |
| 214 | var dk = [_]u8{0} ** dk_len; | |
| 225 | 215 | |
| 226 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | |
| 216 | try pbkdf2(&dk, p, s, c, HmacSha1); | |
| 227 | 217 | |
| 228 | 218 | const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; |
| 229 | 219 | |
| 230 | htest.assertEqual(expected, derivedKey[0..]); | |
| 220 | htest.assertEqual(expected, dk[0..]); | |
| 231 | 221 | } |
| 232 | 222 | |
| 233 | 223 | test "RFC 6070 multi-block salt and password" { |
| 234 | 224 | const p = "passwordPASSWORDpassword"; |
| 235 | 225 | const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; |
| 236 | 226 | const c = 4096; |
| 237 | const dkLen = 25; | |
| 227 | const dk_len = 25; | |
| 238 | 228 | |
| 239 | var derivedKey: [dkLen]u8 = undefined; | |
| 229 | var dk: [dk_len]u8 = undefined; | |
| 240 | 230 | |
| 241 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | |
| 231 | try pbkdf2(&dk, p, s, c, HmacSha1); | |
| 242 | 232 | |
| 243 | 233 | const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; |
| 244 | 234 | |
| 245 | htest.assertEqual(expected, derivedKey[0..]); | |
| 235 | htest.assertEqual(expected, dk[0..]); | |
| 246 | 236 | } |
| 247 | 237 | |
| 248 | 238 | test "RFC 6070 embedded NUL" { |
| 249 | 239 | const p = "pass\x00word"; |
| 250 | 240 | const s = "sa\x00lt"; |
| 251 | 241 | const c = 4096; |
| 252 | const dkLen = 16; | |
| 242 | const dk_len = 16; | |
| 253 | 243 | |
| 254 | var derivedKey: [dkLen]u8 = undefined; | |
| 244 | var dk: [dk_len]u8 = undefined; | |
| 255 | 245 | |
| 256 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | |
| 246 | try pbkdf2(&dk, p, s, c, HmacSha1); | |
| 257 | 247 | |
| 258 | 248 | const expected = "56fa6aa75548099dcc37d7f03425e0c3"; |
| 259 | 249 | |
| 260 | htest.assertEqual(expected, derivedKey[0..]); | |
| 250 | htest.assertEqual(expected, dk[0..]); | |
| 261 | 251 | } |
| 262 | 252 | |
| 263 | test "Very large dkLen" { | |
| 253 | test "Very large dk_len" { | |
| 264 | 254 | // This test allocates 8GB of memory and is expected to take several hours to run. |
| 265 | 255 | if (true) { |
| 266 | 256 | return error.SkipZigTest; |
| ... | ... | @@ -268,13 +258,13 @@ test "Very large dkLen" { |
| 268 | 258 | const p = "password"; |
| 269 | 259 | const s = "salt"; |
| 270 | 260 | const c = 1; |
| 271 | const dkLen = 1 << 33; | |
| 261 | const dk_len = 1 << 33; | |
| 272 | 262 | |
| 273 | var derivedKey = try std.testing.allocator.alloc(u8, dkLen); | |
| 263 | var dk = try std.testing.allocator.alloc(u8, dk_len); | |
| 274 | 264 | defer { |
| 275 | std.testing.allocator.free(derivedKey); | |
| 265 | std.testing.allocator.free(dk); | |
| 276 | 266 | } |
| 277 | 267 | |
| 278 | try pbkdf2(derivedKey, p, s, c, HmacSha1); | |
| 279 | 268 | // Just verify this doesn't crash with an overflow |
| 269 | try pbkdf2(dk, p, s, c, HmacSha1); | |
| 280 | 270 | } |
lib/std/crypto/salsa20.zig+8-7| ... | ... | @@ -15,6 +15,7 @@ const Vector = std.meta.Vector; |
| 15 | 15 | const Poly1305 = crypto.onetimeauth.Poly1305; |
| 16 | 16 | const Blake2b = crypto.hash.blake2.Blake2b; |
| 17 | 17 | const X25519 = crypto.dh.X25519; |
| 18 | const Error = crypto.Error; | |
| 18 | 19 | |
| 19 | 20 | const Salsa20VecImpl = struct { |
| 20 | 21 | const Lane = Vector(4, u32); |
| ... | ... | @@ -398,7 +399,7 @@ pub const XSalsa20Poly1305 = struct { |
| 398 | 399 | /// ad: Associated Data |
| 399 | 400 | /// npub: public nonce |
| 400 | 401 | /// k: private key |
| 401 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { | |
| 402 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { | |
| 402 | 403 | debug.assert(c.len == m.len); |
| 403 | 404 | const extended = extend(k, npub); |
| 404 | 405 | var block0 = [_]u8{0} ** 64; |
| ... | ... | @@ -446,7 +447,7 @@ pub const SecretBox = struct { |
| 446 | 447 | |
| 447 | 448 | /// Verify and decrypt `c` using a nonce `npub` and a key `k`. |
| 448 | 449 | /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message. |
| 449 | pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { | |
| 450 | pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { | |
| 450 | 451 | if (c.len < tag_length) { |
| 451 | 452 | return error.AuthenticationFailed; |
| 452 | 453 | } |
| ... | ... | @@ -481,20 +482,20 @@ pub const Box = struct { |
| 481 | 482 | pub const KeyPair = X25519.KeyPair; |
| 482 | 483 | |
| 483 | 484 | /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key. |
| 484 | pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 { | |
| 485 | pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) Error![shared_length]u8 { | |
| 485 | 486 | const p = try X25519.scalarmult(secret_key, public_key); |
| 486 | 487 | const zero = [_]u8{0} ** 16; |
| 487 | 488 | return Salsa20Impl.hsalsa20(zero, p); |
| 488 | 489 | } |
| 489 | 490 | |
| 490 | 491 | /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`. |
| 491 | pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void { | |
| 492 | pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) Error!void { | |
| 492 | 493 | const shared_key = try createSharedSecret(public_key, secret_key); |
| 493 | 494 | return SecretBox.seal(c, m, npub, shared_key); |
| 494 | 495 | } |
| 495 | 496 | |
| 496 | 497 | /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`. |
| 497 | pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void { | |
| 498 | pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) Error!void { | |
| 498 | 499 | const shared_key = try createSharedSecret(public_key, secret_key); |
| 499 | 500 | return SecretBox.open(m, c, npub, shared_key); |
| 500 | 501 | } |
| ... | ... | @@ -527,7 +528,7 @@ pub const SealedBox = struct { |
| 527 | 528 | |
| 528 | 529 | /// Encrypt a message `m` for a recipient whose public key is `public_key`. |
| 529 | 530 | /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added. |
| 530 | pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) !void { | |
| 531 | pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) Error!void { | |
| 531 | 532 | debug.assert(c.len == m.len + seal_length); |
| 532 | 533 | var ekp = try KeyPair.create(null); |
| 533 | 534 | const nonce = createNonce(ekp.public_key, public_key); |
| ... | ... | @@ -538,7 +539,7 @@ pub const SealedBox = struct { |
| 538 | 539 | |
| 539 | 540 | /// Decrypt a message using a key pair. |
| 540 | 541 | /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata. |
| 541 | pub fn open(m: []u8, c: []const u8, keypair: KeyPair) !void { | |
| 542 | pub fn open(m: []u8, c: []const u8, keypair: KeyPair) Error!void { | |
| 542 | 543 | if (c.len < seal_length) { |
| 543 | 544 | return error.AuthenticationFailed; |
| 544 | 545 | } |
lib/std/debug.zig-18| ... | ... | @@ -250,24 +250,6 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 250 | 250 | resetSegfaultHandler(); |
| 251 | 251 | } |
| 252 | 252 | |
| 253 | if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) | |
| 254 | nosuspend { | |
| 255 | // As a workaround for not having threadlocal variable support in LLD for this target, | |
| 256 | // we have a simpler panic implementation that does not use threadlocal variables. | |
| 257 | // TODO https://github.com/ziglang/zig/issues/7527 | |
| 258 | const stderr = io.getStdErr().writer(); | |
| 259 | if (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst) == 0) { | |
| 260 | stderr.print("panic: " ++ format ++ "\n", args) catch os.abort(); | |
| 261 | if (trace) |t| { | |
| 262 | dumpStackTrace(t.*); | |
| 263 | } | |
| 264 | dumpCurrentStackTrace(first_trace_addr); | |
| 265 | } else { | |
| 266 | stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); | |
| 267 | } | |
| 268 | os.abort(); | |
| 269 | }; | |
| 270 | ||
| 271 | 253 | nosuspend switch (panic_stage) { |
| 272 | 254 | 0 => { |
| 273 | 255 | panic_stage = 1; |
lib/std/enums.zig created+1281| ... | ... | @@ -0,0 +1,1281 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 Zig Contributors | |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | |
| 4 | // The MIT license requires this copyright notice to be included in all copies | |
| 5 | // and substantial portions of the software. | |
| 6 | ||
| 7 | //! This module contains utilities and data structures for working with enums. | |
| 8 | ||
| 9 | const std = @import("std.zig"); | |
| 10 | const assert = std.debug.assert; | |
| 11 | const testing = std.testing; | |
| 12 | const EnumField = std.builtin.TypeInfo.EnumField; | |
| 13 | ||
| 14 | /// Returns a struct with a field matching each unique named enum element. | |
| 15 | /// If the enum is extern and has multiple names for the same value, only | |
| 16 | /// the first name is used. Each field is of type Data and has the provided | |
| 17 | /// default, which may be undefined. | |
| 18 | pub fn EnumFieldStruct(comptime E: type, comptime Data: type, comptime field_default: ?Data) type { | |
| 19 | const StructField = std.builtin.TypeInfo.StructField; | |
| 20 | var fields: []const StructField = &[_]StructField{}; | |
| 21 | for (uniqueFields(E)) |field, i| { | |
| 22 | fields = fields ++ &[_]StructField{.{ | |
| 23 | .name = field.name, | |
| 24 | .field_type = Data, | |
| 25 | .default_value = field_default, | |
| 26 | .is_comptime = false, | |
| 27 | .alignment = if (@sizeOf(Data) > 0) @alignOf(Data) else 0, | |
| 28 | }}; | |
| 29 | } | |
| 30 | return @Type(.{ .Struct = .{ | |
| 31 | .layout = .Auto, | |
| 32 | .fields = fields, | |
| 33 | .decls = &[_]std.builtin.TypeInfo.Declaration{}, | |
| 34 | .is_tuple = false, | |
| 35 | }}); | |
| 36 | } | |
| 37 | ||
| 38 | /// Looks up the supplied fields in the given enum type. | |
| 39 | /// Uses only the field names, field values are ignored. | |
| 40 | /// The result array is in the same order as the input. | |
| 41 | pub fn valuesFromFields(comptime E: type, comptime fields: []const EnumField) []const E { | |
| 42 | comptime { | |
| 43 | var result: [fields.len]E = undefined; | |
| 44 | for (fields) |f, i| { | |
| 45 | result[i] = @field(E, f.name); | |
| 46 | } | |
| 47 | return &result; | |
| 48 | } | |
| 49 | } | |
| 50 | ||
| 51 | test "std.enums.valuesFromFields" { | |
| 52 | const E = extern enum { a, b, c, d = 0 }; | |
| 53 | const fields = valuesFromFields(E, &[_]EnumField{ | |
| 54 | .{ .name = "b", .value = undefined }, | |
| 55 | .{ .name = "a", .value = undefined }, | |
| 56 | .{ .name = "a", .value = undefined }, | |
| 57 | .{ .name = "d", .value = undefined }, | |
| 58 | }); | |
| 59 | testing.expectEqual(E.b, fields[0]); | |
| 60 | testing.expectEqual(E.a, fields[1]); | |
| 61 | testing.expectEqual(E.d, fields[2]); // a == d | |
| 62 | testing.expectEqual(E.d, fields[3]); | |
| 63 | } | |
| 64 | ||
| 65 | /// Returns the set of all named values in the given enum, in | |
| 66 | /// declaration order. | |
| 67 | pub fn values(comptime E: type) []const E { | |
| 68 | return comptime valuesFromFields(E, @typeInfo(E).Enum.fields); | |
| 69 | } | |
| 70 | ||
| 71 | test "std.enum.values" { | |
| 72 | const E = extern enum { a, b, c, d = 0 }; | |
| 73 | testing.expectEqualSlices(E, &.{.a, .b, .c, .d}, values(E)); | |
| 74 | } | |
| 75 | ||
| 76 | /// Returns the set of all unique named values in the given enum, in | |
| 77 | /// declaration order. For repeated values in extern enums, only the | |
| 78 | /// first name for each value is included. | |
| 79 | pub fn uniqueValues(comptime E: type) []const E { | |
| 80 | return comptime valuesFromFields(E, uniqueFields(E)); | |
| 81 | } | |
| 82 | ||
| 83 | test "std.enum.uniqueValues" { | |
| 84 | const E = extern enum { a, b, c, d = 0, e, f = 3 }; | |
| 85 | testing.expectEqualSlices(E, &.{.a, .b, .c, .f}, uniqueValues(E)); | |
| 86 | ||
| 87 | const F = enum { a, b, c }; | |
| 88 | testing.expectEqualSlices(F, &.{.a, .b, .c}, uniqueValues(F)); | |
| 89 | } | |
| 90 | ||
| 91 | /// Returns the set of all unique field values in the given enum, in | |
| 92 | /// declaration order. For repeated values in extern enums, only the | |
| 93 | /// first name for each value is included. | |
| 94 | pub fn uniqueFields(comptime E: type) []const EnumField { | |
| 95 | comptime { | |
| 96 | const info = @typeInfo(E).Enum; | |
| 97 | const raw_fields = info.fields; | |
| 98 | // Only extern enums can contain duplicates, | |
| 99 | // so fast path other types. | |
| 100 | if (info.layout != .Extern) { | |
| 101 | return raw_fields; | |
| 102 | } | |
| 103 | ||
| 104 | var unique_fields: []const EnumField = &[_]EnumField{}; | |
| 105 | outer: | |
| 106 | for (raw_fields) |candidate| { | |
| 107 | for (unique_fields) |u| { | |
| 108 | if (u.value == candidate.value) | |
| 109 | continue :outer; | |
| 110 | } | |
| 111 | unique_fields = unique_fields ++ &[_]EnumField{candidate}; | |
| 112 | } | |
| 113 | ||
| 114 | return unique_fields; | |
| 115 | } | |
| 116 | } | |
| 117 | ||
| 118 | /// Determines the length of a direct-mapped enum array, indexed by | |
| 119 | /// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. | |
| 120 | /// If the enum contains any fields with values that cannot be represented | |
| 121 | /// by usize, a compile error is issued. The max_unused_slots parameter limits | |
| 122 | /// the total number of items which have no matching enum key (holes in the enum | |
| 123 | /// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots | |
| 124 | /// must be at least 3, to allow unused slots 0, 3, and 4. | |
| 125 | fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int) comptime_int { | |
| 126 | const info = @typeInfo(E).Enum; | |
| 127 | if (!info.is_exhaustive) { | |
| 128 | @compileError("Cannot create direct array of non-exhaustive enum "++@typeName(E)); | |
| 129 | } | |
| 130 | ||
| 131 | var max_value: comptime_int = -1; | |
| 132 | const max_usize: comptime_int = ~@as(usize, 0); | |
| 133 | const fields = uniqueFields(E); | |
| 134 | for (fields) |f| { | |
| 135 | if (f.value < 0) { | |
| 136 | @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" has a negative value."); | |
| 137 | } | |
| 138 | if (f.value > max_value) { | |
| 139 | if (f.value > max_usize) { | |
| 140 | @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" is larger than the max value of usize."); | |
| 141 | } | |
| 142 | max_value = f.value; | |
| 143 | } | |
| 144 | } | |
| 145 | ||
| 146 | const unused_slots = max_value + 1 - fields.len; | |
| 147 | if (unused_slots > max_unused_slots) { | |
| 148 | const unused_str = std.fmt.comptimePrint("{d}", .{unused_slots}); | |
| 149 | const allowed_str = std.fmt.comptimePrint("{d}", .{max_unused_slots}); | |
| 150 | @compileError("Cannot create a direct enum array for "++@typeName(E)++". It would have "++unused_str++" unused slots, but only "++allowed_str++" are allowed."); | |
| 151 | } | |
| 152 | ||
| 153 | return max_value + 1; | |
| 154 | } | |
| 155 | ||
| 156 | /// Initializes an array of Data which can be indexed by | |
| 157 | /// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. | |
| 158 | /// If the enum contains any fields with values that cannot be represented | |
| 159 | /// by usize, a compile error is issued. The max_unused_slots parameter limits | |
| 160 | /// the total number of items which have no matching enum key (holes in the enum | |
| 161 | /// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots | |
| 162 | /// must be at least 3, to allow unused slots 0, 3, and 4. | |
| 163 | /// The init_values parameter must be a struct with field names that match the enum values. | |
| 164 | /// If the enum has multiple fields with the same value, the name of the first one must | |
| 165 | /// be used. | |
| 166 | pub fn directEnumArray( | |
| 167 | comptime E: type, | |
| 168 | comptime Data: type, | |
| 169 | comptime max_unused_slots: comptime_int, | |
| 170 | init_values: EnumFieldStruct(E, Data, null), | |
| 171 | ) [directEnumArrayLen(E, max_unused_slots)]Data { | |
| 172 | return directEnumArrayDefault(E, Data, null, max_unused_slots, init_values); | |
| 173 | } | |
| 174 | ||
| 175 | test "std.enums.directEnumArray" { | |
| 176 | const E = enum(i4) { a = 4, b = 6, c = 2 }; | |
| 177 | var runtime_false: bool = false; | |
| 178 | const array = directEnumArray(E, bool, 4, .{ | |
| 179 | .a = true, | |
| 180 | .b = runtime_false, | |
| 181 | .c = true, | |
| 182 | }); | |
| 183 | ||
| 184 | testing.expectEqual([7]bool, @TypeOf(array)); | |
| 185 | testing.expectEqual(true, array[4]); | |
| 186 | testing.expectEqual(false, array[6]); | |
| 187 | testing.expectEqual(true, array[2]); | |
| 188 | } | |
| 189 | ||
| 190 | /// Initializes an array of Data which can be indexed by | |
| 191 | /// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. | |
| 192 | /// If the enum contains any fields with values that cannot be represented | |
| 193 | /// by usize, a compile error is issued. The max_unused_slots parameter limits | |
| 194 | /// the total number of items which have no matching enum key (holes in the enum | |
| 195 | /// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots | |
| 196 | /// must be at least 3, to allow unused slots 0, 3, and 4. | |
| 197 | /// The init_values parameter must be a struct with field names that match the enum values. | |
| 198 | /// If the enum has multiple fields with the same value, the name of the first one must | |
| 199 | /// be used. | |
| 200 | pub fn directEnumArrayDefault( | |
| 201 | comptime E: type, | |
| 202 | comptime Data: type, | |
| 203 | comptime default: ?Data, | |
| 204 | comptime max_unused_slots: comptime_int, | |
| 205 | init_values: EnumFieldStruct(E, Data, default), | |
| 206 | ) [directEnumArrayLen(E, max_unused_slots)]Data { | |
| 207 | const len = comptime directEnumArrayLen(E, max_unused_slots); | |
| 208 | var result: [len]Data = if (default) |d| [_]Data{d} ** len else undefined; | |
| 209 | inline for (@typeInfo(@TypeOf(init_values)).Struct.fields) |f, i| { | |
| 210 | const enum_value = @field(E, f.name); | |
| 211 | const index = @intCast(usize, @enumToInt(enum_value)); | |
| 212 | result[index] = @field(init_values, f.name); | |
| 213 | } | |
| 214 | return result; | |
| 215 | } | |
| 216 | ||
| 217 | test "std.enums.directEnumArrayDefault" { | |
| 218 | const E = enum(i4) { a = 4, b = 6, c = 2 }; | |
| 219 | var runtime_false: bool = false; | |
| 220 | const array = directEnumArrayDefault(E, bool, false, 4, .{ | |
| 221 | .a = true, | |
| 222 | .b = runtime_false, | |
| 223 | }); | |
| 224 | ||
| 225 | testing.expectEqual([7]bool, @TypeOf(array)); | |
| 226 | testing.expectEqual(true, array[4]); | |
| 227 | testing.expectEqual(false, array[6]); | |
| 228 | testing.expectEqual(false, array[2]); | |
| 229 | } | |
| 230 | ||
| 231 | /// Cast an enum literal, value, or string to the enum value of type E | |
| 232 | /// with the same name. | |
| 233 | pub fn nameCast(comptime E: type, comptime value: anytype) E { | |
| 234 | comptime { | |
| 235 | const V = @TypeOf(value); | |
| 236 | if (V == E) return value; | |
| 237 | var name: ?[]const u8 = switch (@typeInfo(V)) { | |
| 238 | .EnumLiteral, .Enum => @tagName(value), | |
| 239 | .Pointer => if (std.meta.trait.isZigString(V)) value else null, | |
| 240 | else => null, | |
| 241 | }; | |
| 242 | if (name) |n| { | |
| 243 | if (@hasField(E, n)) { | |
| 244 | return @field(E, n); | |
| 245 | } | |
| 246 | @compileError("Enum "++@typeName(E)++" has no field named "++n); | |
| 247 | } | |
| 248 | @compileError("Cannot cast from "++@typeName(@TypeOf(value))++" to "++@typeName(E)); | |
| 249 | } | |
| 250 | } | |
| 251 | ||
| 252 | test "std.enums.nameCast" { | |
| 253 | const A = enum { a = 0, b = 1 }; | |
| 254 | const B = enum { a = 1, b = 0 }; | |
| 255 | testing.expectEqual(A.a, nameCast(A, .a)); | |
| 256 | testing.expectEqual(A.a, nameCast(A, A.a)); | |
| 257 | testing.expectEqual(A.a, nameCast(A, B.a)); | |
| 258 | testing.expectEqual(A.a, nameCast(A, "a")); | |
| 259 | testing.expectEqual(A.a, nameCast(A, @as(*const[1]u8, "a"))); | |
| 260 | testing.expectEqual(A.a, nameCast(A, @as([:0]const u8, "a"))); | |
| 261 | testing.expectEqual(A.a, nameCast(A, @as([]const u8, "a"))); | |
| 262 | ||
| 263 | testing.expectEqual(B.a, nameCast(B, .a)); | |
| 264 | testing.expectEqual(B.a, nameCast(B, A.a)); | |
| 265 | testing.expectEqual(B.a, nameCast(B, B.a)); | |
| 266 | testing.expectEqual(B.a, nameCast(B, "a")); | |
| 267 | ||
| 268 | testing.expectEqual(B.b, nameCast(B, .b)); | |
| 269 | testing.expectEqual(B.b, nameCast(B, A.b)); | |
| 270 | testing.expectEqual(B.b, nameCast(B, B.b)); | |
| 271 | testing.expectEqual(B.b, nameCast(B, "b")); | |
| 272 | } | |
| 273 | ||
| 274 | /// A set of enum elements, backed by a bitfield. If the enum | |
| 275 | /// is not dense, a mapping will be constructed from enum values | |
| 276 | /// to dense indices. This type does no dynamic allocation and | |
| 277 | /// can be copied by value. | |
| 278 | pub fn EnumSet(comptime E: type) type { | |
| 279 | const mixin = struct { | |
| 280 | fn EnumSetExt(comptime Self: type) type { | |
| 281 | const Indexer = Self.Indexer; | |
| 282 | return struct { | |
| 283 | /// Initializes the set using a struct of bools | |
| 284 | pub fn init(init_values: EnumFieldStruct(E, bool, false)) Self { | |
| 285 | var result = Self{}; | |
| 286 | comptime var i: usize = 0; | |
| 287 | inline while (i < Self.len) : (i += 1) { | |
| 288 | comptime const key = Indexer.keyForIndex(i); | |
| 289 | comptime const tag = @tagName(key); | |
| 290 | if (@field(init_values, tag)) { | |
| 291 | result.bits.set(i); | |
| 292 | } | |
| 293 | } | |
| 294 | return result; | |
| 295 | } | |
| 296 | }; | |
| 297 | } | |
| 298 | }; | |
| 299 | return IndexedSet(EnumIndexer(E), mixin.EnumSetExt); | |
| 300 | } | |
| 301 | ||
| 302 | /// A map keyed by an enum, backed by a bitfield and a dense array. | |
| 303 | /// If the enum is not dense, a mapping will be constructed from | |
| 304 | /// enum values to dense indices. This type does no dynamic | |
| 305 | /// allocation and can be copied by value. | |
| 306 | pub fn EnumMap(comptime E: type, comptime V: type) type { | |
| 307 | const mixin = struct { | |
| 308 | fn EnumMapExt(comptime Self: type) type { | |
| 309 | const Indexer = Self.Indexer; | |
| 310 | return struct { | |
| 311 | /// Initializes the map using a sparse struct of optionals | |
| 312 | pub fn init(init_values: EnumFieldStruct(E, ?V, @as(?V, null))) Self { | |
| 313 | var result = Self{}; | |
| 314 | comptime var i: usize = 0; | |
| 315 | inline while (i < Self.len) : (i += 1) { | |
| 316 | comptime const key = Indexer.keyForIndex(i); | |
| 317 | comptime const tag = @tagName(key); | |
| 318 | if (@field(init_values, tag)) |*v| { | |
| 319 | result.bits.set(i); | |
| 320 | result.values[i] = v.*; | |
| 321 | } | |
| 322 | } | |
| 323 | return result; | |
| 324 | } | |
| 325 | /// Initializes a full mapping with all keys set to value. | |
| 326 | /// Consider using EnumArray instead if the map will remain full. | |
| 327 | pub fn initFull(value: V) Self { | |
| 328 | var result = Self{ | |
| 329 | .bits = Self.BitSet.initFull(), | |
| 330 | .values = undefined, | |
| 331 | }; | |
| 332 | std.mem.set(V, &result.values, value); | |
| 333 | return result; | |
| 334 | } | |
| 335 | /// Initializes a full mapping with supplied values. | |
| 336 | /// Consider using EnumArray instead if the map will remain full. | |
| 337 | pub fn initFullWith(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { | |
| 338 | return initFullWithDefault(@as(?V, null), init_values); | |
| 339 | } | |
| 340 | /// Initializes a full mapping with a provided default. | |
| 341 | /// Consider using EnumArray instead if the map will remain full. | |
| 342 | pub fn initFullWithDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { | |
| 343 | var result = Self{ | |
| 344 | .bits = Self.BitSet.initFull(), | |
| 345 | .values = undefined, | |
| 346 | }; | |
| 347 | comptime var i: usize = 0; | |
| 348 | inline while (i < Self.len) : (i += 1) { | |
| 349 | comptime const key = Indexer.keyForIndex(i); | |
| 350 | comptime const tag = @tagName(key); | |
| 351 | result.values[i] = @field(init_values, tag); | |
| 352 | } | |
| 353 | return result; | |
| 354 | } | |
| 355 | }; | |
| 356 | } | |
| 357 | }; | |
| 358 | return IndexedMap(EnumIndexer(E), V, mixin.EnumMapExt); | |
| 359 | } | |
| 360 | ||
| 361 | /// An array keyed by an enum, backed by a dense array. | |
| 362 | /// If the enum is not dense, a mapping will be constructed from | |
| 363 | /// enum values to dense indices. This type does no dynamic | |
| 364 | /// allocation and can be copied by value. | |
| 365 | pub fn EnumArray(comptime E: type, comptime V: type) type { | |
| 366 | const mixin = struct { | |
| 367 | fn EnumArrayExt(comptime Self: type) type { | |
| 368 | const Indexer = Self.Indexer; | |
| 369 | return struct { | |
| 370 | /// Initializes all values in the enum array | |
| 371 | pub fn init(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { | |
| 372 | return initDefault(@as(?V, null), init_values); | |
| 373 | } | |
| 374 | ||
| 375 | /// Initializes values in the enum array, with the specified default. | |
| 376 | pub fn initDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { | |
| 377 | var result = Self{ .values = undefined }; | |
| 378 | comptime var i: usize = 0; | |
| 379 | inline while (i < Self.len) : (i += 1) { | |
| 380 | const key = comptime Indexer.keyForIndex(i); | |
| 381 | const tag = @tagName(key); | |
| 382 | result.values[i] = @field(init_values, tag); | |
| 383 | } | |
| 384 | return result; | |
| 385 | } | |
| 386 | }; | |
| 387 | } | |
| 388 | }; | |
| 389 | return IndexedArray(EnumIndexer(E), V, mixin.EnumArrayExt); | |
| 390 | } | |
| 391 | ||
| 392 | /// Pass this function as the Ext parameter to Indexed* if you | |
| 393 | /// do not want to attach any extensions. This parameter was | |
| 394 | /// originally an optional, but optional generic functions | |
| 395 | /// seem to be broken at the moment. | |
| 396 | /// TODO: Once #8169 is fixed, consider switching this param | |
| 397 | /// back to an optional. | |
| 398 | pub fn NoExtension(comptime Self: type) type { | |
| 399 | return NoExt; | |
| 400 | } | |
| 401 | const NoExt = struct{}; | |
| 402 | ||
| 403 | /// A set type with an Indexer mapping from keys to indices. | |
| 404 | /// Presence or absence is stored as a dense bitfield. This | |
| 405 | /// type does no allocation and can be copied by value. | |
| 406 | pub fn IndexedSet(comptime I: type, comptime Ext: fn(type)type) type { | |
| 407 | comptime ensureIndexer(I); | |
| 408 | return struct { | |
| 409 | const Self = @This(); | |
| 410 | ||
| 411 | pub usingnamespace Ext(Self); | |
| 412 | ||
| 413 | /// The indexing rules for converting between keys and indices. | |
| 414 | pub const Indexer = I; | |
| 415 | /// The element type for this set. | |
| 416 | pub const Key = Indexer.Key; | |
| 417 | ||
| 418 | const BitSet = std.StaticBitSet(Indexer.count); | |
| 419 | ||
| 420 | /// The maximum number of items in this set. | |
| 421 | pub const len = Indexer.count; | |
| 422 | ||
| 423 | bits: BitSet = BitSet.initEmpty(), | |
| 424 | ||
| 425 | /// Returns a set containing all possible keys. | |
| 426 | pub fn initFull() Self { | |
| 427 | return .{ .bits = BitSet.initFull() }; | |
| 428 | } | |
| 429 | ||
| 430 | /// Returns the number of keys in the set. | |
| 431 | pub fn count(self: Self) usize { | |
| 432 | return self.bits.count(); | |
| 433 | } | |
| 434 | ||
| 435 | /// Checks if a key is in the set. | |
| 436 | pub fn contains(self: Self, key: Key) bool { | |
| 437 | return self.bits.isSet(Indexer.indexOf(key)); | |
| 438 | } | |
| 439 | ||
| 440 | /// Puts a key in the set. | |
| 441 | pub fn insert(self: *Self, key: Key) void { | |
| 442 | self.bits.set(Indexer.indexOf(key)); | |
| 443 | } | |
| 444 | ||
| 445 | /// Removes a key from the set. | |
| 446 | pub fn remove(self: *Self, key: Key) void { | |
| 447 | self.bits.unset(Indexer.indexOf(key)); | |
| 448 | } | |
| 449 | ||
| 450 | /// Changes the presence of a key in the set to match the passed bool. | |
| 451 | pub fn setPresent(self: *Self, key: Key, present: bool) void { | |
| 452 | self.bits.setValue(Indexer.indexOf(key), present); | |
| 453 | } | |
| 454 | ||
| 455 | /// Toggles the presence of a key in the set. If the key is in | |
| 456 | /// the set, removes it. Otherwise adds it. | |
| 457 | pub fn toggle(self: *Self, key: Key) void { | |
| 458 | self.bits.toggle(Indexer.indexOf(key)); | |
| 459 | } | |
| 460 | ||
| 461 | /// Toggles the presence of all keys in the passed set. | |
| 462 | pub fn toggleSet(self: *Self, other: Self) void { | |
| 463 | self.bits.toggleSet(other.bits); | |
| 464 | } | |
| 465 | ||
| 466 | /// Toggles all possible keys in the set. | |
| 467 | pub fn toggleAll(self: *Self) void { | |
| 468 | self.bits.toggleAll(); | |
| 469 | } | |
| 470 | ||
| 471 | /// Adds all keys in the passed set to this set. | |
| 472 | pub fn setUnion(self: *Self, other: Self) void { | |
| 473 | self.bits.setUnion(other.bits); | |
| 474 | } | |
| 475 | ||
| 476 | /// Removes all keys which are not in the passed set. | |
| 477 | pub fn setIntersection(self: *Self, other: Self) void { | |
| 478 | self.bits.setIntersection(other.bits); | |
| 479 | } | |
| 480 | ||
| 481 | /// Returns an iterator over this set, which iterates in | |
| 482 | /// index order. Modifications to the set during iteration | |
| 483 | /// may or may not be observed by the iterator, but will | |
| 484 | /// not invalidate it. | |
| 485 | pub fn iterator(self: *Self) Iterator { | |
| 486 | return .{ .inner = self.bits.iterator(.{}) }; | |
| 487 | } | |
| 488 | ||
| 489 | pub const Iterator = struct { | |
| 490 | inner: BitSet.Iterator(.{}), | |
| 491 | ||
| 492 | pub fn next(self: *Iterator) ?Key { | |
| 493 | return if (self.inner.next()) |index| | |
| 494 | Indexer.keyForIndex(index) | |
| 495 | else null; | |
| 496 | } | |
| 497 | }; | |
| 498 | }; | |
| 499 | } | |
| 500 | ||
| 501 | /// A map from keys to values, using an index lookup. Uses a | |
| 502 | /// bitfield to track presence and a dense array of values. | |
| 503 | /// This type does no allocation and can be copied by value. | |
| 504 | pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type { | |
| 505 | comptime ensureIndexer(I); | |
| 506 | return struct { | |
| 507 | const Self = @This(); | |
| 508 | ||
| 509 | pub usingnamespace Ext(Self); | |
| 510 | ||
| 511 | /// The index mapping for this map | |
| 512 | pub const Indexer = I; | |
| 513 | /// The key type used to index this map | |
| 514 | pub const Key = Indexer.Key; | |
| 515 | /// The value type stored in this map | |
| 516 | pub const Value = V; | |
| 517 | /// The number of possible keys in the map | |
| 518 | pub const len = Indexer.count; | |
| 519 | ||
| 520 | const BitSet = std.StaticBitSet(Indexer.count); | |
| 521 | ||
| 522 | /// Bits determining whether items are in the map | |
| 523 | bits: BitSet = BitSet.initEmpty(), | |
| 524 | /// Values of items in the map. If the associated | |
| 525 | /// bit is zero, the value is undefined. | |
| 526 | values: [Indexer.count]Value = undefined, | |
| 527 | ||
| 528 | /// The number of items in the map. | |
| 529 | pub fn count(self: Self) usize { | |
| 530 | return self.bits.count(); | |
| 531 | } | |
| 532 | ||
| 533 | /// Checks if the map contains an item. | |
| 534 | pub fn contains(self: Self, key: Key) bool { | |
| 535 | return self.bits.isSet(Indexer.indexOf(key)); | |
| 536 | } | |
| 537 | ||
| 538 | /// Gets the value associated with a key. | |
| 539 | /// If the key is not in the map, returns null. | |
| 540 | pub fn get(self: Self, key: Key) ?Value { | |
| 541 | const index = Indexer.indexOf(key); | |
| 542 | return if (self.bits.isSet(index)) self.values[index] else null; | |
| 543 | } | |
| 544 | ||
| 545 | /// Gets the value associated with a key, which must | |
| 546 | /// exist in the map. | |
| 547 | pub fn getAssertContains(self: Self, key: Key) Value { | |
| 548 | const index = Indexer.indexOf(key); | |
| 549 | assert(self.bits.isSet(index)); | |
| 550 | return self.values[index]; | |
| 551 | } | |
| 552 | ||
| 553 | /// Gets the address of the value associated with a key. | |
| 554 | /// If the key is not in the map, returns null. | |
| 555 | pub fn getPtr(self: *Self, key: Key) ?*Value { | |
| 556 | const index = Indexer.indexOf(key); | |
| 557 | return if (self.bits.isSet(index)) &self.values[index] else null; | |
| 558 | } | |
| 559 | ||
| 560 | /// Gets the address of the const value associated with a key. | |
| 561 | /// If the key is not in the map, returns null. | |
| 562 | pub fn getPtrConst(self: *const Self, key: Key) ?*const Value { | |
| 563 | const index = Indexer.indexOf(key); | |
| 564 | return if (self.bits.isSet(index)) &self.values[index] else null; | |
| 565 | } | |
| 566 | ||
| 567 | /// Gets the address of the value associated with a key. | |
| 568 | /// The key must be present in the map. | |
| 569 | pub fn getPtrAssertContains(self: *Self, key: Key) *Value { | |
| 570 | const index = Indexer.indexOf(key); | |
| 571 | assert(self.bits.isSet(index)); | |
| 572 | return &self.values[index]; | |
| 573 | } | |
| 574 | ||
| 575 | /// Adds the key to the map with the supplied value. | |
| 576 | /// If the key is already in the map, overwrites the value. | |
| 577 | pub fn put(self: *Self, key: Key, value: Value) void { | |
| 578 | const index = Indexer.indexOf(key); | |
| 579 | self.bits.set(index); | |
| 580 | self.values[index] = value; | |
| 581 | } | |
| 582 | ||
| 583 | /// Adds the key to the map with an undefined value. | |
| 584 | /// If the key is already in the map, the value becomes undefined. | |
| 585 | /// A pointer to the value is returned, which should be | |
| 586 | /// used to initialize the value. | |
| 587 | pub fn putUninitialized(self: *Self, key: Key) *Value { | |
| 588 | const index = Indexer.indexOf(key); | |
| 589 | self.bits.set(index); | |
| 590 | self.values[index] = undefined; | |
| 591 | return &self.values[index]; | |
| 592 | } | |
| 593 | ||
| 594 | /// Sets the value associated with the key in the map, | |
| 595 | /// and returns the old value. If the key was not in | |
| 596 | /// the map, returns null. | |
| 597 | pub fn fetchPut(self: *Self, key: Key, value: Value) ?Value { | |
| 598 | const index = Indexer.indexOf(key); | |
| 599 | const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; | |
| 600 | self.bits.set(index); | |
| 601 | self.values[index] = value; | |
| 602 | return result; | |
| 603 | } | |
| 604 | ||
| 605 | /// Removes a key from the map. If the key was not in the map, | |
| 606 | /// does nothing. | |
| 607 | pub fn remove(self: *Self, key: Key) void { | |
| 608 | const index = Indexer.indexOf(key); | |
| 609 | self.bits.unset(index); | |
| 610 | self.values[index] = undefined; | |
| 611 | } | |
| 612 | ||
| 613 | /// Removes a key from the map, and returns the old value. | |
| 614 | /// If the key was not in the map, returns null. | |
| 615 | pub fn fetchRemove(self: *Self, key: Key) ?Value { | |
| 616 | const index = Indexer.indexOf(key); | |
| 617 | const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; | |
| 618 | self.bits.unset(index); | |
| 619 | self.values[index] = undefined; | |
| 620 | return result; | |
| 621 | } | |
| 622 | ||
| 623 | /// Returns an iterator over the map, which visits items in index order. | |
| 624 | /// Modifications to the underlying map may or may not be observed by | |
| 625 | /// the iterator, but will not invalidate it. | |
| 626 | pub fn iterator(self: *Self) Iterator { | |
| 627 | return .{ | |
| 628 | .inner = self.bits.iterator(.{}), | |
| 629 | .values = &self.values, | |
| 630 | }; | |
| 631 | } | |
| 632 | ||
| 633 | /// An entry in the map. | |
| 634 | pub const Entry = struct { | |
| 635 | /// The key associated with this entry. | |
| 636 | /// Modifying this key will not change the map. | |
| 637 | key: Key, | |
| 638 | ||
| 639 | /// A pointer to the value in the map associated | |
| 640 | /// with this key. Modifications through this | |
| 641 | /// pointer will modify the underlying data. | |
| 642 | value: *Value, | |
| 643 | }; | |
| 644 | ||
| 645 | pub const Iterator = struct { | |
| 646 | inner: BitSet.Iterator(.{}), | |
| 647 | values: *[Indexer.count]Value, | |
| 648 | ||
| 649 | pub fn next(self: *Iterator) ?Entry { | |
| 650 | return if (self.inner.next()) |index| | |
| 651 | Entry{ | |
| 652 | .key = Indexer.keyForIndex(index), | |
| 653 | .value = &self.values[index], | |
| 654 | } | |
| 655 | else null; | |
| 656 | } | |
| 657 | }; | |
| 658 | }; | |
| 659 | } | |
| 660 | ||
| 661 | /// A dense array of values, using an indexed lookup. | |
| 662 | /// This type does no allocation and can be copied by value. | |
| 663 | pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type { | |
| 664 | comptime ensureIndexer(I); | |
| 665 | return struct { | |
| 666 | const Self = @This(); | |
| 667 | ||
| 668 | pub usingnamespace Ext(Self); | |
| 669 | ||
| 670 | /// The index mapping for this map | |
| 671 | pub const Indexer = I; | |
| 672 | /// The key type used to index this map | |
| 673 | pub const Key = Indexer.Key; | |
| 674 | /// The value type stored in this map | |
| 675 | pub const Value = V; | |
| 676 | /// The number of possible keys in the map | |
| 677 | pub const len = Indexer.count; | |
| 678 | ||
| 679 | values: [Indexer.count]Value, | |
| 680 | ||
| 681 | pub fn initUndefined() Self { | |
| 682 | return Self{ .values = undefined }; | |
| 683 | } | |
| 684 | ||
| 685 | pub fn initFill(v: Value) Self { | |
| 686 | var self: Self = undefined; | |
| 687 | std.mem.set(Value, &self.values, v); | |
| 688 | return self; | |
| 689 | } | |
| 690 | ||
| 691 | /// Returns the value in the array associated with a key. | |
| 692 | pub fn get(self: Self, key: Key) Value { | |
| 693 | return self.values[Indexer.indexOf(key)]; | |
| 694 | } | |
| 695 | ||
| 696 | /// Returns a pointer to the slot in the array associated with a key. | |
| 697 | pub fn getPtr(self: *Self, key: Key) *Value { | |
| 698 | return &self.values[Indexer.indexOf(key)]; | |
| 699 | } | |
| 700 | ||
| 701 | /// Returns a const pointer to the slot in the array associated with a key. | |
| 702 | pub fn getPtrConst(self: *const Self, key: Key) *const Value { | |
| 703 | return &self.values[Indexer.indexOf(key)]; | |
| 704 | } | |
| 705 | ||
| 706 | /// Sets the value in the slot associated with a key. | |
| 707 | pub fn set(self: *Self, key: Key, value: Value) void { | |
| 708 | self.values[Indexer.indexOf(key)] = value; | |
| 709 | } | |
| 710 | ||
| 711 | /// Iterates over the items in the array, in index order. | |
| 712 | pub fn iterator(self: *Self) Iterator { | |
| 713 | return .{ | |
| 714 | .values = &self.values, | |
| 715 | }; | |
| 716 | } | |
| 717 | ||
| 718 | /// An entry in the array. | |
| 719 | pub const Entry = struct { | |
| 720 | /// The key associated with this entry. | |
| 721 | /// Modifying this key will not change the array. | |
| 722 | key: Key, | |
| 723 | ||
| 724 | /// A pointer to the value in the array associated | |
| 725 | /// with this key. Modifications through this | |
| 726 | /// pointer will modify the underlying data. | |
| 727 | value: *Value, | |
| 728 | }; | |
| 729 | ||
| 730 | pub const Iterator = struct { | |
| 731 | index: usize = 0, | |
| 732 | values: *[Indexer.count]Value, | |
| 733 | ||
| 734 | pub fn next(self: *Iterator) ?Entry { | |
| 735 | const index = self.index; | |
| 736 | if (index < Indexer.count) { | |
| 737 | self.index += 1; | |
| 738 | return Entry{ | |
| 739 | .key = Indexer.keyForIndex(index), | |
| 740 | .value = &self.values[index], | |
| 741 | }; | |
| 742 | } | |
| 743 | return null; | |
| 744 | } | |
| 745 | }; | |
| 746 | }; | |
| 747 | } | |
| 748 | ||
| 749 | /// Verifies that a type is a valid Indexer, providing a helpful | |
| 750 | /// compile error if not. An Indexer maps a comptime known set | |
| 751 | /// of keys to a dense set of zero-based indices. | |
| 752 | /// The indexer interface must look like this: | |
| 753 | /// ``` | |
| 754 | /// struct { | |
| 755 | /// /// The key type which this indexer converts to indices | |
| 756 | /// pub const Key: type, | |
| 757 | /// /// The number of indexes in the dense mapping | |
| 758 | /// pub const count: usize, | |
| 759 | /// /// Converts from a key to an index | |
| 760 | /// pub fn indexOf(Key) usize; | |
| 761 | /// /// Converts from an index to a key | |
| 762 | /// pub fn keyForIndex(usize) Key; | |
| 763 | /// } | |
| 764 | /// ``` | |
| 765 | pub fn ensureIndexer(comptime T: type) void { | |
| 766 | comptime { | |
| 767 | if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type."); | |
| 768 | if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type."); | |
| 769 | if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: usize."); | |
| 770 | if (@TypeOf(T.count) != usize) @compileError("Indexer.count must be a usize."); | |
| 771 | if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn(Key)usize."); | |
| 772 | if (@TypeOf(T.indexOf) != fn(T.Key)usize) @compileError("Indexer must have decl indexOf: fn(Key)usize."); | |
| 773 | if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn(usize)Key."); | |
| 774 | if (@TypeOf(T.keyForIndex) != fn(usize)T.Key) @compileError("Indexer.keyForIndex must be a fn(usize)Key."); | |
| 775 | } | |
| 776 | } | |
| 777 | ||
| 778 | test "std.enums.ensureIndexer" { | |
| 779 | ensureIndexer(struct { | |
| 780 | pub const Key = u32; | |
| 781 | pub const count: usize = 8; | |
| 782 | pub fn indexOf(k: Key) usize { | |
| 783 | return @intCast(usize, k); | |
| 784 | } | |
| 785 | pub fn keyForIndex(index: usize) Key { | |
| 786 | return @intCast(Key, index); | |
| 787 | } | |
| 788 | }); | |
| 789 | } | |
| 790 | ||
| 791 | fn ascByValue(ctx: void, comptime a: EnumField, comptime b: EnumField) bool { | |
| 792 | return a.value < b.value; | |
| 793 | } | |
| 794 | pub fn EnumIndexer(comptime E: type) type { | |
| 795 | if (!@typeInfo(E).Enum.is_exhaustive) { | |
| 796 | @compileError("Cannot create an enum indexer for a non-exhaustive enum."); | |
| 797 | } | |
| 798 | ||
| 799 | const const_fields = uniqueFields(E); | |
| 800 | var fields = const_fields[0..const_fields.len].*; | |
| 801 | if (fields.len == 0) { | |
| 802 | return struct { | |
| 803 | pub const Key = E; | |
| 804 | pub const count: usize = 0; | |
| 805 | pub fn indexOf(e: E) usize { unreachable; } | |
| 806 | pub fn keyForIndex(i: usize) E { unreachable; } | |
| 807 | }; | |
| 808 | } | |
| 809 | std.sort.sort(EnumField, &fields, {}, ascByValue); | |
| 810 | const min = fields[0].value; | |
| 811 | const max = fields[fields.len-1].value; | |
| 812 | if (max - min == fields.len-1) { | |
| 813 | return struct { | |
| 814 | pub const Key = E; | |
| 815 | pub const count = fields.len; | |
| 816 | pub fn indexOf(e: E) usize { | |
| 817 | return @intCast(usize, @enumToInt(e) - min); | |
| 818 | } | |
| 819 | pub fn keyForIndex(i: usize) E { | |
| 820 | // TODO fix addition semantics. This calculation | |
| 821 | // gives up some safety to avoid artificially limiting | |
| 822 | // the range of signed enum values to max_isize. | |
| 823 | const enum_value = if (min < 0) @bitCast(isize, i) +% min else i + min; | |
| 824 | return @intToEnum(E, @intCast(std.meta.Tag(E), enum_value)); | |
| 825 | } | |
| 826 | }; | |
| 827 | } | |
| 828 | ||
| 829 | const keys = valuesFromFields(E, &fields); | |
| 830 | ||
| 831 | return struct { | |
| 832 | pub const Key = E; | |
| 833 | pub const count = fields.len; | |
| 834 | pub fn indexOf(e: E) usize { | |
| 835 | for (keys) |k, i| { | |
| 836 | if (k == e) return i; | |
| 837 | } | |
| 838 | unreachable; | |
| 839 | } | |
| 840 | pub fn keyForIndex(i: usize) E { | |
| 841 | return keys[i]; | |
| 842 | } | |
| 843 | }; | |
| 844 | } | |
| 845 | ||
| 846 | test "std.enums.EnumIndexer dense zeroed" { | |
| 847 | const E = enum{ b = 1, a = 0, c = 2 }; | |
| 848 | const Indexer = EnumIndexer(E); | |
| 849 | ensureIndexer(Indexer); | |
| 850 | testing.expectEqual(E, Indexer.Key); | |
| 851 | testing.expectEqual(@as(usize, 3), Indexer.count); | |
| 852 | ||
| 853 | testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); | |
| 854 | testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); | |
| 855 | testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); | |
| 856 | ||
| 857 | testing.expectEqual(E.a, Indexer.keyForIndex(0)); | |
| 858 | testing.expectEqual(E.b, Indexer.keyForIndex(1)); | |
| 859 | testing.expectEqual(E.c, Indexer.keyForIndex(2)); | |
| 860 | } | |
| 861 | ||
| 862 | test "std.enums.EnumIndexer dense positive" { | |
| 863 | const E = enum(u4) { c = 6, a = 4, b = 5 }; | |
| 864 | const Indexer = EnumIndexer(E); | |
| 865 | ensureIndexer(Indexer); | |
| 866 | testing.expectEqual(E, Indexer.Key); | |
| 867 | testing.expectEqual(@as(usize, 3), Indexer.count); | |
| 868 | ||
| 869 | testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); | |
| 870 | testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); | |
| 871 | testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); | |
| 872 | ||
| 873 | testing.expectEqual(E.a, Indexer.keyForIndex(0)); | |
| 874 | testing.expectEqual(E.b, Indexer.keyForIndex(1)); | |
| 875 | testing.expectEqual(E.c, Indexer.keyForIndex(2)); | |
| 876 | } | |
| 877 | ||
| 878 | test "std.enums.EnumIndexer dense negative" { | |
| 879 | const E = enum(i4) { a = -6, c = -4, b = -5 }; | |
| 880 | const Indexer = EnumIndexer(E); | |
| 881 | ensureIndexer(Indexer); | |
| 882 | testing.expectEqual(E, Indexer.Key); | |
| 883 | testing.expectEqual(@as(usize, 3), Indexer.count); | |
| 884 | ||
| 885 | testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); | |
| 886 | testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); | |
| 887 | testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); | |
| 888 | ||
| 889 | testing.expectEqual(E.a, Indexer.keyForIndex(0)); | |
| 890 | testing.expectEqual(E.b, Indexer.keyForIndex(1)); | |
| 891 | testing.expectEqual(E.c, Indexer.keyForIndex(2)); | |
| 892 | } | |
| 893 | ||
| 894 | test "std.enums.EnumIndexer sparse" { | |
| 895 | const E = enum(i4) { a = -2, c = 6, b = 4 }; | |
| 896 | const Indexer = EnumIndexer(E); | |
| 897 | ensureIndexer(Indexer); | |
| 898 | testing.expectEqual(E, Indexer.Key); | |
| 899 | testing.expectEqual(@as(usize, 3), Indexer.count); | |
| 900 | ||
| 901 | testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); | |
| 902 | testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); | |
| 903 | testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); | |
| 904 | ||
| 905 | testing.expectEqual(E.a, Indexer.keyForIndex(0)); | |
| 906 | testing.expectEqual(E.b, Indexer.keyForIndex(1)); | |
| 907 | testing.expectEqual(E.c, Indexer.keyForIndex(2)); | |
| 908 | } | |
| 909 | ||
| 910 | test "std.enums.EnumIndexer repeats" { | |
| 911 | const E = extern enum{ a = -2, c = 6, b = 4, b2 = 4 }; | |
| 912 | const Indexer = EnumIndexer(E); | |
| 913 | ensureIndexer(Indexer); | |
| 914 | testing.expectEqual(E, Indexer.Key); | |
| 915 | testing.expectEqual(@as(usize, 3), Indexer.count); | |
| 916 | ||
| 917 | testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); | |
| 918 | testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); | |
| 919 | testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); | |
| 920 | ||
| 921 | testing.expectEqual(E.a, Indexer.keyForIndex(0)); | |
| 922 | testing.expectEqual(E.b, Indexer.keyForIndex(1)); | |
| 923 | testing.expectEqual(E.c, Indexer.keyForIndex(2)); | |
| 924 | } | |
| 925 | ||
| 926 | test "std.enums.EnumSet" { | |
| 927 | const E = extern enum { a, b, c, d, e = 0 }; | |
| 928 | const Set = EnumSet(E); | |
| 929 | testing.expectEqual(E, Set.Key); | |
| 930 | testing.expectEqual(EnumIndexer(E), Set.Indexer); | |
| 931 | testing.expectEqual(@as(usize, 4), Set.len); | |
| 932 | ||
| 933 | // Empty sets | |
| 934 | const empty = Set{}; | |
| 935 | comptime testing.expect(empty.count() == 0); | |
| 936 | ||
| 937 | var empty_b = Set.init(.{}); | |
| 938 | testing.expect(empty_b.count() == 0); | |
| 939 | ||
| 940 | const empty_c = comptime Set.init(.{}); | |
| 941 | comptime testing.expect(empty_c.count() == 0); | |
| 942 | ||
| 943 | const full = Set.initFull(); | |
| 944 | testing.expect(full.count() == Set.len); | |
| 945 | ||
| 946 | const full_b = comptime Set.initFull(); | |
| 947 | comptime testing.expect(full_b.count() == Set.len); | |
| 948 | ||
| 949 | testing.expectEqual(false, empty.contains(.a)); | |
| 950 | testing.expectEqual(false, empty.contains(.b)); | |
| 951 | testing.expectEqual(false, empty.contains(.c)); | |
| 952 | testing.expectEqual(false, empty.contains(.d)); | |
| 953 | testing.expectEqual(false, empty.contains(.e)); | |
| 954 | { | |
| 955 | var iter = empty_b.iterator(); | |
| 956 | testing.expectEqual(@as(?E, null), iter.next()); | |
| 957 | } | |
| 958 | ||
| 959 | var mut = Set.init(.{ | |
| 960 | .a=true, .c=true, | |
| 961 | }); | |
| 962 | testing.expectEqual(@as(usize, 2), mut.count()); | |
| 963 | testing.expectEqual(true, mut.contains(.a)); | |
| 964 | testing.expectEqual(false, mut.contains(.b)); | |
| 965 | testing.expectEqual(true, mut.contains(.c)); | |
| 966 | testing.expectEqual(false, mut.contains(.d)); | |
| 967 | testing.expectEqual(true, mut.contains(.e)); // aliases a | |
| 968 | { | |
| 969 | var it = mut.iterator(); | |
| 970 | testing.expectEqual(@as(?E, .a), it.next()); | |
| 971 | testing.expectEqual(@as(?E, .c), it.next()); | |
| 972 | testing.expectEqual(@as(?E, null), it.next()); | |
| 973 | } | |
| 974 | ||
| 975 | mut.toggleAll(); | |
| 976 | testing.expectEqual(@as(usize, 2), mut.count()); | |
| 977 | testing.expectEqual(false, mut.contains(.a)); | |
| 978 | testing.expectEqual(true, mut.contains(.b)); | |
| 979 | testing.expectEqual(false, mut.contains(.c)); | |
| 980 | testing.expectEqual(true, mut.contains(.d)); | |
| 981 | testing.expectEqual(false, mut.contains(.e)); // aliases a | |
| 982 | { | |
| 983 | var it = mut.iterator(); | |
| 984 | testing.expectEqual(@as(?E, .b), it.next()); | |
| 985 | testing.expectEqual(@as(?E, .d), it.next()); | |
| 986 | testing.expectEqual(@as(?E, null), it.next()); | |
| 987 | } | |
| 988 | ||
| 989 | mut.toggleSet(Set.init(.{ .a=true, .b=true })); | |
| 990 | testing.expectEqual(@as(usize, 2), mut.count()); | |
| 991 | testing.expectEqual(true, mut.contains(.a)); | |
| 992 | testing.expectEqual(false, mut.contains(.b)); | |
| 993 | testing.expectEqual(false, mut.contains(.c)); | |
| 994 | testing.expectEqual(true, mut.contains(.d)); | |
| 995 | testing.expectEqual(true, mut.contains(.e)); // aliases a | |
| 996 | ||
| 997 | mut.setUnion(Set.init(.{ .a=true, .b=true })); | |
| 998 | testing.expectEqual(@as(usize, 3), mut.count()); | |
| 999 | testing.expectEqual(true, mut.contains(.a)); | |
| 1000 | testing.expectEqual(true, mut.contains(.b)); | |
| 1001 | testing.expectEqual(false, mut.contains(.c)); | |
| 1002 | testing.expectEqual(true, mut.contains(.d)); | |
| 1003 | ||
| 1004 | mut.remove(.c); | |
| 1005 | mut.remove(.b); | |
| 1006 | testing.expectEqual(@as(usize, 2), mut.count()); | |
| 1007 | testing.expectEqual(true, mut.contains(.a)); | |
| 1008 | testing.expectEqual(false, mut.contains(.b)); | |
| 1009 | testing.expectEqual(false, mut.contains(.c)); | |
| 1010 | testing.expectEqual(true, mut.contains(.d)); | |
| 1011 | ||
| 1012 | mut.setIntersection(Set.init(.{ .a=true, .b=true })); | |
| 1013 | testing.expectEqual(@as(usize, 1), mut.count()); | |
| 1014 | testing.expectEqual(true, mut.contains(.a)); | |
| 1015 | testing.expectEqual(false, mut.contains(.b)); | |
| 1016 | testing.expectEqual(false, mut.contains(.c)); | |
| 1017 | testing.expectEqual(false, mut.contains(.d)); | |
| 1018 | ||
| 1019 | mut.insert(.a); | |
| 1020 | mut.insert(.b); | |
| 1021 | testing.expectEqual(@as(usize, 2), mut.count()); | |
| 1022 | testing.expectEqual(true, mut.contains(.a)); | |
| 1023 | testing.expectEqual(true, mut.contains(.b)); | |
| 1024 | testing.expectEqual(false, mut.contains(.c)); | |
| 1025 | testing.expectEqual(false, mut.contains(.d)); | |
| 1026 | ||
| 1027 | mut.setPresent(.a, false); | |
| 1028 | mut.toggle(.b); | |
| 1029 | mut.toggle(.c); | |
| 1030 | mut.setPresent(.d, true); | |
| 1031 | testing.expectEqual(@as(usize, 2), mut.count()); | |
| 1032 | testing.expectEqual(false, mut.contains(.a)); | |
| 1033 | testing.expectEqual(false, mut.contains(.b)); | |
| 1034 | testing.expectEqual(true, mut.contains(.c)); | |
| 1035 | testing.expectEqual(true, mut.contains(.d)); | |
| 1036 | } | |
| 1037 | ||
| 1038 | test "std.enums.EnumArray void" { | |
| 1039 | const E = extern enum { a, b, c, d, e = 0 }; | |
| 1040 | const ArrayVoid = EnumArray(E, void); | |
| 1041 | testing.expectEqual(E, ArrayVoid.Key); | |
| 1042 | testing.expectEqual(EnumIndexer(E), ArrayVoid.Indexer); | |
| 1043 | testing.expectEqual(void, ArrayVoid.Value); | |
| 1044 | testing.expectEqual(@as(usize, 4), ArrayVoid.len); | |
| 1045 | ||
| 1046 | const undef = ArrayVoid.initUndefined(); | |
| 1047 | var inst = ArrayVoid.initFill({}); | |
| 1048 | const inst2 = ArrayVoid.init(.{ .a = {}, .b = {}, .c = {}, .d = {} }); | |
| 1049 | const inst3 = ArrayVoid.initDefault({}, .{}); | |
| 1050 | ||
| 1051 | _ = inst.get(.a); | |
| 1052 | _ = inst.getPtr(.b); | |
| 1053 | _ = inst.getPtrConst(.c); | |
| 1054 | inst.set(.a, {}); | |
| 1055 | ||
| 1056 | var it = inst.iterator(); | |
| 1057 | testing.expectEqual(E.a, it.next().?.key); | |
| 1058 | testing.expectEqual(E.b, it.next().?.key); | |
| 1059 | testing.expectEqual(E.c, it.next().?.key); | |
| 1060 | testing.expectEqual(E.d, it.next().?.key); | |
| 1061 | testing.expect(it.next() == null); | |
| 1062 | } | |
| 1063 | ||
| 1064 | test "std.enums.EnumArray sized" { | |
| 1065 | const E = extern enum { a, b, c, d, e = 0 }; | |
| 1066 | const Array = EnumArray(E, usize); | |
| 1067 | testing.expectEqual(E, Array.Key); | |
| 1068 | testing.expectEqual(EnumIndexer(E), Array.Indexer); | |
| 1069 | testing.expectEqual(usize, Array.Value); | |
| 1070 | testing.expectEqual(@as(usize, 4), Array.len); | |
| 1071 | ||
| 1072 | const undef = Array.initUndefined(); | |
| 1073 | var inst = Array.initFill(5); | |
| 1074 | const inst2 = Array.init(.{ .a = 1, .b = 2, .c = 3, .d = 4 }); | |
| 1075 | const inst3 = Array.initDefault(6, .{.b = 4, .c = 2}); | |
| 1076 | ||
| 1077 | testing.expectEqual(@as(usize, 5), inst.get(.a)); | |
| 1078 | testing.expectEqual(@as(usize, 5), inst.get(.b)); | |
| 1079 | testing.expectEqual(@as(usize, 5), inst.get(.c)); | |
| 1080 | testing.expectEqual(@as(usize, 5), inst.get(.d)); | |
| 1081 | ||
| 1082 | testing.expectEqual(@as(usize, 1), inst2.get(.a)); | |
| 1083 | testing.expectEqual(@as(usize, 2), inst2.get(.b)); | |
| 1084 | testing.expectEqual(@as(usize, 3), inst2.get(.c)); | |
| 1085 | testing.expectEqual(@as(usize, 4), inst2.get(.d)); | |
| 1086 | ||
| 1087 | testing.expectEqual(@as(usize, 6), inst3.get(.a)); | |
| 1088 | testing.expectEqual(@as(usize, 4), inst3.get(.b)); | |
| 1089 | testing.expectEqual(@as(usize, 2), inst3.get(.c)); | |
| 1090 | testing.expectEqual(@as(usize, 6), inst3.get(.d)); | |
| 1091 | ||
| 1092 | testing.expectEqual(&inst.values[0], inst.getPtr(.a)); | |
| 1093 | testing.expectEqual(&inst.values[1], inst.getPtr(.b)); | |
| 1094 | testing.expectEqual(&inst.values[2], inst.getPtr(.c)); | |
| 1095 | testing.expectEqual(&inst.values[3], inst.getPtr(.d)); | |
| 1096 | ||
| 1097 | testing.expectEqual(@as(*const usize, &inst.values[0]), inst.getPtrConst(.a)); | |
| 1098 | testing.expectEqual(@as(*const usize, &inst.values[1]), inst.getPtrConst(.b)); | |
| 1099 | testing.expectEqual(@as(*const usize, &inst.values[2]), inst.getPtrConst(.c)); | |
| 1100 | testing.expectEqual(@as(*const usize, &inst.values[3]), inst.getPtrConst(.d)); | |
| 1101 | ||
| 1102 | inst.set(.c, 8); | |
| 1103 | testing.expectEqual(@as(usize, 5), inst.get(.a)); | |
| 1104 | testing.expectEqual(@as(usize, 5), inst.get(.b)); | |
| 1105 | testing.expectEqual(@as(usize, 8), inst.get(.c)); | |
| 1106 | testing.expectEqual(@as(usize, 5), inst.get(.d)); | |
| 1107 | ||
| 1108 | var it = inst.iterator(); | |
| 1109 | const Entry = Array.Entry; | |
| 1110 | testing.expectEqual(@as(?Entry, Entry{ | |
| 1111 | .key = .a, | |
| 1112 | .value = &inst.values[0], | |
| 1113 | }), it.next()); | |
| 1114 | testing.expectEqual(@as(?Entry, Entry{ | |
| 1115 | .key = .b, | |
| 1116 | .value = &inst.values[1], | |
| 1117 | }), it.next()); | |
| 1118 | testing.expectEqual(@as(?Entry, Entry{ | |
| 1119 | .key = .c, | |
| 1120 | .value = &inst.values[2], | |
| 1121 | }), it.next()); | |
| 1122 | testing.expectEqual(@as(?Entry, Entry{ | |
| 1123 | .key = .d, | |
| 1124 | .value = &inst.values[3], | |
| 1125 | }), it.next()); | |
| 1126 | testing.expectEqual(@as(?Entry, null), it.next()); | |
| 1127 | } | |
| 1128 | ||
| 1129 | test "std.enums.EnumMap void" { | |
| 1130 | const E = extern enum { a, b, c, d, e = 0 }; | |
| 1131 | const Map = EnumMap(E, void); | |
| 1132 | testing.expectEqual(E, Map.Key); | |
| 1133 | testing.expectEqual(EnumIndexer(E), Map.Indexer); | |
| 1134 | testing.expectEqual(void, Map.Value); | |
| 1135 | testing.expectEqual(@as(usize, 4), Map.len); | |
| 1136 | ||
| 1137 | const b = Map.initFull({}); | |
| 1138 | testing.expectEqual(@as(usize, 4), b.count()); | |
| 1139 | ||
| 1140 | const c = Map.initFullWith(.{ .a = {}, .b = {}, .c = {}, .d = {} }); | |
| 1141 | testing.expectEqual(@as(usize, 4), c.count()); | |
| 1142 | ||
| 1143 | const d = Map.initFullWithDefault({}, .{ .b = {} }); | |
| 1144 | testing.expectEqual(@as(usize, 4), d.count()); | |
| 1145 | ||
| 1146 | var a = Map.init(.{ .b = {}, .d = {} }); | |
| 1147 | testing.expectEqual(@as(usize, 2), a.count()); | |
| 1148 | testing.expectEqual(false, a.contains(.a)); | |
| 1149 | testing.expectEqual(true, a.contains(.b)); | |
| 1150 | testing.expectEqual(false, a.contains(.c)); | |
| 1151 | testing.expectEqual(true, a.contains(.d)); | |
| 1152 | testing.expect(a.get(.a) == null); | |
| 1153 | testing.expect(a.get(.b) != null); | |
| 1154 | testing.expect(a.get(.c) == null); | |
| 1155 | testing.expect(a.get(.d) != null); | |
| 1156 | testing.expect(a.getPtr(.a) == null); | |
| 1157 | testing.expect(a.getPtr(.b) != null); | |
| 1158 | testing.expect(a.getPtr(.c) == null); | |
| 1159 | testing.expect(a.getPtr(.d) != null); | |
| 1160 | testing.expect(a.getPtrConst(.a) == null); | |
| 1161 | testing.expect(a.getPtrConst(.b) != null); | |
| 1162 | testing.expect(a.getPtrConst(.c) == null); | |
| 1163 | testing.expect(a.getPtrConst(.d) != null); | |
| 1164 | _ = a.getPtrAssertContains(.b); | |
| 1165 | _ = a.getAssertContains(.d); | |
| 1166 | ||
| 1167 | a.put(.a, {}); | |
| 1168 | a.put(.a, {}); | |
| 1169 | a.putUninitialized(.c).* = {}; | |
| 1170 | a.putUninitialized(.c).* = {}; | |
| 1171 | ||
| 1172 | testing.expectEqual(@as(usize, 4), a.count()); | |
| 1173 | testing.expect(a.get(.a) != null); | |
| 1174 | testing.expect(a.get(.b) != null); | |
| 1175 | testing.expect(a.get(.c) != null); | |
| 1176 | testing.expect(a.get(.d) != null); | |
| 1177 | ||
| 1178 | a.remove(.a); | |
| 1179 | _ = a.fetchRemove(.c); | |
| 1180 | ||
| 1181 | var iter = a.iterator(); | |
| 1182 | const Entry = Map.Entry; | |
| 1183 | testing.expectEqual(E.b, iter.next().?.key); | |
| 1184 | testing.expectEqual(E.d, iter.next().?.key); | |
| 1185 | testing.expect(iter.next() == null); | |
| 1186 | } | |
| 1187 | ||
| 1188 | test "std.enums.EnumMap sized" { | |
| 1189 | const E = extern enum { a, b, c, d, e = 0 }; | |
| 1190 | const Map = EnumMap(E, usize); | |
| 1191 | testing.expectEqual(E, Map.Key); | |
| 1192 | testing.expectEqual(EnumIndexer(E), Map.Indexer); | |
| 1193 | testing.expectEqual(usize, Map.Value); | |
| 1194 | testing.expectEqual(@as(usize, 4), Map.len); | |
| 1195 | ||
| 1196 | const b = Map.initFull(5); | |
| 1197 | testing.expectEqual(@as(usize, 4), b.count()); | |
| 1198 | testing.expect(b.contains(.a)); | |
| 1199 | testing.expect(b.contains(.b)); | |
| 1200 | testing.expect(b.contains(.c)); | |
| 1201 | testing.expect(b.contains(.d)); | |
| 1202 | testing.expectEqual(@as(?usize, 5), b.get(.a)); | |
| 1203 | testing.expectEqual(@as(?usize, 5), b.get(.b)); | |
| 1204 | testing.expectEqual(@as(?usize, 5), b.get(.c)); | |
| 1205 | testing.expectEqual(@as(?usize, 5), b.get(.d)); | |
| 1206 | ||
| 1207 | const c = Map.initFullWith(.{ .a = 1, .b = 2, .c = 3, .d = 4 }); | |
| 1208 | testing.expectEqual(@as(usize, 4), c.count()); | |
| 1209 | testing.expect(c.contains(.a)); | |
| 1210 | testing.expect(c.contains(.b)); | |
| 1211 | testing.expect(c.contains(.c)); | |
| 1212 | testing.expect(c.contains(.d)); | |
| 1213 | testing.expectEqual(@as(?usize, 1), c.get(.a)); | |
| 1214 | testing.expectEqual(@as(?usize, 2), c.get(.b)); | |
| 1215 | testing.expectEqual(@as(?usize, 3), c.get(.c)); | |
| 1216 | testing.expectEqual(@as(?usize, 4), c.get(.d)); | |
| 1217 | ||
| 1218 | const d = Map.initFullWithDefault(6, .{ .b = 2, .c = 4 }); | |
| 1219 | testing.expectEqual(@as(usize, 4), d.count()); | |
| 1220 | testing.expect(d.contains(.a)); | |
| 1221 | testing.expect(d.contains(.b)); | |
| 1222 | testing.expect(d.contains(.c)); | |
| 1223 | testing.expect(d.contains(.d)); | |
| 1224 | testing.expectEqual(@as(?usize, 6), d.get(.a)); | |
| 1225 | testing.expectEqual(@as(?usize, 2), d.get(.b)); | |
| 1226 | testing.expectEqual(@as(?usize, 4), d.get(.c)); | |
| 1227 | testing.expectEqual(@as(?usize, 6), d.get(.d)); | |
| 1228 | ||
| 1229 | var a = Map.init(.{ .b = 2, .d = 4 }); | |
| 1230 | testing.expectEqual(@as(usize, 2), a.count()); | |
| 1231 | testing.expectEqual(false, a.contains(.a)); | |
| 1232 | testing.expectEqual(true, a.contains(.b)); | |
| 1233 | testing.expectEqual(false, a.contains(.c)); | |
| 1234 | testing.expectEqual(true, a.contains(.d)); | |
| 1235 | ||
| 1236 | testing.expectEqual(@as(?usize, null), a.get(.a)); | |
| 1237 | testing.expectEqual(@as(?usize, 2), a.get(.b)); | |
| 1238 | testing.expectEqual(@as(?usize, null), a.get(.c)); | |
| 1239 | testing.expectEqual(@as(?usize, 4), a.get(.d)); | |
| 1240 | ||
| 1241 | testing.expectEqual(@as(?*usize, null), a.getPtr(.a)); | |
| 1242 | testing.expectEqual(@as(?*usize, &a.values[1]), a.getPtr(.b)); | |
| 1243 | testing.expectEqual(@as(?*usize, null), a.getPtr(.c)); | |
| 1244 | testing.expectEqual(@as(?*usize, &a.values[3]), a.getPtr(.d)); | |
| 1245 | ||
| 1246 | testing.expectEqual(@as(?*const usize, null), a.getPtrConst(.a)); | |
| 1247 | testing.expectEqual(@as(?*const usize, &a.values[1]), a.getPtrConst(.b)); | |
| 1248 | testing.expectEqual(@as(?*const usize, null), a.getPtrConst(.c)); | |
| 1249 | testing.expectEqual(@as(?*const usize, &a.values[3]), a.getPtrConst(.d)); | |
| 1250 | ||
| 1251 | testing.expectEqual(@as(*const usize, &a.values[1]), a.getPtrAssertContains(.b)); | |
| 1252 | testing.expectEqual(@as(*const usize, &a.values[3]), a.getPtrAssertContains(.d)); | |
| 1253 | testing.expectEqual(@as(usize, 2), a.getAssertContains(.b)); | |
| 1254 | testing.expectEqual(@as(usize, 4), a.getAssertContains(.d)); | |
| 1255 | ||
| 1256 | a.put(.a, 3); | |
| 1257 | a.put(.a, 5); | |
| 1258 | a.putUninitialized(.c).* = 7; | |
| 1259 | a.putUninitialized(.c).* = 9; | |
| 1260 | ||
| 1261 | testing.expectEqual(@as(usize, 4), a.count()); | |
| 1262 | testing.expectEqual(@as(?usize, 5), a.get(.a)); | |
| 1263 | testing.expectEqual(@as(?usize, 2), a.get(.b)); | |
| 1264 | testing.expectEqual(@as(?usize, 9), a.get(.c)); | |
| 1265 | testing.expectEqual(@as(?usize, 4), a.get(.d)); | |
| 1266 | ||
| 1267 | a.remove(.a); | |
| 1268 | testing.expectEqual(@as(?usize, null), a.fetchRemove(.a)); | |
| 1269 | testing.expectEqual(@as(?usize, 9), a.fetchRemove(.c)); | |
| 1270 | a.remove(.c); | |
| 1271 | ||
| 1272 | var iter = a.iterator(); | |
| 1273 | const Entry = Map.Entry; | |
| 1274 | testing.expectEqual(@as(?Entry, Entry{ | |
| 1275 | .key = .b, .value = &a.values[1], | |
| 1276 | }), iter.next()); | |
| 1277 | testing.expectEqual(@as(?Entry, Entry{ | |
| 1278 | .key = .d, .value = &a.values[3], | |
| 1279 | }), iter.next()); | |
| 1280 | testing.expectEqual(@as(?Entry, null), iter.next()); | |
| 1281 | } |
lib/std/fmt.zig+7-2| ... | ... | @@ -1250,9 +1250,9 @@ fn formatDuration(ns: u64, comptime fmt: []const u8, options: std.fmt.FormatOpti |
| 1250 | 1250 | const kunits = ns_remaining * 1000 / unit.ns; |
| 1251 | 1251 | if (kunits >= 1000) { |
| 1252 | 1252 | try formatInt(kunits / 1000, 10, false, .{}, writer); |
| 1253 | if (kunits > 1000) { | |
| 1253 | const frac = kunits % 1000; | |
| 1254 | if (frac > 0) { | |
| 1254 | 1255 | // Write up to 3 decimal places |
| 1255 | const frac = kunits % 1000; | |
| 1256 | 1256 | var buf = [_]u8{ '.', 0, 0, 0 }; |
| 1257 | 1257 | _ = formatIntBuf(buf[1..], frac, 10, false, .{ .fill = '0', .width = 3 }); |
| 1258 | 1258 | var end: usize = 4; |
| ... | ... | @@ -1286,9 +1286,14 @@ test "fmtDuration" { |
| 1286 | 1286 | .{ .s = "1us", .d = std.time.ns_per_us }, |
| 1287 | 1287 | .{ .s = "1.45us", .d = 1450 }, |
| 1288 | 1288 | .{ .s = "1.5us", .d = 3 * std.time.ns_per_us / 2 }, |
| 1289 | .{ .s = "14.5us", .d = 14500 }, | |
| 1290 | .{ .s = "145us", .d = 145000 }, | |
| 1289 | 1291 | .{ .s = "999.999us", .d = std.time.ns_per_ms - 1 }, |
| 1290 | 1292 | .{ .s = "1ms", .d = std.time.ns_per_ms + 1 }, |
| 1291 | 1293 | .{ .s = "1.5ms", .d = 3 * std.time.ns_per_ms / 2 }, |
| 1294 | .{ .s = "1.11ms", .d = 1110000 }, | |
| 1295 | .{ .s = "1.111ms", .d = 1111000 }, | |
| 1296 | .{ .s = "1.111ms", .d = 1111100 }, | |
| 1292 | 1297 | .{ .s = "999.999ms", .d = std.time.ns_per_s - 1 }, |
| 1293 | 1298 | .{ .s = "1s", .d = std.time.ns_per_s }, |
| 1294 | 1299 | .{ .s = "59.999s", .d = std.time.ns_per_min - 1 }, |
lib/std/fs.zig+5-5| ... | ... | @@ -50,13 +50,13 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) { |
| 50 | 50 | else => @compileError("Unsupported OS"), |
| 51 | 51 | }; |
| 52 | 52 | |
| 53 | pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; | |
| 53 | pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; | |
| 54 | 54 | |
| 55 | 55 | /// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. |
| 56 | pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, base64.standard_pad_char); | |
| 56 | pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null); | |
| 57 | 57 | |
| 58 | 58 | /// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. |
| 59 | pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, base64.standard_pad_char); | |
| 59 | pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null); | |
| 60 | 60 | |
| 61 | 61 | /// Whether or not async file system syscalls need a dedicated thread because the operating |
| 62 | 62 | /// system does not support non-blocking I/O on the file system. |
| ... | ... | @@ -77,7 +77,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 77 | 77 | const dirname = path.dirname(new_path) orelse "."; |
| 78 | 78 | |
| 79 | 79 | var rand_buf: [AtomicFile.RANDOM_BYTES]u8 = undefined; |
| 80 | const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); | |
| 80 | const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64_encoder.calcSize(rand_buf.len)); | |
| 81 | 81 | defer allocator.free(tmp_path); |
| 82 | 82 | mem.copy(u8, tmp_path[0..], dirname); |
| 83 | 83 | tmp_path[dirname.len] = path.sep; |
| ... | ... | @@ -142,7 +142,7 @@ pub const AtomicFile = struct { |
| 142 | 142 | const InitError = File.OpenError; |
| 143 | 143 | |
| 144 | 144 | const RANDOM_BYTES = 12; |
| 145 | const TMP_PATH_LEN = base64.Base64Encoder.calcSize(RANDOM_BYTES); | |
| 145 | const TMP_PATH_LEN = base64_encoder.calcSize(RANDOM_BYTES); | |
| 146 | 146 | |
| 147 | 147 | /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. |
| 148 | 148 | pub fn init( |
lib/std/fs/path.zig+11-1| ... | ... | @@ -92,7 +92,7 @@ pub fn join(allocator: *Allocator, paths: []const []const u8) ![]u8 { |
| 92 | 92 | /// Naively combines a series of paths with the native path seperator and null terminator. |
| 93 | 93 | /// Allocates memory for the result, which must be freed by the caller. |
| 94 | 94 | pub fn joinZ(allocator: *Allocator, paths: []const []const u8) ![:0]u8 { |
| 95 | const out = joinSepMaybeZ(allocator, sep, isSep, paths, true); | |
| 95 | const out = try joinSepMaybeZ(allocator, sep, isSep, paths, true); | |
| 96 | 96 | return out[0 .. out.len - 1 :0]; |
| 97 | 97 | } |
| 98 | 98 | |
| ... | ... | @@ -119,6 +119,16 @@ fn testJoinMaybeZPosix(paths: []const []const u8, expected: []const u8, zero: bo |
| 119 | 119 | } |
| 120 | 120 | |
| 121 | 121 | test "join" { |
| 122 | { | |
| 123 | const actual: []u8 = try join(testing.allocator, &[_][]const u8{}); | |
| 124 | defer testing.allocator.free(actual); | |
| 125 | testing.expectEqualSlices(u8, "", actual); | |
| 126 | } | |
| 127 | { | |
| 128 | const actual: [:0]u8 = try joinZ(testing.allocator, &[_][]const u8{}); | |
| 129 | defer testing.allocator.free(actual); | |
| 130 | testing.expectEqualSlices(u8, "", actual); | |
| 131 | } | |
| 122 | 132 | for (&[_]bool{ false, true }) |zero| { |
| 123 | 133 | testJoinMaybeZWindows(&[_][]const u8{}, "", zero); |
| 124 | 134 | testJoinMaybeZWindows(&[_][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c", zero); |
lib/std/hash/auto_hash.zig+1-1| ... | ... | @@ -95,7 +95,7 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { |
| 95 | 95 | .EnumLiteral, |
| 96 | 96 | .Frame, |
| 97 | 97 | .Float, |
| 98 | => @compileError("cannot hash this type"), | |
| 98 | => @compileError("unable to hash type " ++ @typeName(Key)), | |
| 99 | 99 | |
| 100 | 100 | // Help the optimizer see that hashing an int is easy by inlining! |
| 101 | 101 | // TODO Check if the situation is better after #561 is resolved. |
lib/std/macho.zig+40| ... | ... | @@ -1227,6 +1227,24 @@ pub const S_ATTR_EXT_RELOC = 0x200; |
| 1227 | 1227 | /// section has local relocation entries |
| 1228 | 1228 | pub const S_ATTR_LOC_RELOC = 0x100; |
| 1229 | 1229 | |
| 1230 | /// template of initial values for TLVs | |
| 1231 | pub const S_THREAD_LOCAL_REGULAR = 0x11; | |
| 1232 | ||
| 1233 | /// template of initial values for TLVs | |
| 1234 | pub const S_THREAD_LOCAL_ZEROFILL = 0x12; | |
| 1235 | ||
| 1236 | /// TLV descriptors | |
| 1237 | pub const S_THREAD_LOCAL_VARIABLES = 0x13; | |
| 1238 | ||
| 1239 | /// pointers to TLV descriptors | |
| 1240 | pub const S_THREAD_LOCAL_VARIABLE_POINTERS = 0x14; | |
| 1241 | ||
| 1242 | /// functions to call to initialize TLV values | |
| 1243 | pub const S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15; | |
| 1244 | ||
| 1245 | /// 32-bit offsets to initializers | |
| 1246 | pub const S_INIT_FUNC_OFFSETS = 0x16; | |
| 1247 | ||
| 1230 | 1248 | pub const cpu_type_t = integer_t; |
| 1231 | 1249 | pub const cpu_subtype_t = integer_t; |
| 1232 | 1250 | pub const integer_t = c_int; |
| ... | ... | @@ -1422,6 +1440,14 @@ pub const EXPORT_SYMBOL_FLAGS_KIND_WEAK_DEFINITION: u8 = 0x04; |
| 1422 | 1440 | pub const EXPORT_SYMBOL_FLAGS_REEXPORT: u8 = 0x08; |
| 1423 | 1441 | pub const EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER: u8 = 0x10; |
| 1424 | 1442 | |
| 1443 | // An indirect symbol table entry is simply a 32bit index into the symbol table | |
| 1444 | // to the symbol that the pointer or stub is refering to. Unless it is for a | |
| 1445 | // non-lazy symbol pointer section for a defined symbol which strip(1) as | |
| 1446 | // removed. In which case it has the value INDIRECT_SYMBOL_LOCAL. If the | |
| 1447 | // symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that. | |
| 1448 | pub const INDIRECT_SYMBOL_LOCAL: u32 = 0x80000000; | |
| 1449 | pub const INDIRECT_SYMBOL_ABS: u32 = 0x40000000; | |
| 1450 | ||
| 1425 | 1451 | // Codesign consts and structs taken from: |
| 1426 | 1452 | // https://opensource.apple.com/source/xnu/xnu-6153.81.5/osfmk/kern/cs_blobs.h.auto.html |
| 1427 | 1453 | |
| ... | ... | @@ -1589,3 +1615,17 @@ pub const GenericBlob = extern struct { |
| 1589 | 1615 | /// Total length of blob |
| 1590 | 1616 | length: u32, |
| 1591 | 1617 | }; |
| 1618 | ||
| 1619 | /// The LC_DATA_IN_CODE load commands uses a linkedit_data_command | |
| 1620 | /// to point to an array of data_in_code_entry entries. Each entry | |
| 1621 | /// describes a range of data in a code section. | |
| 1622 | pub const data_in_code_entry = extern struct { | |
| 1623 | /// From mach_header to start of data range. | |
| 1624 | offset: u32, | |
| 1625 | ||
| 1626 | /// Number of bytes in data range. | |
| 1627 | length: u16, | |
| 1628 | ||
| 1629 | /// A DICE_KIND value. | |
| 1630 | kind: u16, | |
| 1631 | }; |
lib/std/mem.zig+19| ... | ... | @@ -1373,6 +1373,20 @@ test "mem.tokenize (multibyte)" { |
| 1373 | 1373 | testing.expect(it.next() == null); |
| 1374 | 1374 | } |
| 1375 | 1375 | |
| 1376 | test "mem.tokenize (reset)" { | |
| 1377 | var it = tokenize(" abc def ghi ", " "); | |
| 1378 | testing.expect(eql(u8, it.next().?, "abc")); | |
| 1379 | testing.expect(eql(u8, it.next().?, "def")); | |
| 1380 | testing.expect(eql(u8, it.next().?, "ghi")); | |
| 1381 | ||
| 1382 | it.reset(); | |
| 1383 | ||
| 1384 | testing.expect(eql(u8, it.next().?, "abc")); | |
| 1385 | testing.expect(eql(u8, it.next().?, "def")); | |
| 1386 | testing.expect(eql(u8, it.next().?, "ghi")); | |
| 1387 | testing.expect(it.next() == null); | |
| 1388 | } | |
| 1389 | ||
| 1376 | 1390 | /// Returns an iterator that iterates over the slices of `buffer` that |
| 1377 | 1391 | /// are separated by bytes in `delimiter`. |
| 1378 | 1392 | /// split("abc|def||ghi", "|") |
| ... | ... | @@ -1471,6 +1485,11 @@ pub const TokenIterator = struct { |
| 1471 | 1485 | return self.buffer[index..]; |
| 1472 | 1486 | } |
| 1473 | 1487 | |
| 1488 | /// Resets the iterator to the initial token. | |
| 1489 | pub fn reset(self: *TokenIterator) void { | |
| 1490 | self.index = 0; | |
| 1491 | } | |
| 1492 | ||
| 1474 | 1493 | fn isSplitByte(self: TokenIterator, byte: u8) bool { |
| 1475 | 1494 | for (self.delimiter_bytes) |delimiter_byte| { |
| 1476 | 1495 | if (byte == delimiter_byte) { |
lib/std/meta.zig+51-18| ... | ... | @@ -888,19 +888,20 @@ pub fn Vector(comptime len: u32, comptime child: type) type { |
| 888 | 888 | /// Given a type and value, cast the value to the type as c would. |
| 889 | 889 | /// This is for translate-c and is not intended for general use. |
| 890 | 890 | pub fn cast(comptime DestType: type, target: anytype) DestType { |
| 891 | const TargetType = @TypeOf(target); | |
| 891 | // this function should behave like transCCast in translate-c, except it's for macros | |
| 892 | const SourceType = @TypeOf(target); | |
| 892 | 893 | switch (@typeInfo(DestType)) { |
| 893 | .Pointer => |dest_ptr| { | |
| 894 | switch (@typeInfo(TargetType)) { | |
| 894 | .Pointer => { | |
| 895 | switch (@typeInfo(SourceType)) { | |
| 895 | 896 | .Int, .ComptimeInt => { |
| 896 | 897 | return @intToPtr(DestType, target); |
| 897 | 898 | }, |
| 898 | .Pointer => |ptr| { | |
| 899 | return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); | |
| 899 | .Pointer => { | |
| 900 | return castPtr(DestType, target); | |
| 900 | 901 | }, |
| 901 | 902 | .Optional => |opt| { |
| 902 | 903 | if (@typeInfo(opt.child) == .Pointer) { |
| 903 | return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); | |
| 904 | return castPtr(DestType, target); | |
| 904 | 905 | } |
| 905 | 906 | }, |
| 906 | 907 | else => {}, |
| ... | ... | @@ -908,17 +909,16 @@ pub fn cast(comptime DestType: type, target: anytype) DestType { |
| 908 | 909 | }, |
| 909 | 910 | .Optional => |dest_opt| { |
| 910 | 911 | if (@typeInfo(dest_opt.child) == .Pointer) { |
| 911 | const dest_ptr = @typeInfo(dest_opt.child).Pointer; | |
| 912 | switch (@typeInfo(TargetType)) { | |
| 912 | switch (@typeInfo(SourceType)) { | |
| 913 | 913 | .Int, .ComptimeInt => { |
| 914 | 914 | return @intToPtr(DestType, target); |
| 915 | 915 | }, |
| 916 | 916 | .Pointer => { |
| 917 | return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); | |
| 917 | return castPtr(DestType, target); | |
| 918 | 918 | }, |
| 919 | 919 | .Optional => |target_opt| { |
| 920 | 920 | if (@typeInfo(target_opt.child) == .Pointer) { |
| 921 | return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); | |
| 921 | return castPtr(DestType, target); | |
| 922 | 922 | } |
| 923 | 923 | }, |
| 924 | 924 | else => {}, |
| ... | ... | @@ -926,25 +926,25 @@ pub fn cast(comptime DestType: type, target: anytype) DestType { |
| 926 | 926 | } |
| 927 | 927 | }, |
| 928 | 928 | .Enum => { |
| 929 | if (@typeInfo(TargetType) == .Int or @typeInfo(TargetType) == .ComptimeInt) { | |
| 929 | if (@typeInfo(SourceType) == .Int or @typeInfo(SourceType) == .ComptimeInt) { | |
| 930 | 930 | return @intToEnum(DestType, target); |
| 931 | 931 | } |
| 932 | 932 | }, |
| 933 | .Int, .ComptimeInt => { | |
| 934 | switch (@typeInfo(TargetType)) { | |
| 933 | .Int => { | |
| 934 | switch (@typeInfo(SourceType)) { | |
| 935 | 935 | .Pointer => { |
| 936 | return @intCast(DestType, @ptrToInt(target)); | |
| 936 | return castInt(DestType, @ptrToInt(target)); | |
| 937 | 937 | }, |
| 938 | 938 | .Optional => |opt| { |
| 939 | 939 | if (@typeInfo(opt.child) == .Pointer) { |
| 940 | return @intCast(DestType, @ptrToInt(target)); | |
| 940 | return castInt(DestType, @ptrToInt(target)); | |
| 941 | 941 | } |
| 942 | 942 | }, |
| 943 | 943 | .Enum => { |
| 944 | return @intCast(DestType, @enumToInt(target)); | |
| 944 | return castInt(DestType, @enumToInt(target)); | |
| 945 | 945 | }, |
| 946 | .Int, .ComptimeInt => { | |
| 947 | return @intCast(DestType, target); | |
| 946 | .Int => { | |
| 947 | return castInt(DestType, target); | |
| 948 | 948 | }, |
| 949 | 949 | else => {}, |
| 950 | 950 | } |
| ... | ... | @@ -954,6 +954,34 @@ pub fn cast(comptime DestType: type, target: anytype) DestType { |
| 954 | 954 | return @as(DestType, target); |
| 955 | 955 | } |
| 956 | 956 | |
| 957 | fn castInt(comptime DestType: type, target: anytype) DestType { | |
| 958 | const dest = @typeInfo(DestType).Int; | |
| 959 | const source = @typeInfo(@TypeOf(target)).Int; | |
| 960 | ||
| 961 | if (dest.bits < source.bits) | |
| 962 | return @bitCast(DestType, @truncate(Int(source.signedness, dest.bits), target)) | |
| 963 | else | |
| 964 | return @bitCast(DestType, @as(Int(source.signedness, dest.bits), target)); | |
| 965 | } | |
| 966 | ||
| 967 | fn castPtr(comptime DestType: type, target: anytype) DestType { | |
| 968 | const dest = ptrInfo(DestType); | |
| 969 | const source = ptrInfo(@TypeOf(target)); | |
| 970 | ||
| 971 | if (source.is_const and !dest.is_const or source.is_volatile and !dest.is_volatile) | |
| 972 | return @intToPtr(DestType, @ptrToInt(target)) | |
| 973 | else | |
| 974 | return @ptrCast(DestType, @alignCast(dest.alignment, target)); | |
| 975 | } | |
| 976 | ||
| 977 | fn ptrInfo(comptime PtrType: type) TypeInfo.Pointer { | |
| 978 | return switch(@typeInfo(PtrType)){ | |
| 979 | .Optional => |opt_info| @typeInfo(opt_info.child).Pointer, | |
| 980 | .Pointer => |ptr_info| ptr_info, | |
| 981 | else => unreachable, | |
| 982 | }; | |
| 983 | } | |
| 984 | ||
| 957 | 985 | test "std.meta.cast" { |
| 958 | 986 | const E = enum(u2) { |
| 959 | 987 | Zero, |
| ... | ... | @@ -977,6 +1005,11 @@ test "std.meta.cast" { |
| 977 | 1005 | testing.expectEqual(@as(u32, 4), cast(u32, @intToPtr(?*u32, 4))); |
| 978 | 1006 | testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); |
| 979 | 1007 | testing.expectEqual(@as(u8, 2), cast(u8, E.Two)); |
| 1008 | ||
| 1009 | testing.expectEqual(@bitCast(i32, @as(u32, 0x8000_0000)), cast(i32, @as(u32, 0x8000_0000))); | |
| 1010 | ||
| 1011 | testing.expectEqual(@intToPtr(*u8, 2), cast(*u8, @intToPtr(*const u8, 2))); | |
| 1012 | testing.expectEqual(@intToPtr(*u8, 2), cast(*u8, @intToPtr(*volatile u8, 2))); | |
| 980 | 1013 | } |
| 981 | 1014 | |
| 982 | 1015 | /// Given a value returns its size as C's sizeof operator would. |
lib/std/meta/trait.zig+78| ... | ... | @@ -408,6 +408,84 @@ test "std.meta.trait.isTuple" { |
| 408 | 408 | testing.expect(isTuple(@TypeOf(t3))); |
| 409 | 409 | } |
| 410 | 410 | |
| 411 | /// Returns true if the passed type will coerce to []const u8. | |
| 412 | /// Any of the following are considered strings: | |
| 413 | /// ``` | |
| 414 | /// []const u8, [:S]const u8, *const [N]u8, *const [N:S]u8, | |
| 415 | /// []u8, [:S]u8, *[:S]u8, *[N:S]u8. | |
| 416 | /// ``` | |
| 417 | /// These types are not considered strings: | |
| 418 | /// ``` | |
| 419 | /// u8, [N]u8, [*]const u8, [*:0]const u8, | |
| 420 | /// [*]const [N]u8, []const u16, []const i8, | |
| 421 | /// *const u8, ?[]const u8, ?*const [N]u8. | |
| 422 | /// ``` | |
| 423 | pub fn isZigString(comptime T: type) bool { | |
| 424 | comptime { | |
| 425 | // Only pointer types can be strings, no optionals | |
| 426 | const info = @typeInfo(T); | |
| 427 | if (info != .Pointer) return false; | |
| 428 | ||
| 429 | const ptr = &info.Pointer; | |
| 430 | // Check for CV qualifiers that would prevent coerction to []const u8 | |
| 431 | if (ptr.is_volatile or ptr.is_allowzero) return false; | |
| 432 | ||
| 433 | // If it's already a slice, simple check. | |
| 434 | if (ptr.size == .Slice) { | |
| 435 | return ptr.child == u8; | |
| 436 | } | |
| 437 | ||
| 438 | // Otherwise check if it's an array type that coerces to slice. | |
| 439 | if (ptr.size == .One) { | |
| 440 | const child = @typeInfo(ptr.child); | |
| 441 | if (child == .Array) { | |
| 442 | const arr = &child.Array; | |
| 443 | return arr.child == u8; | |
| 444 | } | |
| 445 | } | |
| 446 | ||
| 447 | return false; | |
| 448 | } | |
| 449 | } | |
| 450 | ||
| 451 | test "std.meta.trait.isZigString" { | |
| 452 | testing.expect(isZigString([]const u8)); | |
| 453 | testing.expect(isZigString([]u8)); | |
| 454 | testing.expect(isZigString([:0]const u8)); | |
| 455 | testing.expect(isZigString([:0]u8)); | |
| 456 | testing.expect(isZigString([:5]const u8)); | |
| 457 | testing.expect(isZigString([:5]u8)); | |
| 458 | testing.expect(isZigString(*const [0]u8)); | |
| 459 | testing.expect(isZigString(*[0]u8)); | |
| 460 | testing.expect(isZigString(*const [0:0]u8)); | |
| 461 | testing.expect(isZigString(*[0:0]u8)); | |
| 462 | testing.expect(isZigString(*const [0:5]u8)); | |
| 463 | testing.expect(isZigString(*[0:5]u8)); | |
| 464 | testing.expect(isZigString(*const [10]u8)); | |
| 465 | testing.expect(isZigString(*[10]u8)); | |
| 466 | testing.expect(isZigString(*const [10:0]u8)); | |
| 467 | testing.expect(isZigString(*[10:0]u8)); | |
| 468 | testing.expect(isZigString(*const [10:5]u8)); | |
| 469 | testing.expect(isZigString(*[10:5]u8)); | |
| 470 | ||
| 471 | testing.expect(!isZigString(u8)); | |
| 472 | testing.expect(!isZigString([4]u8)); | |
| 473 | testing.expect(!isZigString([4:0]u8)); | |
| 474 | testing.expect(!isZigString([*]const u8)); | |
| 475 | testing.expect(!isZigString([*]const [4]u8)); | |
| 476 | testing.expect(!isZigString([*c]const u8)); | |
| 477 | testing.expect(!isZigString([*c]const [4]u8)); | |
| 478 | testing.expect(!isZigString([*:0]const u8)); | |
| 479 | testing.expect(!isZigString([*:0]const u8)); | |
| 480 | testing.expect(!isZigString(*[]const u8)); | |
| 481 | testing.expect(!isZigString(?[]const u8)); | |
| 482 | testing.expect(!isZigString(?*const [4]u8)); | |
| 483 | testing.expect(!isZigString([]allowzero u8)); | |
| 484 | testing.expect(!isZigString([]volatile u8)); | |
| 485 | testing.expect(!isZigString(*allowzero [4]u8)); | |
| 486 | testing.expect(!isZigString(*volatile [4]u8)); | |
| 487 | } | |
| 488 | ||
| 411 | 489 | pub fn hasDecls(comptime T: type, comptime names: anytype) bool { |
| 412 | 490 | inline for (names) |name| { |
| 413 | 491 | if (!@hasDecl(T, name)) |
lib/std/os.zig+3-2| ... | ... | @@ -2879,7 +2879,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi |
| 2879 | 2879 | unreachable; |
| 2880 | 2880 | } |
| 2881 | 2881 | |
| 2882 | const ListenError = error{ | |
| 2882 | pub const ListenError = error{ | |
| 2883 | 2883 | /// Another socket is already listening on the same port. |
| 2884 | 2884 | /// For Internet domain sockets, the socket referred to by sockfd had not previously |
| 2885 | 2885 | /// been bound to an address and, upon attempting to bind it to an ephemeral port, it |
| ... | ... | @@ -5610,6 +5610,7 @@ pub fn recvfrom( |
| 5610 | 5610 | EAGAIN => return error.WouldBlock, |
| 5611 | 5611 | ENOMEM => return error.SystemResources, |
| 5612 | 5612 | ECONNREFUSED => return error.ConnectionRefused, |
| 5613 | ECONNRESET => return error.ConnectionResetByPeer, | |
| 5613 | 5614 | else => |err| return unexpectedErrno(err), |
| 5614 | 5615 | } |
| 5615 | 5616 | } |
| ... | ... | @@ -5827,7 +5828,7 @@ pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) Termio |
| 5827 | 5828 | } |
| 5828 | 5829 | } |
| 5829 | 5830 | |
| 5830 | const IoCtl_SIOCGIFINDEX_Error = error{ | |
| 5831 | pub const IoCtl_SIOCGIFINDEX_Error = error{ | |
| 5831 | 5832 | FileSystem, |
| 5832 | 5833 | InterfaceNotFound, |
| 5833 | 5834 | } || UnexpectedError; |
lib/std/os/linux/io_uring.zig+1-1| ... | ... | @@ -1353,7 +1353,7 @@ test "timeout (after a relative time)" { |
| 1353 | 1353 | .res = -linux.ETIME, |
| 1354 | 1354 | .flags = 0, |
| 1355 | 1355 | }, cqe); |
| 1356 | testing.expectWithinMargin(@intToFloat(f64, ms), @intToFloat(f64, stopped - started), margin); | |
| 1356 | testing.expectApproxEqAbs(@intToFloat(f64, ms), @intToFloat(f64, stopped - started), margin); | |
| 1357 | 1357 | } |
| 1358 | 1358 | |
| 1359 | 1359 | test "timeout (after a number of completions)" { |
lib/std/os/linux/mips.zig+37| ... | ... | @@ -115,6 +115,9 @@ pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, |
| 115 | 115 | ); |
| 116 | 116 | } |
| 117 | 117 | |
| 118 | // NOTE: The o32 calling convention requires the callee to reserve 16 bytes for | |
| 119 | // the first four arguments even though they're passed in $a0-$a3. | |
| 120 | ||
| 118 | 121 | pub fn syscall6( |
| 119 | 122 | number: SYS, |
| 120 | 123 | arg1: usize, |
| ... | ... | @@ -146,6 +149,40 @@ pub fn syscall6( |
| 146 | 149 | ); |
| 147 | 150 | } |
| 148 | 151 | |
| 152 | pub fn syscall7( | |
| 153 | number: SYS, | |
| 154 | arg1: usize, | |
| 155 | arg2: usize, | |
| 156 | arg3: usize, | |
| 157 | arg4: usize, | |
| 158 | arg5: usize, | |
| 159 | arg6: usize, | |
| 160 | arg7: usize, | |
| 161 | ) usize { | |
| 162 | return asm volatile ( | |
| 163 | \\ .set noat | |
| 164 | \\ subu $sp, $sp, 32 | |
| 165 | \\ sw %[arg5], 16($sp) | |
| 166 | \\ sw %[arg6], 20($sp) | |
| 167 | \\ sw %[arg7], 24($sp) | |
| 168 | \\ syscall | |
| 169 | \\ addu $sp, $sp, 32 | |
| 170 | \\ blez $7, 1f | |
| 171 | \\ subu $2, $0, $2 | |
| 172 | \\ 1: | |
| 173 | : [ret] "={$2}" (-> usize) | |
| 174 | : [number] "{$2}" (@enumToInt(number)), | |
| 175 | [arg1] "{$4}" (arg1), | |
| 176 | [arg2] "{$5}" (arg2), | |
| 177 | [arg3] "{$6}" (arg3), | |
| 178 | [arg4] "{$7}" (arg4), | |
| 179 | [arg5] "r" (arg5), | |
| 180 | [arg6] "r" (arg6), | |
| 181 | [arg7] "r" (arg7) | |
| 182 | : "memory", "cc", "$7" | |
| 183 | ); | |
| 184 | } | |
| 185 | ||
| 149 | 186 | /// This matches the libc clone function. |
| 150 | 187 | pub extern fn clone(func: fn (arg: usize) callconv(.C) u8, stack: usize, flags: u32, arg: usize, ptid: *i32, tls: usize, ctid: *i32) usize; |
| 151 | 188 |
lib/std/os/uefi/tables/boot_services.zig+2-1| ... | ... | @@ -78,7 +78,8 @@ pub const BootServices = extern struct { |
| 78 | 78 | /// Returns an array of handles that support a specified protocol. |
| 79 | 79 | locateHandle: fn (LocateSearchType, ?*align(8) const Guid, ?*const c_void, *usize, [*]Handle) callconv(.C) Status, |
| 80 | 80 | |
| 81 | locateDevicePath: Status, // TODO | |
| 81 | /// Locates the handle to a device on the device path that supports the specified protocol | |
| 82 | locateDevicePath: fn (*align(8) const Guid, **const DevicePathProtocol, *?Handle) callconv(.C) Status, | |
| 82 | 83 | installConfigurationTable: Status, // TODO |
| 83 | 84 | |
| 84 | 85 | /// Loads an EFI image into memory. |
lib/std/os/windows/user32.zig+1-1| ... | ... | @@ -373,7 +373,7 @@ pub fn createWindowExA(dwExStyle: u32, lpClassName: [*:0]const u8, lpWindowName: |
| 373 | 373 | } |
| 374 | 374 | |
| 375 | 375 | pub extern "user32" fn CreateWindowExW(dwExStyle: DWORD, lpClassName: [*:0]const u16, lpWindowName: [*:0]const u16, dwStyle: DWORD, X: i32, Y: i32, nWidth: i32, nHeight: i32, hWindParent: ?HWND, hMenu: ?HMENU, hInstance: HINSTANCE, lpParam: ?LPVOID) callconv(WINAPI) ?HWND; |
| 376 | pub var pfnCreateWindowExW: @TypeOf(RegisterClassExW) = undefined; | |
| 376 | pub var pfnCreateWindowExW: @TypeOf(CreateWindowExW) = undefined; | |
| 377 | 377 | pub fn createWindowExW(dwExStyle: u32, lpClassName: [*:0]const u16, lpWindowName: [*:0]const u16, dwStyle: u32, X: i32, Y: i32, nWidth: i32, nHeight: i32, hWindParent: ?HWND, hMenu: ?HMENU, hInstance: HINSTANCE, lpParam: ?*c_void) !HWND { |
| 378 | 378 | const function = selectSymbol(CreateWindowExW, pfnCreateWindowExW, .win2k); |
| 379 | 379 | const window = function(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWindParent, hMenu, hInstance, lpParam); |
lib/std/special/build_runner.zig+4-4| ... | ... | @@ -60,6 +60,7 @@ pub fn main() !void { |
| 60 | 60 | const stderr_stream = io.getStdErr().writer(); |
| 61 | 61 | const stdout_stream = io.getStdOut().writer(); |
| 62 | 62 | |
| 63 | var install_prefix: ?[]const u8 = null; | |
| 63 | 64 | while (nextArg(args, &arg_idx)) |arg| { |
| 64 | 65 | if (mem.startsWith(u8, arg, "-D")) { |
| 65 | 66 | const option_contents = arg[2..]; |
| ... | ... | @@ -82,7 +83,7 @@ pub fn main() !void { |
| 82 | 83 | } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 83 | 84 | return usage(builder, false, stdout_stream); |
| 84 | 85 | } else if (mem.eql(u8, arg, "--prefix")) { |
| 85 | builder.install_prefix = nextArg(args, &arg_idx) orelse { | |
| 86 | install_prefix = nextArg(args, &arg_idx) orelse { | |
| 86 | 87 | warn("Expected argument after --prefix\n\n", .{}); |
| 87 | 88 | return usageAndErr(builder, false, stderr_stream); |
| 88 | 89 | }; |
| ... | ... | @@ -134,7 +135,7 @@ pub fn main() !void { |
| 134 | 135 | } |
| 135 | 136 | } |
| 136 | 137 | |
| 137 | builder.resolveInstallPrefix(); | |
| 138 | builder.resolveInstallPrefix(install_prefix); | |
| 138 | 139 | try runBuild(builder); |
| 139 | 140 | |
| 140 | 141 | if (builder.validateUserInputDidItFail()) |
| ... | ... | @@ -162,8 +163,7 @@ fn runBuild(builder: *Builder) anyerror!void { |
| 162 | 163 | fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void { |
| 163 | 164 | // run the build script to collect the options |
| 164 | 165 | if (!already_ran_build) { |
| 165 | builder.setInstallPrefix(null); | |
| 166 | builder.resolveInstallPrefix(); | |
| 166 | builder.resolveInstallPrefix(null); | |
| 167 | 167 | try runBuild(builder); |
| 168 | 168 | } |
| 169 | 169 |
lib/std/std.zig+4| ... | ... | @@ -20,6 +20,9 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM |
| 20 | 20 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 21 | 21 | pub const DynamicBitSet = bit_set.DynamicBitSet; |
| 22 | 22 | pub const DynamicBitSetUnmanaged = bit_set.DynamicBitSetUnmanaged; |
| 23 | pub const EnumArray = enums.EnumArray; | |
| 24 | pub const EnumMap = enums.EnumMap; | |
| 25 | pub const EnumSet = enums.EnumSet; | |
| 23 | 26 | pub const HashMap = hash_map.HashMap; |
| 24 | 27 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; |
| 25 | 28 | pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList; |
| ... | ... | @@ -54,6 +57,7 @@ pub const cstr = @import("cstr.zig"); |
| 54 | 57 | pub const debug = @import("debug.zig"); |
| 55 | 58 | pub const dwarf = @import("dwarf.zig"); |
| 56 | 59 | pub const elf = @import("elf.zig"); |
| 60 | pub const enums = @import("enums.zig"); | |
| 57 | 61 | pub const event = @import("event.zig"); |
| 58 | 62 | pub const fifo = @import("fifo.zig"); |
| 59 | 63 | pub const fmt = @import("fmt.zig"); |
lib/std/testing.zig+39-37| ... | ... | @@ -200,67 +200,69 @@ pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anyt |
| 200 | 200 | return error.TestFailed; |
| 201 | 201 | } |
| 202 | 202 | |
| 203 | /// This function is intended to be used only in tests. When the actual value is not | |
| 204 | /// within the margin of the expected value, | |
| 205 | /// prints diagnostics to stderr to show exactly how they are not equal, then aborts. | |
| 203 | pub const expectWithinMargin = @compileError("expectWithinMargin is deprecated, use expectApproxEqAbs or expectApproxEqRel"); | |
| 204 | pub const expectWithinEpsilon = @compileError("expectWithinEpsilon is deprecated, use expectApproxEqAbs or expectApproxEqRel"); | |
| 205 | ||
| 206 | /// This function is intended to be used only in tests. When the actual value is | |
| 207 | /// not approximately equal to the expected value, prints diagnostics to stderr | |
| 208 | /// to show exactly how they are not equal, then aborts. | |
| 209 | /// See `math.approxEqAbs` for more informations on the tolerance parameter. | |
| 206 | 210 | /// The types must be floating point |
| 207 | pub fn expectWithinMargin(expected: anytype, actual: @TypeOf(expected), margin: @TypeOf(expected)) void { | |
| 208 | std.debug.assert(margin >= 0.0); | |
| 211 | pub fn expectApproxEqAbs(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) void { | |
| 212 | const T = @TypeOf(expected); | |
| 213 | ||
| 214 | switch (@typeInfo(T)) { | |
| 215 | .Float => if (!math.approxEqAbs(T, expected, actual, tolerance)) | |
| 216 | std.debug.panic("actual {}, not within absolute tolerance {} of expected {}", .{ actual, tolerance, expected }), | |
| 217 | ||
| 218 | .ComptimeFloat => @compileError("Cannot approximately compare two comptime_float values"), | |
| 209 | 219 | |
| 210 | switch (@typeInfo(@TypeOf(actual))) { | |
| 211 | .Float, | |
| 212 | .ComptimeFloat, | |
| 213 | => { | |
| 214 | if (@fabs(expected - actual) > margin) { | |
| 215 | std.debug.panic("actual {}, not within margin {} of expected {}", .{ actual, margin, expected }); | |
| 216 | } | |
| 217 | }, | |
| 218 | 220 | else => @compileError("Unable to compare non floating point values"), |
| 219 | 221 | } |
| 220 | 222 | } |
| 221 | 223 | |
| 222 | test "expectWithinMargin" { | |
| 224 | test "expectApproxEqAbs" { | |
| 223 | 225 | inline for ([_]type{ f16, f32, f64, f128 }) |T| { |
| 224 | 226 | const pos_x: T = 12.0; |
| 225 | 227 | const pos_y: T = 12.06; |
| 226 | 228 | const neg_x: T = -12.0; |
| 227 | 229 | const neg_y: T = -12.06; |
| 228 | 230 | |
| 229 | expectWithinMargin(pos_x, pos_y, 0.1); | |
| 230 | expectWithinMargin(neg_x, neg_y, 0.1); | |
| 231 | expectApproxEqAbs(pos_x, pos_y, 0.1); | |
| 232 | expectApproxEqAbs(neg_x, neg_y, 0.1); | |
| 231 | 233 | } |
| 232 | 234 | } |
| 233 | 235 | |
| 234 | /// This function is intended to be used only in tests. When the actual value is not | |
| 235 | /// within the epsilon of the expected value, | |
| 236 | /// prints diagnostics to stderr to show exactly how they are not equal, then aborts. | |
| 236 | /// This function is intended to be used only in tests. When the actual value is | |
| 237 | /// not approximately equal to the expected value, prints diagnostics to stderr | |
| 238 | /// to show exactly how they are not equal, then aborts. | |
| 239 | /// See `math.approxEqRel` for more informations on the tolerance parameter. | |
| 237 | 240 | /// The types must be floating point |
| 238 | pub fn expectWithinEpsilon(expected: anytype, actual: @TypeOf(expected), epsilon: @TypeOf(expected)) void { | |
| 239 | std.debug.assert(epsilon >= 0.0 and epsilon <= 1.0); | |
| 241 | pub fn expectApproxEqRel(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) void { | |
| 242 | const T = @TypeOf(expected); | |
| 243 | ||
| 244 | switch (@typeInfo(T)) { | |
| 245 | .Float => if (!math.approxEqRel(T, expected, actual, tolerance)) | |
| 246 | std.debug.panic("actual {}, not within relative tolerance {} of expected {}", .{ actual, tolerance, expected }), | |
| 247 | ||
| 248 | .ComptimeFloat => @compileError("Cannot approximately compare two comptime_float values"), | |
| 240 | 249 | |
| 241 | // Relative epsilon test. | |
| 242 | const margin = math.max(math.fabs(expected), math.fabs(actual)) * epsilon; | |
| 243 | switch (@typeInfo(@TypeOf(actual))) { | |
| 244 | .Float, | |
| 245 | .ComptimeFloat, | |
| 246 | => { | |
| 247 | if (@fabs(expected - actual) > margin) { | |
| 248 | std.debug.panic("actual {}, not within epsilon {}, of expected {}", .{ actual, epsilon, expected }); | |
| 249 | } | |
| 250 | }, | |
| 251 | 250 | else => @compileError("Unable to compare non floating point values"), |
| 252 | 251 | } |
| 253 | 252 | } |
| 254 | 253 | |
| 255 | test "expectWithinEpsilon" { | |
| 254 | test "expectApproxEqRel" { | |
| 256 | 255 | inline for ([_]type{ f16, f32, f64, f128 }) |T| { |
| 256 | const eps_value = comptime math.epsilon(T); | |
| 257 | const sqrt_eps_value = comptime math.sqrt(eps_value); | |
| 258 | ||
| 257 | 259 | const pos_x: T = 12.0; |
| 258 | const pos_y: T = 13.2; | |
| 260 | const pos_y: T = pos_x + 2 * eps_value; | |
| 259 | 261 | const neg_x: T = -12.0; |
| 260 | const neg_y: T = -13.2; | |
| 262 | const neg_y: T = neg_x - 2 * eps_value; | |
| 261 | 263 | |
| 262 | expectWithinEpsilon(pos_x, pos_y, 0.1); | |
| 263 | expectWithinEpsilon(neg_x, neg_y, 0.1); | |
| 264 | expectApproxEqRel(pos_x, pos_y, sqrt_eps_value); | |
| 265 | expectApproxEqRel(neg_x, neg_y, sqrt_eps_value); | |
| 264 | 266 | } |
| 265 | 267 | } |
| 266 | 268 | |
| ... | ... | @@ -296,7 +298,7 @@ pub const TmpDir = struct { |
| 296 | 298 | sub_path: [sub_path_len]u8, |
| 297 | 299 | |
| 298 | 300 | const random_bytes_count = 12; |
| 299 | const sub_path_len = std.base64.Base64Encoder.calcSize(random_bytes_count); | |
| 301 | const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); | |
| 300 | 302 | |
| 301 | 303 | pub fn cleanup(self: *TmpDir) void { |
| 302 | 304 | self.dir.close(); |
lib/std/zig/parser_test.zig+319| ... | ... | @@ -4,6 +4,31 @@ |
| 4 | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | |
| 7 | test "zig fmt: respect line breaks in struct field value declaration" { | |
| 8 | try testCanonical( | |
| 9 | \\const Foo = struct { | |
| 10 | \\ bar: u32 = | |
| 11 | \\ 42, | |
| 12 | \\ bar: u32 = | |
| 13 | \\ // a comment | |
| 14 | \\ 42, | |
| 15 | \\ bar: u32 = | |
| 16 | \\ 42, | |
| 17 | \\ // a comment | |
| 18 | \\ bar: []const u8 = | |
| 19 | \\ \\ foo | |
| 20 | \\ \\ bar | |
| 21 | \\ \\ baz | |
| 22 | \\ , | |
| 23 | \\ bar: u32 = | |
| 24 | \\ blk: { | |
| 25 | \\ break :blk 42; | |
| 26 | \\ }, | |
| 27 | \\}; | |
| 28 | \\ | |
| 29 | ); | |
| 30 | } | |
| 31 | ||
| 7 | 32 | // TODO Remove this after zig 0.9.0 is released. |
| 8 | 33 | test "zig fmt: rewrite inline functions as callconv(.Inline)" { |
| 9 | 34 | try testTransform( |
| ... | ... | @@ -3038,6 +3063,54 @@ test "zig fmt: switch" { |
| 3038 | 3063 | \\} |
| 3039 | 3064 | \\ |
| 3040 | 3065 | ); |
| 3066 | ||
| 3067 | try testTransform( | |
| 3068 | \\test { | |
| 3069 | \\ switch (x) { | |
| 3070 | \\ foo => | |
| 3071 | \\ "bar", | |
| 3072 | \\ } | |
| 3073 | \\} | |
| 3074 | \\ | |
| 3075 | , | |
| 3076 | \\test { | |
| 3077 | \\ switch (x) { | |
| 3078 | \\ foo => "bar", | |
| 3079 | \\ } | |
| 3080 | \\} | |
| 3081 | \\ | |
| 3082 | ); | |
| 3083 | } | |
| 3084 | ||
| 3085 | test "zig fmt: switch multiline string" { | |
| 3086 | try testCanonical( | |
| 3087 | \\test "switch multiline string" { | |
| 3088 | \\ const x: u32 = 0; | |
| 3089 | \\ const str = switch (x) { | |
| 3090 | \\ 1 => "one", | |
| 3091 | \\ 2 => | |
| 3092 | \\ \\ Comma after the multiline string | |
| 3093 | \\ \\ is needed | |
| 3094 | \\ , | |
| 3095 | \\ 3 => "three", | |
| 3096 | \\ else => "else", | |
| 3097 | \\ }; | |
| 3098 | \\ | |
| 3099 | \\ const Union = union(enum) { | |
| 3100 | \\ Int: i64, | |
| 3101 | \\ Float: f64, | |
| 3102 | \\ }; | |
| 3103 | \\ | |
| 3104 | \\ const str = switch (u) { | |
| 3105 | \\ Union.Int => |int| | |
| 3106 | \\ \\ Comma after the multiline string | |
| 3107 | \\ \\ is needed | |
| 3108 | \\ , | |
| 3109 | \\ Union.Float => |*float| unreachable, | |
| 3110 | \\ }; | |
| 3111 | \\} | |
| 3112 | \\ | |
| 3113 | ); | |
| 3041 | 3114 | } |
| 3042 | 3115 | |
| 3043 | 3116 | test "zig fmt: while" { |
| ... | ... | @@ -3068,6 +3141,11 @@ test "zig fmt: while" { |
| 3068 | 3141 | \\ while (i < 10) : ({ |
| 3069 | 3142 | \\ i += 1; |
| 3070 | 3143 | \\ j += 1; |
| 3144 | \\ }) continue; | |
| 3145 | \\ | |
| 3146 | \\ while (i < 10) : ({ | |
| 3147 | \\ i += 1; | |
| 3148 | \\ j += 1; | |
| 3071 | 3149 | \\ }) { |
| 3072 | 3150 | \\ continue; |
| 3073 | 3151 | \\ } |
| ... | ... | @@ -3184,6 +3262,156 @@ test "zig fmt: for" { |
| 3184 | 3262 | ); |
| 3185 | 3263 | } |
| 3186 | 3264 | |
| 3265 | test "zig fmt: for if" { | |
| 3266 | try testCanonical( | |
| 3267 | \\test { | |
| 3268 | \\ for (a) |x| if (x) f(x); | |
| 3269 | \\ | |
| 3270 | \\ for (a) |x| if (x) | |
| 3271 | \\ f(x); | |
| 3272 | \\ | |
| 3273 | \\ for (a) |x| if (x) { | |
| 3274 | \\ f(x); | |
| 3275 | \\ }; | |
| 3276 | \\ | |
| 3277 | \\ for (a) |x| | |
| 3278 | \\ if (x) | |
| 3279 | \\ f(x); | |
| 3280 | \\ | |
| 3281 | \\ for (a) |x| | |
| 3282 | \\ if (x) { | |
| 3283 | \\ f(x); | |
| 3284 | \\ }; | |
| 3285 | \\} | |
| 3286 | \\ | |
| 3287 | ); | |
| 3288 | } | |
| 3289 | ||
| 3290 | test "zig fmt: if for" { | |
| 3291 | try testCanonical( | |
| 3292 | \\test { | |
| 3293 | \\ if (a) for (x) |x| f(x); | |
| 3294 | \\ | |
| 3295 | \\ if (a) for (x) |x| | |
| 3296 | \\ f(x); | |
| 3297 | \\ | |
| 3298 | \\ if (a) for (x) |x| { | |
| 3299 | \\ f(x); | |
| 3300 | \\ }; | |
| 3301 | \\ | |
| 3302 | \\ if (a) | |
| 3303 | \\ for (x) |x| | |
| 3304 | \\ f(x); | |
| 3305 | \\ | |
| 3306 | \\ if (a) | |
| 3307 | \\ for (x) |x| { | |
| 3308 | \\ f(x); | |
| 3309 | \\ }; | |
| 3310 | \\} | |
| 3311 | \\ | |
| 3312 | ); | |
| 3313 | } | |
| 3314 | ||
| 3315 | test "zig fmt: while if" { | |
| 3316 | try testCanonical( | |
| 3317 | \\test { | |
| 3318 | \\ while (a) if (x) f(x); | |
| 3319 | \\ | |
| 3320 | \\ while (a) if (x) | |
| 3321 | \\ f(x); | |
| 3322 | \\ | |
| 3323 | \\ while (a) if (x) { | |
| 3324 | \\ f(x); | |
| 3325 | \\ }; | |
| 3326 | \\ | |
| 3327 | \\ while (a) | |
| 3328 | \\ if (x) | |
| 3329 | \\ f(x); | |
| 3330 | \\ | |
| 3331 | \\ while (a) | |
| 3332 | \\ if (x) { | |
| 3333 | \\ f(x); | |
| 3334 | \\ }; | |
| 3335 | \\} | |
| 3336 | \\ | |
| 3337 | ); | |
| 3338 | } | |
| 3339 | ||
| 3340 | test "zig fmt: if while" { | |
| 3341 | try testCanonical( | |
| 3342 | \\test { | |
| 3343 | \\ if (a) while (x) : (cont) f(x); | |
| 3344 | \\ | |
| 3345 | \\ if (a) while (x) : (cont) | |
| 3346 | \\ f(x); | |
| 3347 | \\ | |
| 3348 | \\ if (a) while (x) : (cont) { | |
| 3349 | \\ f(x); | |
| 3350 | \\ }; | |
| 3351 | \\ | |
| 3352 | \\ if (a) | |
| 3353 | \\ while (x) : (cont) | |
| 3354 | \\ f(x); | |
| 3355 | \\ | |
| 3356 | \\ if (a) | |
| 3357 | \\ while (x) : (cont) { | |
| 3358 | \\ f(x); | |
| 3359 | \\ }; | |
| 3360 | \\} | |
| 3361 | \\ | |
| 3362 | ); | |
| 3363 | } | |
| 3364 | ||
| 3365 | test "zig fmt: while for" { | |
| 3366 | try testCanonical( | |
| 3367 | \\test { | |
| 3368 | \\ while (a) for (x) |x| f(x); | |
| 3369 | \\ | |
| 3370 | \\ while (a) for (x) |x| | |
| 3371 | \\ f(x); | |
| 3372 | \\ | |
| 3373 | \\ while (a) for (x) |x| { | |
| 3374 | \\ f(x); | |
| 3375 | \\ }; | |
| 3376 | \\ | |
| 3377 | \\ while (a) | |
| 3378 | \\ for (x) |x| | |
| 3379 | \\ f(x); | |
| 3380 | \\ | |
| 3381 | \\ while (a) | |
| 3382 | \\ for (x) |x| { | |
| 3383 | \\ f(x); | |
| 3384 | \\ }; | |
| 3385 | \\} | |
| 3386 | \\ | |
| 3387 | ); | |
| 3388 | } | |
| 3389 | ||
| 3390 | test "zig fmt: for while" { | |
| 3391 | try testCanonical( | |
| 3392 | \\test { | |
| 3393 | \\ for (a) |a| while (x) |x| f(x); | |
| 3394 | \\ | |
| 3395 | \\ for (a) |a| while (x) |x| | |
| 3396 | \\ f(x); | |
| 3397 | \\ | |
| 3398 | \\ for (a) |a| while (x) |x| { | |
| 3399 | \\ f(x); | |
| 3400 | \\ }; | |
| 3401 | \\ | |
| 3402 | \\ for (a) |a| | |
| 3403 | \\ while (x) |x| | |
| 3404 | \\ f(x); | |
| 3405 | \\ | |
| 3406 | \\ for (a) |a| | |
| 3407 | \\ while (x) |x| { | |
| 3408 | \\ f(x); | |
| 3409 | \\ }; | |
| 3410 | \\} | |
| 3411 | \\ | |
| 3412 | ); | |
| 3413 | } | |
| 3414 | ||
| 3187 | 3415 | test "zig fmt: if" { |
| 3188 | 3416 | try testCanonical( |
| 3189 | 3417 | \\test "if" { |
| ... | ... | @@ -3233,6 +3461,82 @@ test "zig fmt: if" { |
| 3233 | 3461 | ); |
| 3234 | 3462 | } |
| 3235 | 3463 | |
| 3464 | test "zig fmt: fix single statement if/for/while line breaks" { | |
| 3465 | try testTransform( | |
| 3466 | \\test { | |
| 3467 | \\ if (cond) a | |
| 3468 | \\ else b; | |
| 3469 | \\ | |
| 3470 | \\ if (cond) | |
| 3471 | \\ a | |
| 3472 | \\ else b; | |
| 3473 | \\ | |
| 3474 | \\ for (xs) |x| foo() | |
| 3475 | \\ else bar(); | |
| 3476 | \\ | |
| 3477 | \\ for (xs) |x| | |
| 3478 | \\ foo() | |
| 3479 | \\ else bar(); | |
| 3480 | \\ | |
| 3481 | \\ while (a) : (b) foo() | |
| 3482 | \\ else bar(); | |
| 3483 | \\ | |
| 3484 | \\ while (a) : (b) | |
| 3485 | \\ foo() | |
| 3486 | \\ else bar(); | |
| 3487 | \\} | |
| 3488 | \\ | |
| 3489 | , | |
| 3490 | \\test { | |
| 3491 | \\ if (cond) a else b; | |
| 3492 | \\ | |
| 3493 | \\ if (cond) | |
| 3494 | \\ a | |
| 3495 | \\ else | |
| 3496 | \\ b; | |
| 3497 | \\ | |
| 3498 | \\ for (xs) |x| foo() else bar(); | |
| 3499 | \\ | |
| 3500 | \\ for (xs) |x| | |
| 3501 | \\ foo() | |
| 3502 | \\ else | |
| 3503 | \\ bar(); | |
| 3504 | \\ | |
| 3505 | \\ while (a) : (b) foo() else bar(); | |
| 3506 | \\ | |
| 3507 | \\ while (a) : (b) | |
| 3508 | \\ foo() | |
| 3509 | \\ else | |
| 3510 | \\ bar(); | |
| 3511 | \\} | |
| 3512 | \\ | |
| 3513 | ); | |
| 3514 | } | |
| 3515 | ||
| 3516 | test "zig fmt: anon struct/array literal in if" { | |
| 3517 | try testCanonical( | |
| 3518 | \\test { | |
| 3519 | \\ const a = if (cond) .{ | |
| 3520 | \\ 1, 2, | |
| 3521 | \\ 3, 4, | |
| 3522 | \\ } else .{ | |
| 3523 | \\ 1, | |
| 3524 | \\ 2, | |
| 3525 | \\ 3, | |
| 3526 | \\ }; | |
| 3527 | \\ | |
| 3528 | \\ const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{ | |
| 3529 | \\ .rl = .ref, | |
| 3530 | \\ .tag = .switchbr_ref, | |
| 3531 | \\ } else .{ | |
| 3532 | \\ .rl = .none, | |
| 3533 | \\ .tag = .switchbr, | |
| 3534 | \\ }; | |
| 3535 | \\} | |
| 3536 | \\ | |
| 3537 | ); | |
| 3538 | } | |
| 3539 | ||
| 3236 | 3540 | test "zig fmt: defer" { |
| 3237 | 3541 | try testCanonical( |
| 3238 | 3542 | \\test "defer" { |
| ... | ... | @@ -3820,6 +4124,7 @@ test "zig fmt: comments in ternary ifs" { |
| 3820 | 4124 | \\ // Comment |
| 3821 | 4125 | \\ 1 |
| 3822 | 4126 | \\else |
| 4127 | \\ // Comment | |
| 3823 | 4128 | \\ 0; |
| 3824 | 4129 | \\ |
| 3825 | 4130 | \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; |
| ... | ... | @@ -3827,6 +4132,20 @@ test "zig fmt: comments in ternary ifs" { |
| 3827 | 4132 | ); |
| 3828 | 4133 | } |
| 3829 | 4134 | |
| 4135 | test "zig fmt: while statement in blockless if" { | |
| 4136 | try testCanonical( | |
| 4137 | \\pub fn main() void { | |
| 4138 | \\ const zoom_node = if (focused_node == layout_first) | |
| 4139 | \\ while (it.next()) |node| { | |
| 4140 | \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; | |
| 4141 | \\ } else null | |
| 4142 | \\ else | |
| 4143 | \\ focused_node; | |
| 4144 | \\} | |
| 4145 | \\ | |
| 4146 | ); | |
| 4147 | } | |
| 4148 | ||
| 3830 | 4149 | test "zig fmt: test comments in field access chain" { |
| 3831 | 4150 | try testCanonical( |
| 3832 | 4151 | \\pub const str = struct { |
lib/std/zig/render.zig+98-161| ... | ... | @@ -1018,147 +1018,14 @@ fn renderWhile(gpa: *Allocator, ais: *Ais, tree: ast.Tree, while_node: ast.full. |
| 1018 | 1018 | try renderToken(ais, tree, inline_token, .space); // inline |
| 1019 | 1019 | } |
| 1020 | 1020 | |
| 1021 | try renderToken(ais, tree, while_node.ast.while_token, .space); // if | |
| 1021 | try renderToken(ais, tree, while_node.ast.while_token, .space); // if/for/while | |
| 1022 | 1022 | try renderToken(ais, tree, while_node.ast.while_token + 1, .none); // lparen |
| 1023 | 1023 | try renderExpression(gpa, ais, tree, while_node.ast.cond_expr, .none); // condition |
| 1024 | 1024 | |
| 1025 | const then_tag = node_tags[while_node.ast.then_expr]; | |
| 1026 | if (nodeIsBlock(then_tag) and !nodeIsIf(then_tag)) { | |
| 1027 | if (while_node.payload_token) |payload_token| { | |
| 1028 | try renderToken(ais, tree, payload_token - 2, .space); // rparen | |
| 1029 | try renderToken(ais, tree, payload_token - 1, .none); // | | |
| 1030 | const ident = blk: { | |
| 1031 | if (token_tags[payload_token] == .asterisk) { | |
| 1032 | try renderToken(ais, tree, payload_token, .none); // * | |
| 1033 | break :blk payload_token + 1; | |
| 1034 | } else { | |
| 1035 | break :blk payload_token; | |
| 1036 | } | |
| 1037 | }; | |
| 1038 | try renderToken(ais, tree, ident, .none); // identifier | |
| 1039 | const pipe = blk: { | |
| 1040 | if (token_tags[ident + 1] == .comma) { | |
| 1041 | try renderToken(ais, tree, ident + 1, .space); // , | |
| 1042 | try renderToken(ais, tree, ident + 2, .none); // index | |
| 1043 | break :blk ident + 3; | |
| 1044 | } else { | |
| 1045 | break :blk ident + 1; | |
| 1046 | } | |
| 1047 | }; | |
| 1048 | const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented()) | |
| 1049 | Space.newline | |
| 1050 | else | |
| 1051 | Space.space; | |
| 1052 | try renderToken(ais, tree, pipe, brace_space); // | | |
| 1053 | } else { | |
| 1054 | const rparen = tree.lastToken(while_node.ast.cond_expr) + 1; | |
| 1055 | const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented()) | |
| 1056 | Space.newline | |
| 1057 | else | |
| 1058 | Space.space; | |
| 1059 | try renderToken(ais, tree, rparen, brace_space); // rparen | |
| 1060 | } | |
| 1061 | if (while_node.ast.cont_expr != 0) { | |
| 1062 | const rparen = tree.lastToken(while_node.ast.cont_expr) + 1; | |
| 1063 | const lparen = tree.firstToken(while_node.ast.cont_expr) - 1; | |
| 1064 | try renderToken(ais, tree, lparen - 1, .space); // : | |
| 1065 | try renderToken(ais, tree, lparen, .none); // lparen | |
| 1066 | try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none); | |
| 1067 | const brace_space: Space = if (ais.isLineOverIndented()) .newline else .space; | |
| 1068 | try renderToken(ais, tree, rparen, brace_space); // rparen | |
| 1069 | } | |
| 1070 | if (while_node.ast.else_expr != 0) { | |
| 1071 | try renderExpression(gpa, ais, tree, while_node.ast.then_expr, Space.space); | |
| 1072 | try renderToken(ais, tree, while_node.else_token, .space); // else | |
| 1073 | if (while_node.error_token) |error_token| { | |
| 1074 | try renderToken(ais, tree, error_token - 1, .none); // | | |
| 1075 | try renderToken(ais, tree, error_token, .none); // identifier | |
| 1076 | try renderToken(ais, tree, error_token + 1, .space); // | | |
| 1077 | } | |
| 1078 | return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); | |
| 1079 | } else { | |
| 1080 | return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space); | |
| 1081 | } | |
| 1082 | } | |
| 1083 | ||
| 1084 | const rparen = tree.lastToken(while_node.ast.cond_expr) + 1; | |
| 1085 | const last_then_token = tree.lastToken(while_node.ast.then_expr); | |
| 1086 | const src_has_newline = !tree.tokensOnSameLine(rparen, last_then_token); | |
| 1087 | ||
| 1088 | if (src_has_newline) { | |
| 1089 | if (while_node.payload_token) |payload_token| { | |
| 1090 | try renderToken(ais, tree, payload_token - 2, .space); // rparen | |
| 1091 | try renderToken(ais, tree, payload_token - 1, .none); // | | |
| 1092 | const ident = blk: { | |
| 1093 | if (token_tags[payload_token] == .asterisk) { | |
| 1094 | try renderToken(ais, tree, payload_token, .none); // * | |
| 1095 | break :blk payload_token + 1; | |
| 1096 | } else { | |
| 1097 | break :blk payload_token; | |
| 1098 | } | |
| 1099 | }; | |
| 1100 | try renderToken(ais, tree, ident, .none); // identifier | |
| 1101 | const pipe = blk: { | |
| 1102 | if (token_tags[ident + 1] == .comma) { | |
| 1103 | try renderToken(ais, tree, ident + 1, .space); // , | |
| 1104 | try renderToken(ais, tree, ident + 2, .none); // index | |
| 1105 | break :blk ident + 3; | |
| 1106 | } else { | |
| 1107 | break :blk ident + 1; | |
| 1108 | } | |
| 1109 | }; | |
| 1110 | const after_space: Space = if (while_node.ast.cont_expr != 0) .space else .newline; | |
| 1111 | try renderToken(ais, tree, pipe, after_space); // | | |
| 1112 | } else { | |
| 1113 | ais.pushIndent(); | |
| 1114 | const after_space: Space = if (while_node.ast.cont_expr != 0) .space else .newline; | |
| 1115 | try renderToken(ais, tree, rparen, after_space); // rparen | |
| 1116 | ais.popIndent(); | |
| 1117 | } | |
| 1118 | if (while_node.ast.cont_expr != 0) { | |
| 1119 | const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1; | |
| 1120 | const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1; | |
| 1121 | try renderToken(ais, tree, cont_lparen - 1, .space); // : | |
| 1122 | try renderToken(ais, tree, cont_lparen, .none); // lparen | |
| 1123 | try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none); | |
| 1124 | try renderToken(ais, tree, cont_rparen, .newline); // rparen | |
| 1125 | } | |
| 1126 | if (while_node.ast.else_expr != 0) { | |
| 1127 | ais.pushIndent(); | |
| 1128 | try renderExpression(gpa, ais, tree, while_node.ast.then_expr, Space.newline); | |
| 1129 | ais.popIndent(); | |
| 1130 | const else_is_block = nodeIsBlock(node_tags[while_node.ast.else_expr]); | |
| 1131 | if (else_is_block) { | |
| 1132 | try renderToken(ais, tree, while_node.else_token, .space); // else | |
| 1133 | if (while_node.error_token) |error_token| { | |
| 1134 | try renderToken(ais, tree, error_token - 1, .none); // | | |
| 1135 | try renderToken(ais, tree, error_token, .none); // identifier | |
| 1136 | try renderToken(ais, tree, error_token + 1, .space); // | | |
| 1137 | } | |
| 1138 | return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); | |
| 1139 | } else { | |
| 1140 | if (while_node.error_token) |error_token| { | |
| 1141 | try renderToken(ais, tree, while_node.else_token, .space); // else | |
| 1142 | try renderToken(ais, tree, error_token - 1, .none); // | | |
| 1143 | try renderToken(ais, tree, error_token, .none); // identifier | |
| 1144 | try renderToken(ais, tree, error_token + 1, .space); // | | |
| 1145 | } else { | |
| 1146 | try renderToken(ais, tree, while_node.else_token, .newline); // else | |
| 1147 | } | |
| 1148 | try renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space); | |
| 1149 | return; | |
| 1150 | } | |
| 1151 | } else { | |
| 1152 | try renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space); | |
| 1153 | return; | |
| 1154 | } | |
| 1155 | } | |
| 1156 | ||
| 1157 | // Render everything on a single line. | |
| 1025 | var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen | |
| 1158 | 1026 | |
| 1159 | 1027 | if (while_node.payload_token) |payload_token| { |
| 1160 | assert(payload_token - 2 == rparen); | |
| 1161 | try renderToken(ais, tree, payload_token - 2, .space); // ) | |
| 1028 | try renderToken(ais, tree, last_prefix_token, .space); | |
| 1162 | 1029 | try renderToken(ais, tree, payload_token - 1, .none); // | |
| 1163 | 1030 | const ident = blk: { |
| 1164 | 1031 | if (token_tags[payload_token] == .asterisk) { |
| ... | ... | @@ -1178,33 +1045,67 @@ fn renderWhile(gpa: *Allocator, ais: *Ais, tree: ast.Tree, while_node: ast.full. |
| 1178 | 1045 | break :blk ident + 1; |
| 1179 | 1046 | } |
| 1180 | 1047 | }; |
| 1181 | try renderToken(ais, tree, pipe, .space); // | | |
| 1182 | } else { | |
| 1183 | try renderToken(ais, tree, rparen, .space); // ) | |
| 1048 | last_prefix_token = pipe; | |
| 1184 | 1049 | } |
| 1185 | 1050 | |
| 1186 | 1051 | if (while_node.ast.cont_expr != 0) { |
| 1187 | const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1; | |
| 1188 | const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1; | |
| 1189 | try renderToken(ais, tree, cont_lparen - 1, .space); // : | |
| 1190 | try renderToken(ais, tree, cont_lparen, .none); // lparen | |
| 1052 | try renderToken(ais, tree, last_prefix_token, .space); | |
| 1053 | const lparen = tree.firstToken(while_node.ast.cont_expr) - 1; | |
| 1054 | try renderToken(ais, tree, lparen - 1, .space); // : | |
| 1055 | try renderToken(ais, tree, lparen, .none); // lparen | |
| 1191 | 1056 | try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none); |
| 1192 | try renderToken(ais, tree, cont_rparen, .space); // rparen | |
| 1057 | last_prefix_token = tree.lastToken(while_node.ast.cont_expr) + 1; // rparen | |
| 1058 | } | |
| 1059 | ||
| 1060 | const then_expr_is_block = nodeIsBlock(node_tags[while_node.ast.then_expr]); | |
| 1061 | const indent_then_expr = !then_expr_is_block and | |
| 1062 | !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(while_node.ast.then_expr)); | |
| 1063 | if (indent_then_expr or (then_expr_is_block and ais.isLineOverIndented())) { | |
| 1064 | ais.pushIndentNextLine(); | |
| 1065 | try renderToken(ais, tree, last_prefix_token, .newline); | |
| 1066 | ais.popIndent(); | |
| 1067 | } else { | |
| 1068 | try renderToken(ais, tree, last_prefix_token, .space); | |
| 1193 | 1069 | } |
| 1194 | 1070 | |
| 1195 | 1071 | if (while_node.ast.else_expr != 0) { |
| 1196 | try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space); | |
| 1197 | try renderToken(ais, tree, while_node.else_token, .space); // else | |
| 1072 | const first_else_expr_tok = tree.firstToken(while_node.ast.else_expr); | |
| 1073 | ||
| 1074 | if (indent_then_expr) { | |
| 1075 | ais.pushIndent(); | |
| 1076 | try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .newline); | |
| 1077 | ais.popIndent(); | |
| 1078 | } else { | |
| 1079 | try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space); | |
| 1080 | } | |
| 1081 | ||
| 1082 | var last_else_token = while_node.else_token; | |
| 1198 | 1083 | |
| 1199 | 1084 | if (while_node.error_token) |error_token| { |
| 1085 | try renderToken(ais, tree, while_node.else_token, .space); // else | |
| 1200 | 1086 | try renderToken(ais, tree, error_token - 1, .none); // | |
| 1201 | 1087 | try renderToken(ais, tree, error_token, .none); // identifier |
| 1202 | try renderToken(ais, tree, error_token + 1, .space); // | | |
| 1088 | last_else_token = error_token + 1; // | | |
| 1203 | 1089 | } |
| 1204 | 1090 | |
| 1205 | return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); | |
| 1091 | const indent_else_expr = indent_then_expr and | |
| 1092 | !nodeIsBlock(node_tags[while_node.ast.else_expr]) and | |
| 1093 | !nodeIsIfForWhileSwitch(node_tags[while_node.ast.else_expr]); | |
| 1094 | if (indent_else_expr) { | |
| 1095 | ais.pushIndentNextLine(); | |
| 1096 | try renderToken(ais, tree, last_else_token, .newline); | |
| 1097 | ais.popIndent(); | |
| 1098 | try renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space); | |
| 1099 | } else { | |
| 1100 | try renderToken(ais, tree, last_else_token, .space); | |
| 1101 | try renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); | |
| 1102 | } | |
| 1206 | 1103 | } else { |
| 1207 | return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space); | |
| 1104 | if (indent_then_expr) { | |
| 1105 | try renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space); | |
| 1106 | } else { | |
| 1107 | try renderExpression(gpa, ais, tree, while_node.ast.then_expr, space); | |
| 1108 | } | |
| 1208 | 1109 | } |
| 1209 | 1110 | } |
| 1210 | 1111 | |
| ... | ... | @@ -1258,8 +1159,29 @@ fn renderContainerField( |
| 1258 | 1159 | try renderToken(ais, tree, rparen_token, .space); // ) |
| 1259 | 1160 | } |
| 1260 | 1161 | const eq_token = tree.firstToken(field.ast.value_expr) - 1; |
| 1261 | try renderToken(ais, tree, eq_token, .space); // = | |
| 1262 | return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value | |
| 1162 | const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; | |
| 1163 | { | |
| 1164 | ais.pushIndent(); | |
| 1165 | try renderToken(ais, tree, eq_token, eq_space); // = | |
| 1166 | ais.popIndent(); | |
| 1167 | } | |
| 1168 | ||
| 1169 | if (eq_space == .space) | |
| 1170 | return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value | |
| 1171 | ||
| 1172 | const token_tags = tree.tokens.items(.tag); | |
| 1173 | const maybe_comma = tree.lastToken(field.ast.value_expr) + 1; | |
| 1174 | ||
| 1175 | if (token_tags[maybe_comma] == .comma) { | |
| 1176 | ais.pushIndent(); | |
| 1177 | try renderExpression(gpa, ais, tree, field.ast.value_expr, .none); // value | |
| 1178 | ais.popIndent(); | |
| 1179 | try renderToken(ais, tree, maybe_comma, space); | |
| 1180 | } else { | |
| 1181 | ais.pushIndent(); | |
| 1182 | try renderExpression(gpa, ais, tree, field.ast.value_expr, space); // value | |
| 1183 | ais.popIndent(); | |
| 1184 | } | |
| 1263 | 1185 | } |
| 1264 | 1186 | |
| 1265 | 1187 | fn renderBuiltinCall( |
| ... | ... | @@ -1522,6 +1444,7 @@ fn renderSwitchCase( |
| 1522 | 1444 | switch_case: ast.full.SwitchCase, |
| 1523 | 1445 | space: Space, |
| 1524 | 1446 | ) Error!void { |
| 1447 | const node_tags = tree.nodes.items(.tag); | |
| 1525 | 1448 | const token_tags = tree.tokens.items(.tag); |
| 1526 | 1449 | const trailing_comma = token_tags[switch_case.ast.arrow_token - 1] == .comma; |
| 1527 | 1450 | |
| ... | ... | @@ -1544,17 +1467,23 @@ fn renderSwitchCase( |
| 1544 | 1467 | } |
| 1545 | 1468 | |
| 1546 | 1469 | // Render the arrow and everything after it |
| 1547 | try renderToken(ais, tree, switch_case.ast.arrow_token, .space); | |
| 1470 | const pre_target_space = if (node_tags[switch_case.ast.target_expr] == .multiline_string_literal) | |
| 1471 | // Newline gets inserted when rendering the target expr. | |
| 1472 | Space.none | |
| 1473 | else | |
| 1474 | Space.space; | |
| 1475 | const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space; | |
| 1476 | try renderToken(ais, tree, switch_case.ast.arrow_token, after_arrow_space); | |
| 1548 | 1477 | |
| 1549 | 1478 | if (switch_case.payload_token) |payload_token| { |
| 1550 | 1479 | try renderToken(ais, tree, payload_token - 1, .none); // pipe |
| 1551 | 1480 | if (token_tags[payload_token] == .asterisk) { |
| 1552 | 1481 | try renderToken(ais, tree, payload_token, .none); // asterisk |
| 1553 | 1482 | try renderToken(ais, tree, payload_token + 1, .none); // identifier |
| 1554 | try renderToken(ais, tree, payload_token + 2, .space); // pipe | |
| 1483 | try renderToken(ais, tree, payload_token + 2, pre_target_space); // pipe | |
| 1555 | 1484 | } else { |
| 1556 | 1485 | try renderToken(ais, tree, payload_token, .none); // identifier |
| 1557 | try renderToken(ais, tree, payload_token + 1, .space); // pipe | |
| 1486 | try renderToken(ais, tree, payload_token + 1, pre_target_space); // pipe | |
| 1558 | 1487 | } |
| 1559 | 1488 | } |
| 1560 | 1489 | |
| ... | ... | @@ -2493,6 +2422,21 @@ fn nodeIsBlock(tag: ast.Node.Tag) bool { |
| 2493 | 2422 | .block_semicolon, |
| 2494 | 2423 | .block_two, |
| 2495 | 2424 | .block_two_semicolon, |
| 2425 | .struct_init_dot, | |
| 2426 | .struct_init_dot_comma, | |
| 2427 | .struct_init_dot_two, | |
| 2428 | .struct_init_dot_two_comma, | |
| 2429 | .array_init_dot, | |
| 2430 | .array_init_dot_comma, | |
| 2431 | .array_init_dot_two, | |
| 2432 | .array_init_dot_two_comma, | |
| 2433 | => true, | |
| 2434 | else => false, | |
| 2435 | }; | |
| 2436 | } | |
| 2437 | ||
| 2438 | fn nodeIsIfForWhileSwitch(tag: ast.Node.Tag) bool { | |
| 2439 | return switch (tag) { | |
| 2496 | 2440 | .@"if", |
| 2497 | 2441 | .if_simple, |
| 2498 | 2442 | .@"for", |
| ... | ... | @@ -2507,13 +2451,6 @@ fn nodeIsBlock(tag: ast.Node.Tag) bool { |
| 2507 | 2451 | }; |
| 2508 | 2452 | } |
| 2509 | 2453 | |
| 2510 | fn nodeIsIf(tag: ast.Node.Tag) bool { | |
| 2511 | return switch (tag) { | |
| 2512 | .@"if", .if_simple => true, | |
| 2513 | else => false, | |
| 2514 | }; | |
| 2515 | } | |
| 2516 | ||
| 2517 | 2454 | fn nodeCausesSliceOpSpace(tag: ast.Node.Tag) bool { |
| 2518 | 2455 | return switch (tag) { |
| 2519 | 2456 | .@"catch", |
src/BuiltinFn.zig+1-1| ... | ... | @@ -477,7 +477,7 @@ pub const list = list: { |
| 477 | 477 | "@intCast", |
| 478 | 478 | .{ |
| 479 | 479 | .tag = .int_cast, |
| 480 | .param_count = 1, | |
| 480 | .param_count = 2, | |
| 481 | 481 | }, |
| 482 | 482 | }, |
| 483 | 483 | .{ |
src/Compilation.zig+17-6| ... | ... | @@ -3180,7 +3180,11 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 3180 | 3180 | id_symlink_basename, |
| 3181 | 3181 | &prev_digest_buf, |
| 3182 | 3182 | ) catch |err| blk: { |
| 3183 | log.debug("stage1 {s} new_digest={} error: {s}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); | |
| 3183 | log.debug("stage1 {s} new_digest={s} error: {s}", .{ | |
| 3184 | mod.root_pkg.root_src_path, | |
| 3185 | std.fmt.fmtSliceHexLower(&digest), | |
| 3186 | @errorName(err), | |
| 3187 | }); | |
| 3184 | 3188 | // Handle this as a cache miss. |
| 3185 | 3189 | break :blk prev_digest_buf[0..0]; |
| 3186 | 3190 | }; |
| ... | ... | @@ -3188,10 +3192,13 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 3188 | 3192 | if (!mem.eql(u8, prev_digest[0..digest.len], &digest)) |
| 3189 | 3193 | break :hit; |
| 3190 | 3194 | |
| 3191 | log.debug("stage1 {s} digest={} match - skipping invocation", .{ mod.root_pkg.root_src_path, digest }); | |
| 3195 | log.debug("stage1 {s} digest={s} match - skipping invocation", .{ | |
| 3196 | mod.root_pkg.root_src_path, | |
| 3197 | std.fmt.fmtSliceHexLower(&digest), | |
| 3198 | }); | |
| 3192 | 3199 | var flags_bytes: [1]u8 = undefined; |
| 3193 | 3200 | _ = std.fmt.hexToBytes(&flags_bytes, prev_digest[digest.len..]) catch { |
| 3194 | log.warn("bad cache stage1 digest: '{s}'", .{prev_digest}); | |
| 3201 | log.warn("bad cache stage1 digest: '{s}'", .{std.fmt.fmtSliceHexLower(prev_digest)}); | |
| 3195 | 3202 | break :hit; |
| 3196 | 3203 | }; |
| 3197 | 3204 | |
| ... | ... | @@ -3211,7 +3218,11 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 3211 | 3218 | mod.stage1_flags = @bitCast(@TypeOf(mod.stage1_flags), flags_bytes[0]); |
| 3212 | 3219 | return; |
| 3213 | 3220 | } |
| 3214 | log.debug("stage1 {s} prev_digest={} new_digest={}", .{ mod.root_pkg.root_src_path, prev_digest, digest }); | |
| 3221 | log.debug("stage1 {s} prev_digest={s} new_digest={s}", .{ | |
| 3222 | mod.root_pkg.root_src_path, | |
| 3223 | std.fmt.fmtSliceHexLower(prev_digest), | |
| 3224 | std.fmt.fmtSliceHexLower(&digest), | |
| 3225 | }); | |
| 3215 | 3226 | man.unhit(prev_hash_state, input_file_count); |
| 3216 | 3227 | } |
| 3217 | 3228 | |
| ... | ... | @@ -3358,8 +3369,8 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 3358 | 3369 | // Update the small file with the digest. If it fails we can continue; it only |
| 3359 | 3370 | // means that the next invocation will have an unnecessary cache miss. |
| 3360 | 3371 | const stage1_flags_byte = @bitCast(u8, mod.stage1_flags); |
| 3361 | log.debug("stage1 {s} final digest={} flags={x}", .{ | |
| 3362 | mod.root_pkg.root_src_path, digest, stage1_flags_byte, | |
| 3372 | log.debug("stage1 {s} final digest={s} flags={x}", .{ | |
| 3373 | mod.root_pkg.root_src_path, std.fmt.fmtSliceHexLower(&digest), stage1_flags_byte, | |
| 3363 | 3374 | }); |
| 3364 | 3375 | var digest_plus_flags: [digest.len + 2]u8 = undefined; |
| 3365 | 3376 | digest_plus_flags[0..digest.len].* = digest; |
src/clang.zig+5| ... | ... | @@ -537,6 +537,11 @@ pub const FunctionType = opaque { |
| 537 | 537 | extern fn ZigClangFunctionType_getReturnType(*const FunctionType) QualType; |
| 538 | 538 | }; |
| 539 | 539 | |
| 540 | pub const GenericSelectionExpr = opaque { | |
| 541 | pub const getResultExpr = ZigClangGenericSelectionExpr_getResultExpr; | |
| 542 | extern fn ZigClangGenericSelectionExpr_getResultExpr(*const GenericSelectionExpr) *const Expr; | |
| 543 | }; | |
| 544 | ||
| 540 | 545 | pub const IfStmt = opaque { |
| 541 | 546 | pub const getThen = ZigClangIfStmt_getThen; |
| 542 | 547 | extern fn ZigClangIfStmt_getThen(*const IfStmt) *const Stmt; |
src/clang_options_data.zig+8-1| ... | ... | @@ -2415,7 +2415,14 @@ flagpd1("dwarf-ext-refs"), |
| 2415 | 2415 | sepd1("dylib_file"), |
| 2416 | 2416 | flagpd1("dylinker"), |
| 2417 | 2417 | flagpd1("dynamic"), |
| 2418 | flagpd1("dynamiclib"), | |
| 2418 | .{ | |
| 2419 | .name = "dynamiclib", | |
| 2420 | .syntax = .flag, | |
| 2421 | .zig_equivalent = .shared, | |
| 2422 | .pd1 = true, | |
| 2423 | .pd2 = false, | |
| 2424 | .psl = false, | |
| 2425 | }, | |
| 2419 | 2426 | flagpd1("emit-ast"), |
| 2420 | 2427 | flagpd1("emit-codegen-only"), |
| 2421 | 2428 | flagpd1("emit-header-module"), |
src/codegen.zig+61-134| ... | ... | @@ -2132,9 +2132,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2132 | 2132 | if (inst.func.value()) |func_value| { |
| 2133 | 2133 | if (func_value.castTag(.function)) |func_payload| { |
| 2134 | 2134 | const func = func_payload.data; |
| 2135 | const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment; | |
| 2136 | const got = &text_segment.sections.items[macho_file.got_section_index.?]; | |
| 2137 | const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); | |
| 2135 | const got_addr = blk: { | |
| 2136 | const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; | |
| 2137 | const got = seg.sections.items[macho_file.got_section_index.?]; | |
| 2138 | break :blk got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); | |
| 2139 | }; | |
| 2140 | log.debug("got_addr = 0x{x}", .{got_addr}); | |
| 2138 | 2141 | switch (arch) { |
| 2139 | 2142 | .x86_64 => { |
| 2140 | 2143 | try self.genSetReg(inst.base.src, Type.initTag(.u32), .rax, .{ .memory = got_addr }); |
| ... | ... | @@ -2152,8 +2155,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2152 | 2155 | const decl = func_payload.data; |
| 2153 | 2156 | const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name}); |
| 2154 | 2157 | defer self.bin_file.allocator.free(decl_name); |
| 2155 | const already_defined = macho_file.extern_lazy_symbols.contains(decl_name); | |
| 2156 | const symbol: u32 = if (macho_file.extern_lazy_symbols.getIndex(decl_name)) |index| | |
| 2158 | const already_defined = macho_file.lazy_imports.contains(decl_name); | |
| 2159 | const symbol: u32 = if (macho_file.lazy_imports.getIndex(decl_name)) |index| | |
| 2157 | 2160 | @intCast(u32, index) |
| 2158 | 2161 | else |
| 2159 | 2162 | try macho_file.addExternSymbol(decl_name); |
| ... | ... | @@ -3111,7 +3114,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3111 | 3114 | 4, 8 => { |
| 3112 | 3115 | const offset = if (math.cast(i9, adj_off)) |imm| |
| 3113 | 3116 | Instruction.LoadStoreOffset.imm_post_index(-imm) |
| 3114 | else |_| Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off })); | |
| 3117 | else |_| | |
| 3118 | Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off })); | |
| 3115 | 3119 | const rn: Register = switch (arch) { |
| 3116 | 3120 | .aarch64, .aarch64_be => .x29, |
| 3117 | 3121 | .aarch64_32 => .w29, |
| ... | ... | @@ -3302,80 +3306,32 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3302 | 3306 | }, |
| 3303 | 3307 | .memory => |addr| { |
| 3304 | 3308 | if (self.bin_file.options.pie) { |
| 3305 | // For MachO, the binary, with the exception of object files, has to be a PIE. | |
| 3306 | // Therefore we cannot load an absolute address. | |
| 3307 | // Instead, we need to make use of PC-relative addressing. | |
| 3308 | if (reg.id() == 0) { // x0 is special-cased | |
| 3309 | // TODO This needs to be optimised in the stack usage (perhaps use a shadow stack | |
| 3310 | // like described here: | |
| 3311 | // https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop) | |
| 3312 | // str x28, [sp, #-16] | |
| 3313 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{ | |
| 3314 | .offset = Instruction.LoadStoreOffset.imm_pre_index(-16), | |
| 3315 | }).toU32()); | |
| 3316 | // adr x28, #8 | |
| 3317 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); | |
| 3318 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 3319 | try macho_file.pie_fixups.append(self.bin_file.allocator, .{ | |
| 3320 | .address = addr, | |
| 3321 | .start = self.code.items.len, | |
| 3322 | .len = 4, | |
| 3323 | }); | |
| 3324 | } else { | |
| 3325 | return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); | |
| 3326 | } | |
| 3327 | // b [label] | |
| 3328 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); | |
| 3329 | // mov r, x0 | |
| 3330 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr( | |
| 3331 | reg, | |
| 3332 | .xzr, | |
| 3333 | .x0, | |
| 3334 | Instruction.Shift.none, | |
| 3335 | ).toU32()); | |
| 3336 | // ldr x28, [sp], #16 | |
| 3337 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ | |
| 3338 | .register = .{ | |
| 3339 | .rn = Register.sp, | |
| 3340 | .offset = Instruction.LoadStoreOffset.imm_post_index(16), | |
| 3341 | }, | |
| 3342 | }).toU32()); | |
| 3309 | // PC-relative displacement to the entry in the GOT table. | |
| 3310 | // TODO we should come up with our own, backend independent relocation types | |
| 3311 | // which each backend (Elf, MachO, etc.) would then translate into an actual | |
| 3312 | // fixup when linking. | |
| 3313 | // adrp reg, pages | |
| 3314 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 3315 | try macho_file.pie_fixups.append(self.bin_file.allocator, .{ | |
| 3316 | .target_addr = addr, | |
| 3317 | .offset = self.code.items.len, | |
| 3318 | .size = 4, | |
| 3319 | }); | |
| 3343 | 3320 | } else { |
| 3344 | // stp x0, x28, [sp, #-16] | |
| 3345 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.stp( | |
| 3346 | .x0, | |
| 3347 | .x28, | |
| 3348 | Register.sp, | |
| 3349 | Instruction.LoadStorePairOffset.pre_index(-16), | |
| 3350 | ).toU32()); | |
| 3351 | // adr x28, #8 | |
| 3352 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); | |
| 3353 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 3354 | try macho_file.pie_fixups.append(self.bin_file.allocator, .{ | |
| 3355 | .address = addr, | |
| 3356 | .start = self.code.items.len, | |
| 3357 | .len = 4, | |
| 3358 | }); | |
| 3359 | } else { | |
| 3360 | return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); | |
| 3361 | } | |
| 3362 | // b [label] | |
| 3363 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); | |
| 3364 | // mov r, x0 | |
| 3365 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr( | |
| 3366 | reg, | |
| 3367 | .xzr, | |
| 3368 | .x0, | |
| 3369 | Instruction.Shift.none, | |
| 3370 | ).toU32()); | |
| 3371 | // ldp x0, x28, [sp, #16] | |
| 3372 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldp( | |
| 3373 | .x0, | |
| 3374 | .x28, | |
| 3375 | Register.sp, | |
| 3376 | Instruction.LoadStorePairOffset.post_index(16), | |
| 3377 | ).toU32()); | |
| 3321 | return self.fail(src, "TODO implement genSetReg for PIE GOT indirection on this platform", .{}); | |
| 3378 | 3322 | } |
| 3323 | mem.writeIntLittle( | |
| 3324 | u32, | |
| 3325 | try self.code.addManyAsArray(4), | |
| 3326 | Instruction.adrp(reg, 0).toU32(), | |
| 3327 | ); | |
| 3328 | // ldr reg, reg, offset | |
| 3329 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ | |
| 3330 | .register = .{ | |
| 3331 | .rn = reg, | |
| 3332 | .offset = Instruction.LoadStoreOffset.imm(0), | |
| 3333 | }, | |
| 3334 | }).toU32()); | |
| 3379 | 3335 | } else { |
| 3380 | 3336 | // The value is in memory at a hard-coded address. |
| 3381 | 3337 | // If the type is a pointer, it means the pointer address is at this memory location. |
| ... | ... | @@ -3559,62 +3515,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3559 | 3515 | }, |
| 3560 | 3516 | .memory => |x| { |
| 3561 | 3517 | if (self.bin_file.options.pie) { |
| 3562 | // For MachO, the binary, with the exception of object files, has to be a PIE. | |
| 3563 | // Therefore, we cannot load an absolute address. | |
| 3564 | assert(x > math.maxInt(u32)); // 32bit direct addressing is not supported by MachO. | |
| 3565 | // The plan here is to use unconditional relative jump to GOT entry, where we store | |
| 3566 | // pre-calculated and stored effective address to load into the target register. | |
| 3567 | // We leave the actual displacement information empty (0-padded) and fixing it up | |
| 3568 | // later in the linker. | |
| 3569 | if (reg.id() == 0) { // %rax is special-cased | |
| 3570 | try self.code.ensureCapacity(self.code.items.len + 5); | |
| 3571 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 3572 | try macho_file.pie_fixups.append(self.bin_file.allocator, .{ | |
| 3573 | .address = x, | |
| 3574 | .start = self.code.items.len, | |
| 3575 | .len = 5, | |
| 3576 | }); | |
| 3577 | } else { | |
| 3578 | return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); | |
| 3579 | } | |
| 3580 | // call [label] | |
| 3581 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 3582 | 0xE8, | |
| 3583 | 0x0, | |
| 3584 | 0x0, | |
| 3585 | 0x0, | |
| 3586 | 0x0, | |
| 3518 | // RIP-relative displacement to the entry in the GOT table. | |
| 3519 | // TODO we should come up with our own, backend independent relocation types | |
| 3520 | // which each backend (Elf, MachO, etc.) would then translate into an actual | |
| 3521 | // fixup when linking. | |
| 3522 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 3523 | try macho_file.pie_fixups.append(self.bin_file.allocator, .{ | |
| 3524 | .target_addr = x, | |
| 3525 | .offset = self.code.items.len + 3, | |
| 3526 | .size = 4, | |
| 3587 | 3527 | }); |
| 3588 | 3528 | } else { |
| 3589 | try self.code.ensureCapacity(self.code.items.len + 10); | |
| 3590 | // push %rax | |
| 3591 | self.code.appendSliceAssumeCapacity(&[_]u8{0x50}); | |
| 3592 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 3593 | try macho_file.pie_fixups.append(self.bin_file.allocator, .{ | |
| 3594 | .address = x, | |
| 3595 | .start = self.code.items.len, | |
| 3596 | .len = 5, | |
| 3597 | }); | |
| 3598 | } else { | |
| 3599 | return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); | |
| 3600 | } | |
| 3601 | // call [label] | |
| 3602 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 3603 | 0xE8, | |
| 3604 | 0x0, | |
| 3605 | 0x0, | |
| 3606 | 0x0, | |
| 3607 | 0x0, | |
| 3608 | }); | |
| 3609 | // mov %r, %rax | |
| 3610 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 3611 | 0x48, | |
| 3612 | 0x89, | |
| 3613 | 0xC0 | @as(u8, reg.id()), | |
| 3614 | }); | |
| 3615 | // pop %rax | |
| 3616 | self.code.appendSliceAssumeCapacity(&[_]u8{0x58}); | |
| 3529 | return self.fail(src, "TODO implement genSetReg for PIE GOT indirection on this platform", .{}); | |
| 3617 | 3530 | } |
| 3531 | try self.code.ensureCapacity(self.code.items.len + 7); | |
| 3532 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); | |
| 3533 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 3534 | 0x8D, | |
| 3535 | 0x05 | (@as(u8, reg.id() & 0b111) << 3), | |
| 3536 | }); | |
| 3537 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), 0); | |
| 3538 | ||
| 3539 | try self.code.ensureCapacity(self.code.items.len + 3); | |
| 3540 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() }); | |
| 3541 | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); | |
| 3542 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); | |
| 3618 | 3543 | } else if (x <= math.maxInt(u32)) { |
| 3619 | 3544 | // Moving from memory to a register is a variant of `8B /r`. |
| 3620 | 3545 | // Since we're using 64-bit moves, we require a REX. |
| ... | ... | @@ -3777,9 +3702,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3777 | 3702 | return MCValue{ .memory = got_addr }; |
| 3778 | 3703 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| 3779 | 3704 | const decl = payload.data; |
| 3780 | const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment; | |
| 3781 | const got = &text_segment.sections.items[macho_file.got_section_index.?]; | |
| 3782 | const got_addr = got.addr + decl.link.macho.offset_table_index * ptr_bytes; | |
| 3705 | const got_addr = blk: { | |
| 3706 | const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; | |
| 3707 | const got = seg.sections.items[macho_file.got_section_index.?]; | |
| 3708 | break :blk got.addr + decl.link.macho.offset_table_index * ptr_bytes; | |
| 3709 | }; | |
| 3783 | 3710 | return MCValue{ .memory = got_addr }; |
| 3784 | 3711 | } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { |
| 3785 | 3712 | const decl = payload.data; |
src/codegen/aarch64.zig+4-1| ... | ... | @@ -221,7 +221,8 @@ pub const Instruction = union(enum) { |
| 221 | 221 | offset: u12, |
| 222 | 222 | opc: u2, |
| 223 | 223 | op1: u2, |
| 224 | fixed: u4 = 0b111_0, | |
| 224 | v: u1, | |
| 225 | fixed: u3 = 0b111, | |
| 225 | 226 | size: u2, |
| 226 | 227 | }, |
| 227 | 228 | LoadStorePairOfRegisters: packed struct { |
| ... | ... | @@ -505,6 +506,7 @@ pub const Instruction = union(enum) { |
| 505 | 506 | .offset = offset.toU12(), |
| 506 | 507 | .opc = opc, |
| 507 | 508 | .op1 = op1, |
| 509 | .v = 0, | |
| 508 | 510 | .size = 0b10, |
| 509 | 511 | }, |
| 510 | 512 | }; |
| ... | ... | @@ -517,6 +519,7 @@ pub const Instruction = union(enum) { |
| 517 | 519 | .offset = offset.toU12(), |
| 518 | 520 | .opc = opc, |
| 519 | 521 | .op1 = op1, |
| 522 | .v = 0, | |
| 520 | 523 | .size = 0b11, |
| 521 | 524 | }, |
| 522 | 525 | }; |
src/codegen/llvm.zig+11-11| ... | ... | @@ -222,7 +222,7 @@ pub const LLVMIRModule = struct { |
| 222 | 222 | |
| 223 | 223 | var error_message: [*:0]const u8 = undefined; |
| 224 | 224 | var target: *const llvm.Target = undefined; |
| 225 | if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message)) { | |
| 225 | if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message).toBool()) { | |
| 226 | 226 | defer llvm.disposeMessage(error_message); |
| 227 | 227 | |
| 228 | 228 | const stderr = std.io.getStdErr().writer(); |
| ... | ... | @@ -306,7 +306,7 @@ pub const LLVMIRModule = struct { |
| 306 | 306 | // verifyModule always allocs the error_message even if there is no error |
| 307 | 307 | defer llvm.disposeMessage(error_message); |
| 308 | 308 | |
| 309 | if (self.llvm_module.verify(.ReturnStatus, &error_message)) { | |
| 309 | if (self.llvm_module.verify(.ReturnStatus, &error_message).toBool()) { | |
| 310 | 310 | const stderr = std.io.getStdErr().writer(); |
| 311 | 311 | try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); |
| 312 | 312 | return error.BrokenLLVMModule; |
| ... | ... | @@ -322,7 +322,7 @@ pub const LLVMIRModule = struct { |
| 322 | 322 | object_pathZ.ptr, |
| 323 | 323 | .ObjectFile, |
| 324 | 324 | &error_message, |
| 325 | )) { | |
| 325 | ).toBool()) { | |
| 326 | 326 | defer llvm.disposeMessage(error_message); |
| 327 | 327 | |
| 328 | 328 | const stderr = std.io.getStdErr().writer(); |
| ... | ... | @@ -617,7 +617,7 @@ pub const LLVMIRModule = struct { |
| 617 | 617 | |
| 618 | 618 | var indices: [2]*const llvm.Value = .{ |
| 619 | 619 | index_type.constNull(), |
| 620 | index_type.constInt(1, false), | |
| 620 | index_type.constInt(1, .False), | |
| 621 | 621 | }; |
| 622 | 622 | |
| 623 | 623 | return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, 2, ""), ""); |
| ... | ... | @@ -679,7 +679,7 @@ pub const LLVMIRModule = struct { |
| 679 | 679 | const signed = inst.base.ty.isSignedInt(); |
| 680 | 680 | // TODO: Should we use intcast here or just a simple bitcast? |
| 681 | 681 | // LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes |
| 682 | return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), signed, ""); | |
| 682 | return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), llvm.Bool.fromBool(signed), ""); | |
| 683 | 683 | } |
| 684 | 684 | |
| 685 | 685 | fn genBitCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { |
| ... | ... | @@ -785,7 +785,7 @@ pub const LLVMIRModule = struct { |
| 785 | 785 | if (bigint.limbs.len != 1) { |
| 786 | 786 | return self.fail(src, "TODO implement bigger bigint", .{}); |
| 787 | 787 | } |
| 788 | const llvm_int = llvm_type.constInt(bigint.limbs[0], false); | |
| 788 | const llvm_int = llvm_type.constInt(bigint.limbs[0], .False); | |
| 789 | 789 | if (!bigint.positive) { |
| 790 | 790 | return llvm.constNeg(llvm_int); |
| 791 | 791 | } |
| ... | ... | @@ -823,7 +823,7 @@ pub const LLVMIRModule = struct { |
| 823 | 823 | return self.fail(src, "TODO handle other sentinel values", .{}); |
| 824 | 824 | } else false; |
| 825 | 825 | |
| 826 | return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), !zero_sentinel); | |
| 826 | return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel)); | |
| 827 | 827 | } else { |
| 828 | 828 | return self.fail(src, "TODO handle more array values", .{}); |
| 829 | 829 | } |
| ... | ... | @@ -839,13 +839,13 @@ pub const LLVMIRModule = struct { |
| 839 | 839 | llvm_child_type.constNull(), |
| 840 | 840 | self.context.intType(1).constNull(), |
| 841 | 841 | }; |
| 842 | return self.context.constStruct(&optional_values, 2, false); | |
| 842 | return self.context.constStruct(&optional_values, 2, .False); | |
| 843 | 843 | } else { |
| 844 | 844 | var optional_values: [2]*const llvm.Value = .{ |
| 845 | 845 | try self.genTypedValue(src, .{ .ty = child_type, .val = tv.val }), |
| 846 | 846 | self.context.intType(1).constAllOnes(), |
| 847 | 847 | }; |
| 848 | return self.context.constStruct(&optional_values, 2, false); | |
| 848 | return self.context.constStruct(&optional_values, 2, .False); | |
| 849 | 849 | } |
| 850 | 850 | } else { |
| 851 | 851 | return self.fail(src, "TODO implement const of optional pointer", .{}); |
| ... | ... | @@ -885,7 +885,7 @@ pub const LLVMIRModule = struct { |
| 885 | 885 | try self.getLLVMType(child_type, src), |
| 886 | 886 | self.context.intType(1), |
| 887 | 887 | }; |
| 888 | return self.context.structType(&optional_types, 2, false); | |
| 888 | return self.context.structType(&optional_types, 2, .False); | |
| 889 | 889 | } else { |
| 890 | 890 | return self.fail(src, "TODO implement optional pointers as actual pointers", .{}); |
| 891 | 891 | } |
| ... | ... | @@ -937,7 +937,7 @@ pub const LLVMIRModule = struct { |
| 937 | 937 | try self.getLLVMType(return_type, src), |
| 938 | 938 | if (fn_param_len == 0) null else llvm_param.ptr, |
| 939 | 939 | @intCast(c_uint, fn_param_len), |
| 940 | false, | |
| 940 | .False, | |
| 941 | 941 | ); |
| 942 | 942 | const llvm_fn = self.llvm_module.addFunction(func.name, fn_type); |
| 943 | 943 |
src/codegen/llvm/bindings.zig+23-10| ... | ... | @@ -1,7 +1,20 @@ |
| 1 | 1 | //! We do this instead of @cImport because the self-hosted compiler is easier |
| 2 | 2 | //! to bootstrap if it does not depend on translate-c. |
| 3 | 3 | |
| 4 | const LLVMBool = bool; | |
| 4 | /// Do not compare directly to .True, use toBool() instead. | |
| 5 | pub const Bool = enum(c_int) { | |
| 6 | False, | |
| 7 | True, | |
| 8 | _, | |
| 9 | ||
| 10 | pub fn fromBool(b: bool) Bool { | |
| 11 | return @intToEnum(Bool, @boolToInt(b)); | |
| 12 | } | |
| 13 | ||
| 14 | pub fn toBool(b: Bool) bool { | |
| 15 | return b != .False; | |
| 16 | } | |
| 17 | }; | |
| 5 | 18 | pub const AttributeIndex = c_uint; |
| 6 | 19 | |
| 7 | 20 | /// Make sure to use the *InContext functions instead of the global ones. |
| ... | ... | @@ -22,13 +35,13 @@ pub const Context = opaque { |
| 22 | 35 | extern fn LLVMVoidTypeInContext(C: *const Context) *const Type; |
| 23 | 36 | |
| 24 | 37 | pub const structType = LLVMStructTypeInContext; |
| 25 | extern fn LLVMStructTypeInContext(C: *const Context, ElementTypes: [*]*const Type, ElementCount: c_uint, Packed: LLVMBool) *const Type; | |
| 38 | extern fn LLVMStructTypeInContext(C: *const Context, ElementTypes: [*]*const Type, ElementCount: c_uint, Packed: Bool) *const Type; | |
| 26 | 39 | |
| 27 | 40 | pub const constString = LLVMConstStringInContext; |
| 28 | extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: LLVMBool) *const Value; | |
| 41 | extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) *const Value; | |
| 29 | 42 | |
| 30 | 43 | pub const constStruct = LLVMConstStructInContext; |
| 31 | extern fn LLVMConstStructInContext(C: *const Context, ConstantVals: [*]*const Value, Count: c_uint, Packed: LLVMBool) *const Value; | |
| 44 | extern fn LLVMConstStructInContext(C: *const Context, ConstantVals: [*]*const Value, Count: c_uint, Packed: Bool) *const Value; | |
| 32 | 45 | |
| 33 | 46 | pub const createBasicBlock = LLVMCreateBasicBlockInContext; |
| 34 | 47 | extern fn LLVMCreateBasicBlockInContext(C: *const Context, Name: [*:0]const u8) *const BasicBlock; |
| ... | ... | @@ -59,7 +72,7 @@ pub const Value = opaque { |
| 59 | 72 | |
| 60 | 73 | pub const Type = opaque { |
| 61 | 74 | pub const functionType = LLVMFunctionType; |
| 62 | extern fn LLVMFunctionType(ReturnType: *const Type, ParamTypes: ?[*]*const Type, ParamCount: c_uint, IsVarArg: LLVMBool) *const Type; | |
| 75 | extern fn LLVMFunctionType(ReturnType: *const Type, ParamTypes: ?[*]*const Type, ParamCount: c_uint, IsVarArg: Bool) *const Type; | |
| 63 | 76 | |
| 64 | 77 | pub const constNull = LLVMConstNull; |
| 65 | 78 | extern fn LLVMConstNull(Ty: *const Type) *const Value; |
| ... | ... | @@ -68,7 +81,7 @@ pub const Type = opaque { |
| 68 | 81 | extern fn LLVMConstAllOnes(Ty: *const Type) *const Value; |
| 69 | 82 | |
| 70 | 83 | pub const constInt = LLVMConstInt; |
| 71 | extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: LLVMBool) *const Value; | |
| 84 | extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value; | |
| 72 | 85 | |
| 73 | 86 | pub const constArray = LLVMConstArray; |
| 74 | 87 | extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: ?[*]*const Value, Length: c_uint) *const Value; |
| ... | ... | @@ -91,7 +104,7 @@ pub const Module = opaque { |
| 91 | 104 | extern fn LLVMDisposeModule(*const Module) void; |
| 92 | 105 | |
| 93 | 106 | pub const verify = LLVMVerifyModule; |
| 94 | extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) LLVMBool; | |
| 107 | extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) Bool; | |
| 95 | 108 | |
| 96 | 109 | pub const addFunction = LLVMAddFunction; |
| 97 | 110 | extern fn LLVMAddFunction(*const Module, Name: [*:0]const u8, FunctionTy: *const Type) *const Value; |
| ... | ... | @@ -191,7 +204,7 @@ pub const Builder = opaque { |
| 191 | 204 | extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; |
| 192 | 205 | |
| 193 | 206 | pub const buildIntCast2 = LLVMBuildIntCast2; |
| 194 | extern fn LLVMBuildIntCast2(*const Builder, Val: *const Value, DestTy: *const Type, IsSigned: LLVMBool, Name: [*:0]const u8) *const Value; | |
| 207 | extern fn LLVMBuildIntCast2(*const Builder, Val: *const Value, DestTy: *const Type, IsSigned: Bool, Name: [*:0]const u8) *const Value; | |
| 195 | 208 | |
| 196 | 209 | pub const buildBitCast = LLVMBuildBitCast; |
| 197 | 210 | extern fn LLVMBuildBitCast(*const Builder, Val: *const Value, DestTy: *const Type, Name: [*:0]const u8) *const Value; |
| ... | ... | @@ -258,7 +271,7 @@ pub const TargetMachine = opaque { |
| 258 | 271 | Filename: [*:0]const u8, |
| 259 | 272 | codegen: CodeGenFileType, |
| 260 | 273 | ErrorMessage: *[*:0]const u8, |
| 261 | ) LLVMBool; | |
| 274 | ) Bool; | |
| 262 | 275 | }; |
| 263 | 276 | |
| 264 | 277 | pub const CodeMode = extern enum { |
| ... | ... | @@ -295,7 +308,7 @@ pub const CodeGenFileType = extern enum { |
| 295 | 308 | |
| 296 | 309 | pub const Target = opaque { |
| 297 | 310 | pub const getFromTriple = LLVMGetTargetFromTriple; |
| 298 | extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) LLVMBool; | |
| 311 | extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) Bool; | |
| 299 | 312 | }; |
| 300 | 313 | |
| 301 | 314 | extern fn LLVMInitializeAArch64TargetInfo() void; |
src/codegen/wasm.zig+41-3| ... | ... | @@ -95,7 +95,7 @@ pub const Context = struct { |
| 95 | 95 | return switch (ty.tag()) { |
| 96 | 96 | .f32 => wasm.valtype(.f32), |
| 97 | 97 | .f64 => wasm.valtype(.f64), |
| 98 | .u32, .i32 => wasm.valtype(.i32), | |
| 98 | .u32, .i32, .bool => wasm.valtype(.i32), | |
| 99 | 99 | .u64, .i64 => wasm.valtype(.i64), |
| 100 | 100 | else => self.fail(src, "TODO - Wasm genValtype for type '{s}'", .{ty.tag()}), |
| 101 | 101 | }; |
| ... | ... | @@ -208,6 +208,7 @@ pub const Context = struct { |
| 208 | 208 | .alloc => self.genAlloc(inst.castTag(.alloc).?), |
| 209 | 209 | .arg => self.genArg(inst.castTag(.arg).?), |
| 210 | 210 | .block => self.genBlock(inst.castTag(.block).?), |
| 211 | .breakpoint => self.genBreakpoint(inst.castTag(.breakpoint).?), | |
| 211 | 212 | .br => self.genBr(inst.castTag(.br).?), |
| 212 | 213 | .call => self.genCall(inst.castTag(.call).?), |
| 213 | 214 | .cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq), |
| ... | ... | @@ -221,9 +222,11 @@ pub const Context = struct { |
| 221 | 222 | .dbg_stmt => WValue.none, |
| 222 | 223 | .load => self.genLoad(inst.castTag(.load).?), |
| 223 | 224 | .loop => self.genLoop(inst.castTag(.loop).?), |
| 225 | .not => self.genNot(inst.castTag(.not).?), | |
| 224 | 226 | .ret => self.genRet(inst.castTag(.ret).?), |
| 225 | 227 | .retvoid => WValue.none, |
| 226 | 228 | .store => self.genStore(inst.castTag(.store).?), |
| 229 | .unreach => self.genUnreachable(inst.castTag(.unreach).?), | |
| 227 | 230 | else => self.fail(inst.src, "TODO: Implement wasm inst: {s}", .{inst.tag}), |
| 228 | 231 | }; |
| 229 | 232 | } |
| ... | ... | @@ -329,7 +332,7 @@ pub const Context = struct { |
| 329 | 332 | try writer.writeByte(wasm.opcode(.i32_const)); |
| 330 | 333 | try leb.writeILEB128(writer, inst.val.toUnsignedInt()); |
| 331 | 334 | }, |
| 332 | .i32 => { | |
| 335 | .i32, .bool => { | |
| 333 | 336 | try writer.writeByte(wasm.opcode(.i32_const)); |
| 334 | 337 | try leb.writeILEB128(writer, inst.val.toSignedInt()); |
| 335 | 338 | }, |
| ... | ... | @@ -414,7 +417,14 @@ pub const Context = struct { |
| 414 | 417 | |
| 415 | 418 | // insert blocks at the position of `offset` so |
| 416 | 419 | // the condition can jump to it |
| 417 | const offset = condition.code_offset; | |
| 420 | const offset = switch (condition) { | |
| 421 | .code_offset => |offset| offset, | |
| 422 | else => blk: { | |
| 423 | const offset = self.code.items.len; | |
| 424 | try self.emitWValue(condition); | |
| 425 | break :blk offset; | |
| 426 | }, | |
| 427 | }; | |
| 418 | 428 | const block_ty = try self.genBlockType(condbr.base.src, condbr.base.ty); |
| 419 | 429 | try self.startBlock(.block, block_ty, offset); |
| 420 | 430 | |
| ... | ... | @@ -523,4 +533,32 @@ pub const Context = struct { |
| 523 | 533 | |
| 524 | 534 | return .none; |
| 525 | 535 | } |
| 536 | ||
| 537 | fn genNot(self: *Context, not: *Inst.UnOp) InnerError!WValue { | |
| 538 | const offset = self.code.items.len; | |
| 539 | ||
| 540 | const operand = self.resolveInst(not.operand); | |
| 541 | try self.emitWValue(operand); | |
| 542 | ||
| 543 | // wasm does not have booleans nor the `not` instruction, therefore compare with 0 | |
| 544 | // to create the same logic | |
| 545 | const writer = self.code.writer(); | |
| 546 | try writer.writeByte(wasm.opcode(.i32_const)); | |
| 547 | try leb.writeILEB128(writer, @as(i32, 0)); | |
| 548 | ||
| 549 | try writer.writeByte(wasm.opcode(.i32_eq)); | |
| 550 | ||
| 551 | return WValue{ .code_offset = offset }; | |
| 552 | } | |
| 553 | ||
| 554 | fn genBreakpoint(self: *Context, breakpoint: *Inst.NoOp) InnerError!WValue { | |
| 555 | // unsupported by wasm itself. Can be implemented once we support DWARF | |
| 556 | // for wasm | |
| 557 | return .none; | |
| 558 | } | |
| 559 | ||
| 560 | fn genUnreachable(self: *Context, unreach: *Inst.NoOp) InnerError!WValue { | |
| 561 | try self.code.append(wasm.opcode(.@"unreachable")); | |
| 562 | return .none; | |
| 563 | } | |
| 526 | 564 | }; |
src/config.zig.in+1-1| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | pub const have_llvm = true; |
| 2 | 2 | pub const version: [:0]const u8 = "@ZIG_VERSION@"; |
| 3 | 3 | pub const semver = try @import("std").SemanticVersion.parse(version); |
| 4 | pub const enable_logging: bool = false; | |
| 4 | pub const enable_logging: bool = @ZIG_ENABLE_LOGGING_BOOL@; | |
| 5 | 5 | pub const enable_tracy = false; |
| 6 | 6 | pub const is_stage1 = true; |
| 7 | 7 | pub const skip_non_native = false; |
src/introspect.zig+8| ... | ... | @@ -61,6 +61,14 @@ pub fn findZigLibDirFromSelfExe( |
| 61 | 61 | |
| 62 | 62 | /// Caller owns returned memory. |
| 63 | 63 | pub fn resolveGlobalCacheDir(allocator: *mem.Allocator) ![]u8 { |
| 64 | if (std.process.getEnvVarOwned(allocator, "ZIG_GLOBAL_CACHE_DIR")) |value| { | |
| 65 | if (value.len > 0) { | |
| 66 | return value; | |
| 67 | } else { | |
| 68 | allocator.free(value); | |
| 69 | } | |
| 70 | } else |_| {} | |
| 71 | ||
| 64 | 72 | const appname = "zig"; |
| 65 | 73 | |
| 66 | 74 | if (std.Target.current.os.tag != .windows) { |
src/link/MachO.zig+551-540| ... | ... | @@ -11,7 +11,9 @@ const codegen = @import("../codegen.zig"); |
| 11 | 11 | const aarch64 = @import("../codegen/aarch64.zig"); |
| 12 | 12 | const math = std.math; |
| 13 | 13 | const mem = std.mem; |
| 14 | const meta = std.meta; | |
| 14 | 15 | |
| 16 | const bind = @import("MachO/bind.zig"); | |
| 15 | 17 | const trace = @import("../tracy.zig").trace; |
| 16 | 18 | const build_options = @import("build_options"); |
| 17 | 19 | const Module = @import("../Module.zig"); |
| ... | ... | @@ -24,9 +26,9 @@ const target_util = @import("../target.zig"); |
| 24 | 26 | const DebugSymbols = @import("MachO/DebugSymbols.zig"); |
| 25 | 27 | const Trie = @import("MachO/Trie.zig"); |
| 26 | 28 | const CodeSignature = @import("MachO/CodeSignature.zig"); |
| 29 | const Zld = @import("MachO/Zld.zig"); | |
| 27 | 30 | |
| 28 | 31 | usingnamespace @import("MachO/commands.zig"); |
| 29 | usingnamespace @import("MachO/imports.zig"); | |
| 30 | 32 | |
| 31 | 33 | pub const base_tag: File.Tag = File.Tag.macho; |
| 32 | 34 | |
| ... | ... | @@ -87,14 +89,12 @@ code_signature_cmd_index: ?u16 = null, |
| 87 | 89 | |
| 88 | 90 | /// Index into __TEXT,__text section. |
| 89 | 91 | text_section_index: ?u16 = null, |
| 90 | /// Index into __TEXT,__ziggot section. | |
| 91 | got_section_index: ?u16 = null, | |
| 92 | 92 | /// Index into __TEXT,__stubs section. |
| 93 | 93 | stubs_section_index: ?u16 = null, |
| 94 | 94 | /// Index into __TEXT,__stub_helper section. |
| 95 | 95 | stub_helper_section_index: ?u16 = null, |
| 96 | 96 | /// Index into __DATA_CONST,__got section. |
| 97 | data_got_section_index: ?u16 = null, | |
| 97 | got_section_index: ?u16 = null, | |
| 98 | 98 | /// Index into __DATA,__la_symbol_ptr section. |
| 99 | 99 | la_symbol_ptr_section_index: ?u16 = null, |
| 100 | 100 | /// Index into __DATA,__data section. |
| ... | ... | @@ -104,16 +104,16 @@ entry_addr: ?u64 = null, |
| 104 | 104 | |
| 105 | 105 | /// Table of all local symbols |
| 106 | 106 | /// Internally references string table for names (which are optional). |
| 107 | local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | |
| 107 | locals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | |
| 108 | 108 | /// Table of all global symbols |
| 109 | global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | |
| 109 | globals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | |
| 110 | 110 | /// Table of all extern nonlazy symbols, indexed by name. |
| 111 | extern_nonlazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, | |
| 111 | nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, | |
| 112 | 112 | /// Table of all extern lazy symbols, indexed by name. |
| 113 | extern_lazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, | |
| 113 | lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, | |
| 114 | 114 | |
| 115 | local_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, | |
| 116 | global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, | |
| 115 | locals_free_list: std.ArrayListUnmanaged(u32) = .{}, | |
| 116 | globals_free_list: std.ArrayListUnmanaged(u32) = .{}, | |
| 117 | 117 | offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, |
| 118 | 118 | |
| 119 | 119 | stub_helper_stubs_start_off: ?u64 = null, |
| ... | ... | @@ -122,8 +122,8 @@ stub_helper_stubs_start_off: ?u64 = null, |
| 122 | 122 | string_table: std.ArrayListUnmanaged(u8) = .{}, |
| 123 | 123 | string_table_directory: std.StringHashMapUnmanaged(u32) = .{}, |
| 124 | 124 | |
| 125 | /// Table of trampolines to the actual symbols in __text section. | |
| 126 | offset_table: std.ArrayListUnmanaged(u64) = .{}, | |
| 125 | /// Table of GOT entries. | |
| 126 | offset_table: std.ArrayListUnmanaged(GOTEntry) = .{}, | |
| 127 | 127 | |
| 128 | 128 | error_flags: File.ErrorFlags = File.ErrorFlags{}, |
| 129 | 129 | |
| ... | ... | @@ -154,14 +154,19 @@ string_table_needs_relocation: bool = false, |
| 154 | 154 | /// allocate a fresh text block, which will have ideal capacity, and then grow it |
| 155 | 155 | /// by 1 byte. It will then have -1 overcapacity. |
| 156 | 156 | text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, |
| 157 | ||
| 157 | 158 | /// Pointer to the last allocated text block |
| 158 | 159 | last_text_block: ?*TextBlock = null, |
| 160 | ||
| 159 | 161 | /// A list of all PIE fixups required for this run of the linker. |
| 160 | 162 | /// Warning, this is currently NOT thread-safe. See the TODO below. |
| 161 | 163 | /// TODO Move this list inside `updateDecl` where it should be allocated |
| 162 | 164 | /// prior to calling `generateSymbol`, and then immediately deallocated |
| 163 | 165 | /// rather than sitting in the global scope. |
| 164 | pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, | |
| 166 | /// TODO We should also rewrite this using generic relocations common to all | |
| 167 | /// backends. | |
| 168 | pie_fixups: std.ArrayListUnmanaged(PIEFixup) = .{}, | |
| 169 | ||
| 165 | 170 | /// A list of all stub (extern decls) fixups required for this run of the linker. |
| 166 | 171 | /// Warning, this is currently NOT thread-safe. See the TODO below. |
| 167 | 172 | /// TODO Move this list inside `updateDecl` where it should be allocated |
| ... | ... | @@ -169,14 +174,42 @@ pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, |
| 169 | 174 | /// rather than sitting in the global scope. |
| 170 | 175 | stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, |
| 171 | 176 | |
| 172 | pub const PieFixup = struct { | |
| 173 | /// Target address we wanted to address in absolute terms. | |
| 174 | address: u64, | |
| 175 | /// Where in the byte stream we should perform the fixup. | |
| 176 | start: usize, | |
| 177 | /// The length of the byte stream. For x86_64, this will be | |
| 178 | /// variable. For aarch64, it will be fixed at 4 bytes. | |
| 179 | len: usize, | |
| 177 | pub const GOTEntry = struct { | |
| 178 | /// GOT entry can either be a local pointer or an extern (nonlazy) import. | |
| 179 | kind: enum { | |
| 180 | Local, | |
| 181 | Extern, | |
| 182 | }, | |
| 183 | ||
| 184 | /// Id to the macho.nlist_64 from the respective table: either locals or nonlazy imports. | |
| 185 | /// TODO I'm more and more inclined to just manage a single, max two symbol tables | |
| 186 | /// rather than 4 as we currently do, but I'll follow up in the future PR. | |
| 187 | symbol: u32, | |
| 188 | ||
| 189 | /// Index of this entry in the GOT. | |
| 190 | index: u32, | |
| 191 | }; | |
| 192 | ||
| 193 | pub const Import = struct { | |
| 194 | /// MachO symbol table entry. | |
| 195 | symbol: macho.nlist_64, | |
| 196 | ||
| 197 | /// Id of the dynamic library where the specified entries can be found. | |
| 198 | dylib_ordinal: i64, | |
| 199 | ||
| 200 | /// Index of this import within the import list. | |
| 201 | index: u32, | |
| 202 | }; | |
| 203 | ||
| 204 | pub const PIEFixup = struct { | |
| 205 | /// Target VM address of this relocation. | |
| 206 | target_addr: u64, | |
| 207 | ||
| 208 | /// Offset within the byte stream. | |
| 209 | offset: usize, | |
| 210 | ||
| 211 | /// Size of the relocation. | |
| 212 | size: usize, | |
| 180 | 213 | }; |
| 181 | 214 | |
| 182 | 215 | pub const StubFixup = struct { |
| ... | ... | @@ -260,9 +293,9 @@ pub const TextBlock = struct { |
| 260 | 293 | /// File offset relocation happens transparently, so it is not included in |
| 261 | 294 | /// this calculation. |
| 262 | 295 | fn capacity(self: TextBlock, macho_file: MachO) u64 { |
| 263 | const self_sym = macho_file.local_symbols.items[self.local_sym_index]; | |
| 296 | const self_sym = macho_file.locals.items[self.local_sym_index]; | |
| 264 | 297 | if (self.next) |next| { |
| 265 | const next_sym = macho_file.local_symbols.items[next.local_sym_index]; | |
| 298 | const next_sym = macho_file.locals.items[next.local_sym_index]; | |
| 266 | 299 | return next_sym.n_value - self_sym.n_value; |
| 267 | 300 | } else { |
| 268 | 301 | // We are the last block. |
| ... | ... | @@ -274,8 +307,8 @@ pub const TextBlock = struct { |
| 274 | 307 | fn freeListEligible(self: TextBlock, macho_file: MachO) bool { |
| 275 | 308 | // No need to keep a free list node for the last block. |
| 276 | 309 | const next = self.next orelse return false; |
| 277 | const self_sym = macho_file.local_symbols.items[self.local_sym_index]; | |
| 278 | const next_sym = macho_file.local_symbols.items[next.local_sym_index]; | |
| 310 | const self_sym = macho_file.locals.items[self.local_sym_index]; | |
| 311 | const next_sym = macho_file.locals.items[next.local_sym_index]; | |
| 279 | 312 | const cap = next_sym.n_value - self_sym.n_value; |
| 280 | 313 | const ideal_cap = padToIdeal(self.size); |
| 281 | 314 | if (cap <= ideal_cap) return false; |
| ... | ... | @@ -344,7 +377,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio |
| 344 | 377 | }; |
| 345 | 378 | |
| 346 | 379 | // Index 0 is always a null symbol. |
| 347 | try self.local_symbols.append(allocator, .{ | |
| 380 | try self.locals.append(allocator, .{ | |
| 348 | 381 | .n_strx = 0, |
| 349 | 382 | .n_type = 0, |
| 350 | 383 | .n_sect = 0, |
| ... | ... | @@ -600,7 +633,74 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { |
| 600 | 633 | if (!mem.eql(u8, the_object_path, full_out_path)) { |
| 601 | 634 | try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); |
| 602 | 635 | } |
| 603 | } else { | |
| 636 | } else outer: { | |
| 637 | const use_zld = blk: { | |
| 638 | if (self.base.options.is_native_os and self.base.options.system_linker_hack) { | |
| 639 | // If the user forces the use of ld64, make sure we are running native! | |
| 640 | break :blk false; | |
| 641 | } | |
| 642 | ||
| 643 | if (self.base.options.target.cpu.arch == .aarch64) { | |
| 644 | // On aarch64, always use zld. | |
| 645 | break :blk true; | |
| 646 | } | |
| 647 | ||
| 648 | if (self.base.options.link_libcpp or | |
| 649 | self.base.options.output_mode == .Lib or | |
| 650 | self.base.options.linker_script != null) | |
| 651 | { | |
| 652 | // Fallback to LLD in this handful of cases on x86_64 only. | |
| 653 | break :blk false; | |
| 654 | } | |
| 655 | ||
| 656 | break :blk true; | |
| 657 | }; | |
| 658 | ||
| 659 | if (use_zld) { | |
| 660 | var zld = Zld.init(self.base.allocator); | |
| 661 | defer zld.deinit(); | |
| 662 | zld.arch = target.cpu.arch; | |
| 663 | ||
| 664 | var input_files = std.ArrayList([]const u8).init(self.base.allocator); | |
| 665 | defer input_files.deinit(); | |
| 666 | // Positional arguments to the linker such as object files. | |
| 667 | try input_files.appendSlice(self.base.options.objects); | |
| 668 | for (comp.c_object_table.items()) |entry| { | |
| 669 | try input_files.append(entry.key.status.success.object_path); | |
| 670 | } | |
| 671 | if (module_obj_path) |p| { | |
| 672 | try input_files.append(p); | |
| 673 | } | |
| 674 | try input_files.append(comp.compiler_rt_static_lib.?.full_object_path); | |
| 675 | // libc++ dep | |
| 676 | if (self.base.options.link_libcpp) { | |
| 677 | try input_files.append(comp.libcxxabi_static_lib.?.full_object_path); | |
| 678 | try input_files.append(comp.libcxx_static_lib.?.full_object_path); | |
| 679 | } | |
| 680 | ||
| 681 | if (self.base.options.verbose_link) { | |
| 682 | var argv = std.ArrayList([]const u8).init(self.base.allocator); | |
| 683 | defer argv.deinit(); | |
| 684 | ||
| 685 | try argv.append("zig"); | |
| 686 | try argv.append("ld"); | |
| 687 | ||
| 688 | try argv.ensureCapacity(input_files.items.len); | |
| 689 | for (input_files.items) |f| { | |
| 690 | argv.appendAssumeCapacity(f); | |
| 691 | } | |
| 692 | ||
| 693 | try argv.append("-o"); | |
| 694 | try argv.append(full_out_path); | |
| 695 | ||
| 696 | Compilation.dump_argv(argv.items); | |
| 697 | } | |
| 698 | ||
| 699 | try zld.link(input_files.items, full_out_path); | |
| 700 | ||
| 701 | break :outer; | |
| 702 | } | |
| 703 | ||
| 604 | 704 | // Create an LLD command line and invoke it. |
| 605 | 705 | var argv = std.ArrayList([]const u8).init(self.base.allocator); |
| 606 | 706 | defer argv.deinit(); |
| ... | ... | @@ -644,9 +744,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { |
| 644 | 744 | try argv.append("defs"); |
| 645 | 745 | } |
| 646 | 746 | |
| 647 | if (is_dyn_lib) { | |
| 648 | try argv.append("-static"); | |
| 649 | } else { | |
| 747 | if (is_exe_or_dyn_lib) { | |
| 650 | 748 | try argv.append("-dynamic"); |
| 651 | 749 | } |
| 652 | 750 | |
| ... | ... | @@ -836,7 +934,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { |
| 836 | 934 | } |
| 837 | 935 | }, |
| 838 | 936 | else => { |
| 839 | log.err("{s} terminated", .{ argv.items[0] }); | |
| 937 | log.err("{s} terminated", .{argv.items[0]}); | |
| 840 | 938 | return error.LLDCrashed; |
| 841 | 939 | }, |
| 842 | 940 | } |
| ... | ... | @@ -873,119 +971,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { |
| 873 | 971 | log.warn("unexpected LLD stderr:\n{s}", .{stderr}); |
| 874 | 972 | } |
| 875 | 973 | } |
| 876 | ||
| 877 | // At this stage, LLD has done its job. It is time to patch the resultant | |
| 878 | // binaries up! | |
| 879 | const out_file = try directory.handle.openFile(self.base.options.emit.?.sub_path, .{ .write = true }); | |
| 880 | try self.parseFromFile(out_file); | |
| 881 | ||
| 882 | if (self.libsystem_cmd_index == null and self.header.?.filetype == macho.MH_EXECUTE) { | |
| 883 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 884 | const text_section = text_segment.sections.items[self.text_section_index.?]; | |
| 885 | const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64); | |
| 886 | const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command)); | |
| 887 | ||
| 888 | if (needed_size + after_last_cmd_offset > text_section.offset) { | |
| 889 | log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); | |
| 890 | log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); | |
| 891 | log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); | |
| 892 | return error.NotEnoughPadding; | |
| 893 | } | |
| 894 | ||
| 895 | // Calculate next available dylib ordinal. | |
| 896 | const next_ordinal = blk: { | |
| 897 | var ordinal: u32 = 1; | |
| 898 | for (self.load_commands.items) |cmd| { | |
| 899 | switch (cmd) { | |
| 900 | .Dylib => ordinal += 1, | |
| 901 | else => {}, | |
| 902 | } | |
| 903 | } | |
| 904 | break :blk ordinal; | |
| 905 | }; | |
| 906 | ||
| 907 | // Add load dylib load command | |
| 908 | self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 909 | const cmdsize = @intCast(u32, mem.alignForwardGeneric( | |
| 910 | u64, | |
| 911 | @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), | |
| 912 | @sizeOf(u64), | |
| 913 | )); | |
| 914 | // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. | |
| 915 | // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. | |
| 916 | const min_version = 0x0; | |
| 917 | var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ | |
| 918 | .cmd = macho.LC_LOAD_DYLIB, | |
| 919 | .cmdsize = cmdsize, | |
| 920 | .dylib = .{ | |
| 921 | .name = @sizeOf(macho.dylib_command), | |
| 922 | .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files | |
| 923 | .current_version = min_version, | |
| 924 | .compatibility_version = min_version, | |
| 925 | }, | |
| 926 | }); | |
| 927 | dylib_cmd.data = try self.base.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); | |
| 928 | mem.set(u8, dylib_cmd.data, 0); | |
| 929 | mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); | |
| 930 | try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd }); | |
| 931 | self.header_dirty = true; | |
| 932 | self.load_commands_dirty = true; | |
| 933 | ||
| 934 | if (self.symtab_cmd_index == null or self.dysymtab_cmd_index == null) { | |
| 935 | log.err("Incomplete Mach-O binary: no LC_SYMTAB or LC_DYSYMTAB load command found!", .{}); | |
| 936 | log.err("Without the symbol table, it is not possible to patch up the binary for cross-compilation.", .{}); | |
| 937 | return error.NoSymbolTableFound; | |
| 938 | } | |
| 939 | ||
| 940 | // Patch dyld info | |
| 941 | try self.fixupBindInfo(next_ordinal); | |
| 942 | try self.fixupLazyBindInfo(next_ordinal); | |
| 943 | ||
| 944 | // Write updated load commands and the header | |
| 945 | try self.writeLoadCommands(); | |
| 946 | try self.writeHeader(); | |
| 947 | ||
| 948 | assert(!self.header_dirty); | |
| 949 | assert(!self.load_commands_dirty); | |
| 950 | } | |
| 951 | if (self.code_signature_cmd_index == null) outer: { | |
| 952 | if (target.cpu.arch != .aarch64) break :outer; // This is currently needed only for aarch64 targets. | |
| 953 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 954 | const text_section = text_segment.sections.items[self.text_section_index.?]; | |
| 955 | const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64); | |
| 956 | const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command)); | |
| 957 | ||
| 958 | if (needed_size + after_last_cmd_offset > text_section.offset) { | |
| 959 | log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); | |
| 960 | log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); | |
| 961 | log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); | |
| 962 | return error.NotEnoughPadding; | |
| 963 | } | |
| 964 | ||
| 965 | // Add code signature load command | |
| 966 | self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 967 | try self.load_commands.append(self.base.allocator, .{ | |
| 968 | .LinkeditData = .{ | |
| 969 | .cmd = macho.LC_CODE_SIGNATURE, | |
| 970 | .cmdsize = @sizeOf(macho.linkedit_data_command), | |
| 971 | .dataoff = 0, | |
| 972 | .datasize = 0, | |
| 973 | }, | |
| 974 | }); | |
| 975 | self.header_dirty = true; | |
| 976 | self.load_commands_dirty = true; | |
| 977 | ||
| 978 | // Pad out space for code signature | |
| 979 | try self.writeCodeSignaturePadding(); | |
| 980 | // Write updated load commands and the header | |
| 981 | try self.writeLoadCommands(); | |
| 982 | try self.writeHeader(); | |
| 983 | // Generate adhoc code signature | |
| 984 | try self.writeCodeSignature(); | |
| 985 | ||
| 986 | assert(!self.header_dirty); | |
| 987 | assert(!self.load_commands_dirty); | |
| 988 | } | |
| 989 | 974 | } |
| 990 | 975 | } |
| 991 | 976 | |
| ... | ... | @@ -1021,14 +1006,14 @@ pub fn deinit(self: *MachO) void { |
| 1021 | 1006 | if (self.d_sym) |*ds| { |
| 1022 | 1007 | ds.deinit(self.base.allocator); |
| 1023 | 1008 | } |
| 1024 | for (self.extern_lazy_symbols.items()) |*entry| { | |
| 1009 | for (self.lazy_imports.items()) |*entry| { | |
| 1025 | 1010 | self.base.allocator.free(entry.key); |
| 1026 | 1011 | } |
| 1027 | self.extern_lazy_symbols.deinit(self.base.allocator); | |
| 1028 | for (self.extern_nonlazy_symbols.items()) |*entry| { | |
| 1012 | self.lazy_imports.deinit(self.base.allocator); | |
| 1013 | for (self.nonlazy_imports.items()) |*entry| { | |
| 1029 | 1014 | self.base.allocator.free(entry.key); |
| 1030 | 1015 | } |
| 1031 | self.extern_nonlazy_symbols.deinit(self.base.allocator); | |
| 1016 | self.nonlazy_imports.deinit(self.base.allocator); | |
| 1032 | 1017 | self.pie_fixups.deinit(self.base.allocator); |
| 1033 | 1018 | self.stub_fixups.deinit(self.base.allocator); |
| 1034 | 1019 | self.text_block_free_list.deinit(self.base.allocator); |
| ... | ... | @@ -1042,10 +1027,10 @@ pub fn deinit(self: *MachO) void { |
| 1042 | 1027 | } |
| 1043 | 1028 | self.string_table_directory.deinit(self.base.allocator); |
| 1044 | 1029 | self.string_table.deinit(self.base.allocator); |
| 1045 | self.global_symbols.deinit(self.base.allocator); | |
| 1046 | self.global_symbol_free_list.deinit(self.base.allocator); | |
| 1047 | self.local_symbols.deinit(self.base.allocator); | |
| 1048 | self.local_symbol_free_list.deinit(self.base.allocator); | |
| 1030 | self.globals.deinit(self.base.allocator); | |
| 1031 | self.globals_free_list.deinit(self.base.allocator); | |
| 1032 | self.locals.deinit(self.base.allocator); | |
| 1033 | self.locals_free_list.deinit(self.base.allocator); | |
| 1049 | 1034 | for (self.load_commands.items) |*lc| { |
| 1050 | 1035 | lc.deinit(self.base.allocator); |
| 1051 | 1036 | } |
| ... | ... | @@ -1100,7 +1085,7 @@ fn shrinkTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64) vo |
| 1100 | 1085 | } |
| 1101 | 1086 | |
| 1102 | 1087 | fn growTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { |
| 1103 | const sym = self.local_symbols.items[text_block.local_sym_index]; | |
| 1088 | const sym = self.locals.items[text_block.local_sym_index]; | |
| 1104 | 1089 | const align_ok = mem.alignBackwardGeneric(u64, sym.n_value, alignment) == sym.n_value; |
| 1105 | 1090 | const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); |
| 1106 | 1091 | if (!need_realloc) return sym.n_value; |
| ... | ... | @@ -1110,34 +1095,41 @@ fn growTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alig |
| 1110 | 1095 | pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { |
| 1111 | 1096 | if (decl.link.macho.local_sym_index != 0) return; |
| 1112 | 1097 | |
| 1113 | try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1); | |
| 1098 | try self.locals.ensureCapacity(self.base.allocator, self.locals.items.len + 1); | |
| 1114 | 1099 | try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); |
| 1115 | 1100 | |
| 1116 | if (self.local_symbol_free_list.popOrNull()) |i| { | |
| 1101 | if (self.locals_free_list.popOrNull()) |i| { | |
| 1117 | 1102 | log.debug("reusing symbol index {d} for {s}", .{ i, decl.name }); |
| 1118 | 1103 | decl.link.macho.local_sym_index = i; |
| 1119 | 1104 | } else { |
| 1120 | log.debug("allocating symbol index {d} for {s}", .{ self.local_symbols.items.len, decl.name }); | |
| 1121 | decl.link.macho.local_sym_index = @intCast(u32, self.local_symbols.items.len); | |
| 1122 | _ = self.local_symbols.addOneAssumeCapacity(); | |
| 1105 | log.debug("allocating symbol index {d} for {s}", .{ self.locals.items.len, decl.name }); | |
| 1106 | decl.link.macho.local_sym_index = @intCast(u32, self.locals.items.len); | |
| 1107 | _ = self.locals.addOneAssumeCapacity(); | |
| 1123 | 1108 | } |
| 1124 | 1109 | |
| 1125 | 1110 | if (self.offset_table_free_list.popOrNull()) |i| { |
| 1111 | log.debug("reusing offset table entry index {d} for {s}", .{ i, decl.name }); | |
| 1126 | 1112 | decl.link.macho.offset_table_index = i; |
| 1127 | 1113 | } else { |
| 1114 | log.debug("allocating offset table entry index {d} for {s}", .{ self.offset_table.items.len, decl.name }); | |
| 1128 | 1115 | decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len); |
| 1129 | 1116 | _ = self.offset_table.addOneAssumeCapacity(); |
| 1130 | 1117 | self.offset_table_count_dirty = true; |
| 1118 | self.rebase_info_dirty = true; | |
| 1131 | 1119 | } |
| 1132 | 1120 | |
| 1133 | self.local_symbols.items[decl.link.macho.local_sym_index] = .{ | |
| 1121 | self.locals.items[decl.link.macho.local_sym_index] = .{ | |
| 1134 | 1122 | .n_strx = 0, |
| 1135 | 1123 | .n_type = 0, |
| 1136 | 1124 | .n_sect = 0, |
| 1137 | 1125 | .n_desc = 0, |
| 1138 | 1126 | .n_value = 0, |
| 1139 | 1127 | }; |
| 1140 | self.offset_table.items[decl.link.macho.offset_table_index] = 0; | |
| 1128 | self.offset_table.items[decl.link.macho.offset_table_index] = .{ | |
| 1129 | .kind = .Local, | |
| 1130 | .symbol = decl.link.macho.local_sym_index, | |
| 1131 | .index = decl.link.macho.offset_table_index, | |
| 1132 | }; | |
| 1141 | 1133 | } |
| 1142 | 1134 | |
| 1143 | 1135 | pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| ... | ... | @@ -1180,8 +1172,9 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| 1180 | 1172 | .externally_managed => |x| x, |
| 1181 | 1173 | .appended => code_buffer.items, |
| 1182 | 1174 | .fail => |em| { |
| 1183 | // Clear any PIE fixups and stub fixups for this decl. | |
| 1175 | // Clear any PIE fixups for this decl. | |
| 1184 | 1176 | self.pie_fixups.shrinkRetainingCapacity(0); |
| 1177 | // Clear any stub fixups for this decl. | |
| 1185 | 1178 | self.stub_fixups.shrinkRetainingCapacity(0); |
| 1186 | 1179 | decl.analysis = .codegen_failure; |
| 1187 | 1180 | try module.failed_decls.put(module.gpa, decl, em); |
| ... | ... | @@ -1191,7 +1184,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| 1191 | 1184 | |
| 1192 | 1185 | const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); |
| 1193 | 1186 | assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes() |
| 1194 | const symbol = &self.local_symbols.items[decl.link.macho.local_sym_index]; | |
| 1187 | const symbol = &self.locals.items[decl.link.macho.local_sym_index]; | |
| 1195 | 1188 | |
| 1196 | 1189 | if (decl.link.macho.size != 0) { |
| 1197 | 1190 | const capacity = decl.link.macho.capacity(self.*); |
| ... | ... | @@ -1200,9 +1193,12 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| 1200 | 1193 | const vaddr = try self.growTextBlock(&decl.link.macho, code.len, required_alignment); |
| 1201 | 1194 | log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr }); |
| 1202 | 1195 | if (vaddr != symbol.n_value) { |
| 1203 | symbol.n_value = vaddr; | |
| 1204 | 1196 | log.debug(" (writing new offset table entry)", .{}); |
| 1205 | self.offset_table.items[decl.link.macho.offset_table_index] = vaddr; | |
| 1197 | self.offset_table.items[decl.link.macho.offset_table_index] = .{ | |
| 1198 | .kind = .Local, | |
| 1199 | .symbol = decl.link.macho.local_sym_index, | |
| 1200 | .index = decl.link.macho.offset_table_index, | |
| 1201 | }; | |
| 1206 | 1202 | try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); |
| 1207 | 1203 | } |
| 1208 | 1204 | } else if (code.len < decl.link.macho.size) { |
| ... | ... | @@ -1231,7 +1227,11 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| 1231 | 1227 | .n_desc = 0, |
| 1232 | 1228 | .n_value = addr, |
| 1233 | 1229 | }; |
| 1234 | self.offset_table.items[decl.link.macho.offset_table_index] = addr; | |
| 1230 | self.offset_table.items[decl.link.macho.offset_table_index] = .{ | |
| 1231 | .kind = .Local, | |
| 1232 | .symbol = decl.link.macho.local_sym_index, | |
| 1233 | .index = decl.link.macho.offset_table_index, | |
| 1234 | }; | |
| 1235 | 1235 | |
| 1236 | 1236 | try self.writeLocalSymbol(decl.link.macho.local_sym_index); |
| 1237 | 1237 | if (self.d_sym) |*ds| |
| ... | ... | @@ -1239,30 +1239,48 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| 1239 | 1239 | try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); |
| 1240 | 1240 | } |
| 1241 | 1241 | |
| 1242 | // Perform PIE fixups (if any) | |
| 1243 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1244 | const got_section = text_segment.sections.items[self.got_section_index.?]; | |
| 1242 | // Calculate displacements to target addr (if any). | |
| 1245 | 1243 | while (self.pie_fixups.popOrNull()) |fixup| { |
| 1246 | const target_addr = fixup.address; | |
| 1247 | const this_addr = symbol.n_value + fixup.start; | |
| 1244 | assert(fixup.size == 4); | |
| 1245 | const this_addr = symbol.n_value + fixup.offset; | |
| 1246 | const target_addr = fixup.target_addr; | |
| 1247 | ||
| 1248 | 1248 | switch (self.base.options.target.cpu.arch) { |
| 1249 | 1249 | .x86_64 => { |
| 1250 | assert(target_addr >= this_addr + fixup.len); | |
| 1251 | const displacement = try math.cast(u32, target_addr - this_addr - fixup.len); | |
| 1252 | var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; | |
| 1253 | mem.writeIntSliceLittle(u32, placeholder, displacement); | |
| 1250 | const displacement = try math.cast(u32, target_addr - this_addr - 4); | |
| 1251 | mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement); | |
| 1254 | 1252 | }, |
| 1255 | 1253 | .aarch64 => { |
| 1256 | assert(target_addr >= this_addr); | |
| 1257 | const displacement = try math.cast(u27, target_addr - this_addr); | |
| 1258 | var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; | |
| 1259 | mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@as(i28, displacement)).toU32()); | |
| 1254 | // TODO optimize instruction based on jump length (use ldr(literal) + nop if possible). | |
| 1255 | { | |
| 1256 | const inst = code_buffer.items[fixup.offset..][0..4]; | |
| 1257 | var parsed = mem.bytesAsValue(meta.TagPayload( | |
| 1258 | aarch64.Instruction, | |
| 1259 | aarch64.Instruction.PCRelativeAddress, | |
| 1260 | ), inst); | |
| 1261 | const this_page = @intCast(i32, this_addr >> 12); | |
| 1262 | const target_page = @intCast(i32, target_addr >> 12); | |
| 1263 | const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); | |
| 1264 | parsed.immhi = @truncate(u19, pages >> 2); | |
| 1265 | parsed.immlo = @truncate(u2, pages); | |
| 1266 | } | |
| 1267 | { | |
| 1268 | const inst = code_buffer.items[fixup.offset + 4 ..][0..4]; | |
| 1269 | var parsed = mem.bytesAsValue(meta.TagPayload( | |
| 1270 | aarch64.Instruction, | |
| 1271 | aarch64.Instruction.LoadStoreRegister, | |
| 1272 | ), inst); | |
| 1273 | const narrowed = @truncate(u12, target_addr); | |
| 1274 | const offset = try math.divExact(u12, narrowed, 8); | |
| 1275 | parsed.offset = offset; | |
| 1276 | } | |
| 1260 | 1277 | }, |
| 1261 | 1278 | else => unreachable, // unsupported target architecture |
| 1262 | 1279 | } |
| 1263 | 1280 | } |
| 1264 | 1281 | |
| 1265 | 1282 | // Resolve stubs (if any) |
| 1283 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1266 | 1284 | const stubs = text_segment.sections.items[self.stubs_section_index.?]; |
| 1267 | 1285 | for (self.stub_fixups.items) |fixup| { |
| 1268 | 1286 | const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2; |
| ... | ... | @@ -1287,9 +1305,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { |
| 1287 | 1305 | try self.writeStubInStubHelper(fixup.symbol); |
| 1288 | 1306 | try self.writeLazySymbolPointer(fixup.symbol); |
| 1289 | 1307 | |
| 1290 | const extern_sym = &self.extern_lazy_symbols.items()[fixup.symbol].value; | |
| 1291 | extern_sym.segment = self.data_segment_cmd_index.?; | |
| 1292 | extern_sym.offset = fixup.symbol * @sizeOf(u64); | |
| 1293 | 1308 | self.rebase_info_dirty = true; |
| 1294 | 1309 | self.lazy_binding_info_dirty = true; |
| 1295 | 1310 | } |
| ... | ... | @@ -1331,9 +1346,9 @@ pub fn updateDeclExports( |
| 1331 | 1346 | const tracy = trace(@src()); |
| 1332 | 1347 | defer tracy.end(); |
| 1333 | 1348 | |
| 1334 | try self.global_symbols.ensureCapacity(self.base.allocator, self.global_symbols.items.len + exports.len); | |
| 1349 | try self.globals.ensureCapacity(self.base.allocator, self.globals.items.len + exports.len); | |
| 1335 | 1350 | if (decl.link.macho.local_sym_index == 0) return; |
| 1336 | const decl_sym = &self.local_symbols.items[decl.link.macho.local_sym_index]; | |
| 1351 | const decl_sym = &self.locals.items[decl.link.macho.local_sym_index]; | |
| 1337 | 1352 | |
| 1338 | 1353 | for (exports) |exp| { |
| 1339 | 1354 | if (exp.options.section) |section_name| { |
| ... | ... | @@ -1366,7 +1381,7 @@ pub fn updateDeclExports( |
| 1366 | 1381 | }; |
| 1367 | 1382 | const n_type = decl_sym.n_type | macho.N_EXT; |
| 1368 | 1383 | if (exp.link.macho.sym_index) |i| { |
| 1369 | const sym = &self.global_symbols.items[i]; | |
| 1384 | const sym = &self.globals.items[i]; | |
| 1370 | 1385 | sym.* = .{ |
| 1371 | 1386 | .n_strx = try self.updateString(sym.n_strx, exp.options.name), |
| 1372 | 1387 | .n_type = n_type, |
| ... | ... | @@ -1376,12 +1391,12 @@ pub fn updateDeclExports( |
| 1376 | 1391 | }; |
| 1377 | 1392 | } else { |
| 1378 | 1393 | const name_str_index = try self.makeString(exp.options.name); |
| 1379 | const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { | |
| 1380 | _ = self.global_symbols.addOneAssumeCapacity(); | |
| 1394 | const i = if (self.globals_free_list.popOrNull()) |i| i else blk: { | |
| 1395 | _ = self.globals.addOneAssumeCapacity(); | |
| 1381 | 1396 | self.export_info_dirty = true; |
| 1382 | break :blk self.global_symbols.items.len - 1; | |
| 1397 | break :blk self.globals.items.len - 1; | |
| 1383 | 1398 | }; |
| 1384 | self.global_symbols.items[i] = .{ | |
| 1399 | self.globals.items[i] = .{ | |
| 1385 | 1400 | .n_strx = name_str_index, |
| 1386 | 1401 | .n_type = n_type, |
| 1387 | 1402 | .n_sect = @intCast(u8, self.text_section_index.?) + 1, |
| ... | ... | @@ -1396,18 +1411,18 @@ pub fn updateDeclExports( |
| 1396 | 1411 | |
| 1397 | 1412 | pub fn deleteExport(self: *MachO, exp: Export) void { |
| 1398 | 1413 | const sym_index = exp.sym_index orelse return; |
| 1399 | self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {}; | |
| 1400 | self.global_symbols.items[sym_index].n_type = 0; | |
| 1414 | self.globals_free_list.append(self.base.allocator, sym_index) catch {}; | |
| 1415 | self.globals.items[sym_index].n_type = 0; | |
| 1401 | 1416 | } |
| 1402 | 1417 | |
| 1403 | 1418 | pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { |
| 1404 | 1419 | // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. |
| 1405 | 1420 | self.freeTextBlock(&decl.link.macho); |
| 1406 | 1421 | if (decl.link.macho.local_sym_index != 0) { |
| 1407 | self.local_symbol_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {}; | |
| 1422 | self.locals_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {}; | |
| 1408 | 1423 | self.offset_table_free_list.append(self.base.allocator, decl.link.macho.offset_table_index) catch {}; |
| 1409 | 1424 | |
| 1410 | self.local_symbols.items[decl.link.macho.local_sym_index].n_type = 0; | |
| 1425 | self.locals.items[decl.link.macho.local_sym_index].n_type = 0; | |
| 1411 | 1426 | |
| 1412 | 1427 | decl.link.macho.local_sym_index = 0; |
| 1413 | 1428 | } |
| ... | ... | @@ -1415,7 +1430,7 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { |
| 1415 | 1430 | |
| 1416 | 1431 | pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { |
| 1417 | 1432 | assert(decl.link.macho.local_sym_index != 0); |
| 1418 | return self.local_symbols.items[decl.link.macho.local_sym_index].n_value; | |
| 1433 | return self.locals.items[decl.link.macho.local_sym_index].n_value; | |
| 1419 | 1434 | } |
| 1420 | 1435 | |
| 1421 | 1436 | pub fn populateMissingMetadata(self: *MachO) !void { |
| ... | ... | @@ -1555,39 +1570,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { |
| 1555 | 1570 | self.header_dirty = true; |
| 1556 | 1571 | self.load_commands_dirty = true; |
| 1557 | 1572 | } |
| 1558 | if (self.got_section_index == null) { | |
| 1559 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1560 | self.got_section_index = @intCast(u16, text_segment.sections.items.len); | |
| 1561 | ||
| 1562 | const alignment: u2 = switch (self.base.options.target.cpu.arch) { | |
| 1563 | .x86_64 => 0, | |
| 1564 | .aarch64 => 2, | |
| 1565 | else => unreachable, // unhandled architecture type | |
| 1566 | }; | |
| 1567 | const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; | |
| 1568 | const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; | |
| 1569 | const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); | |
| 1570 | assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. | |
| 1571 | ||
| 1572 | log.debug("found __ziggot section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); | |
| 1573 | ||
| 1574 | try text_segment.addSection(self.base.allocator, .{ | |
| 1575 | .sectname = makeStaticString("__ziggot"), | |
| 1576 | .segname = makeStaticString("__TEXT"), | |
| 1577 | .addr = text_segment.inner.vmaddr + off, | |
| 1578 | .size = needed_size, | |
| 1579 | .offset = @intCast(u32, off), | |
| 1580 | .@"align" = alignment, | |
| 1581 | .reloff = 0, | |
| 1582 | .nreloc = 0, | |
| 1583 | .flags = flags, | |
| 1584 | .reserved1 = 0, | |
| 1585 | .reserved2 = 0, | |
| 1586 | .reserved3 = 0, | |
| 1587 | }); | |
| 1588 | self.header_dirty = true; | |
| 1589 | self.load_commands_dirty = true; | |
| 1590 | } | |
| 1591 | 1573 | if (self.stubs_section_index == null) { |
| 1592 | 1574 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 1593 | 1575 | self.stubs_section_index = @intCast(u16, text_segment.sections.items.len); |
| ... | ... | @@ -1599,7 +1581,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { |
| 1599 | 1581 | }; |
| 1600 | 1582 | const stub_size: u4 = switch (self.base.options.target.cpu.arch) { |
| 1601 | 1583 | .x86_64 => 6, |
| 1602 | .aarch64 => 2 * @sizeOf(u32), | |
| 1584 | .aarch64 => 3 * @sizeOf(u32), | |
| 1603 | 1585 | else => unreachable, // unhandled architecture type |
| 1604 | 1586 | }; |
| 1605 | 1587 | const flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; |
| ... | ... | @@ -1688,9 +1670,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { |
| 1688 | 1670 | self.header_dirty = true; |
| 1689 | 1671 | self.load_commands_dirty = true; |
| 1690 | 1672 | } |
| 1691 | if (self.data_got_section_index == null) { | |
| 1673 | if (self.got_section_index == null) { | |
| 1692 | 1674 | const dc_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; |
| 1693 | self.data_got_section_index = @intCast(u16, dc_segment.sections.items.len); | |
| 1675 | self.got_section_index = @intCast(u16, dc_segment.sections.items.len); | |
| 1694 | 1676 | |
| 1695 | 1677 | const flags = macho.S_NON_LAZY_SYMBOL_POINTERS; |
| 1696 | 1678 | const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; |
| ... | ... | @@ -2062,12 +2044,12 @@ pub fn populateMissingMetadata(self: *MachO) !void { |
| 2062 | 2044 | self.header_dirty = true; |
| 2063 | 2045 | self.load_commands_dirty = true; |
| 2064 | 2046 | } |
| 2065 | if (!self.extern_nonlazy_symbols.contains("dyld_stub_binder")) { | |
| 2066 | const index = @intCast(u32, self.extern_nonlazy_symbols.items().len); | |
| 2047 | if (!self.nonlazy_imports.contains("dyld_stub_binder")) { | |
| 2048 | const index = @intCast(u32, self.nonlazy_imports.items().len); | |
| 2067 | 2049 | const name = try self.base.allocator.dupe(u8, "dyld_stub_binder"); |
| 2068 | 2050 | const offset = try self.makeString("dyld_stub_binder"); |
| 2069 | try self.extern_nonlazy_symbols.putNoClobber(self.base.allocator, name, .{ | |
| 2070 | .inner = .{ | |
| 2051 | try self.nonlazy_imports.putNoClobber(self.base.allocator, name, .{ | |
| 2052 | .symbol = .{ | |
| 2071 | 2053 | .n_strx = offset, |
| 2072 | 2054 | .n_type = std.macho.N_UNDF | std.macho.N_EXT, |
| 2073 | 2055 | .n_sect = 0, |
| ... | ... | @@ -2075,68 +2057,19 @@ pub fn populateMissingMetadata(self: *MachO) !void { |
| 2075 | 2057 | .n_value = 0, |
| 2076 | 2058 | }, |
| 2077 | 2059 | .dylib_ordinal = 1, // TODO this is currently hardcoded. |
| 2078 | .segment = self.data_const_segment_cmd_index.?, | |
| 2079 | .offset = index * @sizeOf(u64), | |
| 2060 | .index = index, | |
| 2061 | }); | |
| 2062 | const off_index = @intCast(u32, self.offset_table.items.len); | |
| 2063 | try self.offset_table.append(self.base.allocator, .{ | |
| 2064 | .kind = .Extern, | |
| 2065 | .symbol = index, | |
| 2066 | .index = off_index, | |
| 2080 | 2067 | }); |
| 2068 | try self.writeOffsetTableEntry(off_index); | |
| 2081 | 2069 | self.binding_info_dirty = true; |
| 2082 | 2070 | } |
| 2083 | 2071 | if (self.stub_helper_stubs_start_off == null) { |
| 2084 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2085 | const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; | |
| 2086 | const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2087 | const data = &data_segment.sections.items[self.data_section_index.?]; | |
| 2088 | const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2089 | const got = &data_const_segment.sections.items[self.data_got_section_index.?]; | |
| 2090 | switch (self.base.options.target.cpu.arch) { | |
| 2091 | .x86_64 => { | |
| 2092 | const code_size = 15; | |
| 2093 | var code: [code_size]u8 = undefined; | |
| 2094 | // lea %r11, [rip + disp] | |
| 2095 | code[0] = 0x4c; | |
| 2096 | code[1] = 0x8d; | |
| 2097 | code[2] = 0x1d; | |
| 2098 | { | |
| 2099 | const displacement = try math.cast(u32, data.addr - stub_helper.addr - 7); | |
| 2100 | mem.writeIntLittle(u32, code[3..7], displacement); | |
| 2101 | } | |
| 2102 | // push %r11 | |
| 2103 | code[7] = 0x41; | |
| 2104 | code[8] = 0x53; | |
| 2105 | // jmp [rip + disp] | |
| 2106 | code[9] = 0xff; | |
| 2107 | code[10] = 0x25; | |
| 2108 | { | |
| 2109 | const displacement = try math.cast(u32, got.addr - stub_helper.addr - code_size); | |
| 2110 | mem.writeIntLittle(u32, code[11..], displacement); | |
| 2111 | } | |
| 2112 | self.stub_helper_stubs_start_off = stub_helper.offset + code_size; | |
| 2113 | try self.base.file.?.pwriteAll(&code, stub_helper.offset); | |
| 2114 | }, | |
| 2115 | .aarch64 => { | |
| 2116 | var code: [4 * @sizeOf(u32)]u8 = undefined; | |
| 2117 | { | |
| 2118 | const displacement = try math.cast(i21, data.addr - stub_helper.addr); | |
| 2119 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); | |
| 2120 | } | |
| 2121 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp( | |
| 2122 | .x16, | |
| 2123 | .x17, | |
| 2124 | aarch64.Register.sp, | |
| 2125 | aarch64.Instruction.LoadStorePairOffset.pre_index(-16), | |
| 2126 | ).toU32()); | |
| 2127 | { | |
| 2128 | const displacement = try math.divExact(u64, got.addr - stub_helper.addr - 2 * @sizeOf(u32), 4); | |
| 2129 | const literal = try math.cast(u19, displacement); | |
| 2130 | mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{ | |
| 2131 | .literal = literal, | |
| 2132 | }).toU32()); | |
| 2133 | } | |
| 2134 | mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32()); | |
| 2135 | self.stub_helper_stubs_start_off = stub_helper.offset + 4 * @sizeOf(u32); | |
| 2136 | try self.base.file.?.pwriteAll(&code, stub_helper.offset); | |
| 2137 | }, | |
| 2138 | else => unreachable, | |
| 2139 | } | |
| 2072 | try self.writeStubHelperPreamble(); | |
| 2140 | 2073 | } |
| 2141 | 2074 | } |
| 2142 | 2075 | |
| ... | ... | @@ -2161,7 +2094,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, |
| 2161 | 2094 | const big_block = self.text_block_free_list.items[i]; |
| 2162 | 2095 | // We now have a pointer to a live text block that has too much capacity. |
| 2163 | 2096 | // Is it enough that we could fit this new text block? |
| 2164 | const sym = self.local_symbols.items[big_block.local_sym_index]; | |
| 2097 | const sym = self.locals.items[big_block.local_sym_index]; | |
| 2165 | 2098 | const capacity = big_block.capacity(self.*); |
| 2166 | 2099 | const ideal_capacity = padToIdeal(capacity); |
| 2167 | 2100 | const ideal_capacity_end_vaddr = sym.n_value + ideal_capacity; |
| ... | ... | @@ -2192,7 +2125,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, |
| 2192 | 2125 | } |
| 2193 | 2126 | break :blk new_start_vaddr; |
| 2194 | 2127 | } else if (self.last_text_block) |last| { |
| 2195 | const last_symbol = self.local_symbols.items[last.local_sym_index]; | |
| 2128 | const last_symbol = self.locals.items[last.local_sym_index]; | |
| 2196 | 2129 | // TODO We should pad out the excess capacity with NOPs. For executables, |
| 2197 | 2130 | // no padding seems to be OK, but it will probably not be for objects. |
| 2198 | 2131 | const ideal_capacity = padToIdeal(last.size); |
| ... | ... | @@ -2290,12 +2223,12 @@ fn updateString(self: *MachO, old_str_off: u32, new_name: []const u8) !u32 { |
| 2290 | 2223 | } |
| 2291 | 2224 | |
| 2292 | 2225 | pub fn addExternSymbol(self: *MachO, name: []const u8) !u32 { |
| 2293 | const index = @intCast(u32, self.extern_lazy_symbols.items().len); | |
| 2226 | const index = @intCast(u32, self.lazy_imports.items().len); | |
| 2294 | 2227 | const offset = try self.makeString(name); |
| 2295 | 2228 | const sym_name = try self.base.allocator.dupe(u8, name); |
| 2296 | 2229 | const dylib_ordinal = 1; // TODO this is now hardcoded, since we only support libSystem. |
| 2297 | try self.extern_lazy_symbols.putNoClobber(self.base.allocator, sym_name, .{ | |
| 2298 | .inner = .{ | |
| 2230 | try self.lazy_imports.putNoClobber(self.base.allocator, sym_name, .{ | |
| 2231 | .symbol = .{ | |
| 2299 | 2232 | .n_strx = offset, |
| 2300 | 2233 | .n_type = macho.N_UNDF | macho.N_EXT, |
| 2301 | 2234 | .n_sect = 0, |
| ... | ... | @@ -2303,6 +2236,7 @@ pub fn addExternSymbol(self: *MachO, name: []const u8) !u32 { |
| 2303 | 2236 | .n_value = 0, |
| 2304 | 2237 | }, |
| 2305 | 2238 | .dylib_ordinal = dylib_ordinal, |
| 2239 | .index = index, | |
| 2306 | 2240 | }); |
| 2307 | 2241 | log.debug("adding new extern symbol '{s}' with dylib ordinal '{}'", .{ name, dylib_ordinal }); |
| 2308 | 2242 | return index; |
| ... | ... | @@ -2461,41 +2395,29 @@ fn findFreeSpaceLinkedit(self: *MachO, object_size: u64, min_alignment: u16, sta |
| 2461 | 2395 | } |
| 2462 | 2396 | |
| 2463 | 2397 | fn writeOffsetTableEntry(self: *MachO, index: usize) !void { |
| 2464 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2465 | const sect = &text_segment.sections.items[self.got_section_index.?]; | |
| 2398 | const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2399 | const sect = &seg.sections.items[self.got_section_index.?]; | |
| 2466 | 2400 | const off = sect.offset + @sizeOf(u64) * index; |
| 2467 | const vmaddr = sect.addr + @sizeOf(u64) * index; | |
| 2468 | 2401 | |
| 2469 | 2402 | if (self.offset_table_count_dirty) { |
| 2470 | 2403 | // TODO relocate. |
| 2471 | 2404 | self.offset_table_count_dirty = false; |
| 2472 | 2405 | } |
| 2473 | 2406 | |
| 2474 | var code: [8]u8 = undefined; | |
| 2475 | switch (self.base.options.target.cpu.arch) { | |
| 2476 | .x86_64 => { | |
| 2477 | const pos_symbol_off = try math.cast(u31, vmaddr - self.offset_table.items[index] + 7); | |
| 2478 | const symbol_off = @bitCast(u32, @as(i32, pos_symbol_off) * -1); | |
| 2479 | // lea %rax, [rip - disp] | |
| 2480 | code[0] = 0x48; | |
| 2481 | code[1] = 0x8D; | |
| 2482 | code[2] = 0x5; | |
| 2483 | mem.writeIntLittle(u32, code[3..7], symbol_off); | |
| 2484 | // ret | |
| 2485 | code[7] = 0xC3; | |
| 2486 | }, | |
| 2487 | .aarch64 => { | |
| 2488 | const pos_symbol_off = try math.cast(u20, vmaddr - self.offset_table.items[index]); | |
| 2489 | const symbol_off = @as(i21, pos_symbol_off) * -1; | |
| 2490 | // adr x0, #-disp | |
| 2491 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32()); | |
| 2492 | // ret x28 | |
| 2493 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32()); | |
| 2494 | }, | |
| 2495 | else => unreachable, // unsupported target architecture | |
| 2496 | } | |
| 2497 | log.debug("writing offset table entry 0x{x} at 0x{x}", .{ self.offset_table.items[index], off }); | |
| 2498 | try self.base.file.?.pwriteAll(&code, off); | |
| 2407 | const got_entry = self.offset_table.items[index]; | |
| 2408 | const sym = blk: { | |
| 2409 | switch (got_entry.kind) { | |
| 2410 | .Local => { | |
| 2411 | break :blk self.locals.items[got_entry.symbol]; | |
| 2412 | }, | |
| 2413 | .Extern => { | |
| 2414 | break :blk self.nonlazy_imports.items()[got_entry.symbol].value.symbol; | |
| 2415 | }, | |
| 2416 | } | |
| 2417 | }; | |
| 2418 | const sym_name = self.getString(sym.n_strx); | |
| 2419 | log.debug("writing offset table entry [ 0x{x} => 0x{x} ({s}) ]", .{ off, sym.n_value, sym_name }); | |
| 2420 | try self.base.file.?.pwriteAll(mem.asBytes(&sym.n_value), off); | |
| 2499 | 2421 | } |
| 2500 | 2422 | |
| 2501 | 2423 | fn writeLazySymbolPointer(self: *MachO, index: u32) !void { |
| ... | ... | @@ -2518,6 +2440,133 @@ fn writeLazySymbolPointer(self: *MachO, index: u32) !void { |
| 2518 | 2440 | try self.base.file.?.pwriteAll(&buf, off); |
| 2519 | 2441 | } |
| 2520 | 2442 | |
| 2443 | fn writeStubHelperPreamble(self: *MachO) !void { | |
| 2444 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2445 | const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; | |
| 2446 | const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2447 | const got = &data_const_segment.sections.items[self.got_section_index.?]; | |
| 2448 | const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2449 | const data = &data_segment.sections.items[self.data_section_index.?]; | |
| 2450 | ||
| 2451 | switch (self.base.options.target.cpu.arch) { | |
| 2452 | .x86_64 => { | |
| 2453 | const code_size = 15; | |
| 2454 | var code: [code_size]u8 = undefined; | |
| 2455 | // lea %r11, [rip + disp] | |
| 2456 | code[0] = 0x4c; | |
| 2457 | code[1] = 0x8d; | |
| 2458 | code[2] = 0x1d; | |
| 2459 | { | |
| 2460 | const target_addr = data.addr; | |
| 2461 | const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7); | |
| 2462 | mem.writeIntLittle(u32, code[3..7], displacement); | |
| 2463 | } | |
| 2464 | // push %r11 | |
| 2465 | code[7] = 0x41; | |
| 2466 | code[8] = 0x53; | |
| 2467 | // jmp [rip + disp] | |
| 2468 | code[9] = 0xff; | |
| 2469 | code[10] = 0x25; | |
| 2470 | { | |
| 2471 | const displacement = try math.cast(u32, got.addr - stub_helper.addr - code_size); | |
| 2472 | mem.writeIntLittle(u32, code[11..], displacement); | |
| 2473 | } | |
| 2474 | try self.base.file.?.pwriteAll(&code, stub_helper.offset); | |
| 2475 | self.stub_helper_stubs_start_off = stub_helper.offset + code_size; | |
| 2476 | }, | |
| 2477 | .aarch64 => { | |
| 2478 | var code: [6 * @sizeOf(u32)]u8 = undefined; | |
| 2479 | ||
| 2480 | data_blk_outer: { | |
| 2481 | const this_addr = stub_helper.addr; | |
| 2482 | const target_addr = data.addr; | |
| 2483 | data_blk: { | |
| 2484 | const displacement = math.cast(i21, target_addr - this_addr) catch |_| break :data_blk; | |
| 2485 | // adr x17, disp | |
| 2486 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); | |
| 2487 | // nop | |
| 2488 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); | |
| 2489 | break :data_blk_outer; | |
| 2490 | } | |
| 2491 | data_blk: { | |
| 2492 | const new_this_addr = this_addr + @sizeOf(u32); | |
| 2493 | const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; | |
| 2494 | // nop | |
| 2495 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); | |
| 2496 | // adr x17, disp | |
| 2497 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32()); | |
| 2498 | break :data_blk_outer; | |
| 2499 | } | |
| 2500 | // Jump is too big, replace adr with adrp and add. | |
| 2501 | const this_page = @intCast(i32, this_addr >> 12); | |
| 2502 | const target_page = @intCast(i32, target_addr >> 12); | |
| 2503 | const pages = @intCast(i21, target_page - this_page); | |
| 2504 | // adrp x17, pages | |
| 2505 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32()); | |
| 2506 | const narrowed = @truncate(u12, target_addr); | |
| 2507 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32()); | |
| 2508 | } | |
| 2509 | ||
| 2510 | // stp x16, x17, [sp, #-16]! | |
| 2511 | mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.stp( | |
| 2512 | .x16, | |
| 2513 | .x17, | |
| 2514 | aarch64.Register.sp, | |
| 2515 | aarch64.Instruction.LoadStorePairOffset.pre_index(-16), | |
| 2516 | ).toU32()); | |
| 2517 | ||
| 2518 | binder_blk_outer: { | |
| 2519 | const this_addr = stub_helper.addr + 3 * @sizeOf(u32); | |
| 2520 | const target_addr = got.addr; | |
| 2521 | binder_blk: { | |
| 2522 | const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :binder_blk; | |
| 2523 | const literal = math.cast(u18, displacement) catch |_| break :binder_blk; | |
| 2524 | // ldr x16, label | |
| 2525 | mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{ | |
| 2526 | .literal = literal, | |
| 2527 | }).toU32()); | |
| 2528 | // nop | |
| 2529 | mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); | |
| 2530 | break :binder_blk_outer; | |
| 2531 | } | |
| 2532 | binder_blk: { | |
| 2533 | const new_this_addr = this_addr + @sizeOf(u32); | |
| 2534 | const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; | |
| 2535 | const literal = math.cast(u18, displacement) catch |_| break :binder_blk; | |
| 2536 | // nop | |
| 2537 | mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); | |
| 2538 | // ldr x16, label | |
| 2539 | mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ | |
| 2540 | .literal = literal, | |
| 2541 | }).toU32()); | |
| 2542 | break :binder_blk_outer; | |
| 2543 | } | |
| 2544 | // Jump is too big, replace ldr with adrp and ldr(register). | |
| 2545 | const this_page = @intCast(i32, this_addr >> 12); | |
| 2546 | const target_page = @intCast(i32, target_addr >> 12); | |
| 2547 | const pages = @intCast(i21, target_page - this_page); | |
| 2548 | // adrp x16, pages | |
| 2549 | mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32()); | |
| 2550 | const narrowed = @truncate(u12, target_addr); | |
| 2551 | const offset = try math.divExact(u12, narrowed, 8); | |
| 2552 | // ldr x16, x16, offset | |
| 2553 | mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ | |
| 2554 | .register = .{ | |
| 2555 | .rn = .x16, | |
| 2556 | .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), | |
| 2557 | }, | |
| 2558 | }).toU32()); | |
| 2559 | } | |
| 2560 | ||
| 2561 | // br x16 | |
| 2562 | mem.writeIntLittle(u32, code[20..24], aarch64.Instruction.br(.x16).toU32()); | |
| 2563 | try self.base.file.?.pwriteAll(&code, stub_helper.offset); | |
| 2564 | self.stub_helper_stubs_start_off = stub_helper.offset + code.len; | |
| 2565 | }, | |
| 2566 | else => unreachable, | |
| 2567 | } | |
| 2568 | } | |
| 2569 | ||
| 2521 | 2570 | fn writeStub(self: *MachO, index: u32) !void { |
| 2522 | 2571 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 2523 | 2572 | const stubs = text_segment.sections.items[self.stubs_section_index.?]; |
| ... | ... | @@ -2527,9 +2576,12 @@ fn writeStub(self: *MachO, index: u32) !void { |
| 2527 | 2576 | const stub_off = stubs.offset + index * stubs.reserved2; |
| 2528 | 2577 | const stub_addr = stubs.addr + index * stubs.reserved2; |
| 2529 | 2578 | const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); |
| 2579 | ||
| 2530 | 2580 | log.debug("writing stub at 0x{x}", .{stub_off}); |
| 2581 | ||
| 2531 | 2582 | var code = try self.base.allocator.alloc(u8, stubs.reserved2); |
| 2532 | 2583 | defer self.base.allocator.free(code); |
| 2584 | ||
| 2533 | 2585 | switch (self.base.options.target.cpu.arch) { |
| 2534 | 2586 | .x86_64 => { |
| 2535 | 2587 | assert(la_ptr_addr >= stub_addr + stubs.reserved2); |
| ... | ... | @@ -2541,12 +2593,50 @@ fn writeStub(self: *MachO, index: u32) !void { |
| 2541 | 2593 | }, |
| 2542 | 2594 | .aarch64 => { |
| 2543 | 2595 | assert(la_ptr_addr >= stub_addr); |
| 2544 | const displacement = try math.divExact(u64, la_ptr_addr - stub_addr, 4); | |
| 2545 | const literal = try math.cast(u19, displacement); | |
| 2546 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ | |
| 2547 | .literal = literal, | |
| 2548 | }).toU32()); | |
| 2549 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.br(.x16).toU32()); | |
| 2596 | outer: { | |
| 2597 | const this_addr = stub_addr; | |
| 2598 | const target_addr = la_ptr_addr; | |
| 2599 | inner: { | |
| 2600 | const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :inner; | |
| 2601 | const literal = math.cast(u18, displacement) catch |_| break :inner; | |
| 2602 | // ldr x16, literal | |
| 2603 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ | |
| 2604 | .literal = literal, | |
| 2605 | }).toU32()); | |
| 2606 | // nop | |
| 2607 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); | |
| 2608 | break :outer; | |
| 2609 | } | |
| 2610 | inner: { | |
| 2611 | const new_this_addr = this_addr + @sizeOf(u32); | |
| 2612 | const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; | |
| 2613 | const literal = math.cast(u18, displacement) catch |_| break :inner; | |
| 2614 | // nop | |
| 2615 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); | |
| 2616 | // ldr x16, literal | |
| 2617 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ | |
| 2618 | .literal = literal, | |
| 2619 | }).toU32()); | |
| 2620 | break :outer; | |
| 2621 | } | |
| 2622 | // Use adrp followed by ldr(register). | |
| 2623 | const this_page = @intCast(i32, this_addr >> 12); | |
| 2624 | const target_page = @intCast(i32, target_addr >> 12); | |
| 2625 | const pages = @intCast(i21, target_page - this_page); | |
| 2626 | // adrp x16, pages | |
| 2627 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x16, pages).toU32()); | |
| 2628 | const narrowed = @truncate(u12, target_addr); | |
| 2629 | const offset = try math.divExact(u12, narrowed, 8); | |
| 2630 | // ldr x16, x16, offset | |
| 2631 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ | |
| 2632 | .register = .{ | |
| 2633 | .rn = .x16, | |
| 2634 | .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), | |
| 2635 | }, | |
| 2636 | }).toU32()); | |
| 2637 | } | |
| 2638 | // br x16 | |
| 2639 | mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.br(.x16).toU32()); | |
| 2550 | 2640 | }, |
| 2551 | 2641 | else => unreachable, |
| 2552 | 2642 | } |
| ... | ... | @@ -2563,8 +2653,10 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { |
| 2563 | 2653 | else => unreachable, |
| 2564 | 2654 | }; |
| 2565 | 2655 | const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; |
| 2656 | ||
| 2566 | 2657 | var code = try self.base.allocator.alloc(u8, stub_size); |
| 2567 | 2658 | defer self.base.allocator.free(code); |
| 2659 | ||
| 2568 | 2660 | switch (self.base.options.target.cpu.arch) { |
| 2569 | 2661 | .x86_64 => { |
| 2570 | 2662 | const displacement = try math.cast( |
| ... | ... | @@ -2579,12 +2671,19 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { |
| 2579 | 2671 | mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); |
| 2580 | 2672 | }, |
| 2581 | 2673 | .aarch64 => { |
| 2582 | const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); | |
| 2674 | const literal = blk: { | |
| 2675 | const div_res = try math.divExact(u64, stub_size - @sizeOf(u32), 4); | |
| 2676 | break :blk try math.cast(u18, div_res); | |
| 2677 | }; | |
| 2678 | // ldr w16, literal | |
| 2583 | 2679 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ |
| 2584 | .literal = @divExact(stub_size - @sizeOf(u32), 4), | |
| 2680 | .literal = literal, | |
| 2585 | 2681 | }).toU32()); |
| 2682 | const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); | |
| 2683 | // b disp | |
| 2586 | 2684 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(displacement).toU32()); |
| 2587 | mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. | |
| 2685 | // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. | |
| 2686 | mem.writeIntLittle(u32, code[8..12], 0x0); | |
| 2588 | 2687 | }, |
| 2589 | 2688 | else => unreachable, |
| 2590 | 2689 | } |
| ... | ... | @@ -2593,9 +2692,9 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { |
| 2593 | 2692 | |
| 2594 | 2693 | fn relocateSymbolTable(self: *MachO) !void { |
| 2595 | 2694 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 2596 | const nlocals = self.local_symbols.items.len; | |
| 2597 | const nglobals = self.global_symbols.items.len; | |
| 2598 | const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; | |
| 2695 | const nlocals = self.locals.items.len; | |
| 2696 | const nglobals = self.globals.items.len; | |
| 2697 | const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; | |
| 2599 | 2698 | const nsyms = nlocals + nglobals + nundefs; |
| 2600 | 2699 | |
| 2601 | 2700 | if (symtab.nsyms < nsyms) { |
| ... | ... | @@ -2630,7 +2729,7 @@ fn writeLocalSymbol(self: *MachO, index: usize) !void { |
| 2630 | 2729 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 2631 | 2730 | const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; |
| 2632 | 2731 | log.debug("writing local symbol {} at 0x{x}", .{ index, off }); |
| 2633 | try self.base.file.?.pwriteAll(mem.asBytes(&self.local_symbols.items[index]), off); | |
| 2732 | try self.base.file.?.pwriteAll(mem.asBytes(&self.locals.items[index]), off); | |
| 2634 | 2733 | } |
| 2635 | 2734 | |
| 2636 | 2735 | fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { |
| ... | ... | @@ -2639,18 +2738,18 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { |
| 2639 | 2738 | |
| 2640 | 2739 | try self.relocateSymbolTable(); |
| 2641 | 2740 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 2642 | const nlocals = self.local_symbols.items.len; | |
| 2643 | const nglobals = self.global_symbols.items.len; | |
| 2741 | const nlocals = self.locals.items.len; | |
| 2742 | const nglobals = self.globals.items.len; | |
| 2644 | 2743 | |
| 2645 | const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; | |
| 2744 | const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; | |
| 2646 | 2745 | var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator); |
| 2647 | 2746 | defer undefs.deinit(); |
| 2648 | 2747 | try undefs.ensureCapacity(nundefs); |
| 2649 | for (self.extern_lazy_symbols.items()) |entry| { | |
| 2650 | undefs.appendAssumeCapacity(entry.value.inner); | |
| 2748 | for (self.lazy_imports.items()) |entry| { | |
| 2749 | undefs.appendAssumeCapacity(entry.value.symbol); | |
| 2651 | 2750 | } |
| 2652 | for (self.extern_nonlazy_symbols.items()) |entry| { | |
| 2653 | undefs.appendAssumeCapacity(entry.value.inner); | |
| 2751 | for (self.nonlazy_imports.items()) |entry| { | |
| 2752 | undefs.appendAssumeCapacity(entry.value.symbol); | |
| 2654 | 2753 | } |
| 2655 | 2754 | |
| 2656 | 2755 | const locals_off = symtab.symoff; |
| ... | ... | @@ -2659,7 +2758,7 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { |
| 2659 | 2758 | const globals_off = locals_off + locals_size; |
| 2660 | 2759 | const globals_size = nglobals * @sizeOf(macho.nlist_64); |
| 2661 | 2760 | log.debug("writing global symbols from 0x{x} to 0x{x}", .{ globals_off, globals_size + globals_off }); |
| 2662 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), globals_off); | |
| 2761 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), globals_off); | |
| 2663 | 2762 | |
| 2664 | 2763 | const undefs_off = globals_off + globals_size; |
| 2665 | 2764 | const undefs_size = nundefs * @sizeOf(macho.nlist_64); |
| ... | ... | @@ -2685,15 +2784,15 @@ fn writeIndirectSymbolTable(self: *MachO) !void { |
| 2685 | 2784 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 2686 | 2785 | const stubs = &text_segment.sections.items[self.stubs_section_index.?]; |
| 2687 | 2786 | const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; |
| 2688 | const got = &data_const_seg.sections.items[self.data_got_section_index.?]; | |
| 2787 | const got = &data_const_seg.sections.items[self.got_section_index.?]; | |
| 2689 | 2788 | const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; |
| 2690 | 2789 | const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; |
| 2691 | 2790 | const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; |
| 2692 | 2791 | |
| 2693 | const lazy = self.extern_lazy_symbols.items(); | |
| 2694 | const nonlazy = self.extern_nonlazy_symbols.items(); | |
| 2792 | const lazy = self.lazy_imports.items(); | |
| 2793 | const got_entries = self.offset_table.items; | |
| 2695 | 2794 | const allocated_size = self.allocatedSizeLinkedit(dysymtab.indirectsymoff); |
| 2696 | const nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len); | |
| 2795 | const nindirectsyms = @intCast(u32, lazy.len * 2 + got_entries.len); | |
| 2697 | 2796 | const needed_size = @intCast(u32, nindirectsyms * @sizeOf(u32)); |
| 2698 | 2797 | |
| 2699 | 2798 | if (needed_size > allocated_size) { |
| ... | ... | @@ -2712,20 +2811,27 @@ fn writeIndirectSymbolTable(self: *MachO) !void { |
| 2712 | 2811 | var writer = stream.writer(); |
| 2713 | 2812 | |
| 2714 | 2813 | stubs.reserved1 = 0; |
| 2715 | for (self.extern_lazy_symbols.items()) |_, i| { | |
| 2814 | for (lazy) |_, i| { | |
| 2716 | 2815 | const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); |
| 2717 | 2816 | try writer.writeIntLittle(u32, symtab_idx); |
| 2718 | 2817 | } |
| 2719 | 2818 | |
| 2720 | 2819 | const base_id = @intCast(u32, lazy.len); |
| 2721 | 2820 | got.reserved1 = base_id; |
| 2722 | for (self.extern_nonlazy_symbols.items()) |_, i| { | |
| 2723 | const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); | |
| 2724 | try writer.writeIntLittle(u32, symtab_idx); | |
| 2821 | for (got_entries) |entry| { | |
| 2822 | switch (entry.kind) { | |
| 2823 | .Local => { | |
| 2824 | try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL); | |
| 2825 | }, | |
| 2826 | .Extern => { | |
| 2827 | const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.index + base_id); | |
| 2828 | try writer.writeIntLittle(u32, symtab_idx); | |
| 2829 | }, | |
| 2830 | } | |
| 2725 | 2831 | } |
| 2726 | 2832 | |
| 2727 | la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len); | |
| 2728 | for (self.extern_lazy_symbols.items()) |_, i| { | |
| 2833 | la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, got_entries.len); | |
| 2834 | for (lazy) |_, i| { | |
| 2729 | 2835 | const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); |
| 2730 | 2836 | try writer.writeIntLittle(u32, symtab_idx); |
| 2731 | 2837 | } |
| ... | ... | @@ -2791,7 +2897,7 @@ fn writeCodeSignature(self: *MachO) !void { |
| 2791 | 2897 | |
| 2792 | 2898 | fn writeExportTrie(self: *MachO) !void { |
| 2793 | 2899 | if (!self.export_info_dirty) return; |
| 2794 | if (self.global_symbols.items.len == 0) return; | |
| 2900 | if (self.globals.items.len == 0) return; | |
| 2795 | 2901 | |
| 2796 | 2902 | const tracy = trace(@src()); |
| 2797 | 2903 | defer tracy.end(); |
| ... | ... | @@ -2800,7 +2906,7 @@ fn writeExportTrie(self: *MachO) !void { |
| 2800 | 2906 | defer trie.deinit(); |
| 2801 | 2907 | |
| 2802 | 2908 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 2803 | for (self.global_symbols.items) |symbol| { | |
| 2909 | for (self.globals.items) |symbol| { | |
| 2804 | 2910 | // TODO figure out if we should put all global symbols into the export trie |
| 2805 | 2911 | const name = self.getString(symbol.n_strx); |
| 2806 | 2912 | assert(symbol.n_value >= text_segment.inner.vmaddr); |
| ... | ... | @@ -2842,14 +2948,48 @@ fn writeRebaseInfoTable(self: *MachO) !void { |
| 2842 | 2948 | const tracy = trace(@src()); |
| 2843 | 2949 | defer tracy.end(); |
| 2844 | 2950 | |
| 2845 | const size = try rebaseInfoSize(self.extern_lazy_symbols.items()); | |
| 2951 | var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); | |
| 2952 | defer pointers.deinit(); | |
| 2953 | ||
| 2954 | if (self.got_section_index) |idx| { | |
| 2955 | const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2956 | const sect = seg.sections.items[idx]; | |
| 2957 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2958 | const segment_id = self.data_const_segment_cmd_index.?; | |
| 2959 | ||
| 2960 | for (self.offset_table.items) |entry| { | |
| 2961 | if (entry.kind == .Extern) continue; | |
| 2962 | try pointers.append(.{ | |
| 2963 | .offset = base_offset + entry.index * @sizeOf(u64), | |
| 2964 | .segment_id = segment_id, | |
| 2965 | }); | |
| 2966 | } | |
| 2967 | } | |
| 2968 | ||
| 2969 | if (self.la_symbol_ptr_section_index) |idx| { | |
| 2970 | try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); | |
| 2971 | const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2972 | const sect = seg.sections.items[idx]; | |
| 2973 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2974 | const segment_id = self.data_segment_cmd_index.?; | |
| 2975 | ||
| 2976 | for (self.lazy_imports.items()) |entry| { | |
| 2977 | pointers.appendAssumeCapacity(.{ | |
| 2978 | .offset = base_offset + entry.value.index * @sizeOf(u64), | |
| 2979 | .segment_id = segment_id, | |
| 2980 | }); | |
| 2981 | } | |
| 2982 | } | |
| 2983 | ||
| 2984 | std.sort.sort(bind.Pointer, pointers.items, {}, bind.pointerCmp); | |
| 2985 | ||
| 2986 | const size = try bind.rebaseInfoSize(pointers.items); | |
| 2846 | 2987 | var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); |
| 2847 | 2988 | defer self.base.allocator.free(buffer); |
| 2848 | 2989 | |
| 2849 | 2990 | var stream = std.io.fixedBufferStream(buffer); |
| 2850 | try writeRebaseInfo(self.extern_lazy_symbols.items(), stream.writer()); | |
| 2991 | try bind.writeRebaseInfo(pointers.items, stream.writer()); | |
| 2851 | 2992 | |
| 2852 | const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2853 | 2993 | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; |
| 2854 | 2994 | const allocated_size = self.allocatedSizeLinkedit(dyld_info.rebase_off); |
| 2855 | 2995 | const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); |
| ... | ... | @@ -2874,14 +3014,34 @@ fn writeBindingInfoTable(self: *MachO) !void { |
| 2874 | 3014 | const tracy = trace(@src()); |
| 2875 | 3015 | defer tracy.end(); |
| 2876 | 3016 | |
| 2877 | const size = try bindInfoSize(self.extern_nonlazy_symbols.items()); | |
| 3017 | var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); | |
| 3018 | defer pointers.deinit(); | |
| 3019 | ||
| 3020 | if (self.got_section_index) |idx| { | |
| 3021 | const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 3022 | const sect = seg.sections.items[idx]; | |
| 3023 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 3024 | const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); | |
| 3025 | ||
| 3026 | for (self.offset_table.items) |entry| { | |
| 3027 | if (entry.kind == .Local) continue; | |
| 3028 | const import = self.nonlazy_imports.items()[entry.symbol]; | |
| 3029 | try pointers.append(.{ | |
| 3030 | .offset = base_offset + entry.index * @sizeOf(u64), | |
| 3031 | .segment_id = segment_id, | |
| 3032 | .dylib_ordinal = import.value.dylib_ordinal, | |
| 3033 | .name = import.key, | |
| 3034 | }); | |
| 3035 | } | |
| 3036 | } | |
| 3037 | ||
| 3038 | const size = try bind.bindInfoSize(pointers.items); | |
| 2878 | 3039 | var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); |
| 2879 | 3040 | defer self.base.allocator.free(buffer); |
| 2880 | 3041 | |
| 2881 | 3042 | var stream = std.io.fixedBufferStream(buffer); |
| 2882 | try writeBindInfo(self.extern_nonlazy_symbols.items(), stream.writer()); | |
| 3043 | try bind.writeBindInfo(pointers.items, stream.writer()); | |
| 2883 | 3044 | |
| 2884 | const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2885 | 3045 | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; |
| 2886 | 3046 | const allocated_size = self.allocatedSizeLinkedit(dyld_info.bind_off); |
| 2887 | 3047 | const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); |
| ... | ... | @@ -2903,14 +3063,36 @@ fn writeBindingInfoTable(self: *MachO) !void { |
| 2903 | 3063 | fn writeLazyBindingInfoTable(self: *MachO) !void { |
| 2904 | 3064 | if (!self.lazy_binding_info_dirty) return; |
| 2905 | 3065 | |
| 2906 | const size = try lazyBindInfoSize(self.extern_lazy_symbols.items()); | |
| 3066 | const tracy = trace(@src()); | |
| 3067 | defer tracy.end(); | |
| 3068 | ||
| 3069 | var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); | |
| 3070 | defer pointers.deinit(); | |
| 3071 | ||
| 3072 | if (self.la_symbol_ptr_section_index) |idx| { | |
| 3073 | try pointers.ensureCapacity(self.lazy_imports.items().len); | |
| 3074 | const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 3075 | const sect = seg.sections.items[idx]; | |
| 3076 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 3077 | const segment_id = @intCast(u16, self.data_segment_cmd_index.?); | |
| 3078 | ||
| 3079 | for (self.lazy_imports.items()) |entry| { | |
| 3080 | pointers.appendAssumeCapacity(.{ | |
| 3081 | .offset = base_offset + entry.value.index * @sizeOf(u64), | |
| 3082 | .segment_id = segment_id, | |
| 3083 | .dylib_ordinal = entry.value.dylib_ordinal, | |
| 3084 | .name = entry.key, | |
| 3085 | }); | |
| 3086 | } | |
| 3087 | } | |
| 3088 | ||
| 3089 | const size = try bind.lazyBindInfoSize(pointers.items); | |
| 2907 | 3090 | var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); |
| 2908 | 3091 | defer self.base.allocator.free(buffer); |
| 2909 | 3092 | |
| 2910 | 3093 | var stream = std.io.fixedBufferStream(buffer); |
| 2911 | try writeLazyBindInfo(self.extern_lazy_symbols.items(), stream.writer()); | |
| 3094 | try bind.writeLazyBindInfo(pointers.items, stream.writer()); | |
| 2912 | 3095 | |
| 2913 | const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2914 | 3096 | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; |
| 2915 | 3097 | const allocated_size = self.allocatedSizeLinkedit(dyld_info.lazy_bind_off); |
| 2916 | 3098 | const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); |
| ... | ... | @@ -2931,7 +3113,7 @@ fn writeLazyBindingInfoTable(self: *MachO) !void { |
| 2931 | 3113 | } |
| 2932 | 3114 | |
| 2933 | 3115 | fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { |
| 2934 | if (self.extern_lazy_symbols.items().len == 0) return; | |
| 3116 | if (self.lazy_imports.items().len == 0) return; | |
| 2935 | 3117 | |
| 2936 | 3118 | var stream = std.io.fixedBufferStream(buffer); |
| 2937 | 3119 | var reader = stream.reader(); |
| ... | ... | @@ -2977,7 +3159,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { |
| 2977 | 3159 | else => {}, |
| 2978 | 3160 | } |
| 2979 | 3161 | } |
| 2980 | assert(self.extern_lazy_symbols.items().len <= offsets.items.len); | |
| 3162 | assert(self.lazy_imports.items().len <= offsets.items.len); | |
| 2981 | 3163 | |
| 2982 | 3164 | const stub_size: u4 = switch (self.base.options.target.cpu.arch) { |
| 2983 | 3165 | .x86_64 => 10, |
| ... | ... | @@ -2990,7 +3172,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { |
| 2990 | 3172 | else => unreachable, |
| 2991 | 3173 | }; |
| 2992 | 3174 | var buf: [@sizeOf(u32)]u8 = undefined; |
| 2993 | for (self.extern_lazy_symbols.items()) |_, i| { | |
| 3175 | for (self.lazy_imports.items()) |_, i| { | |
| 2994 | 3176 | const placeholder_off = self.stub_helper_stubs_start_off.? + i * stub_size + off; |
| 2995 | 3177 | mem.writeIntLittle(u32, &buf, offsets.items[i]); |
| 2996 | 3178 | try self.base.file.?.pwriteAll(&buf, placeholder_off); |
| ... | ... | @@ -3104,177 +3286,6 @@ fn writeHeader(self: *MachO) !void { |
| 3104 | 3286 | self.header_dirty = false; |
| 3105 | 3287 | } |
| 3106 | 3288 | |
| 3107 | /// Parse MachO contents from existing binary file. | |
| 3108 | fn parseFromFile(self: *MachO, file: fs.File) !void { | |
| 3109 | self.base.file = file; | |
| 3110 | var reader = file.reader(); | |
| 3111 | const header = try reader.readStruct(macho.mach_header_64); | |
| 3112 | try self.load_commands.ensureCapacity(self.base.allocator, header.ncmds); | |
| 3113 | var i: u16 = 0; | |
| 3114 | while (i < header.ncmds) : (i += 1) { | |
| 3115 | const cmd = try LoadCommand.read(self.base.allocator, reader); | |
| 3116 | switch (cmd.cmd()) { | |
| 3117 | macho.LC_SEGMENT_64 => { | |
| 3118 | const x = cmd.Segment; | |
| 3119 | if (parseAndCmpName(&x.inner.segname, "__PAGEZERO")) { | |
| 3120 | self.pagezero_segment_cmd_index = i; | |
| 3121 | } else if (parseAndCmpName(&x.inner.segname, "__LINKEDIT")) { | |
| 3122 | self.linkedit_segment_cmd_index = i; | |
| 3123 | } else if (parseAndCmpName(&x.inner.segname, "__TEXT")) { | |
| 3124 | self.text_segment_cmd_index = i; | |
| 3125 | for (x.sections.items) |sect, j| { | |
| 3126 | if (parseAndCmpName(&sect.sectname, "__text")) { | |
| 3127 | self.text_section_index = @intCast(u16, j); | |
| 3128 | } | |
| 3129 | } | |
| 3130 | } else if (parseAndCmpName(&x.inner.segname, "__DATA")) { | |
| 3131 | self.data_segment_cmd_index = i; | |
| 3132 | } else if (parseAndCmpName(&x.inner.segname, "__DATA_CONST")) { | |
| 3133 | self.data_const_segment_cmd_index = i; | |
| 3134 | } | |
| 3135 | }, | |
| 3136 | macho.LC_DYLD_INFO_ONLY => { | |
| 3137 | self.dyld_info_cmd_index = i; | |
| 3138 | }, | |
| 3139 | macho.LC_SYMTAB => { | |
| 3140 | self.symtab_cmd_index = i; | |
| 3141 | }, | |
| 3142 | macho.LC_DYSYMTAB => { | |
| 3143 | self.dysymtab_cmd_index = i; | |
| 3144 | }, | |
| 3145 | macho.LC_LOAD_DYLINKER => { | |
| 3146 | self.dylinker_cmd_index = i; | |
| 3147 | }, | |
| 3148 | macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => { | |
| 3149 | self.version_min_cmd_index = i; | |
| 3150 | }, | |
| 3151 | macho.LC_SOURCE_VERSION => { | |
| 3152 | self.source_version_cmd_index = i; | |
| 3153 | }, | |
| 3154 | macho.LC_UUID => { | |
| 3155 | self.uuid_cmd_index = i; | |
| 3156 | }, | |
| 3157 | macho.LC_MAIN => { | |
| 3158 | self.main_cmd_index = i; | |
| 3159 | }, | |
| 3160 | macho.LC_LOAD_DYLIB => { | |
| 3161 | const x = cmd.Dylib; | |
| 3162 | if (parseAndCmpName(x.data, mem.spanZ(LIB_SYSTEM_PATH))) { | |
| 3163 | self.libsystem_cmd_index = i; | |
| 3164 | } | |
| 3165 | }, | |
| 3166 | macho.LC_FUNCTION_STARTS => { | |
| 3167 | self.function_starts_cmd_index = i; | |
| 3168 | }, | |
| 3169 | macho.LC_DATA_IN_CODE => { | |
| 3170 | self.data_in_code_cmd_index = i; | |
| 3171 | }, | |
| 3172 | macho.LC_CODE_SIGNATURE => { | |
| 3173 | self.code_signature_cmd_index = i; | |
| 3174 | }, | |
| 3175 | else => { | |
| 3176 | log.warn("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); | |
| 3177 | }, | |
| 3178 | } | |
| 3179 | self.load_commands.appendAssumeCapacity(cmd); | |
| 3180 | } | |
| 3181 | self.header = header; | |
| 3182 | } | |
| 3183 | ||
| 3184 | fn parseAndCmpName(name: []const u8, needle: []const u8) bool { | |
| 3185 | const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; | |
| 3186 | return mem.eql(u8, name[0..len], needle); | |
| 3187 | } | |
| 3188 | ||
| 3189 | fn parseSymbolTable(self: *MachO) !void { | |
| 3190 | const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 3191 | const dysymtab = self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; | |
| 3192 | ||
| 3193 | var buffer = try self.base.allocator.alloc(macho.nlist_64, symtab.nsyms); | |
| 3194 | defer self.base.allocator.free(buffer); | |
| 3195 | const nread = try self.base.file.?.preadAll(@ptrCast([*]u8, buffer)[0 .. symtab.nsyms * @sizeOf(macho.nlist_64)], symtab.symoff); | |
| 3196 | assert(@divExact(nread, @sizeOf(macho.nlist_64)) == buffer.len); | |
| 3197 | ||
| 3198 | try self.local_symbols.ensureCapacity(self.base.allocator, dysymtab.nlocalsym); | |
| 3199 | try self.global_symbols.ensureCapacity(self.base.allocator, dysymtab.nextdefsym); | |
| 3200 | try self.undef_symbols.ensureCapacity(self.base.allocator, dysymtab.nundefsym); | |
| 3201 | ||
| 3202 | self.local_symbols.appendSliceAssumeCapacity(buffer[dysymtab.ilocalsym .. dysymtab.ilocalsym + dysymtab.nlocalsym]); | |
| 3203 | self.global_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iextdefsym .. dysymtab.iextdefsym + dysymtab.nextdefsym]); | |
| 3204 | self.undef_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iundefsym .. dysymtab.iundefsym + dysymtab.nundefsym]); | |
| 3205 | } | |
| 3206 | ||
| 3207 | fn parseStringTable(self: *MachO) !void { | |
| 3208 | const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 3209 | ||
| 3210 | var buffer = try self.base.allocator.alloc(u8, symtab.strsize); | |
| 3211 | defer self.base.allocator.free(buffer); | |
| 3212 | const nread = try self.base.file.?.preadAll(buffer, symtab.stroff); | |
| 3213 | assert(nread == buffer.len); | |
| 3214 | ||
| 3215 | try self.string_table.ensureCapacity(self.base.allocator, symtab.strsize); | |
| 3216 | self.string_table.appendSliceAssumeCapacity(buffer); | |
| 3217 | } | |
| 3218 | ||
| 3219 | fn fixupBindInfo(self: *MachO, dylib_ordinal: u32) !void { | |
| 3220 | const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; | |
| 3221 | var buffer = try self.base.allocator.alloc(u8, dyld_info.bind_size); | |
| 3222 | defer self.base.allocator.free(buffer); | |
| 3223 | const nread = try self.base.file.?.preadAll(buffer, dyld_info.bind_off); | |
| 3224 | assert(nread == buffer.len); | |
| 3225 | try self.fixupInfoCommon(buffer, dylib_ordinal); | |
| 3226 | try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); | |
| 3227 | } | |
| 3228 | ||
| 3229 | fn fixupLazyBindInfo(self: *MachO, dylib_ordinal: u32) !void { | |
| 3230 | const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; | |
| 3231 | var buffer = try self.base.allocator.alloc(u8, dyld_info.lazy_bind_size); | |
| 3232 | defer self.base.allocator.free(buffer); | |
| 3233 | const nread = try self.base.file.?.preadAll(buffer, dyld_info.lazy_bind_off); | |
| 3234 | assert(nread == buffer.len); | |
| 3235 | try self.fixupInfoCommon(buffer, dylib_ordinal); | |
| 3236 | try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); | |
| 3237 | } | |
| 3238 | ||
| 3239 | fn fixupInfoCommon(self: *MachO, buffer: []u8, dylib_ordinal: u32) !void { | |
| 3240 | var stream = std.io.fixedBufferStream(buffer); | |
| 3241 | var reader = stream.reader(); | |
| 3242 | ||
| 3243 | while (true) { | |
| 3244 | const inst = reader.readByte() catch |err| switch (err) { | |
| 3245 | error.EndOfStream => break, | |
| 3246 | else => return err, | |
| 3247 | }; | |
| 3248 | const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; | |
| 3249 | const opcode: u8 = inst & macho.BIND_OPCODE_MASK; | |
| 3250 | ||
| 3251 | switch (opcode) { | |
| 3252 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { | |
| 3253 | var next = try reader.readByte(); | |
| 3254 | while (next != @as(u8, 0)) { | |
| 3255 | next = try reader.readByte(); | |
| 3256 | } | |
| 3257 | }, | |
| 3258 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { | |
| 3259 | _ = try std.leb.readULEB128(u64, reader); | |
| 3260 | }, | |
| 3261 | macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => { | |
| 3262 | // Perform the fixup. | |
| 3263 | try stream.seekBy(-1); | |
| 3264 | var writer = stream.writer(); | |
| 3265 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, dylib_ordinal)); | |
| 3266 | }, | |
| 3267 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { | |
| 3268 | _ = try std.leb.readULEB128(u64, reader); | |
| 3269 | }, | |
| 3270 | macho.BIND_OPCODE_SET_ADDEND_SLEB => { | |
| 3271 | _ = try std.leb.readILEB128(i64, reader); | |
| 3272 | }, | |
| 3273 | else => {}, | |
| 3274 | } | |
| 3275 | } | |
| 3276 | } | |
| 3277 | ||
| 3278 | 3289 | pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { |
| 3279 | 3290 | // TODO https://github.com/ziglang/zig/issues/1284 |
| 3280 | 3291 | return std.math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch |
src/link/MachO/Archive.zig created+278| ... | ... | @@ -0,0 +1,278 @@ |
| 1 | const Archive = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const assert = std.debug.assert; | |
| 5 | const fs = std.fs; | |
| 6 | const log = std.log.scoped(.archive); | |
| 7 | const macho = std.macho; | |
| 8 | const mem = std.mem; | |
| 9 | ||
| 10 | const Allocator = mem.Allocator; | |
| 11 | const Object = @import("Object.zig"); | |
| 12 | const parseName = @import("Zld.zig").parseName; | |
| 13 | ||
| 14 | usingnamespace @import("commands.zig"); | |
| 15 | ||
| 16 | allocator: *Allocator, | |
| 17 | file: fs.File, | |
| 18 | header: ar_hdr, | |
| 19 | name: []u8, | |
| 20 | ||
| 21 | objects: std.ArrayListUnmanaged(Object) = .{}, | |
| 22 | ||
| 23 | /// Parsed table of contents. | |
| 24 | /// Each symbol name points to a list of all definition | |
| 25 | /// sites within the current static archive. | |
| 26 | toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{}, | |
| 27 | ||
| 28 | // Archive files start with the ARMAG identifying string. Then follows a | |
| 29 | // `struct ar_hdr', and as many bytes of member file data as its `ar_size' | |
| 30 | // member indicates, for each member file. | |
| 31 | /// String that begins an archive file. | |
| 32 | const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; | |
| 33 | /// Size of that string. | |
| 34 | const SARMAG: u4 = 8; | |
| 35 | ||
| 36 | /// String in ar_fmag at the end of each header. | |
| 37 | const ARFMAG: *const [2:0]u8 = "`\n"; | |
| 38 | ||
| 39 | const ar_hdr = extern struct { | |
| 40 | /// Member file name, sometimes / terminated. | |
| 41 | ar_name: [16]u8, | |
| 42 | ||
| 43 | /// File date, decimal seconds since Epoch. | |
| 44 | ar_date: [12]u8, | |
| 45 | ||
| 46 | /// User ID, in ASCII format. | |
| 47 | ar_uid: [6]u8, | |
| 48 | ||
| 49 | /// Group ID, in ASCII format. | |
| 50 | ar_gid: [6]u8, | |
| 51 | ||
| 52 | /// File mode, in ASCII octal. | |
| 53 | ar_mode: [8]u8, | |
| 54 | ||
| 55 | /// File size, in ASCII decimal. | |
| 56 | ar_size: [10]u8, | |
| 57 | ||
| 58 | /// Always contains ARFMAG. | |
| 59 | ar_fmag: [2]u8, | |
| 60 | ||
| 61 | const NameOrLength = union(enum) { | |
| 62 | Name: []const u8, | |
| 63 | Length: u64, | |
| 64 | }; | |
| 65 | pub fn nameOrLength(self: ar_hdr) !NameOrLength { | |
| 66 | const value = getValue(&self.ar_name); | |
| 67 | const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive; | |
| 68 | const len = value.len; | |
| 69 | if (slash_index == len - 1) { | |
| 70 | // Name stored directly | |
| 71 | return NameOrLength{ .Name = value }; | |
| 72 | } else { | |
| 73 | // Name follows the header directly and its length is encoded in | |
| 74 | // the name field. | |
| 75 | const length = try std.fmt.parseInt(u64, value[slash_index + 1 ..], 10); | |
| 76 | return NameOrLength{ .Length = length }; | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | pub fn size(self: ar_hdr) !u64 { | |
| 81 | const value = getValue(&self.ar_size); | |
| 82 | return std.fmt.parseInt(u64, value, 10); | |
| 83 | } | |
| 84 | ||
| 85 | fn getValue(raw: []const u8) []const u8 { | |
| 86 | return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); | |
| 87 | } | |
| 88 | }; | |
| 89 | ||
| 90 | pub fn deinit(self: *Archive) void { | |
| 91 | self.allocator.free(self.name); | |
| 92 | for (self.objects.items) |*object| { | |
| 93 | object.deinit(); | |
| 94 | } | |
| 95 | self.objects.deinit(self.allocator); | |
| 96 | for (self.toc.items()) |*entry| { | |
| 97 | self.allocator.free(entry.key); | |
| 98 | entry.value.deinit(self.allocator); | |
| 99 | } | |
| 100 | self.toc.deinit(self.allocator); | |
| 101 | self.file.close(); | |
| 102 | } | |
| 103 | ||
| 104 | /// Caller owns the returned Archive instance and is responsible for calling | |
| 105 | /// `deinit` to free allocated memory. | |
| 106 | pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, ar_name: []const u8, file: fs.File) !Archive { | |
| 107 | var reader = file.reader(); | |
| 108 | var magic = try readMagic(allocator, reader); | |
| 109 | defer allocator.free(magic); | |
| 110 | ||
| 111 | if (!mem.eql(u8, magic, ARMAG)) { | |
| 112 | // Reset file cursor. | |
| 113 | try file.seekTo(0); | |
| 114 | return error.NotArchive; | |
| 115 | } | |
| 116 | ||
| 117 | const header = try reader.readStruct(ar_hdr); | |
| 118 | ||
| 119 | if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) | |
| 120 | return error.MalformedArchive; | |
| 121 | ||
| 122 | var embedded_name = try getName(allocator, header, reader); | |
| 123 | log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, ar_name }); | |
| 124 | defer allocator.free(embedded_name); | |
| 125 | ||
| 126 | var name = try allocator.dupe(u8, ar_name); | |
| 127 | var self = Archive{ | |
| 128 | .allocator = allocator, | |
| 129 | .file = file, | |
| 130 | .header = header, | |
| 131 | .name = name, | |
| 132 | }; | |
| 133 | ||
| 134 | var object_offsets = try self.readTableOfContents(reader); | |
| 135 | defer self.allocator.free(object_offsets); | |
| 136 | ||
| 137 | var i: usize = 1; | |
| 138 | while (i < object_offsets.len) : (i += 1) { | |
| 139 | const offset = object_offsets[i]; | |
| 140 | try reader.context.seekTo(offset); | |
| 141 | try self.readObject(arch, ar_name, reader); | |
| 142 | } | |
| 143 | ||
| 144 | return self; | |
| 145 | } | |
| 146 | ||
| 147 | fn readTableOfContents(self: *Archive, reader: anytype) ![]u32 { | |
| 148 | const symtab_size = try reader.readIntLittle(u32); | |
| 149 | var symtab = try self.allocator.alloc(u8, symtab_size); | |
| 150 | defer self.allocator.free(symtab); | |
| 151 | try reader.readNoEof(symtab); | |
| 152 | ||
| 153 | const strtab_size = try reader.readIntLittle(u32); | |
| 154 | var strtab = try self.allocator.alloc(u8, strtab_size); | |
| 155 | defer self.allocator.free(strtab); | |
| 156 | try reader.readNoEof(strtab); | |
| 157 | ||
| 158 | var symtab_stream = std.io.fixedBufferStream(symtab); | |
| 159 | var symtab_reader = symtab_stream.reader(); | |
| 160 | ||
| 161 | var object_offsets = std.ArrayList(u32).init(self.allocator); | |
| 162 | try object_offsets.append(0); | |
| 163 | var last: usize = 0; | |
| 164 | ||
| 165 | while (true) { | |
| 166 | const n_strx = symtab_reader.readIntLittle(u32) catch |err| switch (err) { | |
| 167 | error.EndOfStream => break, | |
| 168 | else => |e| return e, | |
| 169 | }; | |
| 170 | const object_offset = try symtab_reader.readIntLittle(u32); | |
| 171 | ||
| 172 | const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + n_strx)); | |
| 173 | const owned_name = try self.allocator.dupe(u8, sym_name); | |
| 174 | const res = try self.toc.getOrPut(self.allocator, owned_name); | |
| 175 | defer if (res.found_existing) self.allocator.free(owned_name); | |
| 176 | ||
| 177 | if (!res.found_existing) { | |
| 178 | res.entry.value = .{}; | |
| 179 | } | |
| 180 | ||
| 181 | try res.entry.value.append(self.allocator, object_offset); | |
| 182 | ||
| 183 | // TODO This will go once we properly use archive's TOC to pick | |
| 184 | // an object which defines a missing symbol rather than pasting in | |
| 185 | // all of the objects always. | |
| 186 | // Here, we assume that symbols are NOT sorted in any way, and | |
| 187 | // they point to objects in sequence. | |
| 188 | if (object_offsets.items[last] != object_offset) { | |
| 189 | try object_offsets.append(object_offset); | |
| 190 | last += 1; | |
| 191 | } | |
| 192 | } | |
| 193 | ||
| 194 | return object_offsets.toOwnedSlice(); | |
| 195 | } | |
| 196 | ||
| 197 | fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, reader: anytype) !void { | |
| 198 | const object_header = try reader.readStruct(ar_hdr); | |
| 199 | ||
| 200 | if (!mem.eql(u8, &object_header.ar_fmag, ARFMAG)) | |
| 201 | return error.MalformedArchive; | |
| 202 | ||
| 203 | var object_name = try getName(self.allocator, object_header, reader); | |
| 204 | log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name }); | |
| 205 | ||
| 206 | const offset = @intCast(u32, try reader.context.getPos()); | |
| 207 | const header = try reader.readStruct(macho.mach_header_64); | |
| 208 | ||
| 209 | const this_arch: std.Target.Cpu.Arch = switch (header.cputype) { | |
| 210 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 211 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 212 | else => |value| { | |
| 213 | log.err("unsupported cpu architecture 0x{x}", .{value}); | |
| 214 | return error.UnsupportedCpuArchitecture; | |
| 215 | }, | |
| 216 | }; | |
| 217 | if (this_arch != arch) { | |
| 218 | log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch }); | |
| 219 | return error.MismatchedCpuArchitecture; | |
| 220 | } | |
| 221 | ||
| 222 | // TODO Implement std.fs.File.clone() or similar. | |
| 223 | var new_file = try fs.cwd().openFile(ar_name, .{}); | |
| 224 | var object = Object{ | |
| 225 | .allocator = self.allocator, | |
| 226 | .name = object_name, | |
| 227 | .ar_name = try mem.dupe(self.allocator, u8, ar_name), | |
| 228 | .file = new_file, | |
| 229 | .header = header, | |
| 230 | }; | |
| 231 | ||
| 232 | try object.readLoadCommands(reader, .{ .offset = offset }); | |
| 233 | ||
| 234 | if (object.symtab_cmd_index != null) { | |
| 235 | try object.readSymtab(); | |
| 236 | try object.readStrtab(); | |
| 237 | } | |
| 238 | ||
| 239 | if (object.data_in_code_cmd_index != null) try object.readDataInCode(); | |
| 240 | ||
| 241 | log.debug("\n\n", .{}); | |
| 242 | log.debug("{s} defines symbols", .{object.name}); | |
| 243 | for (object.symtab.items) |sym| { | |
| 244 | const symname = object.getString(sym.n_strx); | |
| 245 | log.debug("'{s}': {}", .{ symname, sym }); | |
| 246 | } | |
| 247 | ||
| 248 | try self.objects.append(self.allocator, object); | |
| 249 | } | |
| 250 | ||
| 251 | fn readMagic(allocator: *Allocator, reader: anytype) ![]u8 { | |
| 252 | var magic = std.ArrayList(u8).init(allocator); | |
| 253 | try magic.ensureCapacity(SARMAG); | |
| 254 | var i: usize = 0; | |
| 255 | while (i < SARMAG) : (i += 1) { | |
| 256 | const next = try reader.readByte(); | |
| 257 | magic.appendAssumeCapacity(next); | |
| 258 | } | |
| 259 | return magic.toOwnedSlice(); | |
| 260 | } | |
| 261 | ||
| 262 | fn getName(allocator: *Allocator, header: ar_hdr, reader: anytype) ![]u8 { | |
| 263 | const name_or_length = try header.nameOrLength(); | |
| 264 | var name: []u8 = undefined; | |
| 265 | switch (name_or_length) { | |
| 266 | .Name => |n| { | |
| 267 | name = try allocator.dupe(u8, n); | |
| 268 | }, | |
| 269 | .Length => |len| { | |
| 270 | var n = try allocator.alloc(u8, len); | |
| 271 | defer allocator.free(n); | |
| 272 | try reader.readNoEof(n); | |
| 273 | const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len; | |
| 274 | name = try allocator.dupe(u8, n[0..actual_len]); | |
| 275 | }, | |
| 276 | } | |
| 277 | return name; | |
| 278 | } |
src/link/MachO/DebugSymbols.zig+4-4| ... | ... | @@ -839,8 +839,8 @@ fn findFreeSpaceLinkedit(self: *DebugSymbols, object_size: u64, min_alignment: u |
| 839 | 839 | |
| 840 | 840 | fn relocateSymbolTable(self: *DebugSymbols) !void { |
| 841 | 841 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 842 | const nlocals = self.base.local_symbols.items.len; | |
| 843 | const nglobals = self.base.global_symbols.items.len; | |
| 842 | const nlocals = self.base.locals.items.len; | |
| 843 | const nglobals = self.base.globals.items.len; | |
| 844 | 844 | const nsyms = nlocals + nglobals; |
| 845 | 845 | |
| 846 | 846 | if (symtab.nsyms < nsyms) { |
| ... | ... | @@ -875,7 +875,7 @@ pub fn writeLocalSymbol(self: *DebugSymbols, index: usize) !void { |
| 875 | 875 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 876 | 876 | const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; |
| 877 | 877 | log.debug("writing dSym local symbol {} at 0x{x}", .{ index, off }); |
| 878 | try self.file.pwriteAll(mem.asBytes(&self.base.local_symbols.items[index]), off); | |
| 878 | try self.file.pwriteAll(mem.asBytes(&self.base.locals.items[index]), off); | |
| 879 | 879 | } |
| 880 | 880 | |
| 881 | 881 | fn writeStringTable(self: *DebugSymbols) !void { |
| ... | ... | @@ -1057,7 +1057,7 @@ pub fn commitDeclDebugInfo( |
| 1057 | 1057 | var dbg_info_buffer = &debug_buffers.dbg_info_buffer; |
| 1058 | 1058 | var dbg_info_type_relocs = &debug_buffers.dbg_info_type_relocs; |
| 1059 | 1059 | |
| 1060 | const symbol = self.base.local_symbols.items[decl.link.macho.local_sym_index]; | |
| 1060 | const symbol = self.base.locals.items[decl.link.macho.local_sym_index]; | |
| 1061 | 1061 | const text_block = &decl.link.macho; |
| 1062 | 1062 | // If the Decl is a function, we need to update the __debug_line program. |
| 1063 | 1063 | const typed_value = decl.typed_value.most_recent.typed_value; |
src/link/MachO/Object.zig created+229| ... | ... | @@ -0,0 +1,229 @@ |
| 1 | const Object = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const assert = std.debug.assert; | |
| 5 | const fs = std.fs; | |
| 6 | const io = std.io; | |
| 7 | const log = std.log.scoped(.object); | |
| 8 | const macho = std.macho; | |
| 9 | const mem = std.mem; | |
| 10 | ||
| 11 | const Allocator = mem.Allocator; | |
| 12 | const parseName = @import("Zld.zig").parseName; | |
| 13 | ||
| 14 | usingnamespace @import("commands.zig"); | |
| 15 | ||
| 16 | allocator: *Allocator, | |
| 17 | file: fs.File, | |
| 18 | name: []u8, | |
| 19 | ar_name: ?[]u8 = null, | |
| 20 | ||
| 21 | header: macho.mach_header_64, | |
| 22 | ||
| 23 | load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, | |
| 24 | ||
| 25 | segment_cmd_index: ?u16 = null, | |
| 26 | symtab_cmd_index: ?u16 = null, | |
| 27 | dysymtab_cmd_index: ?u16 = null, | |
| 28 | build_version_cmd_index: ?u16 = null, | |
| 29 | data_in_code_cmd_index: ?u16 = null, | |
| 30 | text_section_index: ?u16 = null, | |
| 31 | ||
| 32 | // __DWARF segment sections | |
| 33 | dwarf_debug_info_index: ?u16 = null, | |
| 34 | dwarf_debug_abbrev_index: ?u16 = null, | |
| 35 | dwarf_debug_str_index: ?u16 = null, | |
| 36 | dwarf_debug_line_index: ?u16 = null, | |
| 37 | dwarf_debug_ranges_index: ?u16 = null, | |
| 38 | ||
| 39 | symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | |
| 40 | strtab: std.ArrayListUnmanaged(u8) = .{}, | |
| 41 | ||
| 42 | data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, | |
| 43 | ||
| 44 | pub fn deinit(self: *Object) void { | |
| 45 | for (self.load_commands.items) |*lc| { | |
| 46 | lc.deinit(self.allocator); | |
| 47 | } | |
| 48 | self.load_commands.deinit(self.allocator); | |
| 49 | self.symtab.deinit(self.allocator); | |
| 50 | self.strtab.deinit(self.allocator); | |
| 51 | self.data_in_code_entries.deinit(self.allocator); | |
| 52 | self.allocator.free(self.name); | |
| 53 | if (self.ar_name) |v| { | |
| 54 | self.allocator.free(v); | |
| 55 | } | |
| 56 | self.file.close(); | |
| 57 | } | |
| 58 | ||
| 59 | /// Caller owns the returned Object instance and is responsible for calling | |
| 60 | /// `deinit` to free allocated memory. | |
| 61 | pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, name: []const u8, file: fs.File) !Object { | |
| 62 | var reader = file.reader(); | |
| 63 | const header = try reader.readStruct(macho.mach_header_64); | |
| 64 | ||
| 65 | if (header.filetype != macho.MH_OBJECT) { | |
| 66 | // Reset file cursor. | |
| 67 | try file.seekTo(0); | |
| 68 | return error.NotObject; | |
| 69 | } | |
| 70 | ||
| 71 | const this_arch: std.Target.Cpu.Arch = switch (header.cputype) { | |
| 72 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 73 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 74 | else => |value| { | |
| 75 | log.err("unsupported cpu architecture 0x{x}", .{value}); | |
| 76 | return error.UnsupportedCpuArchitecture; | |
| 77 | }, | |
| 78 | }; | |
| 79 | if (this_arch != arch) { | |
| 80 | log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch }); | |
| 81 | return error.MismatchedCpuArchitecture; | |
| 82 | } | |
| 83 | ||
| 84 | var self = Object{ | |
| 85 | .allocator = allocator, | |
| 86 | .name = try allocator.dupe(u8, name), | |
| 87 | .file = file, | |
| 88 | .header = header, | |
| 89 | }; | |
| 90 | ||
| 91 | try self.readLoadCommands(reader, .{}); | |
| 92 | ||
| 93 | if (self.symtab_cmd_index != null) { | |
| 94 | try self.readSymtab(); | |
| 95 | try self.readStrtab(); | |
| 96 | } | |
| 97 | ||
| 98 | if (self.data_in_code_cmd_index != null) try self.readDataInCode(); | |
| 99 | ||
| 100 | log.debug("\n\n", .{}); | |
| 101 | log.debug("{s} defines symbols", .{self.name}); | |
| 102 | for (self.symtab.items) |sym| { | |
| 103 | const symname = self.getString(sym.n_strx); | |
| 104 | log.debug("'{s}': {}", .{ symname, sym }); | |
| 105 | } | |
| 106 | ||
| 107 | return self; | |
| 108 | } | |
| 109 | ||
| 110 | pub const ReadOffset = struct { | |
| 111 | offset: ?u32 = null, | |
| 112 | }; | |
| 113 | ||
| 114 | pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !void { | |
| 115 | const offset_mod = offset.offset orelse 0; | |
| 116 | try self.load_commands.ensureCapacity(self.allocator, self.header.ncmds); | |
| 117 | ||
| 118 | var i: u16 = 0; | |
| 119 | while (i < self.header.ncmds) : (i += 1) { | |
| 120 | var cmd = try LoadCommand.read(self.allocator, reader); | |
| 121 | switch (cmd.cmd()) { | |
| 122 | macho.LC_SEGMENT_64 => { | |
| 123 | self.segment_cmd_index = i; | |
| 124 | var seg = cmd.Segment; | |
| 125 | for (seg.sections.items) |*sect, j| { | |
| 126 | const index = @intCast(u16, j); | |
| 127 | const segname = parseName(&sect.segname); | |
| 128 | const sectname = parseName(&sect.sectname); | |
| 129 | if (mem.eql(u8, segname, "__DWARF")) { | |
| 130 | if (mem.eql(u8, sectname, "__debug_info")) { | |
| 131 | self.dwarf_debug_info_index = index; | |
| 132 | } else if (mem.eql(u8, sectname, "__debug_abbrev")) { | |
| 133 | self.dwarf_debug_abbrev_index = index; | |
| 134 | } else if (mem.eql(u8, sectname, "__debug_str")) { | |
| 135 | self.dwarf_debug_str_index = index; | |
| 136 | } else if (mem.eql(u8, sectname, "__debug_line")) { | |
| 137 | self.dwarf_debug_line_index = index; | |
| 138 | } else if (mem.eql(u8, sectname, "__debug_ranges")) { | |
| 139 | self.dwarf_debug_ranges_index = index; | |
| 140 | } | |
| 141 | } else if (mem.eql(u8, segname, "__TEXT")) { | |
| 142 | if (mem.eql(u8, sectname, "__text")) { | |
| 143 | self.text_section_index = index; | |
| 144 | } | |
| 145 | } | |
| 146 | ||
| 147 | sect.offset += offset_mod; | |
| 148 | if (sect.reloff > 0) | |
| 149 | sect.reloff += offset_mod; | |
| 150 | } | |
| 151 | ||
| 152 | seg.inner.fileoff += offset_mod; | |
| 153 | }, | |
| 154 | macho.LC_SYMTAB => { | |
| 155 | self.symtab_cmd_index = i; | |
| 156 | cmd.Symtab.symoff += offset_mod; | |
| 157 | cmd.Symtab.stroff += offset_mod; | |
| 158 | }, | |
| 159 | macho.LC_DYSYMTAB => { | |
| 160 | self.dysymtab_cmd_index = i; | |
| 161 | }, | |
| 162 | macho.LC_BUILD_VERSION => { | |
| 163 | self.build_version_cmd_index = i; | |
| 164 | }, | |
| 165 | macho.LC_DATA_IN_CODE => { | |
| 166 | self.data_in_code_cmd_index = i; | |
| 167 | cmd.LinkeditData.dataoff += offset_mod; | |
| 168 | }, | |
| 169 | else => { | |
| 170 | log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); | |
| 171 | }, | |
| 172 | } | |
| 173 | self.load_commands.appendAssumeCapacity(cmd); | |
| 174 | } | |
| 175 | } | |
| 176 | ||
| 177 | pub fn readSymtab(self: *Object) !void { | |
| 178 | const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 179 | var buffer = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms); | |
| 180 | defer self.allocator.free(buffer); | |
| 181 | _ = try self.file.preadAll(buffer, symtab_cmd.symoff); | |
| 182 | try self.symtab.ensureCapacity(self.allocator, symtab_cmd.nsyms); | |
| 183 | // TODO this align case should not be needed. | |
| 184 | // Probably a bug in stage1. | |
| 185 | const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, buffer)); | |
| 186 | self.symtab.appendSliceAssumeCapacity(slice); | |
| 187 | } | |
| 188 | ||
| 189 | pub fn readStrtab(self: *Object) !void { | |
| 190 | const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 191 | var buffer = try self.allocator.alloc(u8, symtab_cmd.strsize); | |
| 192 | defer self.allocator.free(buffer); | |
| 193 | _ = try self.file.preadAll(buffer, symtab_cmd.stroff); | |
| 194 | try self.strtab.ensureCapacity(self.allocator, symtab_cmd.strsize); | |
| 195 | self.strtab.appendSliceAssumeCapacity(buffer); | |
| 196 | } | |
| 197 | ||
| 198 | pub fn getString(self: *const Object, str_off: u32) []const u8 { | |
| 199 | assert(str_off < self.strtab.items.len); | |
| 200 | return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off)); | |
| 201 | } | |
| 202 | ||
| 203 | pub fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 { | |
| 204 | const seg = self.load_commands.items[self.segment_cmd_index.?].Segment; | |
| 205 | const sect = seg.sections.items[index]; | |
| 206 | var buffer = try allocator.alloc(u8, sect.size); | |
| 207 | _ = try self.file.preadAll(buffer, sect.offset); | |
| 208 | return buffer; | |
| 209 | } | |
| 210 | ||
| 211 | pub fn readDataInCode(self: *Object) !void { | |
| 212 | const index = self.data_in_code_cmd_index orelse return; | |
| 213 | const data_in_code = self.load_commands.items[index].LinkeditData; | |
| 214 | ||
| 215 | var buffer = try self.allocator.alloc(u8, data_in_code.datasize); | |
| 216 | defer self.allocator.free(buffer); | |
| 217 | ||
| 218 | _ = try self.file.preadAll(buffer, data_in_code.dataoff); | |
| 219 | ||
| 220 | var stream = io.fixedBufferStream(buffer); | |
| 221 | var reader = stream.reader(); | |
| 222 | while (true) { | |
| 223 | const dice = reader.readStruct(macho.data_in_code_entry) catch |err| switch (err) { | |
| 224 | error.EndOfStream => break, | |
| 225 | else => |e| return e, | |
| 226 | }; | |
| 227 | try self.data_in_code_entries.append(self.allocator, dice); | |
| 228 | } | |
| 229 | } |
src/link/MachO/Zld.zig created+3294| ... | ... | @@ -0,0 +1,3294 @@ |
| 1 | const Zld = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const assert = std.debug.assert; | |
| 5 | const dwarf = std.dwarf; | |
| 6 | const leb = std.leb; | |
| 7 | const mem = std.mem; | |
| 8 | const meta = std.meta; | |
| 9 | const fs = std.fs; | |
| 10 | const macho = std.macho; | |
| 11 | const math = std.math; | |
| 12 | const log = std.log.scoped(.zld); | |
| 13 | const aarch64 = @import("../../codegen/aarch64.zig"); | |
| 14 | ||
| 15 | const Allocator = mem.Allocator; | |
| 16 | const CodeSignature = @import("CodeSignature.zig"); | |
| 17 | const Archive = @import("Archive.zig"); | |
| 18 | const Object = @import("Object.zig"); | |
| 19 | const Trie = @import("Trie.zig"); | |
| 20 | ||
| 21 | usingnamespace @import("commands.zig"); | |
| 22 | usingnamespace @import("bind.zig"); | |
| 23 | ||
| 24 | allocator: *Allocator, | |
| 25 | ||
| 26 | arch: ?std.Target.Cpu.Arch = null, | |
| 27 | page_size: ?u16 = null, | |
| 28 | file: ?fs.File = null, | |
| 29 | out_path: ?[]const u8 = null, | |
| 30 | ||
| 31 | // TODO Eventually, we will want to keep track of the archives themselves to be able to exclude objects | |
| 32 | // contained within from landing in the final artifact. For now however, since we don't optimise the binary | |
| 33 | // at all, we just move all objects from the archives into the final artifact. | |
| 34 | objects: std.ArrayListUnmanaged(Object) = .{}, | |
| 35 | ||
| 36 | load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, | |
| 37 | ||
| 38 | pagezero_segment_cmd_index: ?u16 = null, | |
| 39 | text_segment_cmd_index: ?u16 = null, | |
| 40 | data_const_segment_cmd_index: ?u16 = null, | |
| 41 | data_segment_cmd_index: ?u16 = null, | |
| 42 | linkedit_segment_cmd_index: ?u16 = null, | |
| 43 | dyld_info_cmd_index: ?u16 = null, | |
| 44 | symtab_cmd_index: ?u16 = null, | |
| 45 | dysymtab_cmd_index: ?u16 = null, | |
| 46 | dylinker_cmd_index: ?u16 = null, | |
| 47 | libsystem_cmd_index: ?u16 = null, | |
| 48 | data_in_code_cmd_index: ?u16 = null, | |
| 49 | function_starts_cmd_index: ?u16 = null, | |
| 50 | main_cmd_index: ?u16 = null, | |
| 51 | version_min_cmd_index: ?u16 = null, | |
| 52 | source_version_cmd_index: ?u16 = null, | |
| 53 | uuid_cmd_index: ?u16 = null, | |
| 54 | code_signature_cmd_index: ?u16 = null, | |
| 55 | ||
| 56 | // __TEXT segment sections | |
| 57 | text_section_index: ?u16 = null, | |
| 58 | stubs_section_index: ?u16 = null, | |
| 59 | stub_helper_section_index: ?u16 = null, | |
| 60 | text_const_section_index: ?u16 = null, | |
| 61 | cstring_section_index: ?u16 = null, | |
| 62 | ||
| 63 | // __DATA_CONST segment sections | |
| 64 | got_section_index: ?u16 = null, | |
| 65 | mod_init_func_section_index: ?u16 = null, | |
| 66 | mod_term_func_section_index: ?u16 = null, | |
| 67 | data_const_section_index: ?u16 = null, | |
| 68 | ||
| 69 | // __DATA segment sections | |
| 70 | tlv_section_index: ?u16 = null, | |
| 71 | tlv_data_section_index: ?u16 = null, | |
| 72 | tlv_bss_section_index: ?u16 = null, | |
| 73 | la_symbol_ptr_section_index: ?u16 = null, | |
| 74 | data_section_index: ?u16 = null, | |
| 75 | bss_section_index: ?u16 = null, | |
| 76 | ||
| 77 | locals: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(Symbol)) = .{}, | |
| 78 | exports: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, | |
| 79 | nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, | |
| 80 | lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, | |
| 81 | tlv_bootstrap: ?Import = null, | |
| 82 | threadlocal_offsets: std.ArrayListUnmanaged(u64) = .{}, | |
| 83 | local_rebases: std.ArrayListUnmanaged(Pointer) = .{}, | |
| 84 | nonlazy_pointers: std.StringArrayHashMapUnmanaged(GotEntry) = .{}, | |
| 85 | ||
| 86 | strtab: std.ArrayListUnmanaged(u8) = .{}, | |
| 87 | ||
| 88 | stub_helper_stubs_start_off: ?u64 = null, | |
| 89 | ||
| 90 | mappings: std.AutoHashMapUnmanaged(MappingKey, SectionMapping) = .{}, | |
| 91 | unhandled_sections: std.AutoHashMapUnmanaged(MappingKey, u0) = .{}, | |
| 92 | ||
| 93 | // TODO this will require scanning the relocations at least one to work out | |
| 94 | // the exact amount of local GOT indirections. For the time being, set some | |
| 95 | // default value. | |
| 96 | const max_local_got_indirections: u16 = 1000; | |
| 97 | ||
| 98 | const GotEntry = struct { | |
| 99 | index: u32, | |
| 100 | target_addr: u64, | |
| 101 | }; | |
| 102 | ||
| 103 | const MappingKey = struct { | |
| 104 | object_id: u16, | |
| 105 | source_sect_id: u16, | |
| 106 | }; | |
| 107 | ||
| 108 | const SectionMapping = struct { | |
| 109 | source_sect_id: u16, | |
| 110 | target_seg_id: u16, | |
| 111 | target_sect_id: u16, | |
| 112 | offset: u32, | |
| 113 | }; | |
| 114 | ||
| 115 | const Symbol = struct { | |
| 116 | inner: macho.nlist_64, | |
| 117 | tt: Type, | |
| 118 | object_id: u16, | |
| 119 | ||
| 120 | const Type = enum { | |
| 121 | Local, | |
| 122 | WeakGlobal, | |
| 123 | Global, | |
| 124 | }; | |
| 125 | }; | |
| 126 | ||
| 127 | const DebugInfo = struct { | |
| 128 | inner: dwarf.DwarfInfo, | |
| 129 | debug_info: []u8, | |
| 130 | debug_abbrev: []u8, | |
| 131 | debug_str: []u8, | |
| 132 | debug_line: []u8, | |
| 133 | debug_ranges: []u8, | |
| 134 | ||
| 135 | pub fn parseFromObject(allocator: *Allocator, object: Object) !?DebugInfo { | |
| 136 | var debug_info = blk: { | |
| 137 | const index = object.dwarf_debug_info_index orelse return null; | |
| 138 | break :blk try object.readSection(allocator, index); | |
| 139 | }; | |
| 140 | var debug_abbrev = blk: { | |
| 141 | const index = object.dwarf_debug_abbrev_index orelse return null; | |
| 142 | break :blk try object.readSection(allocator, index); | |
| 143 | }; | |
| 144 | var debug_str = blk: { | |
| 145 | const index = object.dwarf_debug_str_index orelse return null; | |
| 146 | break :blk try object.readSection(allocator, index); | |
| 147 | }; | |
| 148 | var debug_line = blk: { | |
| 149 | const index = object.dwarf_debug_line_index orelse return null; | |
| 150 | break :blk try object.readSection(allocator, index); | |
| 151 | }; | |
| 152 | var debug_ranges = blk: { | |
| 153 | if (object.dwarf_debug_ranges_index) |ind| { | |
| 154 | break :blk try object.readSection(allocator, ind); | |
| 155 | } | |
| 156 | break :blk try allocator.alloc(u8, 0); | |
| 157 | }; | |
| 158 | ||
| 159 | var inner: dwarf.DwarfInfo = .{ | |
| 160 | .endian = .Little, | |
| 161 | .debug_info = debug_info, | |
| 162 | .debug_abbrev = debug_abbrev, | |
| 163 | .debug_str = debug_str, | |
| 164 | .debug_line = debug_line, | |
| 165 | .debug_ranges = debug_ranges, | |
| 166 | }; | |
| 167 | try dwarf.openDwarfDebugInfo(&inner, allocator); | |
| 168 | ||
| 169 | return DebugInfo{ | |
| 170 | .inner = inner, | |
| 171 | .debug_info = debug_info, | |
| 172 | .debug_abbrev = debug_abbrev, | |
| 173 | .debug_str = debug_str, | |
| 174 | .debug_line = debug_line, | |
| 175 | .debug_ranges = debug_ranges, | |
| 176 | }; | |
| 177 | } | |
| 178 | ||
| 179 | pub fn deinit(self: *DebugInfo, allocator: *Allocator) void { | |
| 180 | allocator.free(self.debug_info); | |
| 181 | allocator.free(self.debug_abbrev); | |
| 182 | allocator.free(self.debug_str); | |
| 183 | allocator.free(self.debug_line); | |
| 184 | allocator.free(self.debug_ranges); | |
| 185 | self.inner.abbrev_table_list.deinit(); | |
| 186 | self.inner.compile_unit_list.deinit(); | |
| 187 | self.inner.func_list.deinit(); | |
| 188 | } | |
| 189 | }; | |
| 190 | ||
| 191 | pub const Import = struct { | |
| 192 | /// MachO symbol table entry. | |
| 193 | symbol: macho.nlist_64, | |
| 194 | ||
| 195 | /// Id of the dynamic library where the specified entries can be found. | |
| 196 | dylib_ordinal: i64, | |
| 197 | ||
| 198 | /// Index of this import within the import list. | |
| 199 | index: u32, | |
| 200 | }; | |
| 201 | ||
| 202 | /// Default path to dyld | |
| 203 | /// TODO instead of hardcoding it, we should probably look through some env vars and search paths | |
| 204 | /// instead but this will do for now. | |
| 205 | const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld"; | |
| 206 | ||
| 207 | /// Default lib search path | |
| 208 | /// TODO instead of hardcoding it, we should probably look through some env vars and search paths | |
| 209 | /// instead but this will do for now. | |
| 210 | const DEFAULT_LIB_SEARCH_PATH: []const u8 = "/usr/lib"; | |
| 211 | ||
| 212 | const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; | |
| 213 | /// TODO we should search for libSystem and fail if it doesn't exist, instead of hardcoding it | |
| 214 | const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; | |
| 215 | ||
| 216 | pub fn init(allocator: *Allocator) Zld { | |
| 217 | return .{ .allocator = allocator }; | |
| 218 | } | |
| 219 | ||
| 220 | pub fn deinit(self: *Zld) void { | |
| 221 | self.threadlocal_offsets.deinit(self.allocator); | |
| 222 | self.strtab.deinit(self.allocator); | |
| 223 | self.local_rebases.deinit(self.allocator); | |
| 224 | for (self.lazy_imports.items()) |*entry| { | |
| 225 | self.allocator.free(entry.key); | |
| 226 | } | |
| 227 | self.lazy_imports.deinit(self.allocator); | |
| 228 | for (self.nonlazy_imports.items()) |*entry| { | |
| 229 | self.allocator.free(entry.key); | |
| 230 | } | |
| 231 | self.nonlazy_imports.deinit(self.allocator); | |
| 232 | for (self.nonlazy_pointers.items()) |*entry| { | |
| 233 | self.allocator.free(entry.key); | |
| 234 | } | |
| 235 | self.nonlazy_pointers.deinit(self.allocator); | |
| 236 | for (self.exports.items()) |*entry| { | |
| 237 | self.allocator.free(entry.key); | |
| 238 | } | |
| 239 | self.exports.deinit(self.allocator); | |
| 240 | for (self.locals.items()) |*entry| { | |
| 241 | self.allocator.free(entry.key); | |
| 242 | entry.value.deinit(self.allocator); | |
| 243 | } | |
| 244 | self.locals.deinit(self.allocator); | |
| 245 | for (self.objects.items) |*object| { | |
| 246 | object.deinit(); | |
| 247 | } | |
| 248 | self.objects.deinit(self.allocator); | |
| 249 | for (self.load_commands.items) |*lc| { | |
| 250 | lc.deinit(self.allocator); | |
| 251 | } | |
| 252 | self.load_commands.deinit(self.allocator); | |
| 253 | self.mappings.deinit(self.allocator); | |
| 254 | self.unhandled_sections.deinit(self.allocator); | |
| 255 | if (self.file) |*f| f.close(); | |
| 256 | } | |
| 257 | ||
| 258 | pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { | |
| 259 | if (files.len == 0) return error.NoInputFiles; | |
| 260 | if (out_path.len == 0) return error.EmptyOutputPath; | |
| 261 | ||
| 262 | if (self.arch == null) { | |
| 263 | // Try inferring the arch from the object files. | |
| 264 | self.arch = blk: { | |
| 265 | const file = try fs.cwd().openFile(files[0], .{}); | |
| 266 | defer file.close(); | |
| 267 | var reader = file.reader(); | |
| 268 | const header = try reader.readStruct(macho.mach_header_64); | |
| 269 | const arch: std.Target.Cpu.Arch = switch (header.cputype) { | |
| 270 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 271 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 272 | else => |value| { | |
| 273 | log.err("unsupported cpu architecture 0x{x}", .{value}); | |
| 274 | return error.UnsupportedCpuArchitecture; | |
| 275 | }, | |
| 276 | }; | |
| 277 | break :blk arch; | |
| 278 | }; | |
| 279 | } | |
| 280 | ||
| 281 | self.page_size = switch (self.arch.?) { | |
| 282 | .aarch64 => 0x4000, | |
| 283 | .x86_64 => 0x1000, | |
| 284 | else => unreachable, | |
| 285 | }; | |
| 286 | self.out_path = out_path; | |
| 287 | self.file = try fs.cwd().createFile(out_path, .{ | |
| 288 | .truncate = true, | |
| 289 | .read = true, | |
| 290 | .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777, | |
| 291 | }); | |
| 292 | ||
| 293 | try self.populateMetadata(); | |
| 294 | try self.parseInputFiles(files); | |
| 295 | try self.sortSections(); | |
| 296 | try self.resolveImports(); | |
| 297 | try self.allocateTextSegment(); | |
| 298 | try self.allocateDataConstSegment(); | |
| 299 | try self.allocateDataSegment(); | |
| 300 | self.allocateLinkeditSegment(); | |
| 301 | try self.writeStubHelperCommon(); | |
| 302 | try self.resolveSymbols(); | |
| 303 | try self.doRelocs(); | |
| 304 | try self.flush(); | |
| 305 | } | |
| 306 | ||
| 307 | fn parseInputFiles(self: *Zld, files: []const []const u8) !void { | |
| 308 | for (files) |file_name| { | |
| 309 | const file = try fs.cwd().openFile(file_name, .{}); | |
| 310 | ||
| 311 | try_object: { | |
| 312 | var object = Object.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) { | |
| 313 | error.NotObject => break :try_object, | |
| 314 | else => |e| return e, | |
| 315 | }; | |
| 316 | const index = @intCast(u16, self.objects.items.len); | |
| 317 | try self.objects.append(self.allocator, object); | |
| 318 | try self.updateMetadata(index); | |
| 319 | continue; | |
| 320 | } | |
| 321 | ||
| 322 | try_archive: { | |
| 323 | var archive = Archive.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) { | |
| 324 | error.NotArchive => break :try_archive, | |
| 325 | else => |e| return e, | |
| 326 | }; | |
| 327 | defer archive.deinit(); | |
| 328 | while (archive.objects.popOrNull()) |object| { | |
| 329 | const index = @intCast(u16, self.objects.items.len); | |
| 330 | try self.objects.append(self.allocator, object); | |
| 331 | try self.updateMetadata(index); | |
| 332 | } | |
| 333 | continue; | |
| 334 | } | |
| 335 | ||
| 336 | log.err("unexpected file type: expected object '.o' or archive '.a': {s}", .{file_name}); | |
| 337 | return error.UnexpectedInputFileType; | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | fn mapAndUpdateSections( | |
| 342 | self: *Zld, | |
| 343 | object_id: u16, | |
| 344 | source_sect_id: u16, | |
| 345 | target_seg_id: u16, | |
| 346 | target_sect_id: u16, | |
| 347 | ) !void { | |
| 348 | const object = self.objects.items[object_id]; | |
| 349 | const source_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 350 | const source_sect = source_seg.sections.items[source_sect_id]; | |
| 351 | const target_seg = &self.load_commands.items[target_seg_id].Segment; | |
| 352 | const target_sect = &target_seg.sections.items[target_sect_id]; | |
| 353 | ||
| 354 | const alignment = try math.powi(u32, 2, target_sect.@"align"); | |
| 355 | const offset = mem.alignForwardGeneric(u64, target_sect.size, alignment); | |
| 356 | const size = mem.alignForwardGeneric(u64, source_sect.size, alignment); | |
| 357 | const key = MappingKey{ | |
| 358 | .object_id = object_id, | |
| 359 | .source_sect_id = source_sect_id, | |
| 360 | }; | |
| 361 | try self.mappings.putNoClobber(self.allocator, key, .{ | |
| 362 | .source_sect_id = source_sect_id, | |
| 363 | .target_seg_id = target_seg_id, | |
| 364 | .target_sect_id = target_sect_id, | |
| 365 | .offset = @intCast(u32, offset), | |
| 366 | }); | |
| 367 | log.debug("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ | |
| 368 | object.name, | |
| 369 | parseName(&source_sect.segname), | |
| 370 | parseName(&source_sect.sectname), | |
| 371 | parseName(&target_sect.segname), | |
| 372 | parseName(&target_sect.sectname), | |
| 373 | offset, | |
| 374 | offset + size, | |
| 375 | }); | |
| 376 | ||
| 377 | target_sect.size = offset + size; | |
| 378 | } | |
| 379 | ||
| 380 | fn updateMetadata(self: *Zld, object_id: u16) !void { | |
| 381 | const object = self.objects.items[object_id]; | |
| 382 | const object_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 383 | const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 384 | const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 385 | const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 386 | ||
| 387 | // Create missing metadata | |
| 388 | for (object_seg.sections.items) |source_sect, id| { | |
| 389 | if (id == object.text_section_index.?) continue; | |
| 390 | const segname = parseName(&source_sect.segname); | |
| 391 | const sectname = parseName(&source_sect.sectname); | |
| 392 | const flags = source_sect.flags; | |
| 393 | ||
| 394 | switch (flags) { | |
| 395 | macho.S_REGULAR, macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => { | |
| 396 | if (mem.eql(u8, segname, "__TEXT")) { | |
| 397 | if (self.text_const_section_index != null) continue; | |
| 398 | ||
| 399 | self.text_const_section_index = @intCast(u16, text_seg.sections.items.len); | |
| 400 | try text_seg.addSection(self.allocator, .{ | |
| 401 | .sectname = makeStaticString("__const"), | |
| 402 | .segname = makeStaticString("__TEXT"), | |
| 403 | .addr = 0, | |
| 404 | .size = 0, | |
| 405 | .offset = 0, | |
| 406 | .@"align" = 0, | |
| 407 | .reloff = 0, | |
| 408 | .nreloc = 0, | |
| 409 | .flags = macho.S_REGULAR, | |
| 410 | .reserved1 = 0, | |
| 411 | .reserved2 = 0, | |
| 412 | .reserved3 = 0, | |
| 413 | }); | |
| 414 | } else if (mem.eql(u8, segname, "__DATA")) { | |
| 415 | if (!mem.eql(u8, sectname, "__const")) continue; | |
| 416 | if (self.data_const_section_index != null) continue; | |
| 417 | ||
| 418 | self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len); | |
| 419 | try data_const_seg.addSection(self.allocator, .{ | |
| 420 | .sectname = makeStaticString("__const"), | |
| 421 | .segname = makeStaticString("__DATA_CONST"), | |
| 422 | .addr = 0, | |
| 423 | .size = 0, | |
| 424 | .offset = 0, | |
| 425 | .@"align" = 0, | |
| 426 | .reloff = 0, | |
| 427 | .nreloc = 0, | |
| 428 | .flags = macho.S_REGULAR, | |
| 429 | .reserved1 = 0, | |
| 430 | .reserved2 = 0, | |
| 431 | .reserved3 = 0, | |
| 432 | }); | |
| 433 | } | |
| 434 | }, | |
| 435 | macho.S_CSTRING_LITERALS => { | |
| 436 | if (!mem.eql(u8, segname, "__TEXT")) continue; | |
| 437 | if (self.cstring_section_index != null) continue; | |
| 438 | ||
| 439 | self.cstring_section_index = @intCast(u16, text_seg.sections.items.len); | |
| 440 | try text_seg.addSection(self.allocator, .{ | |
| 441 | .sectname = makeStaticString("__cstring"), | |
| 442 | .segname = makeStaticString("__TEXT"), | |
| 443 | .addr = 0, | |
| 444 | .size = 0, | |
| 445 | .offset = 0, | |
| 446 | .@"align" = 0, | |
| 447 | .reloff = 0, | |
| 448 | .nreloc = 0, | |
| 449 | .flags = macho.S_CSTRING_LITERALS, | |
| 450 | .reserved1 = 0, | |
| 451 | .reserved2 = 0, | |
| 452 | .reserved3 = 0, | |
| 453 | }); | |
| 454 | }, | |
| 455 | macho.S_MOD_INIT_FUNC_POINTERS => { | |
| 456 | if (!mem.eql(u8, segname, "__DATA")) continue; | |
| 457 | if (self.mod_init_func_section_index != null) continue; | |
| 458 | ||
| 459 | self.mod_init_func_section_index = @intCast(u16, data_const_seg.sections.items.len); | |
| 460 | try data_const_seg.addSection(self.allocator, .{ | |
| 461 | .sectname = makeStaticString("__mod_init_func"), | |
| 462 | .segname = makeStaticString("__DATA_CONST"), | |
| 463 | .addr = 0, | |
| 464 | .size = 0, | |
| 465 | .offset = 0, | |
| 466 | .@"align" = 0, | |
| 467 | .reloff = 0, | |
| 468 | .nreloc = 0, | |
| 469 | .flags = macho.S_MOD_INIT_FUNC_POINTERS, | |
| 470 | .reserved1 = 0, | |
| 471 | .reserved2 = 0, | |
| 472 | .reserved3 = 0, | |
| 473 | }); | |
| 474 | }, | |
| 475 | macho.S_MOD_TERM_FUNC_POINTERS => { | |
| 476 | if (!mem.eql(u8, segname, "__DATA")) continue; | |
| 477 | if (self.mod_term_func_section_index != null) continue; | |
| 478 | ||
| 479 | self.mod_term_func_section_index = @intCast(u16, data_const_seg.sections.items.len); | |
| 480 | try data_const_seg.addSection(self.allocator, .{ | |
| 481 | .sectname = makeStaticString("__mod_term_func"), | |
| 482 | .segname = makeStaticString("__DATA_CONST"), | |
| 483 | .addr = 0, | |
| 484 | .size = 0, | |
| 485 | .offset = 0, | |
| 486 | .@"align" = 0, | |
| 487 | .reloff = 0, | |
| 488 | .nreloc = 0, | |
| 489 | .flags = macho.S_MOD_TERM_FUNC_POINTERS, | |
| 490 | .reserved1 = 0, | |
| 491 | .reserved2 = 0, | |
| 492 | .reserved3 = 0, | |
| 493 | }); | |
| 494 | }, | |
| 495 | macho.S_ZEROFILL => { | |
| 496 | if (!mem.eql(u8, segname, "__DATA")) continue; | |
| 497 | if (self.bss_section_index != null) continue; | |
| 498 | ||
| 499 | self.bss_section_index = @intCast(u16, data_seg.sections.items.len); | |
| 500 | try data_seg.addSection(self.allocator, .{ | |
| 501 | .sectname = makeStaticString("__bss"), | |
| 502 | .segname = makeStaticString("__DATA"), | |
| 503 | .addr = 0, | |
| 504 | .size = 0, | |
| 505 | .offset = 0, | |
| 506 | .@"align" = 0, | |
| 507 | .reloff = 0, | |
| 508 | .nreloc = 0, | |
| 509 | .flags = macho.S_ZEROFILL, | |
| 510 | .reserved1 = 0, | |
| 511 | .reserved2 = 0, | |
| 512 | .reserved3 = 0, | |
| 513 | }); | |
| 514 | }, | |
| 515 | macho.S_THREAD_LOCAL_VARIABLES => { | |
| 516 | if (!mem.eql(u8, segname, "__DATA")) continue; | |
| 517 | if (self.tlv_section_index != null) continue; | |
| 518 | ||
| 519 | self.tlv_section_index = @intCast(u16, data_seg.sections.items.len); | |
| 520 | try data_seg.addSection(self.allocator, .{ | |
| 521 | .sectname = makeStaticString("__thread_vars"), | |
| 522 | .segname = makeStaticString("__DATA"), | |
| 523 | .addr = 0, | |
| 524 | .size = 0, | |
| 525 | .offset = 0, | |
| 526 | .@"align" = 0, | |
| 527 | .reloff = 0, | |
| 528 | .nreloc = 0, | |
| 529 | .flags = macho.S_THREAD_LOCAL_VARIABLES, | |
| 530 | .reserved1 = 0, | |
| 531 | .reserved2 = 0, | |
| 532 | .reserved3 = 0, | |
| 533 | }); | |
| 534 | }, | |
| 535 | macho.S_THREAD_LOCAL_REGULAR => { | |
| 536 | if (!mem.eql(u8, segname, "__DATA")) continue; | |
| 537 | if (self.tlv_data_section_index != null) continue; | |
| 538 | ||
| 539 | self.tlv_data_section_index = @intCast(u16, data_seg.sections.items.len); | |
| 540 | try data_seg.addSection(self.allocator, .{ | |
| 541 | .sectname = makeStaticString("__thread_data"), | |
| 542 | .segname = makeStaticString("__DATA"), | |
| 543 | .addr = 0, | |
| 544 | .size = 0, | |
| 545 | .offset = 0, | |
| 546 | .@"align" = 0, | |
| 547 | .reloff = 0, | |
| 548 | .nreloc = 0, | |
| 549 | .flags = macho.S_THREAD_LOCAL_REGULAR, | |
| 550 | .reserved1 = 0, | |
| 551 | .reserved2 = 0, | |
| 552 | .reserved3 = 0, | |
| 553 | }); | |
| 554 | }, | |
| 555 | macho.S_THREAD_LOCAL_ZEROFILL => { | |
| 556 | if (!mem.eql(u8, segname, "__DATA")) continue; | |
| 557 | if (self.tlv_bss_section_index != null) continue; | |
| 558 | ||
| 559 | self.tlv_bss_section_index = @intCast(u16, data_seg.sections.items.len); | |
| 560 | try data_seg.addSection(self.allocator, .{ | |
| 561 | .sectname = makeStaticString("__thread_bss"), | |
| 562 | .segname = makeStaticString("__DATA"), | |
| 563 | .addr = 0, | |
| 564 | .size = 0, | |
| 565 | .offset = 0, | |
| 566 | .@"align" = 0, | |
| 567 | .reloff = 0, | |
| 568 | .nreloc = 0, | |
| 569 | .flags = macho.S_THREAD_LOCAL_ZEROFILL, | |
| 570 | .reserved1 = 0, | |
| 571 | .reserved2 = 0, | |
| 572 | .reserved3 = 0, | |
| 573 | }); | |
| 574 | }, | |
| 575 | else => { | |
| 576 | log.debug("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); | |
| 577 | }, | |
| 578 | } | |
| 579 | } | |
| 580 | ||
| 581 | // Find ideal section alignment. | |
| 582 | for (object_seg.sections.items) |source_sect, id| { | |
| 583 | if (self.getMatchingSection(source_sect)) |res| { | |
| 584 | const target_seg = &self.load_commands.items[res.seg].Segment; | |
| 585 | const target_sect = &target_seg.sections.items[res.sect]; | |
| 586 | target_sect.@"align" = math.max(target_sect.@"align", source_sect.@"align"); | |
| 587 | } | |
| 588 | } | |
| 589 | ||
| 590 | // Update section mappings | |
| 591 | for (object_seg.sections.items) |source_sect, id| { | |
| 592 | const source_sect_id = @intCast(u16, id); | |
| 593 | if (self.getMatchingSection(source_sect)) |res| { | |
| 594 | try self.mapAndUpdateSections(object_id, source_sect_id, res.seg, res.sect); | |
| 595 | continue; | |
| 596 | } | |
| 597 | ||
| 598 | const segname = parseName(&source_sect.segname); | |
| 599 | const sectname = parseName(&source_sect.sectname); | |
| 600 | log.debug("section '{s}/{s}' will be unmapped", .{ segname, sectname }); | |
| 601 | try self.unhandled_sections.putNoClobber(self.allocator, .{ | |
| 602 | .object_id = object_id, | |
| 603 | .source_sect_id = source_sect_id, | |
| 604 | }, 0); | |
| 605 | } | |
| 606 | } | |
| 607 | ||
| 608 | const MatchingSection = struct { | |
| 609 | seg: u16, | |
| 610 | sect: u16, | |
| 611 | }; | |
| 612 | ||
| 613 | fn getMatchingSection(self: *Zld, section: macho.section_64) ?MatchingSection { | |
| 614 | const segname = parseName(&section.segname); | |
| 615 | const sectname = parseName(&section.sectname); | |
| 616 | const res: ?MatchingSection = blk: { | |
| 617 | switch (section.flags) { | |
| 618 | macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => { | |
| 619 | break :blk .{ | |
| 620 | .seg = self.text_segment_cmd_index.?, | |
| 621 | .sect = self.text_const_section_index.?, | |
| 622 | }; | |
| 623 | }, | |
| 624 | macho.S_CSTRING_LITERALS => { | |
| 625 | break :blk .{ | |
| 626 | .seg = self.text_segment_cmd_index.?, | |
| 627 | .sect = self.cstring_section_index.?, | |
| 628 | }; | |
| 629 | }, | |
| 630 | macho.S_MOD_INIT_FUNC_POINTERS => { | |
| 631 | break :blk .{ | |
| 632 | .seg = self.data_const_segment_cmd_index.?, | |
| 633 | .sect = self.mod_init_func_section_index.?, | |
| 634 | }; | |
| 635 | }, | |
| 636 | macho.S_MOD_TERM_FUNC_POINTERS => { | |
| 637 | break :blk .{ | |
| 638 | .seg = self.data_const_segment_cmd_index.?, | |
| 639 | .sect = self.mod_term_func_section_index.?, | |
| 640 | }; | |
| 641 | }, | |
| 642 | macho.S_ZEROFILL => { | |
| 643 | break :blk .{ | |
| 644 | .seg = self.data_segment_cmd_index.?, | |
| 645 | .sect = self.bss_section_index.?, | |
| 646 | }; | |
| 647 | }, | |
| 648 | macho.S_THREAD_LOCAL_VARIABLES => { | |
| 649 | break :blk .{ | |
| 650 | .seg = self.data_segment_cmd_index.?, | |
| 651 | .sect = self.tlv_section_index.?, | |
| 652 | }; | |
| 653 | }, | |
| 654 | macho.S_THREAD_LOCAL_REGULAR => { | |
| 655 | break :blk .{ | |
| 656 | .seg = self.data_segment_cmd_index.?, | |
| 657 | .sect = self.tlv_data_section_index.?, | |
| 658 | }; | |
| 659 | }, | |
| 660 | macho.S_THREAD_LOCAL_ZEROFILL => { | |
| 661 | break :blk .{ | |
| 662 | .seg = self.data_segment_cmd_index.?, | |
| 663 | .sect = self.tlv_bss_section_index.?, | |
| 664 | }; | |
| 665 | }, | |
| 666 | macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS => { | |
| 667 | break :blk .{ | |
| 668 | .seg = self.text_segment_cmd_index.?, | |
| 669 | .sect = self.text_section_index.?, | |
| 670 | }; | |
| 671 | }, | |
| 672 | macho.S_REGULAR => { | |
| 673 | if (mem.eql(u8, segname, "__TEXT")) { | |
| 674 | break :blk .{ | |
| 675 | .seg = self.text_segment_cmd_index.?, | |
| 676 | .sect = self.text_const_section_index.?, | |
| 677 | }; | |
| 678 | } else if (mem.eql(u8, segname, "__DATA")) { | |
| 679 | if (mem.eql(u8, sectname, "__data")) { | |
| 680 | break :blk .{ | |
| 681 | .seg = self.data_segment_cmd_index.?, | |
| 682 | .sect = self.data_section_index.?, | |
| 683 | }; | |
| 684 | } else if (mem.eql(u8, sectname, "__const")) { | |
| 685 | break :blk .{ | |
| 686 | .seg = self.data_const_segment_cmd_index.?, | |
| 687 | .sect = self.data_const_section_index.?, | |
| 688 | }; | |
| 689 | } | |
| 690 | } | |
| 691 | break :blk null; | |
| 692 | }, | |
| 693 | else => { | |
| 694 | break :blk null; | |
| 695 | }, | |
| 696 | } | |
| 697 | }; | |
| 698 | return res; | |
| 699 | } | |
| 700 | ||
| 701 | fn sortSections(self: *Zld) !void { | |
| 702 | var text_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); | |
| 703 | defer text_index_mapping.deinit(); | |
| 704 | var data_const_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); | |
| 705 | defer data_const_index_mapping.deinit(); | |
| 706 | var data_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); | |
| 707 | defer data_index_mapping.deinit(); | |
| 708 | ||
| 709 | { | |
| 710 | // __TEXT segment | |
| 711 | const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 712 | var sections = seg.sections.toOwnedSlice(self.allocator); | |
| 713 | defer self.allocator.free(sections); | |
| 714 | try seg.sections.ensureCapacity(self.allocator, sections.len); | |
| 715 | ||
| 716 | const indices = &[_]*?u16{ | |
| 717 | &self.text_section_index, | |
| 718 | &self.stubs_section_index, | |
| 719 | &self.stub_helper_section_index, | |
| 720 | &self.text_const_section_index, | |
| 721 | &self.cstring_section_index, | |
| 722 | }; | |
| 723 | for (indices) |maybe_index| { | |
| 724 | const new_index: u16 = if (maybe_index.*) |index| blk: { | |
| 725 | const idx = @intCast(u16, seg.sections.items.len); | |
| 726 | seg.sections.appendAssumeCapacity(sections[index]); | |
| 727 | try text_index_mapping.putNoClobber(index, idx); | |
| 728 | break :blk idx; | |
| 729 | } else continue; | |
| 730 | maybe_index.* = new_index; | |
| 731 | } | |
| 732 | } | |
| 733 | ||
| 734 | { | |
| 735 | // __DATA_CONST segment | |
| 736 | const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 737 | var sections = seg.sections.toOwnedSlice(self.allocator); | |
| 738 | defer self.allocator.free(sections); | |
| 739 | try seg.sections.ensureCapacity(self.allocator, sections.len); | |
| 740 | ||
| 741 | const indices = &[_]*?u16{ | |
| 742 | &self.got_section_index, | |
| 743 | &self.mod_init_func_section_index, | |
| 744 | &self.mod_term_func_section_index, | |
| 745 | &self.data_const_section_index, | |
| 746 | }; | |
| 747 | for (indices) |maybe_index| { | |
| 748 | const new_index: u16 = if (maybe_index.*) |index| blk: { | |
| 749 | const idx = @intCast(u16, seg.sections.items.len); | |
| 750 | seg.sections.appendAssumeCapacity(sections[index]); | |
| 751 | try data_const_index_mapping.putNoClobber(index, idx); | |
| 752 | break :blk idx; | |
| 753 | } else continue; | |
| 754 | maybe_index.* = new_index; | |
| 755 | } | |
| 756 | } | |
| 757 | ||
| 758 | { | |
| 759 | // __DATA segment | |
| 760 | const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 761 | var sections = seg.sections.toOwnedSlice(self.allocator); | |
| 762 | defer self.allocator.free(sections); | |
| 763 | try seg.sections.ensureCapacity(self.allocator, sections.len); | |
| 764 | ||
| 765 | // __DATA segment | |
| 766 | const indices = &[_]*?u16{ | |
| 767 | &self.la_symbol_ptr_section_index, | |
| 768 | &self.tlv_section_index, | |
| 769 | &self.data_section_index, | |
| 770 | &self.tlv_data_section_index, | |
| 771 | &self.tlv_bss_section_index, | |
| 772 | &self.bss_section_index, | |
| 773 | }; | |
| 774 | for (indices) |maybe_index| { | |
| 775 | const new_index: u16 = if (maybe_index.*) |index| blk: { | |
| 776 | const idx = @intCast(u16, seg.sections.items.len); | |
| 777 | seg.sections.appendAssumeCapacity(sections[index]); | |
| 778 | try data_index_mapping.putNoClobber(index, idx); | |
| 779 | break :blk idx; | |
| 780 | } else continue; | |
| 781 | maybe_index.* = new_index; | |
| 782 | } | |
| 783 | } | |
| 784 | ||
| 785 | var it = self.mappings.iterator(); | |
| 786 | while (it.next()) |entry| { | |
| 787 | const mapping = &entry.value; | |
| 788 | if (self.text_segment_cmd_index.? == mapping.target_seg_id) { | |
| 789 | const new_index = text_index_mapping.get(mapping.target_sect_id) orelse unreachable; | |
| 790 | mapping.target_sect_id = new_index; | |
| 791 | } else if (self.data_const_segment_cmd_index.? == mapping.target_seg_id) { | |
| 792 | const new_index = data_const_index_mapping.get(mapping.target_sect_id) orelse unreachable; | |
| 793 | mapping.target_sect_id = new_index; | |
| 794 | } else if (self.data_segment_cmd_index.? == mapping.target_seg_id) { | |
| 795 | const new_index = data_index_mapping.get(mapping.target_sect_id) orelse unreachable; | |
| 796 | mapping.target_sect_id = new_index; | |
| 797 | } else unreachable; | |
| 798 | } | |
| 799 | } | |
| 800 | ||
| 801 | fn resolveImports(self: *Zld) !void { | |
| 802 | var imports = std.StringArrayHashMap(bool).init(self.allocator); | |
| 803 | defer imports.deinit(); | |
| 804 | ||
| 805 | for (self.objects.items) |object| { | |
| 806 | for (object.symtab.items) |sym| { | |
| 807 | if (isLocal(&sym)) continue; | |
| 808 | ||
| 809 | const name = object.getString(sym.n_strx); | |
| 810 | const res = try imports.getOrPut(name); | |
| 811 | if (isExport(&sym)) { | |
| 812 | res.entry.value = false; | |
| 813 | continue; | |
| 814 | } | |
| 815 | if (res.found_existing and !res.entry.value) | |
| 816 | continue; | |
| 817 | res.entry.value = true; | |
| 818 | } | |
| 819 | } | |
| 820 | ||
| 821 | for (imports.items()) |entry| { | |
| 822 | if (!entry.value) continue; | |
| 823 | ||
| 824 | const sym_name = entry.key; | |
| 825 | const n_strx = try self.makeString(sym_name); | |
| 826 | var new_sym: macho.nlist_64 = .{ | |
| 827 | .n_strx = n_strx, | |
| 828 | .n_type = macho.N_UNDF | macho.N_EXT, | |
| 829 | .n_value = 0, | |
| 830 | .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER, | |
| 831 | .n_sect = 0, | |
| 832 | }; | |
| 833 | var key = try self.allocator.dupe(u8, sym_name); | |
| 834 | // TODO handle symbol resolution from non-libc dylibs. | |
| 835 | const dylib_ordinal = 1; | |
| 836 | ||
| 837 | // TODO need to rework this. Perhaps should create a set of all possible libc | |
| 838 | // symbols which are expected to be nonlazy? | |
| 839 | if (mem.eql(u8, sym_name, "___stdoutp") or | |
| 840 | mem.eql(u8, sym_name, "___stderrp") or | |
| 841 | mem.eql(u8, sym_name, "___stdinp") or | |
| 842 | mem.eql(u8, sym_name, "___stack_chk_guard") or | |
| 843 | mem.eql(u8, sym_name, "_environ") or | |
| 844 | mem.eql(u8, sym_name, "__DefaultRuneLocale") or | |
| 845 | mem.eql(u8, sym_name, "_mach_task_self_")) | |
| 846 | { | |
| 847 | log.debug("writing nonlazy symbol '{s}'", .{sym_name}); | |
| 848 | const index = @intCast(u32, self.nonlazy_imports.items().len); | |
| 849 | try self.nonlazy_imports.putNoClobber(self.allocator, key, .{ | |
| 850 | .symbol = new_sym, | |
| 851 | .dylib_ordinal = dylib_ordinal, | |
| 852 | .index = index, | |
| 853 | }); | |
| 854 | } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { | |
| 855 | log.debug("writing threadlocal symbol '{s}'", .{sym_name}); | |
| 856 | self.tlv_bootstrap = .{ | |
| 857 | .symbol = new_sym, | |
| 858 | .dylib_ordinal = dylib_ordinal, | |
| 859 | .index = 0, | |
| 860 | }; | |
| 861 | } else { | |
| 862 | log.debug("writing lazy symbol '{s}'", .{sym_name}); | |
| 863 | const index = @intCast(u32, self.lazy_imports.items().len); | |
| 864 | try self.lazy_imports.putNoClobber(self.allocator, key, .{ | |
| 865 | .symbol = new_sym, | |
| 866 | .dylib_ordinal = dylib_ordinal, | |
| 867 | .index = index, | |
| 868 | }); | |
| 869 | } | |
| 870 | } | |
| 871 | ||
| 872 | const n_strx = try self.makeString("dyld_stub_binder"); | |
| 873 | const name = try self.allocator.dupe(u8, "dyld_stub_binder"); | |
| 874 | log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{}); | |
| 875 | const index = @intCast(u32, self.nonlazy_imports.items().len); | |
| 876 | try self.nonlazy_imports.putNoClobber(self.allocator, name, .{ | |
| 877 | .symbol = .{ | |
| 878 | .n_strx = n_strx, | |
| 879 | .n_type = std.macho.N_UNDF | std.macho.N_EXT, | |
| 880 | .n_sect = 0, | |
| 881 | .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, | |
| 882 | .n_value = 0, | |
| 883 | }, | |
| 884 | .dylib_ordinal = 1, | |
| 885 | .index = index, | |
| 886 | }); | |
| 887 | } | |
| 888 | ||
| 889 | fn allocateTextSegment(self: *Zld) !void { | |
| 890 | const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 891 | const nexterns = @intCast(u32, self.lazy_imports.items().len); | |
| 892 | ||
| 893 | const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; | |
| 894 | seg.inner.fileoff = 0; | |
| 895 | seg.inner.vmaddr = base_vmaddr; | |
| 896 | ||
| 897 | // Set stubs and stub_helper sizes | |
| 898 | const stubs = &seg.sections.items[self.stubs_section_index.?]; | |
| 899 | const stub_helper = &seg.sections.items[self.stub_helper_section_index.?]; | |
| 900 | stubs.size += nexterns * stubs.reserved2; | |
| 901 | ||
| 902 | const stub_size: u4 = switch (self.arch.?) { | |
| 903 | .x86_64 => 10, | |
| 904 | .aarch64 => 3 * @sizeOf(u32), | |
| 905 | else => unreachable, | |
| 906 | }; | |
| 907 | stub_helper.size += nexterns * stub_size; | |
| 908 | ||
| 909 | var sizeofcmds: u64 = 0; | |
| 910 | for (self.load_commands.items) |lc| { | |
| 911 | sizeofcmds += lc.cmdsize(); | |
| 912 | } | |
| 913 | ||
| 914 | try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds); | |
| 915 | ||
| 916 | // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments. | |
| 917 | var min_alignment: u32 = 0; | |
| 918 | for (seg.sections.items) |sect| { | |
| 919 | const alignment = try math.powi(u32, 2, sect.@"align"); | |
| 920 | min_alignment = math.max(min_alignment, alignment); | |
| 921 | } | |
| 922 | ||
| 923 | assert(min_alignment > 0); | |
| 924 | const last_sect_idx = seg.sections.items.len - 1; | |
| 925 | const last_sect = seg.sections.items[last_sect_idx]; | |
| 926 | const shift: u32 = blk: { | |
| 927 | const diff = seg.inner.filesize - last_sect.offset - last_sect.size; | |
| 928 | const factor = @divTrunc(diff, min_alignment); | |
| 929 | break :blk @intCast(u32, factor * min_alignment); | |
| 930 | }; | |
| 931 | ||
| 932 | if (shift > 0) { | |
| 933 | for (seg.sections.items) |*sect| { | |
| 934 | sect.offset += shift; | |
| 935 | sect.addr += shift; | |
| 936 | } | |
| 937 | } | |
| 938 | } | |
| 939 | ||
| 940 | fn allocateDataConstSegment(self: *Zld) !void { | |
| 941 | const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 942 | const nonlazy = @intCast(u32, self.nonlazy_imports.items().len); | |
| 943 | ||
| 944 | const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 945 | seg.inner.fileoff = text_seg.inner.fileoff + text_seg.inner.filesize; | |
| 946 | seg.inner.vmaddr = text_seg.inner.vmaddr + text_seg.inner.vmsize; | |
| 947 | ||
| 948 | // Set got size | |
| 949 | const got = &seg.sections.items[self.got_section_index.?]; | |
| 950 | // TODO this will require scanning the relocations at least one to work out | |
| 951 | // the exact amount of local GOT indirections. For the time being, set some | |
| 952 | // default value. | |
| 953 | got.size += (max_local_got_indirections + nonlazy) * @sizeOf(u64); | |
| 954 | ||
| 955 | try self.allocateSegment(self.data_const_segment_cmd_index.?, 0); | |
| 956 | } | |
| 957 | ||
| 958 | fn allocateDataSegment(self: *Zld) !void { | |
| 959 | const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 960 | const lazy = @intCast(u32, self.lazy_imports.items().len); | |
| 961 | ||
| 962 | const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 963 | seg.inner.fileoff = data_const_seg.inner.fileoff + data_const_seg.inner.filesize; | |
| 964 | seg.inner.vmaddr = data_const_seg.inner.vmaddr + data_const_seg.inner.vmsize; | |
| 965 | ||
| 966 | // Set la_symbol_ptr and data size | |
| 967 | const la_symbol_ptr = &seg.sections.items[self.la_symbol_ptr_section_index.?]; | |
| 968 | const data = &seg.sections.items[self.data_section_index.?]; | |
| 969 | la_symbol_ptr.size += lazy * @sizeOf(u64); | |
| 970 | data.size += @sizeOf(u64); // TODO when do we need more? | |
| 971 | ||
| 972 | try self.allocateSegment(self.data_segment_cmd_index.?, 0); | |
| 973 | } | |
| 974 | ||
| 975 | fn allocateLinkeditSegment(self: *Zld) void { | |
| 976 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 977 | const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 978 | seg.inner.fileoff = data_seg.inner.fileoff + data_seg.inner.filesize; | |
| 979 | seg.inner.vmaddr = data_seg.inner.vmaddr + data_seg.inner.vmsize; | |
| 980 | } | |
| 981 | ||
| 982 | fn allocateSegment(self: *Zld, index: u16, offset: u64) !void { | |
| 983 | const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; | |
| 984 | const seg = &self.load_commands.items[index].Segment; | |
| 985 | ||
| 986 | // Allocate the sections according to their alignment at the beginning of the segment. | |
| 987 | var start: u64 = offset; | |
| 988 | for (seg.sections.items) |*sect| { | |
| 989 | const alignment = try math.powi(u32, 2, sect.@"align"); | |
| 990 | const start_aligned = mem.alignForwardGeneric(u64, start, alignment); | |
| 991 | const end_aligned = mem.alignForwardGeneric(u64, start_aligned + sect.size, alignment); | |
| 992 | sect.offset = @intCast(u32, seg.inner.fileoff + start_aligned); | |
| 993 | sect.addr = seg.inner.vmaddr + start_aligned; | |
| 994 | start = end_aligned; | |
| 995 | } | |
| 996 | ||
| 997 | const seg_size_aligned = mem.alignForwardGeneric(u64, start, self.page_size.?); | |
| 998 | seg.inner.filesize = seg_size_aligned; | |
| 999 | seg.inner.vmsize = seg_size_aligned; | |
| 1000 | } | |
| 1001 | ||
| 1002 | fn writeStubHelperCommon(self: *Zld) !void { | |
| 1003 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1004 | const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; | |
| 1005 | const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 1006 | const got = &data_const_segment.sections.items[self.got_section_index.?]; | |
| 1007 | const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 1008 | const data = &data_segment.sections.items[self.data_section_index.?]; | |
| 1009 | const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; | |
| 1010 | ||
| 1011 | self.stub_helper_stubs_start_off = blk: { | |
| 1012 | switch (self.arch.?) { | |
| 1013 | .x86_64 => { | |
| 1014 | const code_size = 15; | |
| 1015 | var code: [code_size]u8 = undefined; | |
| 1016 | // lea %r11, [rip + disp] | |
| 1017 | code[0] = 0x4c; | |
| 1018 | code[1] = 0x8d; | |
| 1019 | code[2] = 0x1d; | |
| 1020 | { | |
| 1021 | const target_addr = data.addr + data.size - @sizeOf(u64); | |
| 1022 | const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7); | |
| 1023 | mem.writeIntLittle(u32, code[3..7], displacement); | |
| 1024 | } | |
| 1025 | // push %r11 | |
| 1026 | code[7] = 0x41; | |
| 1027 | code[8] = 0x53; | |
| 1028 | // jmp [rip + disp] | |
| 1029 | code[9] = 0xff; | |
| 1030 | code[10] = 0x25; | |
| 1031 | { | |
| 1032 | const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; | |
| 1033 | const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); | |
| 1034 | const displacement = try math.cast(u32, addr - stub_helper.addr - code_size); | |
| 1035 | mem.writeIntLittle(u32, code[11..], displacement); | |
| 1036 | } | |
| 1037 | try self.file.?.pwriteAll(&code, stub_helper.offset); | |
| 1038 | break :blk stub_helper.offset + code_size; | |
| 1039 | }, | |
| 1040 | .aarch64 => { | |
| 1041 | var code: [6 * @sizeOf(u32)]u8 = undefined; | |
| 1042 | data_blk_outer: { | |
| 1043 | const this_addr = stub_helper.addr; | |
| 1044 | const target_addr = data.addr + data.size - @sizeOf(u64); | |
| 1045 | data_blk: { | |
| 1046 | const displacement = math.cast(i21, target_addr - this_addr) catch |_| break :data_blk; | |
| 1047 | // adr x17, disp | |
| 1048 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); | |
| 1049 | // nop | |
| 1050 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); | |
| 1051 | break :data_blk_outer; | |
| 1052 | } | |
| 1053 | data_blk: { | |
| 1054 | const new_this_addr = this_addr + @sizeOf(u32); | |
| 1055 | const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; | |
| 1056 | // nop | |
| 1057 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); | |
| 1058 | // adr x17, disp | |
| 1059 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32()); | |
| 1060 | break :data_blk_outer; | |
| 1061 | } | |
| 1062 | // Jump is too big, replace adr with adrp and add. | |
| 1063 | const this_page = @intCast(i32, this_addr >> 12); | |
| 1064 | const target_page = @intCast(i32, target_addr >> 12); | |
| 1065 | const pages = @intCast(i21, target_page - this_page); | |
| 1066 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32()); | |
| 1067 | const narrowed = @truncate(u12, target_addr); | |
| 1068 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32()); | |
| 1069 | } | |
| 1070 | // stp x16, x17, [sp, #-16]! | |
| 1071 | code[8] = 0xf0; | |
| 1072 | code[9] = 0x47; | |
| 1073 | code[10] = 0xbf; | |
| 1074 | code[11] = 0xa9; | |
| 1075 | binder_blk_outer: { | |
| 1076 | const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; | |
| 1077 | const this_addr = stub_helper.addr + 3 * @sizeOf(u32); | |
| 1078 | const target_addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); | |
| 1079 | binder_blk: { | |
| 1080 | const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :binder_blk; | |
| 1081 | const literal = math.cast(u18, displacement) catch |_| break :binder_blk; | |
| 1082 | // ldr x16, label | |
| 1083 | mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{ | |
| 1084 | .literal = literal, | |
| 1085 | }).toU32()); | |
| 1086 | // nop | |
| 1087 | mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); | |
| 1088 | break :binder_blk_outer; | |
| 1089 | } | |
| 1090 | binder_blk: { | |
| 1091 | const new_this_addr = this_addr + @sizeOf(u32); | |
| 1092 | const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; | |
| 1093 | const literal = math.cast(u18, displacement) catch |_| break :binder_blk; | |
| 1094 | log.debug("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); | |
| 1095 | // Pad with nop to please division. | |
| 1096 | // nop | |
| 1097 | mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); | |
| 1098 | // ldr x16, label | |
| 1099 | mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ | |
| 1100 | .literal = literal, | |
| 1101 | }).toU32()); | |
| 1102 | break :binder_blk_outer; | |
| 1103 | } | |
| 1104 | // Use adrp followed by ldr(immediate). | |
| 1105 | const this_page = @intCast(i32, this_addr >> 12); | |
| 1106 | const target_page = @intCast(i32, target_addr >> 12); | |
| 1107 | const pages = @intCast(i21, target_page - this_page); | |
| 1108 | mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32()); | |
| 1109 | const narrowed = @truncate(u12, target_addr); | |
| 1110 | const offset = try math.divExact(u12, narrowed, 8); | |
| 1111 | mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ | |
| 1112 | .register = .{ | |
| 1113 | .rn = .x16, | |
| 1114 | .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), | |
| 1115 | }, | |
| 1116 | }).toU32()); | |
| 1117 | } | |
| 1118 | // br x16 | |
| 1119 | code[20] = 0x00; | |
| 1120 | code[21] = 0x02; | |
| 1121 | code[22] = 0x1f; | |
| 1122 | code[23] = 0xd6; | |
| 1123 | try self.file.?.pwriteAll(&code, stub_helper.offset); | |
| 1124 | break :blk stub_helper.offset + 6 * @sizeOf(u32); | |
| 1125 | }, | |
| 1126 | else => unreachable, | |
| 1127 | } | |
| 1128 | }; | |
| 1129 | ||
| 1130 | for (self.lazy_imports.items()) |_, i| { | |
| 1131 | const index = @intCast(u32, i); | |
| 1132 | try self.writeLazySymbolPointer(index); | |
| 1133 | try self.writeStub(index); | |
| 1134 | try self.writeStubInStubHelper(index); | |
| 1135 | } | |
| 1136 | } | |
| 1137 | ||
| 1138 | fn writeLazySymbolPointer(self: *Zld, index: u32) !void { | |
| 1139 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1140 | const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; | |
| 1141 | const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 1142 | const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; | |
| 1143 | ||
| 1144 | const stub_size: u4 = switch (self.arch.?) { | |
| 1145 | .x86_64 => 10, | |
| 1146 | .aarch64 => 3 * @sizeOf(u32), | |
| 1147 | else => unreachable, | |
| 1148 | }; | |
| 1149 | const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; | |
| 1150 | const end = stub_helper.addr + stub_off - stub_helper.offset; | |
| 1151 | var buf: [@sizeOf(u64)]u8 = undefined; | |
| 1152 | mem.writeIntLittle(u64, &buf, end); | |
| 1153 | const off = la_symbol_ptr.offset + index * @sizeOf(u64); | |
| 1154 | log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); | |
| 1155 | try self.file.?.pwriteAll(&buf, off); | |
| 1156 | } | |
| 1157 | ||
| 1158 | fn writeStub(self: *Zld, index: u32) !void { | |
| 1159 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1160 | const stubs = text_segment.sections.items[self.stubs_section_index.?]; | |
| 1161 | const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 1162 | const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; | |
| 1163 | ||
| 1164 | const stub_off = stubs.offset + index * stubs.reserved2; | |
| 1165 | const stub_addr = stubs.addr + index * stubs.reserved2; | |
| 1166 | const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); | |
| 1167 | log.debug("writing stub at 0x{x}", .{stub_off}); | |
| 1168 | var code = try self.allocator.alloc(u8, stubs.reserved2); | |
| 1169 | defer self.allocator.free(code); | |
| 1170 | switch (self.arch.?) { | |
| 1171 | .x86_64 => { | |
| 1172 | assert(la_ptr_addr >= stub_addr + stubs.reserved2); | |
| 1173 | const displacement = try math.cast(u32, la_ptr_addr - stub_addr - stubs.reserved2); | |
| 1174 | // jmp | |
| 1175 | code[0] = 0xff; | |
| 1176 | code[1] = 0x25; | |
| 1177 | mem.writeIntLittle(u32, code[2..][0..4], displacement); | |
| 1178 | }, | |
| 1179 | .aarch64 => { | |
| 1180 | assert(la_ptr_addr >= stub_addr); | |
| 1181 | outer: { | |
| 1182 | const this_addr = stub_addr; | |
| 1183 | const target_addr = la_ptr_addr; | |
| 1184 | inner: { | |
| 1185 | const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :inner; | |
| 1186 | const literal = math.cast(u18, displacement) catch |_| break :inner; | |
| 1187 | // ldr x16, literal | |
| 1188 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ | |
| 1189 | .literal = literal, | |
| 1190 | }).toU32()); | |
| 1191 | // nop | |
| 1192 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); | |
| 1193 | break :outer; | |
| 1194 | } | |
| 1195 | inner: { | |
| 1196 | const new_this_addr = this_addr + @sizeOf(u32); | |
| 1197 | const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; | |
| 1198 | const literal = math.cast(u18, displacement) catch |_| break :inner; | |
| 1199 | // nop | |
| 1200 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); | |
| 1201 | // ldr x16, literal | |
| 1202 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ | |
| 1203 | .literal = literal, | |
| 1204 | }).toU32()); | |
| 1205 | break :outer; | |
| 1206 | } | |
| 1207 | // Use adrp followed by ldr(immediate). | |
| 1208 | const this_page = @intCast(i32, this_addr >> 12); | |
| 1209 | const target_page = @intCast(i32, target_addr >> 12); | |
| 1210 | const pages = @intCast(i21, target_page - this_page); | |
| 1211 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x16, pages).toU32()); | |
| 1212 | const narrowed = @truncate(u12, target_addr); | |
| 1213 | const offset = try math.divExact(u12, narrowed, 8); | |
| 1214 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ | |
| 1215 | .register = .{ | |
| 1216 | .rn = .x16, | |
| 1217 | .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), | |
| 1218 | }, | |
| 1219 | }).toU32()); | |
| 1220 | } | |
| 1221 | // br x16 | |
| 1222 | mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.br(.x16).toU32()); | |
| 1223 | }, | |
| 1224 | else => unreachable, | |
| 1225 | } | |
| 1226 | try self.file.?.pwriteAll(code, stub_off); | |
| 1227 | } | |
| 1228 | ||
| 1229 | fn writeStubInStubHelper(self: *Zld, index: u32) !void { | |
| 1230 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1231 | const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; | |
| 1232 | ||
| 1233 | const stub_size: u4 = switch (self.arch.?) { | |
| 1234 | .x86_64 => 10, | |
| 1235 | .aarch64 => 3 * @sizeOf(u32), | |
| 1236 | else => unreachable, | |
| 1237 | }; | |
| 1238 | const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; | |
| 1239 | var code = try self.allocator.alloc(u8, stub_size); | |
| 1240 | defer self.allocator.free(code); | |
| 1241 | switch (self.arch.?) { | |
| 1242 | .x86_64 => { | |
| 1243 | const displacement = try math.cast( | |
| 1244 | i32, | |
| 1245 | @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - stub_size, | |
| 1246 | ); | |
| 1247 | // pushq | |
| 1248 | code[0] = 0x68; | |
| 1249 | mem.writeIntLittle(u32, code[1..][0..4], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. | |
| 1250 | // jmpq | |
| 1251 | code[5] = 0xe9; | |
| 1252 | mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); | |
| 1253 | }, | |
| 1254 | .aarch64 => { | |
| 1255 | const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); | |
| 1256 | const literal = @divExact(stub_size - @sizeOf(u32), 4); | |
| 1257 | // ldr w16, literal | |
| 1258 | mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ | |
| 1259 | .literal = literal, | |
| 1260 | }).toU32()); | |
| 1261 | // b disp | |
| 1262 | mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(displacement).toU32()); | |
| 1263 | mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. | |
| 1264 | }, | |
| 1265 | else => unreachable, | |
| 1266 | } | |
| 1267 | try self.file.?.pwriteAll(code, stub_off); | |
| 1268 | } | |
| 1269 | ||
| 1270 | fn resolveSymbols(self: *Zld) !void { | |
| 1271 | for (self.objects.items) |object, object_id| { | |
| 1272 | const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 1273 | log.debug("\n\n", .{}); | |
| 1274 | log.debug("resolving symbols in {s}", .{object.name}); | |
| 1275 | ||
| 1276 | for (object.symtab.items) |sym| { | |
| 1277 | if (isImport(&sym)) continue; | |
| 1278 | ||
| 1279 | const sym_name = object.getString(sym.n_strx); | |
| 1280 | const out_name = try self.allocator.dupe(u8, sym_name); | |
| 1281 | const locs = try self.locals.getOrPut(self.allocator, out_name); | |
| 1282 | defer { | |
| 1283 | if (locs.found_existing) self.allocator.free(out_name); | |
| 1284 | } | |
| 1285 | ||
| 1286 | if (!locs.found_existing) { | |
| 1287 | locs.entry.value = .{}; | |
| 1288 | } | |
| 1289 | ||
| 1290 | const tt: Symbol.Type = blk: { | |
| 1291 | if (isLocal(&sym)) { | |
| 1292 | break :blk .Local; | |
| 1293 | } else if (isWeakDef(&sym)) { | |
| 1294 | break :blk .WeakGlobal; | |
| 1295 | } else { | |
| 1296 | break :blk .Global; | |
| 1297 | } | |
| 1298 | }; | |
| 1299 | if (tt == .Global) { | |
| 1300 | for (locs.entry.value.items) |ss| { | |
| 1301 | if (ss.tt == .Global) { | |
| 1302 | log.debug("symbol already defined '{s}'", .{sym_name}); | |
| 1303 | continue; | |
| 1304 | // log.err("symbol '{s}' defined multiple times: {}", .{ sym_name, sym }); | |
| 1305 | // return error.MultipleSymbolDefinitions; | |
| 1306 | } | |
| 1307 | } | |
| 1308 | } | |
| 1309 | ||
| 1310 | const source_sect_id = sym.n_sect - 1; | |
| 1311 | const target_mapping = self.mappings.get(.{ | |
| 1312 | .object_id = @intCast(u16, object_id), | |
| 1313 | .source_sect_id = source_sect_id, | |
| 1314 | }) orelse { | |
| 1315 | if (self.unhandled_sections.get(.{ | |
| 1316 | .object_id = @intCast(u16, object_id), | |
| 1317 | .source_sect_id = source_sect_id, | |
| 1318 | }) != null) continue; | |
| 1319 | ||
| 1320 | log.err("section not mapped for symbol '{s}': {}", .{ sym_name, sym }); | |
| 1321 | return error.SectionNotMappedForSymbol; | |
| 1322 | }; | |
| 1323 | const source_sect = seg.sections.items[source_sect_id]; | |
| 1324 | const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; | |
| 1325 | const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; | |
| 1326 | const target_addr = target_sect.addr + target_mapping.offset; | |
| 1327 | const n_value = sym.n_value - source_sect.addr + target_addr; | |
| 1328 | ||
| 1329 | log.debug("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); | |
| 1330 | ||
| 1331 | // TODO there might be a more generic way of doing this. | |
| 1332 | var n_sect: u16 = 0; | |
| 1333 | for (self.load_commands.items) |cmd, cmd_id| { | |
| 1334 | if (cmd != .Segment) break; | |
| 1335 | if (cmd_id == target_mapping.target_seg_id) { | |
| 1336 | n_sect += target_mapping.target_sect_id + 1; | |
| 1337 | break; | |
| 1338 | } | |
| 1339 | n_sect += @intCast(u16, cmd.Segment.sections.items.len); | |
| 1340 | } | |
| 1341 | ||
| 1342 | const n_strx = try self.makeString(sym_name); | |
| 1343 | try locs.entry.value.append(self.allocator, .{ | |
| 1344 | .inner = .{ | |
| 1345 | .n_strx = n_strx, | |
| 1346 | .n_value = n_value, | |
| 1347 | .n_type = macho.N_SECT, | |
| 1348 | .n_desc = sym.n_desc, | |
| 1349 | .n_sect = @intCast(u8, n_sect), | |
| 1350 | }, | |
| 1351 | .tt = tt, | |
| 1352 | .object_id = @intCast(u16, object_id), | |
| 1353 | }); | |
| 1354 | } | |
| 1355 | } | |
| 1356 | } | |
| 1357 | ||
| 1358 | fn doRelocs(self: *Zld) !void { | |
| 1359 | for (self.objects.items) |object, object_id| { | |
| 1360 | log.debug("\n\n", .{}); | |
| 1361 | log.debug("relocating object {s}", .{object.name}); | |
| 1362 | ||
| 1363 | const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 1364 | ||
| 1365 | for (seg.sections.items) |sect, source_sect_id| { | |
| 1366 | const segname = parseName(&sect.segname); | |
| 1367 | const sectname = parseName(&sect.sectname); | |
| 1368 | ||
| 1369 | var code = try self.allocator.alloc(u8, sect.size); | |
| 1370 | _ = try object.file.preadAll(code, sect.offset); | |
| 1371 | defer self.allocator.free(code); | |
| 1372 | ||
| 1373 | // Parse relocs (if any) | |
| 1374 | var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc); | |
| 1375 | defer self.allocator.free(raw_relocs); | |
| 1376 | _ = try object.file.preadAll(raw_relocs, sect.reloff); | |
| 1377 | const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs); | |
| 1378 | ||
| 1379 | // Get mapping | |
| 1380 | const target_mapping = self.mappings.get(.{ | |
| 1381 | .object_id = @intCast(u16, object_id), | |
| 1382 | .source_sect_id = @intCast(u16, source_sect_id), | |
| 1383 | }) orelse { | |
| 1384 | log.debug("no mapping for {s},{s}; skipping", .{ segname, sectname }); | |
| 1385 | continue; | |
| 1386 | }; | |
| 1387 | const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; | |
| 1388 | const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; | |
| 1389 | const target_sect_addr = target_sect.addr + target_mapping.offset; | |
| 1390 | const target_sect_off = target_sect.offset + target_mapping.offset; | |
| 1391 | ||
| 1392 | var addend: ?u64 = null; | |
| 1393 | var sub: ?i64 = null; | |
| 1394 | ||
| 1395 | for (relocs) |rel| { | |
| 1396 | const off = @intCast(u32, rel.r_address); | |
| 1397 | const this_addr = target_sect_addr + off; | |
| 1398 | ||
| 1399 | switch (self.arch.?) { | |
| 1400 | .aarch64 => { | |
| 1401 | const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); | |
| 1402 | log.debug("{s}", .{rel_type}); | |
| 1403 | log.debug(" | source address 0x{x}", .{this_addr}); | |
| 1404 | log.debug(" | offset 0x{x}", .{off}); | |
| 1405 | ||
| 1406 | if (rel_type == .ARM64_RELOC_ADDEND) { | |
| 1407 | addend = rel.r_symbolnum; | |
| 1408 | log.debug(" | calculated addend = 0x{x}", .{addend}); | |
| 1409 | // TODO followed by either PAGE21 or PAGEOFF12 only. | |
| 1410 | continue; | |
| 1411 | } | |
| 1412 | }, | |
| 1413 | .x86_64 => { | |
| 1414 | const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); | |
| 1415 | log.debug("{s}", .{rel_type}); | |
| 1416 | log.debug(" | source address 0x{x}", .{this_addr}); | |
| 1417 | log.debug(" | offset 0x{x}", .{off}); | |
| 1418 | }, | |
| 1419 | else => {}, | |
| 1420 | } | |
| 1421 | ||
| 1422 | const target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel); | |
| 1423 | log.debug(" | target address 0x{x}", .{target_addr}); | |
| 1424 | if (rel.r_extern == 1) { | |
| 1425 | const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); | |
| 1426 | log.debug(" | target symbol '{s}'", .{target_symname}); | |
| 1427 | } else { | |
| 1428 | const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname; | |
| 1429 | log.debug(" | target section '{s}'", .{parseName(&target_sectname)}); | |
| 1430 | } | |
| 1431 | ||
| 1432 | switch (self.arch.?) { | |
| 1433 | .x86_64 => { | |
| 1434 | const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); | |
| 1435 | ||
| 1436 | switch (rel_type) { | |
| 1437 | .X86_64_RELOC_BRANCH => { | |
| 1438 | assert(rel.r_length == 2); | |
| 1439 | const inst = code[off..][0..4]; | |
| 1440 | const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); | |
| 1441 | mem.writeIntLittle(u32, inst, displacement); | |
| 1442 | }, | |
| 1443 | .X86_64_RELOC_GOT_LOAD => { | |
| 1444 | assert(rel.r_length == 2); | |
| 1445 | const inst = code[off..][0..4]; | |
| 1446 | const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); | |
| 1447 | ||
| 1448 | blk: { | |
| 1449 | const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 1450 | const got = data_const_seg.sections.items[self.got_section_index.?]; | |
| 1451 | if (got.addr <= target_addr and target_addr < got.addr + got.size) break :blk; | |
| 1452 | log.debug(" | rewriting to leaq", .{}); | |
| 1453 | code[off - 2] = 0x8d; | |
| 1454 | } | |
| 1455 | ||
| 1456 | mem.writeIntLittle(u32, inst, displacement); | |
| 1457 | }, | |
| 1458 | .X86_64_RELOC_GOT => { | |
| 1459 | assert(rel.r_length == 2); | |
| 1460 | // TODO Instead of referring to the target symbol directly, we refer to it | |
| 1461 | // indirectly via GOT. Getting actual target address should be done in the | |
| 1462 | // helper relocTargetAddr function rather than here. | |
| 1463 | const sym = object.symtab.items[rel.r_symbolnum]; | |
| 1464 | const sym_name = try self.allocator.dupe(u8, object.getString(sym.n_strx)); | |
| 1465 | const res = try self.nonlazy_pointers.getOrPut(self.allocator, sym_name); | |
| 1466 | defer if (res.found_existing) self.allocator.free(sym_name); | |
| 1467 | ||
| 1468 | const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 1469 | const got = data_const_seg.sections.items[self.got_section_index.?]; | |
| 1470 | ||
| 1471 | if (!res.found_existing) { | |
| 1472 | const index = @intCast(u32, self.nonlazy_pointers.items().len) - 1; | |
| 1473 | assert(index < max_local_got_indirections); // TODO This is just a temp solution. | |
| 1474 | res.entry.value = .{ | |
| 1475 | .index = index, | |
| 1476 | .target_addr = target_addr, | |
| 1477 | }; | |
| 1478 | var buf: [@sizeOf(u64)]u8 = undefined; | |
| 1479 | mem.writeIntLittle(u64, &buf, target_addr); | |
| 1480 | const got_offset = got.offset + (index + self.nonlazy_imports.items().len) * @sizeOf(u64); | |
| 1481 | ||
| 1482 | log.debug(" | GOT off 0x{x}", .{got.offset}); | |
| 1483 | log.debug(" | writing GOT entry 0x{x} at 0x{x}", .{ target_addr, got_offset }); | |
| 1484 | ||
| 1485 | try self.file.?.pwriteAll(&buf, got_offset); | |
| 1486 | } | |
| 1487 | ||
| 1488 | const index = res.entry.value.index + self.nonlazy_imports.items().len; | |
| 1489 | const actual_target_addr = got.addr + index * @sizeOf(u64); | |
| 1490 | ||
| 1491 | log.debug(" | GOT addr 0x{x}", .{got.addr}); | |
| 1492 | log.debug(" | actual target address in GOT 0x{x}", .{actual_target_addr}); | |
| 1493 | ||
| 1494 | const inst = code[off..][0..4]; | |
| 1495 | const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, actual_target_addr) - @intCast(i64, this_addr) - 4)); | |
| 1496 | mem.writeIntLittle(u32, inst, displacement); | |
| 1497 | }, | |
| 1498 | .X86_64_RELOC_TLV => { | |
| 1499 | assert(rel.r_length == 2); | |
| 1500 | // We need to rewrite the opcode from movq to leaq. | |
| 1501 | code[off - 2] = 0x8d; | |
| 1502 | // Add displacement. | |
| 1503 | const inst = code[off..][0..4]; | |
| 1504 | const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); | |
| 1505 | mem.writeIntLittle(u32, inst, displacement); | |
| 1506 | }, | |
| 1507 | .X86_64_RELOC_SIGNED, | |
| 1508 | .X86_64_RELOC_SIGNED_1, | |
| 1509 | .X86_64_RELOC_SIGNED_2, | |
| 1510 | .X86_64_RELOC_SIGNED_4, | |
| 1511 | => { | |
| 1512 | assert(rel.r_length == 2); | |
| 1513 | const inst = code[off..][0..4]; | |
| 1514 | const offset = @intCast(i64, mem.readIntLittle(i32, inst)); | |
| 1515 | log.debug(" | calculated addend 0x{x}", .{offset}); | |
| 1516 | const actual_target_addr = blk: { | |
| 1517 | if (rel.r_extern == 1) { | |
| 1518 | break :blk @intCast(i64, target_addr) + offset; | |
| 1519 | } else { | |
| 1520 | const correction: i4 = switch (rel_type) { | |
| 1521 | .X86_64_RELOC_SIGNED => 0, | |
| 1522 | .X86_64_RELOC_SIGNED_1 => 1, | |
| 1523 | .X86_64_RELOC_SIGNED_2 => 2, | |
| 1524 | .X86_64_RELOC_SIGNED_4 => 4, | |
| 1525 | else => unreachable, | |
| 1526 | }; | |
| 1527 | log.debug(" | calculated correction 0x{x}", .{correction}); | |
| 1528 | ||
| 1529 | // The value encoded in the instruction is a displacement - 4 - correction. | |
| 1530 | // To obtain the adjusted target address in the final binary, we need | |
| 1531 | // calculate the original target address within the object file, establish | |
| 1532 | // what the offset from the original target section was, and apply this | |
| 1533 | // offset to the resultant target section with this relocated binary. | |
| 1534 | const orig_sect_id = @intCast(u16, rel.r_symbolnum - 1); | |
| 1535 | const target_map = self.mappings.get(.{ | |
| 1536 | .object_id = @intCast(u16, object_id), | |
| 1537 | .source_sect_id = orig_sect_id, | |
| 1538 | }) orelse unreachable; | |
| 1539 | const orig_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 1540 | const orig_sect = orig_seg.sections.items[orig_sect_id]; | |
| 1541 | const orig_offset = off + offset + 4 + correction - @intCast(i64, orig_sect.addr); | |
| 1542 | log.debug(" | original offset 0x{x}", .{orig_offset}); | |
| 1543 | const adjusted = @intCast(i64, target_addr) + orig_offset; | |
| 1544 | log.debug(" | adjusted target address 0x{x}", .{adjusted}); | |
| 1545 | break :blk adjusted - correction; | |
| 1546 | } | |
| 1547 | }; | |
| 1548 | const result = actual_target_addr - @intCast(i64, this_addr) - 4; | |
| 1549 | const displacement = @bitCast(u32, @intCast(i32, result)); | |
| 1550 | mem.writeIntLittle(u32, inst, displacement); | |
| 1551 | }, | |
| 1552 | .X86_64_RELOC_SUBTRACTOR => { | |
| 1553 | sub = @intCast(i64, target_addr); | |
| 1554 | }, | |
| 1555 | .X86_64_RELOC_UNSIGNED => { | |
| 1556 | switch (rel.r_length) { | |
| 1557 | 3 => { | |
| 1558 | const inst = code[off..][0..8]; | |
| 1559 | const offset = mem.readIntLittle(i64, inst); | |
| 1560 | ||
| 1561 | const result = outer: { | |
| 1562 | if (rel.r_extern == 1) { | |
| 1563 | log.debug(" | calculated addend 0x{x}", .{offset}); | |
| 1564 | if (sub) |s| { | |
| 1565 | break :outer @intCast(i64, target_addr) - s + offset; | |
| 1566 | } else { | |
| 1567 | break :outer @intCast(i64, target_addr) + offset; | |
| 1568 | } | |
| 1569 | } else { | |
| 1570 | // The value encoded in the instruction is an absolute offset | |
| 1571 | // from the start of MachO header to the target address in the | |
| 1572 | // object file. To extract the address, we calculate the offset from | |
| 1573 | // the beginning of the source section to the address, and apply it to | |
| 1574 | // the target address value. | |
| 1575 | const orig_sect_id = @intCast(u16, rel.r_symbolnum - 1); | |
| 1576 | const target_map = self.mappings.get(.{ | |
| 1577 | .object_id = @intCast(u16, object_id), | |
| 1578 | .source_sect_id = orig_sect_id, | |
| 1579 | }) orelse unreachable; | |
| 1580 | const orig_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 1581 | const orig_sect = orig_seg.sections.items[orig_sect_id]; | |
| 1582 | const orig_offset = offset - @intCast(i64, orig_sect.addr); | |
| 1583 | const actual_target_addr = inner: { | |
| 1584 | if (sub) |s| { | |
| 1585 | break :inner @intCast(i64, target_addr) - s + orig_offset; | |
| 1586 | } else { | |
| 1587 | break :inner @intCast(i64, target_addr) + orig_offset; | |
| 1588 | } | |
| 1589 | }; | |
| 1590 | log.debug(" | adjusted target address 0x{x}", .{actual_target_addr}); | |
| 1591 | break :outer actual_target_addr; | |
| 1592 | } | |
| 1593 | }; | |
| 1594 | mem.writeIntLittle(u64, inst, @bitCast(u64, result)); | |
| 1595 | sub = null; | |
| 1596 | ||
| 1597 | rebases: { | |
| 1598 | var hit: bool = false; | |
| 1599 | if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { | |
| 1600 | if (self.data_section_index) |index| { | |
| 1601 | if (index == target_mapping.target_sect_id) hit = true; | |
| 1602 | } | |
| 1603 | } | |
| 1604 | if (target_mapping.target_seg_id == self.data_const_segment_cmd_index.?) { | |
| 1605 | if (self.data_const_section_index) |index| { | |
| 1606 | if (index == target_mapping.target_sect_id) hit = true; | |
| 1607 | } | |
| 1608 | } | |
| 1609 | ||
| 1610 | if (!hit) break :rebases; | |
| 1611 | ||
| 1612 | try self.local_rebases.append(self.allocator, .{ | |
| 1613 | .offset = this_addr - target_seg.inner.vmaddr, | |
| 1614 | .segment_id = target_mapping.target_seg_id, | |
| 1615 | }); | |
| 1616 | } | |
| 1617 | // TLV is handled via a separate offset mechanism. | |
| 1618 | // Calculate the offset to the initializer. | |
| 1619 | if (target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) tlv: { | |
| 1620 | assert(rel.r_extern == 1); | |
| 1621 | const sym = object.symtab.items[rel.r_symbolnum]; | |
| 1622 | if (isImport(&sym)) break :tlv; | |
| 1623 | ||
| 1624 | const base_addr = blk: { | |
| 1625 | if (self.tlv_data_section_index) |index| { | |
| 1626 | const tlv_data = target_seg.sections.items[index]; | |
| 1627 | break :blk tlv_data.addr; | |
| 1628 | } else { | |
| 1629 | const tlv_bss = target_seg.sections.items[self.tlv_bss_section_index.?]; | |
| 1630 | break :blk tlv_bss.addr; | |
| 1631 | } | |
| 1632 | }; | |
| 1633 | // Since we require TLV data to always preceed TLV bss section, we calculate | |
| 1634 | // offsets wrt to the former if it is defined; otherwise, wrt to the latter. | |
| 1635 | try self.threadlocal_offsets.append(self.allocator, target_addr - base_addr); | |
| 1636 | } | |
| 1637 | }, | |
| 1638 | 2 => { | |
| 1639 | const inst = code[off..][0..4]; | |
| 1640 | const offset = mem.readIntLittle(i32, inst); | |
| 1641 | log.debug(" | calculated addend 0x{x}", .{offset}); | |
| 1642 | const result = if (sub) |s| | |
| 1643 | @intCast(i64, target_addr) - s + offset | |
| 1644 | else | |
| 1645 | @intCast(i64, target_addr) + offset; | |
| 1646 | mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result))); | |
| 1647 | sub = null; | |
| 1648 | }, | |
| 1649 | else => |len| { | |
| 1650 | log.err("unexpected relocation length 0x{x}", .{len}); | |
| 1651 | return error.UnexpectedRelocationLength; | |
| 1652 | }, | |
| 1653 | } | |
| 1654 | }, | |
| 1655 | } | |
| 1656 | }, | |
| 1657 | .aarch64 => { | |
| 1658 | const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); | |
| 1659 | ||
| 1660 | switch (rel_type) { | |
| 1661 | .ARM64_RELOC_BRANCH26 => { | |
| 1662 | assert(rel.r_length == 2); | |
| 1663 | const inst = code[off..][0..4]; | |
| 1664 | const displacement = @intCast( | |
| 1665 | i28, | |
| 1666 | @intCast(i64, target_addr) - @intCast(i64, this_addr), | |
| 1667 | ); | |
| 1668 | var parsed = mem.bytesAsValue( | |
| 1669 | meta.TagPayload( | |
| 1670 | aarch64.Instruction, | |
| 1671 | aarch64.Instruction.UnconditionalBranchImmediate, | |
| 1672 | ), | |
| 1673 | inst, | |
| 1674 | ); | |
| 1675 | parsed.imm26 = @truncate(u26, @bitCast(u28, displacement) >> 2); | |
| 1676 | }, | |
| 1677 | .ARM64_RELOC_PAGE21, | |
| 1678 | .ARM64_RELOC_GOT_LOAD_PAGE21, | |
| 1679 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | |
| 1680 | => { | |
| 1681 | assert(rel.r_length == 2); | |
| 1682 | const inst = code[off..][0..4]; | |
| 1683 | const ta = if (addend) |a| target_addr + a else target_addr; | |
| 1684 | const this_page = @intCast(i32, this_addr >> 12); | |
| 1685 | const target_page = @intCast(i32, ta >> 12); | |
| 1686 | const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); | |
| 1687 | log.debug(" | moving by {} pages", .{pages}); | |
| 1688 | var parsed = mem.bytesAsValue( | |
| 1689 | meta.TagPayload( | |
| 1690 | aarch64.Instruction, | |
| 1691 | aarch64.Instruction.PCRelativeAddress, | |
| 1692 | ), | |
| 1693 | inst, | |
| 1694 | ); | |
| 1695 | parsed.immhi = @truncate(u19, pages >> 2); | |
| 1696 | parsed.immlo = @truncate(u2, pages); | |
| 1697 | addend = null; | |
| 1698 | }, | |
| 1699 | .ARM64_RELOC_PAGEOFF12, | |
| 1700 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, | |
| 1701 | => { | |
| 1702 | const inst = code[off..][0..4]; | |
| 1703 | if (aarch64IsArithmetic(inst)) { | |
| 1704 | log.debug(" | detected ADD opcode", .{}); | |
| 1705 | // add | |
| 1706 | var parsed = mem.bytesAsValue( | |
| 1707 | meta.TagPayload( | |
| 1708 | aarch64.Instruction, | |
| 1709 | aarch64.Instruction.AddSubtractImmediate, | |
| 1710 | ), | |
| 1711 | inst, | |
| 1712 | ); | |
| 1713 | const ta = if (addend) |a| target_addr + a else target_addr; | |
| 1714 | const narrowed = @truncate(u12, ta); | |
| 1715 | parsed.imm12 = narrowed; | |
| 1716 | } else { | |
| 1717 | log.debug(" | detected LDR/STR opcode", .{}); | |
| 1718 | // ldr/str | |
| 1719 | var parsed = mem.bytesAsValue( | |
| 1720 | meta.TagPayload( | |
| 1721 | aarch64.Instruction, | |
| 1722 | aarch64.Instruction.LoadStoreRegister, | |
| 1723 | ), | |
| 1724 | inst, | |
| 1725 | ); | |
| 1726 | ||
| 1727 | const ta = if (addend) |a| target_addr + a else target_addr; | |
| 1728 | const narrowed = @truncate(u12, ta); | |
| 1729 | log.debug(" | narrowed 0x{x}", .{narrowed}); | |
| 1730 | log.debug(" | parsed.size 0x{x}", .{parsed.size}); | |
| 1731 | ||
| 1732 | if (rel_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12) blk: { | |
| 1733 | const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 1734 | const got = data_const_seg.sections.items[self.got_section_index.?]; | |
| 1735 | if (got.addr <= target_addr and target_addr < got.addr + got.size) break :blk; | |
| 1736 | ||
| 1737 | log.debug(" | rewriting to add", .{}); | |
| 1738 | mem.writeIntLittle(u32, inst, aarch64.Instruction.add( | |
| 1739 | @intToEnum(aarch64.Register, parsed.rt), | |
| 1740 | @intToEnum(aarch64.Register, parsed.rn), | |
| 1741 | narrowed, | |
| 1742 | false, | |
| 1743 | ).toU32()); | |
| 1744 | addend = null; | |
| 1745 | continue; | |
| 1746 | } | |
| 1747 | ||
| 1748 | const offset: u12 = blk: { | |
| 1749 | if (parsed.size == 0) { | |
| 1750 | if (parsed.v == 1) { | |
| 1751 | // 128-bit SIMD is scaled by 16. | |
| 1752 | break :blk try math.divExact(u12, narrowed, 16); | |
| 1753 | } | |
| 1754 | // Otherwise, 8-bit SIMD or ldrb. | |
| 1755 | break :blk narrowed; | |
| 1756 | } else { | |
| 1757 | const denom: u4 = try math.powi(u4, 2, parsed.size); | |
| 1758 | break :blk try math.divExact(u12, narrowed, denom); | |
| 1759 | } | |
| 1760 | }; | |
| 1761 | parsed.offset = offset; | |
| 1762 | } | |
| 1763 | addend = null; | |
| 1764 | }, | |
| 1765 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { | |
| 1766 | const RegInfo = struct { | |
| 1767 | rd: u5, | |
| 1768 | rn: u5, | |
| 1769 | size: u1, | |
| 1770 | }; | |
| 1771 | const inst = code[off..][0..4]; | |
| 1772 | const parsed: RegInfo = blk: { | |
| 1773 | if (aarch64IsArithmetic(inst)) { | |
| 1774 | const curr = mem.bytesAsValue( | |
| 1775 | meta.TagPayload( | |
| 1776 | aarch64.Instruction, | |
| 1777 | aarch64.Instruction.AddSubtractImmediate, | |
| 1778 | ), | |
| 1779 | inst, | |
| 1780 | ); | |
| 1781 | break :blk .{ .rd = curr.rd, .rn = curr.rn, .size = curr.sf }; | |
| 1782 | } else { | |
| 1783 | const curr = mem.bytesAsValue( | |
| 1784 | meta.TagPayload( | |
| 1785 | aarch64.Instruction, | |
| 1786 | aarch64.Instruction.LoadStoreRegister, | |
| 1787 | ), | |
| 1788 | inst, | |
| 1789 | ); | |
| 1790 | break :blk .{ .rd = curr.rt, .rn = curr.rn, .size = @truncate(u1, curr.size) }; | |
| 1791 | } | |
| 1792 | }; | |
| 1793 | const ta = if (addend) |a| target_addr + a else target_addr; | |
| 1794 | const narrowed = @truncate(u12, ta); | |
| 1795 | log.debug(" | rewriting TLV access to ADD opcode", .{}); | |
| 1796 | // For TLV, we always generate an add instruction. | |
| 1797 | mem.writeIntLittle(u32, inst, aarch64.Instruction.add( | |
| 1798 | @intToEnum(aarch64.Register, parsed.rd), | |
| 1799 | @intToEnum(aarch64.Register, parsed.rn), | |
| 1800 | narrowed, | |
| 1801 | false, | |
| 1802 | ).toU32()); | |
| 1803 | }, | |
| 1804 | .ARM64_RELOC_SUBTRACTOR => { | |
| 1805 | sub = @intCast(i64, target_addr); | |
| 1806 | }, | |
| 1807 | .ARM64_RELOC_UNSIGNED => { | |
| 1808 | switch (rel.r_length) { | |
| 1809 | 3 => { | |
| 1810 | const inst = code[off..][0..8]; | |
| 1811 | const offset = mem.readIntLittle(i64, inst); | |
| 1812 | log.debug(" | calculated addend 0x{x}", .{offset}); | |
| 1813 | const result = if (sub) |s| | |
| 1814 | @intCast(i64, target_addr) - s + offset | |
| 1815 | else | |
| 1816 | @intCast(i64, target_addr) + offset; | |
| 1817 | mem.writeIntLittle(u64, inst, @bitCast(u64, result)); | |
| 1818 | sub = null; | |
| 1819 | ||
| 1820 | rebases: { | |
| 1821 | var hit: bool = false; | |
| 1822 | if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { | |
| 1823 | if (self.data_section_index) |index| { | |
| 1824 | if (index == target_mapping.target_sect_id) hit = true; | |
| 1825 | } | |
| 1826 | } | |
| 1827 | if (target_mapping.target_seg_id == self.data_const_segment_cmd_index.?) { | |
| 1828 | if (self.data_const_section_index) |index| { | |
| 1829 | if (index == target_mapping.target_sect_id) hit = true; | |
| 1830 | } | |
| 1831 | } | |
| 1832 | ||
| 1833 | if (!hit) break :rebases; | |
| 1834 | ||
| 1835 | try self.local_rebases.append(self.allocator, .{ | |
| 1836 | .offset = this_addr - target_seg.inner.vmaddr, | |
| 1837 | .segment_id = target_mapping.target_seg_id, | |
| 1838 | }); | |
| 1839 | } | |
| 1840 | // TLV is handled via a separate offset mechanism. | |
| 1841 | // Calculate the offset to the initializer. | |
| 1842 | if (target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) tlv: { | |
| 1843 | assert(rel.r_extern == 1); | |
| 1844 | const sym = object.symtab.items[rel.r_symbolnum]; | |
| 1845 | if (isImport(&sym)) break :tlv; | |
| 1846 | ||
| 1847 | const base_addr = blk: { | |
| 1848 | if (self.tlv_data_section_index) |index| { | |
| 1849 | const tlv_data = target_seg.sections.items[index]; | |
| 1850 | break :blk tlv_data.addr; | |
| 1851 | } else { | |
| 1852 | const tlv_bss = target_seg.sections.items[self.tlv_bss_section_index.?]; | |
| 1853 | break :blk tlv_bss.addr; | |
| 1854 | } | |
| 1855 | }; | |
| 1856 | // Since we require TLV data to always preceed TLV bss section, we calculate | |
| 1857 | // offsets wrt to the former if it is defined; otherwise, wrt to the latter. | |
| 1858 | try self.threadlocal_offsets.append(self.allocator, target_addr - base_addr); | |
| 1859 | } | |
| 1860 | }, | |
| 1861 | 2 => { | |
| 1862 | const inst = code[off..][0..4]; | |
| 1863 | const offset = mem.readIntLittle(i32, inst); | |
| 1864 | log.debug(" | calculated addend 0x{x}", .{offset}); | |
| 1865 | const result = if (sub) |s| | |
| 1866 | @intCast(i64, target_addr) - s + offset | |
| 1867 | else | |
| 1868 | @intCast(i64, target_addr) + offset; | |
| 1869 | mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result))); | |
| 1870 | sub = null; | |
| 1871 | }, | |
| 1872 | else => |len| { | |
| 1873 | log.err("unexpected relocation length 0x{x}", .{len}); | |
| 1874 | return error.UnexpectedRelocationLength; | |
| 1875 | }, | |
| 1876 | } | |
| 1877 | }, | |
| 1878 | .ARM64_RELOC_POINTER_TO_GOT => return error.TODOArm64RelocPointerToGot, | |
| 1879 | else => unreachable, | |
| 1880 | } | |
| 1881 | }, | |
| 1882 | else => unreachable, | |
| 1883 | } | |
| 1884 | } | |
| 1885 | ||
| 1886 | log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ | |
| 1887 | segname, | |
| 1888 | sectname, | |
| 1889 | object.name, | |
| 1890 | target_sect_off, | |
| 1891 | target_sect_off + code.len, | |
| 1892 | }); | |
| 1893 | ||
| 1894 | if (target_sect.flags == macho.S_ZEROFILL or | |
| 1895 | target_sect.flags == macho.S_THREAD_LOCAL_ZEROFILL or | |
| 1896 | target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) | |
| 1897 | { | |
| 1898 | log.debug("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ | |
| 1899 | parseName(&target_sect.segname), | |
| 1900 | parseName(&target_sect.sectname), | |
| 1901 | target_sect_off, | |
| 1902 | target_sect_off + code.len, | |
| 1903 | }); | |
| 1904 | // Zero-out the space | |
| 1905 | var zeroes = try self.allocator.alloc(u8, code.len); | |
| 1906 | defer self.allocator.free(zeroes); | |
| 1907 | mem.set(u8, zeroes, 0); | |
| 1908 | try self.file.?.pwriteAll(zeroes, target_sect_off); | |
| 1909 | } else { | |
| 1910 | try self.file.?.pwriteAll(code, target_sect_off); | |
| 1911 | } | |
| 1912 | } | |
| 1913 | } | |
| 1914 | } | |
| 1915 | ||
| 1916 | fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 { | |
| 1917 | const object = self.objects.items[object_id]; | |
| 1918 | const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 1919 | const target_addr = blk: { | |
| 1920 | if (rel.r_extern == 1) { | |
| 1921 | const sym = object.symtab.items[rel.r_symbolnum]; | |
| 1922 | if (isLocal(&sym) or isExport(&sym)) { | |
| 1923 | // Relocate using section offsets only. | |
| 1924 | const target_mapping = self.mappings.get(.{ | |
| 1925 | .object_id = object_id, | |
| 1926 | .source_sect_id = sym.n_sect - 1, | |
| 1927 | }) orelse unreachable; | |
| 1928 | const source_sect = seg.sections.items[target_mapping.source_sect_id]; | |
| 1929 | const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; | |
| 1930 | const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; | |
| 1931 | const target_sect_addr = target_sect.addr + target_mapping.offset; | |
| 1932 | log.debug(" | symbol local to object", .{}); | |
| 1933 | break :blk target_sect_addr + sym.n_value - source_sect.addr; | |
| 1934 | } else if (isImport(&sym)) { | |
| 1935 | // Relocate to either the artifact's local symbol, or an import from | |
| 1936 | // shared library. | |
| 1937 | const sym_name = object.getString(sym.n_strx); | |
| 1938 | if (self.locals.get(sym_name)) |locs| { | |
| 1939 | var n_value: ?u64 = null; | |
| 1940 | for (locs.items) |loc| { | |
| 1941 | switch (loc.tt) { | |
| 1942 | .Global => { | |
| 1943 | n_value = loc.inner.n_value; | |
| 1944 | break; | |
| 1945 | }, | |
| 1946 | .WeakGlobal => { | |
| 1947 | n_value = loc.inner.n_value; | |
| 1948 | }, | |
| 1949 | .Local => {}, | |
| 1950 | } | |
| 1951 | } | |
| 1952 | if (n_value) |v| { | |
| 1953 | break :blk v; | |
| 1954 | } | |
| 1955 | log.err("local symbol export '{s}' not found", .{sym_name}); | |
| 1956 | return error.LocalSymbolExportNotFound; | |
| 1957 | } else if (self.lazy_imports.get(sym_name)) |ext| { | |
| 1958 | const segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 1959 | const stubs = segment.sections.items[self.stubs_section_index.?]; | |
| 1960 | break :blk stubs.addr + ext.index * stubs.reserved2; | |
| 1961 | } else if (self.nonlazy_imports.get(sym_name)) |ext| { | |
| 1962 | const segment = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 1963 | const got = segment.sections.items[self.got_section_index.?]; | |
| 1964 | break :blk got.addr + ext.index * @sizeOf(u64); | |
| 1965 | } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { | |
| 1966 | const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 1967 | const tlv = segment.sections.items[self.tlv_section_index.?]; | |
| 1968 | break :blk tlv.addr + self.tlv_bootstrap.?.index * @sizeOf(u64); | |
| 1969 | } else { | |
| 1970 | log.err("failed to resolve symbol '{s}' as a relocation target", .{sym_name}); | |
| 1971 | return error.FailedToResolveRelocationTarget; | |
| 1972 | } | |
| 1973 | } else { | |
| 1974 | log.err("unexpected symbol {}, {s}", .{ sym, object.getString(sym.n_strx) }); | |
| 1975 | return error.UnexpectedSymbolWhenRelocating; | |
| 1976 | } | |
| 1977 | } else { | |
| 1978 | // TODO I think we need to reparse the relocation_info as scattered_relocation_info | |
| 1979 | // here to get the actual section plus offset into that section of the relocated | |
| 1980 | // symbol. Unless the fine-grained location is encoded within the cell in the code | |
| 1981 | // buffer? | |
| 1982 | const target_mapping = self.mappings.get(.{ | |
| 1983 | .object_id = object_id, | |
| 1984 | .source_sect_id = @intCast(u16, rel.r_symbolnum - 1), | |
| 1985 | }) orelse unreachable; | |
| 1986 | const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; | |
| 1987 | const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; | |
| 1988 | break :blk target_sect.addr + target_mapping.offset; | |
| 1989 | } | |
| 1990 | }; | |
| 1991 | return target_addr; | |
| 1992 | } | |
| 1993 | ||
| 1994 | fn populateMetadata(self: *Zld) !void { | |
| 1995 | if (self.pagezero_segment_cmd_index == null) { | |
| 1996 | self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 1997 | try self.load_commands.append(self.allocator, .{ | |
| 1998 | .Segment = SegmentCommand.empty(.{ | |
| 1999 | .cmd = macho.LC_SEGMENT_64, | |
| 2000 | .cmdsize = @sizeOf(macho.segment_command_64), | |
| 2001 | .segname = makeStaticString("__PAGEZERO"), | |
| 2002 | .vmaddr = 0, | |
| 2003 | .vmsize = 0x100000000, // size always set to 4GB | |
| 2004 | .fileoff = 0, | |
| 2005 | .filesize = 0, | |
| 2006 | .maxprot = 0, | |
| 2007 | .initprot = 0, | |
| 2008 | .nsects = 0, | |
| 2009 | .flags = 0, | |
| 2010 | }), | |
| 2011 | }); | |
| 2012 | } | |
| 2013 | ||
| 2014 | if (self.text_segment_cmd_index == null) { | |
| 2015 | self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2016 | try self.load_commands.append(self.allocator, .{ | |
| 2017 | .Segment = SegmentCommand.empty(.{ | |
| 2018 | .cmd = macho.LC_SEGMENT_64, | |
| 2019 | .cmdsize = @sizeOf(macho.segment_command_64), | |
| 2020 | .segname = makeStaticString("__TEXT"), | |
| 2021 | .vmaddr = 0x100000000, // always starts at 4GB | |
| 2022 | .vmsize = 0, | |
| 2023 | .fileoff = 0, | |
| 2024 | .filesize = 0, | |
| 2025 | .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE, | |
| 2026 | .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE, | |
| 2027 | .nsects = 0, | |
| 2028 | .flags = 0, | |
| 2029 | }), | |
| 2030 | }); | |
| 2031 | } | |
| 2032 | ||
| 2033 | if (self.text_section_index == null) { | |
| 2034 | const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2035 | self.text_section_index = @intCast(u16, text_seg.sections.items.len); | |
| 2036 | const alignment: u2 = switch (self.arch.?) { | |
| 2037 | .x86_64 => 0, | |
| 2038 | .aarch64 => 2, | |
| 2039 | else => unreachable, // unhandled architecture type | |
| 2040 | }; | |
| 2041 | try text_seg.addSection(self.allocator, .{ | |
| 2042 | .sectname = makeStaticString("__text"), | |
| 2043 | .segname = makeStaticString("__TEXT"), | |
| 2044 | .addr = 0, | |
| 2045 | .size = 0, | |
| 2046 | .offset = 0, | |
| 2047 | .@"align" = alignment, | |
| 2048 | .reloff = 0, | |
| 2049 | .nreloc = 0, | |
| 2050 | .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, | |
| 2051 | .reserved1 = 0, | |
| 2052 | .reserved2 = 0, | |
| 2053 | .reserved3 = 0, | |
| 2054 | }); | |
| 2055 | } | |
| 2056 | ||
| 2057 | if (self.stubs_section_index == null) { | |
| 2058 | const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2059 | self.stubs_section_index = @intCast(u16, text_seg.sections.items.len); | |
| 2060 | const alignment: u2 = switch (self.arch.?) { | |
| 2061 | .x86_64 => 0, | |
| 2062 | .aarch64 => 2, | |
| 2063 | else => unreachable, // unhandled architecture type | |
| 2064 | }; | |
| 2065 | const stub_size: u4 = switch (self.arch.?) { | |
| 2066 | .x86_64 => 6, | |
| 2067 | .aarch64 => 3 * @sizeOf(u32), | |
| 2068 | else => unreachable, // unhandled architecture type | |
| 2069 | }; | |
| 2070 | try text_seg.addSection(self.allocator, .{ | |
| 2071 | .sectname = makeStaticString("__stubs"), | |
| 2072 | .segname = makeStaticString("__TEXT"), | |
| 2073 | .addr = 0, | |
| 2074 | .size = 0, | |
| 2075 | .offset = 0, | |
| 2076 | .@"align" = alignment, | |
| 2077 | .reloff = 0, | |
| 2078 | .nreloc = 0, | |
| 2079 | .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, | |
| 2080 | .reserved1 = 0, | |
| 2081 | .reserved2 = stub_size, | |
| 2082 | .reserved3 = 0, | |
| 2083 | }); | |
| 2084 | } | |
| 2085 | ||
| 2086 | if (self.stub_helper_section_index == null) { | |
| 2087 | const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2088 | self.stub_helper_section_index = @intCast(u16, text_seg.sections.items.len); | |
| 2089 | const alignment: u2 = switch (self.arch.?) { | |
| 2090 | .x86_64 => 0, | |
| 2091 | .aarch64 => 2, | |
| 2092 | else => unreachable, // unhandled architecture type | |
| 2093 | }; | |
| 2094 | const stub_helper_size: u6 = switch (self.arch.?) { | |
| 2095 | .x86_64 => 15, | |
| 2096 | .aarch64 => 6 * @sizeOf(u32), | |
| 2097 | else => unreachable, | |
| 2098 | }; | |
| 2099 | try text_seg.addSection(self.allocator, .{ | |
| 2100 | .sectname = makeStaticString("__stub_helper"), | |
| 2101 | .segname = makeStaticString("__TEXT"), | |
| 2102 | .addr = 0, | |
| 2103 | .size = stub_helper_size, | |
| 2104 | .offset = 0, | |
| 2105 | .@"align" = alignment, | |
| 2106 | .reloff = 0, | |
| 2107 | .nreloc = 0, | |
| 2108 | .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, | |
| 2109 | .reserved1 = 0, | |
| 2110 | .reserved2 = 0, | |
| 2111 | .reserved3 = 0, | |
| 2112 | }); | |
| 2113 | } | |
| 2114 | ||
| 2115 | if (self.data_const_segment_cmd_index == null) { | |
| 2116 | self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2117 | try self.load_commands.append(self.allocator, .{ | |
| 2118 | .Segment = SegmentCommand.empty(.{ | |
| 2119 | .cmd = macho.LC_SEGMENT_64, | |
| 2120 | .cmdsize = @sizeOf(macho.segment_command_64), | |
| 2121 | .segname = makeStaticString("__DATA_CONST"), | |
| 2122 | .vmaddr = 0, | |
| 2123 | .vmsize = 0, | |
| 2124 | .fileoff = 0, | |
| 2125 | .filesize = 0, | |
| 2126 | .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, | |
| 2127 | .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, | |
| 2128 | .nsects = 0, | |
| 2129 | .flags = 0, | |
| 2130 | }), | |
| 2131 | }); | |
| 2132 | } | |
| 2133 | ||
| 2134 | if (self.got_section_index == null) { | |
| 2135 | const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2136 | self.got_section_index = @intCast(u16, data_const_seg.sections.items.len); | |
| 2137 | try data_const_seg.addSection(self.allocator, .{ | |
| 2138 | .sectname = makeStaticString("__got"), | |
| 2139 | .segname = makeStaticString("__DATA_CONST"), | |
| 2140 | .addr = 0, | |
| 2141 | .size = 0, | |
| 2142 | .offset = 0, | |
| 2143 | .@"align" = 3, // 2^3 = @sizeOf(u64) | |
| 2144 | .reloff = 0, | |
| 2145 | .nreloc = 0, | |
| 2146 | .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, | |
| 2147 | .reserved1 = 0, | |
| 2148 | .reserved2 = 0, | |
| 2149 | .reserved3 = 0, | |
| 2150 | }); | |
| 2151 | } | |
| 2152 | ||
| 2153 | if (self.data_segment_cmd_index == null) { | |
| 2154 | self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2155 | try self.load_commands.append(self.allocator, .{ | |
| 2156 | .Segment = SegmentCommand.empty(.{ | |
| 2157 | .cmd = macho.LC_SEGMENT_64, | |
| 2158 | .cmdsize = @sizeOf(macho.segment_command_64), | |
| 2159 | .segname = makeStaticString("__DATA"), | |
| 2160 | .vmaddr = 0, | |
| 2161 | .vmsize = 0, | |
| 2162 | .fileoff = 0, | |
| 2163 | .filesize = 0, | |
| 2164 | .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, | |
| 2165 | .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, | |
| 2166 | .nsects = 0, | |
| 2167 | .flags = 0, | |
| 2168 | }), | |
| 2169 | }); | |
| 2170 | } | |
| 2171 | ||
| 2172 | if (self.la_symbol_ptr_section_index == null) { | |
| 2173 | const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2174 | self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len); | |
| 2175 | try data_seg.addSection(self.allocator, .{ | |
| 2176 | .sectname = makeStaticString("__la_symbol_ptr"), | |
| 2177 | .segname = makeStaticString("__DATA"), | |
| 2178 | .addr = 0, | |
| 2179 | .size = 0, | |
| 2180 | .offset = 0, | |
| 2181 | .@"align" = 3, // 2^3 = @sizeOf(u64) | |
| 2182 | .reloff = 0, | |
| 2183 | .nreloc = 0, | |
| 2184 | .flags = macho.S_LAZY_SYMBOL_POINTERS, | |
| 2185 | .reserved1 = 0, | |
| 2186 | .reserved2 = 0, | |
| 2187 | .reserved3 = 0, | |
| 2188 | }); | |
| 2189 | } | |
| 2190 | ||
| 2191 | if (self.data_section_index == null) { | |
| 2192 | const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2193 | self.data_section_index = @intCast(u16, data_seg.sections.items.len); | |
| 2194 | try data_seg.addSection(self.allocator, .{ | |
| 2195 | .sectname = makeStaticString("__data"), | |
| 2196 | .segname = makeStaticString("__DATA"), | |
| 2197 | .addr = 0, | |
| 2198 | .size = 0, | |
| 2199 | .offset = 0, | |
| 2200 | .@"align" = 3, // 2^3 = @sizeOf(u64) | |
| 2201 | .reloff = 0, | |
| 2202 | .nreloc = 0, | |
| 2203 | .flags = macho.S_REGULAR, | |
| 2204 | .reserved1 = 0, | |
| 2205 | .reserved2 = 0, | |
| 2206 | .reserved3 = 0, | |
| 2207 | }); | |
| 2208 | } | |
| 2209 | ||
| 2210 | if (self.linkedit_segment_cmd_index == null) { | |
| 2211 | self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2212 | try self.load_commands.append(self.allocator, .{ | |
| 2213 | .Segment = SegmentCommand.empty(.{ | |
| 2214 | .cmd = macho.LC_SEGMENT_64, | |
| 2215 | .cmdsize = @sizeOf(macho.segment_command_64), | |
| 2216 | .segname = makeStaticString("__LINKEDIT"), | |
| 2217 | .vmaddr = 0, | |
| 2218 | .vmsize = 0, | |
| 2219 | .fileoff = 0, | |
| 2220 | .filesize = 0, | |
| 2221 | .maxprot = macho.VM_PROT_READ, | |
| 2222 | .initprot = macho.VM_PROT_READ, | |
| 2223 | .nsects = 0, | |
| 2224 | .flags = 0, | |
| 2225 | }), | |
| 2226 | }); | |
| 2227 | } | |
| 2228 | ||
| 2229 | if (self.dyld_info_cmd_index == null) { | |
| 2230 | self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2231 | try self.load_commands.append(self.allocator, .{ | |
| 2232 | .DyldInfoOnly = .{ | |
| 2233 | .cmd = macho.LC_DYLD_INFO_ONLY, | |
| 2234 | .cmdsize = @sizeOf(macho.dyld_info_command), | |
| 2235 | .rebase_off = 0, | |
| 2236 | .rebase_size = 0, | |
| 2237 | .bind_off = 0, | |
| 2238 | .bind_size = 0, | |
| 2239 | .weak_bind_off = 0, | |
| 2240 | .weak_bind_size = 0, | |
| 2241 | .lazy_bind_off = 0, | |
| 2242 | .lazy_bind_size = 0, | |
| 2243 | .export_off = 0, | |
| 2244 | .export_size = 0, | |
| 2245 | }, | |
| 2246 | }); | |
| 2247 | } | |
| 2248 | ||
| 2249 | if (self.symtab_cmd_index == null) { | |
| 2250 | self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2251 | try self.load_commands.append(self.allocator, .{ | |
| 2252 | .Symtab = .{ | |
| 2253 | .cmd = macho.LC_SYMTAB, | |
| 2254 | .cmdsize = @sizeOf(macho.symtab_command), | |
| 2255 | .symoff = 0, | |
| 2256 | .nsyms = 0, | |
| 2257 | .stroff = 0, | |
| 2258 | .strsize = 0, | |
| 2259 | }, | |
| 2260 | }); | |
| 2261 | try self.strtab.append(self.allocator, 0); | |
| 2262 | } | |
| 2263 | ||
| 2264 | if (self.dysymtab_cmd_index == null) { | |
| 2265 | self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2266 | try self.load_commands.append(self.allocator, .{ | |
| 2267 | .Dysymtab = .{ | |
| 2268 | .cmd = macho.LC_DYSYMTAB, | |
| 2269 | .cmdsize = @sizeOf(macho.dysymtab_command), | |
| 2270 | .ilocalsym = 0, | |
| 2271 | .nlocalsym = 0, | |
| 2272 | .iextdefsym = 0, | |
| 2273 | .nextdefsym = 0, | |
| 2274 | .iundefsym = 0, | |
| 2275 | .nundefsym = 0, | |
| 2276 | .tocoff = 0, | |
| 2277 | .ntoc = 0, | |
| 2278 | .modtaboff = 0, | |
| 2279 | .nmodtab = 0, | |
| 2280 | .extrefsymoff = 0, | |
| 2281 | .nextrefsyms = 0, | |
| 2282 | .indirectsymoff = 0, | |
| 2283 | .nindirectsyms = 0, | |
| 2284 | .extreloff = 0, | |
| 2285 | .nextrel = 0, | |
| 2286 | .locreloff = 0, | |
| 2287 | .nlocrel = 0, | |
| 2288 | }, | |
| 2289 | }); | |
| 2290 | } | |
| 2291 | ||
| 2292 | if (self.dylinker_cmd_index == null) { | |
| 2293 | self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2294 | const cmdsize = @intCast(u32, mem.alignForwardGeneric( | |
| 2295 | u64, | |
| 2296 | @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH), | |
| 2297 | @sizeOf(u64), | |
| 2298 | )); | |
| 2299 | var dylinker_cmd = emptyGenericCommandWithData(macho.dylinker_command{ | |
| 2300 | .cmd = macho.LC_LOAD_DYLINKER, | |
| 2301 | .cmdsize = cmdsize, | |
| 2302 | .name = @sizeOf(macho.dylinker_command), | |
| 2303 | }); | |
| 2304 | dylinker_cmd.data = try self.allocator.alloc(u8, cmdsize - dylinker_cmd.inner.name); | |
| 2305 | mem.set(u8, dylinker_cmd.data, 0); | |
| 2306 | mem.copy(u8, dylinker_cmd.data, mem.spanZ(DEFAULT_DYLD_PATH)); | |
| 2307 | try self.load_commands.append(self.allocator, .{ .Dylinker = dylinker_cmd }); | |
| 2308 | } | |
| 2309 | ||
| 2310 | if (self.libsystem_cmd_index == null) { | |
| 2311 | self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2312 | const cmdsize = @intCast(u32, mem.alignForwardGeneric( | |
| 2313 | u64, | |
| 2314 | @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), | |
| 2315 | @sizeOf(u64), | |
| 2316 | )); | |
| 2317 | // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. | |
| 2318 | // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. | |
| 2319 | const min_version = 0x0; | |
| 2320 | var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ | |
| 2321 | .cmd = macho.LC_LOAD_DYLIB, | |
| 2322 | .cmdsize = cmdsize, | |
| 2323 | .dylib = .{ | |
| 2324 | .name = @sizeOf(macho.dylib_command), | |
| 2325 | .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files | |
| 2326 | .current_version = min_version, | |
| 2327 | .compatibility_version = min_version, | |
| 2328 | }, | |
| 2329 | }); | |
| 2330 | dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); | |
| 2331 | mem.set(u8, dylib_cmd.data, 0); | |
| 2332 | mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); | |
| 2333 | try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); | |
| 2334 | } | |
| 2335 | ||
| 2336 | if (self.main_cmd_index == null) { | |
| 2337 | self.main_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2338 | try self.load_commands.append(self.allocator, .{ | |
| 2339 | .Main = .{ | |
| 2340 | .cmd = macho.LC_MAIN, | |
| 2341 | .cmdsize = @sizeOf(macho.entry_point_command), | |
| 2342 | .entryoff = 0x0, | |
| 2343 | .stacksize = 0, | |
| 2344 | }, | |
| 2345 | }); | |
| 2346 | } | |
| 2347 | ||
| 2348 | if (self.source_version_cmd_index == null) { | |
| 2349 | self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2350 | try self.load_commands.append(self.allocator, .{ | |
| 2351 | .SourceVersion = .{ | |
| 2352 | .cmd = macho.LC_SOURCE_VERSION, | |
| 2353 | .cmdsize = @sizeOf(macho.source_version_command), | |
| 2354 | .version = 0x0, | |
| 2355 | }, | |
| 2356 | }); | |
| 2357 | } | |
| 2358 | ||
| 2359 | if (self.uuid_cmd_index == null) { | |
| 2360 | self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2361 | var uuid_cmd: macho.uuid_command = .{ | |
| 2362 | .cmd = macho.LC_UUID, | |
| 2363 | .cmdsize = @sizeOf(macho.uuid_command), | |
| 2364 | .uuid = undefined, | |
| 2365 | }; | |
| 2366 | std.crypto.random.bytes(&uuid_cmd.uuid); | |
| 2367 | try self.load_commands.append(self.allocator, .{ .Uuid = uuid_cmd }); | |
| 2368 | } | |
| 2369 | ||
| 2370 | if (self.code_signature_cmd_index == null and self.arch.? == .aarch64) { | |
| 2371 | self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2372 | try self.load_commands.append(self.allocator, .{ | |
| 2373 | .LinkeditData = .{ | |
| 2374 | .cmd = macho.LC_CODE_SIGNATURE, | |
| 2375 | .cmdsize = @sizeOf(macho.linkedit_data_command), | |
| 2376 | .dataoff = 0, | |
| 2377 | .datasize = 0, | |
| 2378 | }, | |
| 2379 | }); | |
| 2380 | } | |
| 2381 | ||
| 2382 | if (self.data_in_code_cmd_index == null and self.arch.? == .x86_64) { | |
| 2383 | self.data_in_code_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 2384 | try self.load_commands.append(self.allocator, .{ | |
| 2385 | .LinkeditData = .{ | |
| 2386 | .cmd = macho.LC_DATA_IN_CODE, | |
| 2387 | .cmdsize = @sizeOf(macho.linkedit_data_command), | |
| 2388 | .dataoff = 0, | |
| 2389 | .datasize = 0, | |
| 2390 | }, | |
| 2391 | }); | |
| 2392 | } | |
| 2393 | } | |
| 2394 | ||
| 2395 | fn flush(self: *Zld) !void { | |
| 2396 | if (self.bss_section_index) |index| { | |
| 2397 | const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2398 | const sect = &seg.sections.items[index]; | |
| 2399 | sect.offset = 0; | |
| 2400 | } | |
| 2401 | ||
| 2402 | if (self.tlv_bss_section_index) |index| { | |
| 2403 | const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2404 | const sect = &seg.sections.items[index]; | |
| 2405 | sect.offset = 0; | |
| 2406 | } | |
| 2407 | ||
| 2408 | if (self.tlv_section_index) |index| { | |
| 2409 | const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2410 | const sect = &seg.sections.items[index]; | |
| 2411 | ||
| 2412 | var buffer = try self.allocator.alloc(u8, sect.size); | |
| 2413 | defer self.allocator.free(buffer); | |
| 2414 | _ = try self.file.?.preadAll(buffer, sect.offset); | |
| 2415 | ||
| 2416 | var stream = std.io.fixedBufferStream(buffer); | |
| 2417 | var writer = stream.writer(); | |
| 2418 | ||
| 2419 | const seek_amt = 2 * @sizeOf(u64); | |
| 2420 | while (self.threadlocal_offsets.popOrNull()) |offset| { | |
| 2421 | try writer.context.seekBy(seek_amt); | |
| 2422 | try writer.writeIntLittle(u64, offset); | |
| 2423 | } | |
| 2424 | ||
| 2425 | try self.file.?.pwriteAll(buffer, sect.offset); | |
| 2426 | } | |
| 2427 | ||
| 2428 | try self.setEntryPoint(); | |
| 2429 | try self.writeRebaseInfoTable(); | |
| 2430 | try self.writeBindInfoTable(); | |
| 2431 | try self.writeLazyBindInfoTable(); | |
| 2432 | try self.writeExportInfo(); | |
| 2433 | if (self.arch.? == .x86_64) { | |
| 2434 | try self.writeDataInCode(); | |
| 2435 | } | |
| 2436 | ||
| 2437 | { | |
| 2438 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2439 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 2440 | symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); | |
| 2441 | } | |
| 2442 | ||
| 2443 | try self.writeDebugInfo(); | |
| 2444 | try self.writeSymbolTable(); | |
| 2445 | try self.writeDynamicSymbolTable(); | |
| 2446 | try self.writeStringTable(); | |
| 2447 | ||
| 2448 | { | |
| 2449 | // Seal __LINKEDIT size | |
| 2450 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2451 | seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); | |
| 2452 | } | |
| 2453 | ||
| 2454 | if (self.arch.? == .aarch64) { | |
| 2455 | try self.writeCodeSignaturePadding(); | |
| 2456 | } | |
| 2457 | ||
| 2458 | try self.writeLoadCommands(); | |
| 2459 | try self.writeHeader(); | |
| 2460 | ||
| 2461 | if (self.arch.? == .aarch64) { | |
| 2462 | try self.writeCodeSignature(); | |
| 2463 | } | |
| 2464 | ||
| 2465 | if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) { | |
| 2466 | try fs.cwd().copyFile(self.out_path.?, fs.cwd(), self.out_path.?, .{}); | |
| 2467 | } | |
| 2468 | } | |
| 2469 | ||
| 2470 | fn setEntryPoint(self: *Zld) !void { | |
| 2471 | // TODO we should respect the -entry flag passed in by the user to set a custom | |
| 2472 | // entrypoint. For now, assume default of `_main`. | |
| 2473 | const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2474 | const text = seg.sections.items[self.text_section_index.?]; | |
| 2475 | const entry_syms = self.locals.get("_main") orelse return error.MissingMainEntrypoint; | |
| 2476 | ||
| 2477 | var entry_sym: ?macho.nlist_64 = null; | |
| 2478 | for (entry_syms.items) |es| { | |
| 2479 | switch (es.tt) { | |
| 2480 | .Global => { | |
| 2481 | entry_sym = es.inner; | |
| 2482 | break; | |
| 2483 | }, | |
| 2484 | .WeakGlobal => { | |
| 2485 | entry_sym = es.inner; | |
| 2486 | }, | |
| 2487 | .Local => {}, | |
| 2488 | } | |
| 2489 | } | |
| 2490 | if (entry_sym == null) { | |
| 2491 | log.err("no (weak) global definition of _main found", .{}); | |
| 2492 | return error.MissingMainEntrypoint; | |
| 2493 | } | |
| 2494 | ||
| 2495 | const name = try self.allocator.dupe(u8, "_main"); | |
| 2496 | try self.exports.putNoClobber(self.allocator, name, .{ | |
| 2497 | .n_strx = entry_sym.?.n_strx, | |
| 2498 | .n_value = entry_sym.?.n_value, | |
| 2499 | .n_type = macho.N_SECT | macho.N_EXT, | |
| 2500 | .n_desc = entry_sym.?.n_desc, | |
| 2501 | .n_sect = entry_sym.?.n_sect, | |
| 2502 | }); | |
| 2503 | ||
| 2504 | const ec = &self.load_commands.items[self.main_cmd_index.?].Main; | |
| 2505 | ec.entryoff = @intCast(u32, entry_sym.?.n_value - seg.inner.vmaddr); | |
| 2506 | } | |
| 2507 | ||
| 2508 | fn writeRebaseInfoTable(self: *Zld) !void { | |
| 2509 | var pointers = std.ArrayList(Pointer).init(self.allocator); | |
| 2510 | defer pointers.deinit(); | |
| 2511 | ||
| 2512 | try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); | |
| 2513 | pointers.appendSliceAssumeCapacity(self.local_rebases.items); | |
| 2514 | ||
| 2515 | if (self.got_section_index) |idx| { | |
| 2516 | // TODO this should be cleaned up! | |
| 2517 | try pointers.ensureCapacity(pointers.items.len + self.nonlazy_pointers.items().len); | |
| 2518 | const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2519 | const sect = seg.sections.items[idx]; | |
| 2520 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2521 | const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); | |
| 2522 | const index_offset = @intCast(u32, self.nonlazy_imports.items().len); | |
| 2523 | for (self.nonlazy_pointers.items()) |entry| { | |
| 2524 | const index = index_offset + entry.value.index; | |
| 2525 | pointers.appendAssumeCapacity(.{ | |
| 2526 | .offset = base_offset + index * @sizeOf(u64), | |
| 2527 | .segment_id = segment_id, | |
| 2528 | }); | |
| 2529 | } | |
| 2530 | } | |
| 2531 | ||
| 2532 | if (self.mod_init_func_section_index) |idx| { | |
| 2533 | // TODO audit and investigate this. | |
| 2534 | const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2535 | const sect = seg.sections.items[idx]; | |
| 2536 | const npointers = sect.size * @sizeOf(u64); | |
| 2537 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2538 | const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); | |
| 2539 | ||
| 2540 | try pointers.ensureCapacity(pointers.items.len + npointers); | |
| 2541 | var i: usize = 0; | |
| 2542 | while (i < npointers) : (i += 1) { | |
| 2543 | pointers.appendAssumeCapacity(.{ | |
| 2544 | .offset = base_offset + i * @sizeOf(u64), | |
| 2545 | .segment_id = segment_id, | |
| 2546 | }); | |
| 2547 | } | |
| 2548 | } | |
| 2549 | ||
| 2550 | if (self.mod_term_func_section_index) |idx| { | |
| 2551 | // TODO audit and investigate this. | |
| 2552 | const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2553 | const sect = seg.sections.items[idx]; | |
| 2554 | const npointers = sect.size * @sizeOf(u64); | |
| 2555 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2556 | const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); | |
| 2557 | ||
| 2558 | try pointers.ensureCapacity(pointers.items.len + npointers); | |
| 2559 | var i: usize = 0; | |
| 2560 | while (i < npointers) : (i += 1) { | |
| 2561 | pointers.appendAssumeCapacity(.{ | |
| 2562 | .offset = base_offset + i * @sizeOf(u64), | |
| 2563 | .segment_id = segment_id, | |
| 2564 | }); | |
| 2565 | } | |
| 2566 | } | |
| 2567 | ||
| 2568 | if (self.la_symbol_ptr_section_index) |idx| { | |
| 2569 | try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); | |
| 2570 | const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2571 | const sect = seg.sections.items[idx]; | |
| 2572 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2573 | const segment_id = @intCast(u16, self.data_segment_cmd_index.?); | |
| 2574 | for (self.lazy_imports.items()) |entry| { | |
| 2575 | pointers.appendAssumeCapacity(.{ | |
| 2576 | .offset = base_offset + entry.value.index * @sizeOf(u64), | |
| 2577 | .segment_id = segment_id, | |
| 2578 | }); | |
| 2579 | } | |
| 2580 | } | |
| 2581 | ||
| 2582 | std.sort.sort(Pointer, pointers.items, {}, pointerCmp); | |
| 2583 | ||
| 2584 | const size = try rebaseInfoSize(pointers.items); | |
| 2585 | var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); | |
| 2586 | defer self.allocator.free(buffer); | |
| 2587 | ||
| 2588 | var stream = std.io.fixedBufferStream(buffer); | |
| 2589 | try writeRebaseInfo(pointers.items, stream.writer()); | |
| 2590 | ||
| 2591 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2592 | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; | |
| 2593 | dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff); | |
| 2594 | dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); | |
| 2595 | seg.inner.filesize += dyld_info.rebase_size; | |
| 2596 | ||
| 2597 | log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); | |
| 2598 | ||
| 2599 | try self.file.?.pwriteAll(buffer, dyld_info.rebase_off); | |
| 2600 | } | |
| 2601 | ||
| 2602 | fn writeBindInfoTable(self: *Zld) !void { | |
| 2603 | var pointers = std.ArrayList(Pointer).init(self.allocator); | |
| 2604 | defer pointers.deinit(); | |
| 2605 | ||
| 2606 | if (self.got_section_index) |idx| { | |
| 2607 | try pointers.ensureCapacity(pointers.items.len + self.nonlazy_imports.items().len); | |
| 2608 | const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 2609 | const sect = seg.sections.items[idx]; | |
| 2610 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2611 | const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); | |
| 2612 | for (self.nonlazy_imports.items()) |entry| { | |
| 2613 | pointers.appendAssumeCapacity(.{ | |
| 2614 | .offset = base_offset + entry.value.index * @sizeOf(u64), | |
| 2615 | .segment_id = segment_id, | |
| 2616 | .dylib_ordinal = entry.value.dylib_ordinal, | |
| 2617 | .name = entry.key, | |
| 2618 | }); | |
| 2619 | } | |
| 2620 | } | |
| 2621 | ||
| 2622 | if (self.tlv_section_index) |idx| { | |
| 2623 | const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2624 | const sect = seg.sections.items[idx]; | |
| 2625 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2626 | const segment_id = @intCast(u16, self.data_segment_cmd_index.?); | |
| 2627 | try pointers.append(.{ | |
| 2628 | .offset = base_offset + self.tlv_bootstrap.?.index * @sizeOf(u64), | |
| 2629 | .segment_id = segment_id, | |
| 2630 | .dylib_ordinal = self.tlv_bootstrap.?.dylib_ordinal, | |
| 2631 | .name = "__tlv_bootstrap", | |
| 2632 | }); | |
| 2633 | } | |
| 2634 | ||
| 2635 | const size = try bindInfoSize(pointers.items); | |
| 2636 | var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); | |
| 2637 | defer self.allocator.free(buffer); | |
| 2638 | ||
| 2639 | var stream = std.io.fixedBufferStream(buffer); | |
| 2640 | try writeBindInfo(pointers.items, stream.writer()); | |
| 2641 | ||
| 2642 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2643 | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; | |
| 2644 | dyld_info.bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); | |
| 2645 | dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); | |
| 2646 | seg.inner.filesize += dyld_info.bind_size; | |
| 2647 | ||
| 2648 | log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); | |
| 2649 | ||
| 2650 | try self.file.?.pwriteAll(buffer, dyld_info.bind_off); | |
| 2651 | } | |
| 2652 | ||
| 2653 | fn writeLazyBindInfoTable(self: *Zld) !void { | |
| 2654 | var pointers = std.ArrayList(Pointer).init(self.allocator); | |
| 2655 | defer pointers.deinit(); | |
| 2656 | try pointers.ensureCapacity(self.lazy_imports.items().len); | |
| 2657 | ||
| 2658 | if (self.la_symbol_ptr_section_index) |idx| { | |
| 2659 | const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 2660 | const sect = seg.sections.items[idx]; | |
| 2661 | const base_offset = sect.addr - seg.inner.vmaddr; | |
| 2662 | const segment_id = @intCast(u16, self.data_segment_cmd_index.?); | |
| 2663 | for (self.lazy_imports.items()) |entry| { | |
| 2664 | pointers.appendAssumeCapacity(.{ | |
| 2665 | .offset = base_offset + entry.value.index * @sizeOf(u64), | |
| 2666 | .segment_id = segment_id, | |
| 2667 | .dylib_ordinal = entry.value.dylib_ordinal, | |
| 2668 | .name = entry.key, | |
| 2669 | }); | |
| 2670 | } | |
| 2671 | } | |
| 2672 | ||
| 2673 | const size = try lazyBindInfoSize(pointers.items); | |
| 2674 | var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); | |
| 2675 | defer self.allocator.free(buffer); | |
| 2676 | ||
| 2677 | var stream = std.io.fixedBufferStream(buffer); | |
| 2678 | try writeLazyBindInfo(pointers.items, stream.writer()); | |
| 2679 | ||
| 2680 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2681 | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; | |
| 2682 | dyld_info.lazy_bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); | |
| 2683 | dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); | |
| 2684 | seg.inner.filesize += dyld_info.lazy_bind_size; | |
| 2685 | ||
| 2686 | log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); | |
| 2687 | ||
| 2688 | try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); | |
| 2689 | try self.populateLazyBindOffsetsInStubHelper(buffer); | |
| 2690 | } | |
| 2691 | ||
| 2692 | fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void { | |
| 2693 | var stream = std.io.fixedBufferStream(buffer); | |
| 2694 | var reader = stream.reader(); | |
| 2695 | var offsets = std.ArrayList(u32).init(self.allocator); | |
| 2696 | try offsets.append(0); | |
| 2697 | defer offsets.deinit(); | |
| 2698 | var valid_block = false; | |
| 2699 | ||
| 2700 | while (true) { | |
| 2701 | const inst = reader.readByte() catch |err| switch (err) { | |
| 2702 | error.EndOfStream => break, | |
| 2703 | else => return err, | |
| 2704 | }; | |
| 2705 | const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; | |
| 2706 | const opcode: u8 = inst & macho.BIND_OPCODE_MASK; | |
| 2707 | ||
| 2708 | switch (opcode) { | |
| 2709 | macho.BIND_OPCODE_DO_BIND => { | |
| 2710 | valid_block = true; | |
| 2711 | }, | |
| 2712 | macho.BIND_OPCODE_DONE => { | |
| 2713 | if (valid_block) { | |
| 2714 | const offset = try stream.getPos(); | |
| 2715 | try offsets.append(@intCast(u32, offset)); | |
| 2716 | } | |
| 2717 | valid_block = false; | |
| 2718 | }, | |
| 2719 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { | |
| 2720 | var next = try reader.readByte(); | |
| 2721 | while (next != @as(u8, 0)) { | |
| 2722 | next = try reader.readByte(); | |
| 2723 | } | |
| 2724 | }, | |
| 2725 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { | |
| 2726 | _ = try leb.readULEB128(u64, reader); | |
| 2727 | }, | |
| 2728 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { | |
| 2729 | _ = try leb.readULEB128(u64, reader); | |
| 2730 | }, | |
| 2731 | macho.BIND_OPCODE_SET_ADDEND_SLEB => { | |
| 2732 | _ = try leb.readILEB128(i64, reader); | |
| 2733 | }, | |
| 2734 | else => {}, | |
| 2735 | } | |
| 2736 | } | |
| 2737 | assert(self.lazy_imports.items().len <= offsets.items.len); | |
| 2738 | ||
| 2739 | const stub_size: u4 = switch (self.arch.?) { | |
| 2740 | .x86_64 => 10, | |
| 2741 | .aarch64 => 3 * @sizeOf(u32), | |
| 2742 | else => unreachable, | |
| 2743 | }; | |
| 2744 | const off: u4 = switch (self.arch.?) { | |
| 2745 | .x86_64 => 1, | |
| 2746 | .aarch64 => 2 * @sizeOf(u32), | |
| 2747 | else => unreachable, | |
| 2748 | }; | |
| 2749 | var buf: [@sizeOf(u32)]u8 = undefined; | |
| 2750 | for (self.lazy_imports.items()) |entry| { | |
| 2751 | const symbol = entry.value; | |
| 2752 | const placeholder_off = self.stub_helper_stubs_start_off.? + symbol.index * stub_size + off; | |
| 2753 | mem.writeIntLittle(u32, &buf, offsets.items[symbol.index]); | |
| 2754 | try self.file.?.pwriteAll(&buf, placeholder_off); | |
| 2755 | } | |
| 2756 | } | |
| 2757 | ||
| 2758 | fn writeExportInfo(self: *Zld) !void { | |
| 2759 | var trie = Trie.init(self.allocator); | |
| 2760 | defer trie.deinit(); | |
| 2761 | ||
| 2762 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 2763 | for (self.exports.items()) |entry| { | |
| 2764 | const name = entry.key; | |
| 2765 | const symbol = entry.value; | |
| 2766 | // TODO figure out if we should put all exports into the export trie | |
| 2767 | assert(symbol.n_value >= text_segment.inner.vmaddr); | |
| 2768 | try trie.put(.{ | |
| 2769 | .name = name, | |
| 2770 | .vmaddr_offset = symbol.n_value - text_segment.inner.vmaddr, | |
| 2771 | .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR, | |
| 2772 | }); | |
| 2773 | } | |
| 2774 | ||
| 2775 | try trie.finalize(); | |
| 2776 | var buffer = try self.allocator.alloc(u8, @intCast(usize, trie.size)); | |
| 2777 | defer self.allocator.free(buffer); | |
| 2778 | var stream = std.io.fixedBufferStream(buffer); | |
| 2779 | const nwritten = try trie.write(stream.writer()); | |
| 2780 | assert(nwritten == trie.size); | |
| 2781 | ||
| 2782 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2783 | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; | |
| 2784 | dyld_info.export_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); | |
| 2785 | dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); | |
| 2786 | seg.inner.filesize += dyld_info.export_size; | |
| 2787 | ||
| 2788 | log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); | |
| 2789 | ||
| 2790 | try self.file.?.pwriteAll(buffer, dyld_info.export_off); | |
| 2791 | } | |
| 2792 | ||
| 2793 | fn writeDebugInfo(self: *Zld) !void { | |
| 2794 | var stabs = std.ArrayList(macho.nlist_64).init(self.allocator); | |
| 2795 | defer stabs.deinit(); | |
| 2796 | ||
| 2797 | for (self.objects.items) |object, object_id| { | |
| 2798 | var debug_info = blk: { | |
| 2799 | var di = try DebugInfo.parseFromObject(self.allocator, object); | |
| 2800 | break :blk di orelse continue; | |
| 2801 | }; | |
| 2802 | defer debug_info.deinit(self.allocator); | |
| 2803 | ||
| 2804 | // We assume there is only one CU. | |
| 2805 | const compile_unit = debug_info.inner.findCompileUnit(0x0) catch |err| switch (err) { | |
| 2806 | error.MissingDebugInfo => { | |
| 2807 | // TODO audit cases with missing debug info and audit our dwarf.zig module. | |
| 2808 | log.debug("invalid or missing debug info in {s}; skipping", .{object.name}); | |
| 2809 | continue; | |
| 2810 | }, | |
| 2811 | else => |e| return e, | |
| 2812 | }; | |
| 2813 | const name = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_name); | |
| 2814 | const comp_dir = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_comp_dir); | |
| 2815 | ||
| 2816 | { | |
| 2817 | const tu_path = try std.fs.path.join(self.allocator, &[_][]const u8{ comp_dir, name }); | |
| 2818 | defer self.allocator.free(tu_path); | |
| 2819 | const dirname = std.fs.path.dirname(tu_path) orelse "./"; | |
| 2820 | // Current dir | |
| 2821 | try stabs.append(.{ | |
| 2822 | .n_strx = try self.makeString(tu_path[0 .. dirname.len + 1]), | |
| 2823 | .n_type = macho.N_SO, | |
| 2824 | .n_sect = 0, | |
| 2825 | .n_desc = 0, | |
| 2826 | .n_value = 0, | |
| 2827 | }); | |
| 2828 | // Artifact name | |
| 2829 | try stabs.append(.{ | |
| 2830 | .n_strx = try self.makeString(tu_path[dirname.len + 1 ..]), | |
| 2831 | .n_type = macho.N_SO, | |
| 2832 | .n_sect = 0, | |
| 2833 | .n_desc = 0, | |
| 2834 | .n_value = 0, | |
| 2835 | }); | |
| 2836 | // Path to object file with debug info | |
| 2837 | var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; | |
| 2838 | const full_path = blk: { | |
| 2839 | if (object.ar_name) |prefix| { | |
| 2840 | const path = try std.os.realpath(prefix, &buffer); | |
| 2841 | break :blk try std.fmt.allocPrint(self.allocator, "{s}({s})", .{ path, object.name }); | |
| 2842 | } else { | |
| 2843 | const path = try std.os.realpath(object.name, &buffer); | |
| 2844 | break :blk try mem.dupe(self.allocator, u8, path); | |
| 2845 | } | |
| 2846 | }; | |
| 2847 | defer self.allocator.free(full_path); | |
| 2848 | const stat = try object.file.stat(); | |
| 2849 | const mtime = @intCast(u64, @divFloor(stat.mtime, 1_000_000_000)); | |
| 2850 | try stabs.append(.{ | |
| 2851 | .n_strx = try self.makeString(full_path), | |
| 2852 | .n_type = macho.N_OSO, | |
| 2853 | .n_sect = 0, | |
| 2854 | .n_desc = 1, | |
| 2855 | .n_value = mtime, | |
| 2856 | }); | |
| 2857 | } | |
| 2858 | log.debug("analyzing debug info in '{s}'", .{object.name}); | |
| 2859 | ||
| 2860 | for (object.symtab.items) |source_sym| { | |
| 2861 | const symname = object.getString(source_sym.n_strx); | |
| 2862 | const source_addr = source_sym.n_value; | |
| 2863 | const target_syms = self.locals.get(symname) orelse continue; | |
| 2864 | const target_sym: Symbol = blk: { | |
| 2865 | for (target_syms.items) |ts| { | |
| 2866 | if (ts.object_id == @intCast(u16, object_id)) break :blk ts; | |
| 2867 | } else continue; | |
| 2868 | }; | |
| 2869 | ||
| 2870 | const maybe_size = blk: for (debug_info.inner.func_list.items) |func| { | |
| 2871 | if (func.pc_range) |range| { | |
| 2872 | if (source_addr >= range.start and source_addr < range.end) { | |
| 2873 | break :blk range.end - range.start; | |
| 2874 | } | |
| 2875 | } | |
| 2876 | } else null; | |
| 2877 | ||
| 2878 | if (maybe_size) |size| { | |
| 2879 | try stabs.append(.{ | |
| 2880 | .n_strx = 0, | |
| 2881 | .n_type = macho.N_BNSYM, | |
| 2882 | .n_sect = target_sym.inner.n_sect, | |
| 2883 | .n_desc = 0, | |
| 2884 | .n_value = target_sym.inner.n_value, | |
| 2885 | }); | |
| 2886 | try stabs.append(.{ | |
| 2887 | .n_strx = target_sym.inner.n_strx, | |
| 2888 | .n_type = macho.N_FUN, | |
| 2889 | .n_sect = target_sym.inner.n_sect, | |
| 2890 | .n_desc = 0, | |
| 2891 | .n_value = target_sym.inner.n_value, | |
| 2892 | }); | |
| 2893 | try stabs.append(.{ | |
| 2894 | .n_strx = 0, | |
| 2895 | .n_type = macho.N_FUN, | |
| 2896 | .n_sect = 0, | |
| 2897 | .n_desc = 0, | |
| 2898 | .n_value = size, | |
| 2899 | }); | |
| 2900 | try stabs.append(.{ | |
| 2901 | .n_strx = 0, | |
| 2902 | .n_type = macho.N_ENSYM, | |
| 2903 | .n_sect = target_sym.inner.n_sect, | |
| 2904 | .n_desc = 0, | |
| 2905 | .n_value = size, | |
| 2906 | }); | |
| 2907 | } else { | |
| 2908 | // TODO need a way to differentiate symbols: global, static, local, etc. | |
| 2909 | try stabs.append(.{ | |
| 2910 | .n_strx = target_sym.inner.n_strx, | |
| 2911 | .n_type = macho.N_STSYM, | |
| 2912 | .n_sect = target_sym.inner.n_sect, | |
| 2913 | .n_desc = 0, | |
| 2914 | .n_value = target_sym.inner.n_value, | |
| 2915 | }); | |
| 2916 | } | |
| 2917 | } | |
| 2918 | ||
| 2919 | // Close the source file! | |
| 2920 | try stabs.append(.{ | |
| 2921 | .n_strx = 0, | |
| 2922 | .n_type = macho.N_SO, | |
| 2923 | .n_sect = 0, | |
| 2924 | .n_desc = 0, | |
| 2925 | .n_value = 0, | |
| 2926 | }); | |
| 2927 | } | |
| 2928 | ||
| 2929 | if (stabs.items.len == 0) return; | |
| 2930 | ||
| 2931 | // Write stabs into the symbol table | |
| 2932 | const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2933 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 2934 | ||
| 2935 | symtab.nsyms = @intCast(u32, stabs.items.len); | |
| 2936 | ||
| 2937 | const stabs_off = symtab.symoff; | |
| 2938 | const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64); | |
| 2939 | log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); | |
| 2940 | try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off); | |
| 2941 | ||
| 2942 | linkedit.inner.filesize += stabs_size; | |
| 2943 | ||
| 2944 | // Update dynamic symbol table. | |
| 2945 | const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; | |
| 2946 | dysymtab.nlocalsym = symtab.nsyms; | |
| 2947 | } | |
| 2948 | ||
| 2949 | fn writeSymbolTable(self: *Zld) !void { | |
| 2950 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 2951 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 2952 | ||
| 2953 | var locals = std.ArrayList(macho.nlist_64).init(self.allocator); | |
| 2954 | defer locals.deinit(); | |
| 2955 | ||
| 2956 | for (self.locals.items()) |entries| { | |
| 2957 | log.debug("'{s}': {} entries", .{ entries.key, entries.value.items.len }); | |
| 2958 | // var symbol: ?macho.nlist_64 = null; | |
| 2959 | for (entries.value.items) |entry| { | |
| 2960 | log.debug(" | {}", .{entry.inner}); | |
| 2961 | log.debug(" | {}", .{entry.tt}); | |
| 2962 | log.debug(" | {s}", .{self.objects.items[entry.object_id].name}); | |
| 2963 | try locals.append(entry.inner); | |
| 2964 | } | |
| 2965 | } | |
| 2966 | const nlocals = locals.items.len; | |
| 2967 | ||
| 2968 | const nexports = self.exports.items().len; | |
| 2969 | var exports = std.ArrayList(macho.nlist_64).init(self.allocator); | |
| 2970 | defer exports.deinit(); | |
| 2971 | ||
| 2972 | try exports.ensureCapacity(nexports); | |
| 2973 | for (self.exports.items()) |entry| { | |
| 2974 | exports.appendAssumeCapacity(entry.value); | |
| 2975 | } | |
| 2976 | ||
| 2977 | const has_tlv: bool = self.tlv_bootstrap != null; | |
| 2978 | ||
| 2979 | var nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; | |
| 2980 | if (has_tlv) nundefs += 1; | |
| 2981 | ||
| 2982 | var undefs = std.ArrayList(macho.nlist_64).init(self.allocator); | |
| 2983 | defer undefs.deinit(); | |
| 2984 | ||
| 2985 | try undefs.ensureCapacity(nundefs); | |
| 2986 | for (self.lazy_imports.items()) |entry| { | |
| 2987 | undefs.appendAssumeCapacity(entry.value.symbol); | |
| 2988 | } | |
| 2989 | for (self.nonlazy_imports.items()) |entry| { | |
| 2990 | undefs.appendAssumeCapacity(entry.value.symbol); | |
| 2991 | } | |
| 2992 | if (has_tlv) { | |
| 2993 | undefs.appendAssumeCapacity(self.tlv_bootstrap.?.symbol); | |
| 2994 | } | |
| 2995 | ||
| 2996 | const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); | |
| 2997 | const locals_size = nlocals * @sizeOf(macho.nlist_64); | |
| 2998 | log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); | |
| 2999 | try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); | |
| 3000 | ||
| 3001 | const exports_off = locals_off + locals_size; | |
| 3002 | const exports_size = nexports * @sizeOf(macho.nlist_64); | |
| 3003 | log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); | |
| 3004 | try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); | |
| 3005 | ||
| 3006 | const undefs_off = exports_off + exports_size; | |
| 3007 | const undefs_size = nundefs * @sizeOf(macho.nlist_64); | |
| 3008 | log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); | |
| 3009 | try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); | |
| 3010 | ||
| 3011 | symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs); | |
| 3012 | seg.inner.filesize += locals_size + exports_size + undefs_size; | |
| 3013 | ||
| 3014 | // Update dynamic symbol table. | |
| 3015 | const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; | |
| 3016 | dysymtab.nlocalsym += @intCast(u32, nlocals); | |
| 3017 | dysymtab.iextdefsym = dysymtab.nlocalsym; | |
| 3018 | dysymtab.nextdefsym = @intCast(u32, nexports); | |
| 3019 | dysymtab.iundefsym = dysymtab.nlocalsym + dysymtab.nextdefsym; | |
| 3020 | dysymtab.nundefsym = @intCast(u32, nundefs); | |
| 3021 | } | |
| 3022 | ||
| 3023 | fn writeDynamicSymbolTable(self: *Zld) !void { | |
| 3024 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 3025 | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 3026 | const stubs = &text_segment.sections.items[self.stubs_section_index.?]; | |
| 3027 | const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; | |
| 3028 | const got = &data_const_segment.sections.items[self.got_section_index.?]; | |
| 3029 | const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; | |
| 3030 | const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; | |
| 3031 | const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; | |
| 3032 | ||
| 3033 | const lazy = self.lazy_imports.items(); | |
| 3034 | const nonlazy = self.nonlazy_imports.items(); | |
| 3035 | const got_locals = self.nonlazy_pointers.items(); | |
| 3036 | dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); | |
| 3037 | dysymtab.nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len + got_locals.len); | |
| 3038 | const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); | |
| 3039 | seg.inner.filesize += needed_size; | |
| 3040 | ||
| 3041 | log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ | |
| 3042 | dysymtab.indirectsymoff, | |
| 3043 | dysymtab.indirectsymoff + needed_size, | |
| 3044 | }); | |
| 3045 | ||
| 3046 | var buf = try self.allocator.alloc(u8, needed_size); | |
| 3047 | defer self.allocator.free(buf); | |
| 3048 | var stream = std.io.fixedBufferStream(buf); | |
| 3049 | var writer = stream.writer(); | |
| 3050 | ||
| 3051 | stubs.reserved1 = 0; | |
| 3052 | for (lazy) |_, i| { | |
| 3053 | const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); | |
| 3054 | try writer.writeIntLittle(u32, symtab_idx); | |
| 3055 | } | |
| 3056 | ||
| 3057 | const base_id = @intCast(u32, lazy.len); | |
| 3058 | got.reserved1 = base_id; | |
| 3059 | for (nonlazy) |_, i| { | |
| 3060 | const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); | |
| 3061 | try writer.writeIntLittle(u32, symtab_idx); | |
| 3062 | } | |
| 3063 | // TODO there should be one common set of GOT entries. | |
| 3064 | for (got_locals) |_| { | |
| 3065 | try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL); | |
| 3066 | } | |
| 3067 | ||
| 3068 | la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len) + @intCast(u32, got_locals.len); | |
| 3069 | for (lazy) |_, i| { | |
| 3070 | const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); | |
| 3071 | try writer.writeIntLittle(u32, symtab_idx); | |
| 3072 | } | |
| 3073 | ||
| 3074 | try self.file.?.pwriteAll(buf, dysymtab.indirectsymoff); | |
| 3075 | } | |
| 3076 | ||
| 3077 | fn writeStringTable(self: *Zld) !void { | |
| 3078 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 3079 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 3080 | symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); | |
| 3081 | symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); | |
| 3082 | seg.inner.filesize += symtab.strsize; | |
| 3083 | ||
| 3084 | log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); | |
| 3085 | ||
| 3086 | try self.file.?.pwriteAll(self.strtab.items, symtab.stroff); | |
| 3087 | ||
| 3088 | if (symtab.strsize > self.strtab.items.len and self.arch.? == .x86_64) { | |
| 3089 | // This is the last section, so we need to pad it out. | |
| 3090 | try self.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1); | |
| 3091 | } | |
| 3092 | } | |
| 3093 | ||
| 3094 | fn writeDataInCode(self: *Zld) !void { | |
| 3095 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 3096 | const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData; | |
| 3097 | const fileoff = seg.inner.fileoff + seg.inner.filesize; | |
| 3098 | ||
| 3099 | var buf = std.ArrayList(u8).init(self.allocator); | |
| 3100 | defer buf.deinit(); | |
| 3101 | ||
| 3102 | const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 3103 | const text_sect = text_seg.sections.items[self.text_section_index.?]; | |
| 3104 | for (self.objects.items) |object, object_id| { | |
| 3105 | const source_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; | |
| 3106 | const source_sect = source_seg.sections.items[object.text_section_index.?]; | |
| 3107 | const target_mapping = self.mappings.get(.{ | |
| 3108 | .object_id = @intCast(u16, object_id), | |
| 3109 | .source_sect_id = object.text_section_index.?, | |
| 3110 | }) orelse continue; | |
| 3111 | ||
| 3112 | try buf.ensureCapacity( | |
| 3113 | buf.items.len + object.data_in_code_entries.items.len * @sizeOf(macho.data_in_code_entry), | |
| 3114 | ); | |
| 3115 | for (object.data_in_code_entries.items) |dice| { | |
| 3116 | const new_dice: macho.data_in_code_entry = .{ | |
| 3117 | .offset = text_sect.offset + target_mapping.offset + dice.offset, | |
| 3118 | .length = dice.length, | |
| 3119 | .kind = dice.kind, | |
| 3120 | }; | |
| 3121 | buf.appendSliceAssumeCapacity(mem.asBytes(&new_dice)); | |
| 3122 | } | |
| 3123 | } | |
| 3124 | const datasize = @intCast(u32, buf.items.len); | |
| 3125 | ||
| 3126 | dice_cmd.dataoff = @intCast(u32, fileoff); | |
| 3127 | dice_cmd.datasize = datasize; | |
| 3128 | seg.inner.filesize += datasize; | |
| 3129 | ||
| 3130 | log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize }); | |
| 3131 | ||
| 3132 | try self.file.?.pwriteAll(buf.items, fileoff); | |
| 3133 | } | |
| 3134 | ||
| 3135 | fn writeCodeSignaturePadding(self: *Zld) !void { | |
| 3136 | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; | |
| 3137 | const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; | |
| 3138 | const fileoff = seg.inner.fileoff + seg.inner.filesize; | |
| 3139 | const needed_size = CodeSignature.calcCodeSignaturePaddingSize( | |
| 3140 | self.out_path.?, | |
| 3141 | fileoff, | |
| 3142 | self.page_size.?, | |
| 3143 | ); | |
| 3144 | code_sig_cmd.dataoff = @intCast(u32, fileoff); | |
| 3145 | code_sig_cmd.datasize = needed_size; | |
| 3146 | ||
| 3147 | // Advance size of __LINKEDIT segment | |
| 3148 | seg.inner.filesize += needed_size; | |
| 3149 | seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); | |
| 3150 | ||
| 3151 | log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); | |
| 3152 | ||
| 3153 | // Pad out the space. We need to do this to calculate valid hashes for everything in the file | |
| 3154 | // except for code signature data. | |
| 3155 | try self.file.?.pwriteAll(&[_]u8{0}, fileoff + needed_size - 1); | |
| 3156 | } | |
| 3157 | ||
| 3158 | fn writeCodeSignature(self: *Zld) !void { | |
| 3159 | const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; | |
| 3160 | const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; | |
| 3161 | ||
| 3162 | var code_sig = CodeSignature.init(self.allocator, self.page_size.?); | |
| 3163 | defer code_sig.deinit(); | |
| 3164 | try code_sig.calcAdhocSignature( | |
| 3165 | self.file.?, | |
| 3166 | self.out_path.?, | |
| 3167 | text_seg.inner, | |
| 3168 | code_sig_cmd, | |
| 3169 | .Exe, | |
| 3170 | ); | |
| 3171 | ||
| 3172 | var buffer = try self.allocator.alloc(u8, code_sig.size()); | |
| 3173 | defer self.allocator.free(buffer); | |
| 3174 | var stream = std.io.fixedBufferStream(buffer); | |
| 3175 | try code_sig.write(stream.writer()); | |
| 3176 | ||
| 3177 | log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); | |
| 3178 | ||
| 3179 | try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); | |
| 3180 | } | |
| 3181 | ||
| 3182 | fn writeLoadCommands(self: *Zld) !void { | |
| 3183 | var sizeofcmds: u32 = 0; | |
| 3184 | for (self.load_commands.items) |lc| { | |
| 3185 | sizeofcmds += lc.cmdsize(); | |
| 3186 | } | |
| 3187 | ||
| 3188 | var buffer = try self.allocator.alloc(u8, sizeofcmds); | |
| 3189 | defer self.allocator.free(buffer); | |
| 3190 | var writer = std.io.fixedBufferStream(buffer).writer(); | |
| 3191 | for (self.load_commands.items) |lc| { | |
| 3192 | try lc.write(writer); | |
| 3193 | } | |
| 3194 | ||
| 3195 | const off = @sizeOf(macho.mach_header_64); | |
| 3196 | log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); | |
| 3197 | try self.file.?.pwriteAll(buffer, off); | |
| 3198 | } | |
| 3199 | ||
| 3200 | fn writeHeader(self: *Zld) !void { | |
| 3201 | var header: macho.mach_header_64 = undefined; | |
| 3202 | header.magic = macho.MH_MAGIC_64; | |
| 3203 | ||
| 3204 | const CpuInfo = struct { | |
| 3205 | cpu_type: macho.cpu_type_t, | |
| 3206 | cpu_subtype: macho.cpu_subtype_t, | |
| 3207 | }; | |
| 3208 | ||
| 3209 | const cpu_info: CpuInfo = switch (self.arch.?) { | |
| 3210 | .aarch64 => .{ | |
| 3211 | .cpu_type = macho.CPU_TYPE_ARM64, | |
| 3212 | .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL, | |
| 3213 | }, | |
| 3214 | .x86_64 => .{ | |
| 3215 | .cpu_type = macho.CPU_TYPE_X86_64, | |
| 3216 | .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL, | |
| 3217 | }, | |
| 3218 | else => return error.UnsupportedCpuArchitecture, | |
| 3219 | }; | |
| 3220 | header.cputype = cpu_info.cpu_type; | |
| 3221 | header.cpusubtype = cpu_info.cpu_subtype; | |
| 3222 | header.filetype = macho.MH_EXECUTE; | |
| 3223 | header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL; | |
| 3224 | header.reserved = 0; | |
| 3225 | ||
| 3226 | if (self.tlv_section_index) |_| | |
| 3227 | header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; | |
| 3228 | ||
| 3229 | header.ncmds = @intCast(u32, self.load_commands.items.len); | |
| 3230 | header.sizeofcmds = 0; | |
| 3231 | for (self.load_commands.items) |cmd| { | |
| 3232 | header.sizeofcmds += cmd.cmdsize(); | |
| 3233 | } | |
| 3234 | log.debug("writing Mach-O header {}", .{header}); | |
| 3235 | try self.file.?.pwriteAll(mem.asBytes(&header), 0); | |
| 3236 | } | |
| 3237 | ||
| 3238 | pub fn makeStaticString(bytes: []const u8) [16]u8 { | |
| 3239 | var buf = [_]u8{0} ** 16; | |
| 3240 | assert(bytes.len <= buf.len); | |
| 3241 | mem.copy(u8, &buf, bytes); | |
| 3242 | return buf; | |
| 3243 | } | |
| 3244 | ||
| 3245 | fn makeString(self: *Zld, bytes: []const u8) !u32 { | |
| 3246 | try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1); | |
| 3247 | const offset = @intCast(u32, self.strtab.items.len); | |
| 3248 | log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); | |
| 3249 | self.strtab.appendSliceAssumeCapacity(bytes); | |
| 3250 | self.strtab.appendAssumeCapacity(0); | |
| 3251 | return offset; | |
| 3252 | } | |
| 3253 | ||
| 3254 | fn getString(self: *const Zld, str_off: u32) []const u8 { | |
| 3255 | assert(str_off < self.strtab.items.len); | |
| 3256 | return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off)); | |
| 3257 | } | |
| 3258 | ||
| 3259 | pub fn parseName(name: *const [16]u8) []const u8 { | |
| 3260 | const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; | |
| 3261 | return name[0..len]; | |
| 3262 | } | |
| 3263 | ||
| 3264 | fn isLocal(sym: *const macho.nlist_64) callconv(.Inline) bool { | |
| 3265 | if (isExtern(sym)) return false; | |
| 3266 | const tt = macho.N_TYPE & sym.n_type; | |
| 3267 | return tt == macho.N_SECT; | |
| 3268 | } | |
| 3269 | ||
| 3270 | fn isExport(sym: *const macho.nlist_64) callconv(.Inline) bool { | |
| 3271 | if (!isExtern(sym)) return false; | |
| 3272 | const tt = macho.N_TYPE & sym.n_type; | |
| 3273 | return tt == macho.N_SECT; | |
| 3274 | } | |
| 3275 | ||
| 3276 | fn isImport(sym: *const macho.nlist_64) callconv(.Inline) bool { | |
| 3277 | if (!isExtern(sym)) return false; | |
| 3278 | const tt = macho.N_TYPE & sym.n_type; | |
| 3279 | return tt == macho.N_UNDF; | |
| 3280 | } | |
| 3281 | ||
| 3282 | fn isExtern(sym: *const macho.nlist_64) callconv(.Inline) bool { | |
| 3283 | if ((sym.n_type & macho.N_EXT) == 0) return false; | |
| 3284 | return (sym.n_type & macho.N_PEXT) == 0; | |
| 3285 | } | |
| 3286 | ||
| 3287 | fn isWeakDef(sym: *const macho.nlist_64) callconv(.Inline) bool { | |
| 3288 | return (sym.n_desc & macho.N_WEAK_DEF) != 0; | |
| 3289 | } | |
| 3290 | ||
| 3291 | fn aarch64IsArithmetic(inst: *const [4]u8) callconv(.Inline) bool { | |
| 3292 | const group_decode = @truncate(u5, inst[3]); | |
| 3293 | return ((group_decode >> 2) == 4); | |
| 3294 | } |
src/link/MachO/bind.zig created+145| ... | ... | @@ -0,0 +1,145 @@ |
| 1 | const std = @import("std"); | |
| 2 | const leb = std.leb; | |
| 3 | const macho = std.macho; | |
| 4 | ||
| 5 | pub const Pointer = struct { | |
| 6 | offset: u64, | |
| 7 | segment_id: u16, | |
| 8 | dylib_ordinal: ?i64 = null, | |
| 9 | name: ?[]const u8 = null, | |
| 10 | }; | |
| 11 | ||
| 12 | pub fn pointerCmp(context: void, a: Pointer, b: Pointer) bool { | |
| 13 | if (a.segment_id < b.segment_id) return true; | |
| 14 | if (a.segment_id == b.segment_id) { | |
| 15 | return a.offset < b.offset; | |
| 16 | } | |
| 17 | return false; | |
| 18 | } | |
| 19 | ||
| 20 | pub fn rebaseInfoSize(pointers: []const Pointer) !u64 { | |
| 21 | var stream = std.io.countingWriter(std.io.null_writer); | |
| 22 | var writer = stream.writer(); | |
| 23 | var size: u64 = 0; | |
| 24 | ||
| 25 | for (pointers) |pointer| { | |
| 26 | size += 2; | |
| 27 | try leb.writeILEB128(writer, pointer.offset); | |
| 28 | size += 1; | |
| 29 | } | |
| 30 | ||
| 31 | size += 1 + stream.bytes_written; | |
| 32 | return size; | |
| 33 | } | |
| 34 | ||
| 35 | pub fn writeRebaseInfo(pointers: []const Pointer, writer: anytype) !void { | |
| 36 | for (pointers) |pointer| { | |
| 37 | try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER)); | |
| 38 | try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); | |
| 39 | ||
| 40 | try leb.writeILEB128(writer, pointer.offset); | |
| 41 | try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); | |
| 42 | } | |
| 43 | try writer.writeByte(macho.REBASE_OPCODE_DONE); | |
| 44 | } | |
| 45 | ||
| 46 | pub fn bindInfoSize(pointers: []const Pointer) !u64 { | |
| 47 | var stream = std.io.countingWriter(std.io.null_writer); | |
| 48 | var writer = stream.writer(); | |
| 49 | var size: u64 = 0; | |
| 50 | ||
| 51 | for (pointers) |pointer| { | |
| 52 | size += 1; | |
| 53 | if (pointer.dylib_ordinal.? > 15) { | |
| 54 | try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); | |
| 55 | } | |
| 56 | size += 1; | |
| 57 | ||
| 58 | size += 1; | |
| 59 | size += pointer.name.?.len; | |
| 60 | size += 1; | |
| 61 | ||
| 62 | size += 1; | |
| 63 | ||
| 64 | try leb.writeILEB128(writer, pointer.offset); | |
| 65 | size += 1; | |
| 66 | } | |
| 67 | ||
| 68 | size += stream.bytes_written + 1; | |
| 69 | return size; | |
| 70 | } | |
| 71 | ||
| 72 | pub fn writeBindInfo(pointers: []const Pointer, writer: anytype) !void { | |
| 73 | for (pointers) |pointer| { | |
| 74 | if (pointer.dylib_ordinal.? > 15) { | |
| 75 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); | |
| 76 | try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); | |
| 77 | } else if (pointer.dylib_ordinal.? > 0) { | |
| 78 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); | |
| 79 | } else { | |
| 80 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); | |
| 81 | } | |
| 82 | try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); | |
| 83 | ||
| 84 | try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. | |
| 85 | try writer.writeAll(pointer.name.?); | |
| 86 | try writer.writeByte(0); | |
| 87 | ||
| 88 | try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); | |
| 89 | ||
| 90 | try leb.writeILEB128(writer, pointer.offset); | |
| 91 | try writer.writeByte(macho.BIND_OPCODE_DO_BIND); | |
| 92 | } | |
| 93 | ||
| 94 | try writer.writeByte(macho.BIND_OPCODE_DONE); | |
| 95 | } | |
| 96 | ||
| 97 | pub fn lazyBindInfoSize(pointers: []const Pointer) !u64 { | |
| 98 | var stream = std.io.countingWriter(std.io.null_writer); | |
| 99 | var writer = stream.writer(); | |
| 100 | var size: u64 = 0; | |
| 101 | ||
| 102 | for (pointers) |pointer| { | |
| 103 | size += 1; | |
| 104 | ||
| 105 | try leb.writeILEB128(writer, pointer.offset); | |
| 106 | ||
| 107 | size += 1; | |
| 108 | if (pointer.dylib_ordinal.? > 15) { | |
| 109 | try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); | |
| 110 | } | |
| 111 | ||
| 112 | size += 1; | |
| 113 | size += pointer.name.?.len; | |
| 114 | size += 1; | |
| 115 | ||
| 116 | size += 2; | |
| 117 | } | |
| 118 | ||
| 119 | size += stream.bytes_written; | |
| 120 | return size; | |
| 121 | } | |
| 122 | ||
| 123 | pub fn writeLazyBindInfo(pointers: []const Pointer, writer: anytype) !void { | |
| 124 | for (pointers) |pointer| { | |
| 125 | try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); | |
| 126 | ||
| 127 | try leb.writeILEB128(writer, pointer.offset); | |
| 128 | ||
| 129 | if (pointer.dylib_ordinal.? > 15) { | |
| 130 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); | |
| 131 | try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); | |
| 132 | } else if (pointer.dylib_ordinal.? > 0) { | |
| 133 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); | |
| 134 | } else { | |
| 135 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); | |
| 136 | } | |
| 137 | ||
| 138 | try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. | |
| 139 | try writer.writeAll(pointer.name.?); | |
| 140 | try writer.writeByte(0); | |
| 141 | ||
| 142 | try writer.writeByte(macho.BIND_OPCODE_DO_BIND); | |
| 143 | try writer.writeByte(macho.BIND_OPCODE_DONE); | |
| 144 | } | |
| 145 | } |
src/link/MachO/imports.zig deleted-152| ... | ... | @@ -1,152 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const leb = std.leb; | |
| 3 | const macho = std.macho; | |
| 4 | const mem = std.mem; | |
| 5 | ||
| 6 | const assert = std.debug.assert; | |
| 7 | const Allocator = mem.Allocator; | |
| 8 | ||
| 9 | pub const ExternSymbol = struct { | |
| 10 | /// MachO symbol table entry. | |
| 11 | inner: macho.nlist_64, | |
| 12 | ||
| 13 | /// Id of the dynamic library where the specified entries can be found. | |
| 14 | /// Id of 0 means self. | |
| 15 | /// TODO this should really be an id into the table of all defined | |
| 16 | /// dylibs. | |
| 17 | dylib_ordinal: i64 = 0, | |
| 18 | ||
| 19 | /// Id of the segment where this symbol is defined (will have its address | |
| 20 | /// resolved). | |
| 21 | segment: u16 = 0, | |
| 22 | ||
| 23 | /// Offset relative to the start address of the `segment`. | |
| 24 | offset: u32 = 0, | |
| 25 | }; | |
| 26 | ||
| 27 | pub fn rebaseInfoSize(symbols: anytype) !u64 { | |
| 28 | var stream = std.io.countingWriter(std.io.null_writer); | |
| 29 | var writer = stream.writer(); | |
| 30 | var size: u64 = 0; | |
| 31 | ||
| 32 | for (symbols) |entry| { | |
| 33 | size += 2; | |
| 34 | try leb.writeILEB128(writer, entry.value.offset); | |
| 35 | size += 1; | |
| 36 | } | |
| 37 | ||
| 38 | size += 1 + stream.bytes_written; | |
| 39 | return size; | |
| 40 | } | |
| 41 | ||
| 42 | pub fn writeRebaseInfo(symbols: anytype, writer: anytype) !void { | |
| 43 | for (symbols) |entry| { | |
| 44 | const symbol = entry.value; | |
| 45 | try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER)); | |
| 46 | try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); | |
| 47 | try leb.writeILEB128(writer, symbol.offset); | |
| 48 | try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); | |
| 49 | } | |
| 50 | try writer.writeByte(macho.REBASE_OPCODE_DONE); | |
| 51 | } | |
| 52 | ||
| 53 | pub fn bindInfoSize(symbols: anytype) !u64 { | |
| 54 | var stream = std.io.countingWriter(std.io.null_writer); | |
| 55 | var writer = stream.writer(); | |
| 56 | var size: u64 = 0; | |
| 57 | ||
| 58 | for (symbols) |entry| { | |
| 59 | const symbol = entry.value; | |
| 60 | ||
| 61 | size += 1; | |
| 62 | if (symbol.dylib_ordinal > 15) { | |
| 63 | try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); | |
| 64 | } | |
| 65 | size += 1; | |
| 66 | ||
| 67 | size += 1; | |
| 68 | size += entry.key.len; | |
| 69 | size += 1; | |
| 70 | ||
| 71 | size += 1; | |
| 72 | try leb.writeILEB128(writer, symbol.offset); | |
| 73 | size += 2; | |
| 74 | } | |
| 75 | ||
| 76 | size += stream.bytes_written; | |
| 77 | return size; | |
| 78 | } | |
| 79 | ||
| 80 | pub fn writeBindInfo(symbols: anytype, writer: anytype) !void { | |
| 81 | for (symbols) |entry| { | |
| 82 | const symbol = entry.value; | |
| 83 | ||
| 84 | if (symbol.dylib_ordinal > 15) { | |
| 85 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); | |
| 86 | try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); | |
| 87 | } else if (symbol.dylib_ordinal > 0) { | |
| 88 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); | |
| 89 | } else { | |
| 90 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); | |
| 91 | } | |
| 92 | try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); | |
| 93 | ||
| 94 | try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. | |
| 95 | try writer.writeAll(entry.key); | |
| 96 | try writer.writeByte(0); | |
| 97 | ||
| 98 | try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); | |
| 99 | try leb.writeILEB128(writer, symbol.offset); | |
| 100 | try writer.writeByte(macho.BIND_OPCODE_DO_BIND); | |
| 101 | try writer.writeByte(macho.BIND_OPCODE_DONE); | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | pub fn lazyBindInfoSize(symbols: anytype) !u64 { | |
| 106 | var stream = std.io.countingWriter(std.io.null_writer); | |
| 107 | var writer = stream.writer(); | |
| 108 | var size: u64 = 0; | |
| 109 | ||
| 110 | for (symbols) |entry| { | |
| 111 | const symbol = entry.value; | |
| 112 | size += 1; | |
| 113 | try leb.writeILEB128(writer, symbol.offset); | |
| 114 | size += 1; | |
| 115 | if (symbol.dylib_ordinal > 15) { | |
| 116 | try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); | |
| 117 | } | |
| 118 | ||
| 119 | size += 1; | |
| 120 | size += entry.key.len; | |
| 121 | size += 1; | |
| 122 | ||
| 123 | size += 2; | |
| 124 | } | |
| 125 | ||
| 126 | size += stream.bytes_written; | |
| 127 | return size; | |
| 128 | } | |
| 129 | ||
| 130 | pub fn writeLazyBindInfo(symbols: anytype, writer: anytype) !void { | |
| 131 | for (symbols) |entry| { | |
| 132 | const symbol = entry.value; | |
| 133 | try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); | |
| 134 | try leb.writeILEB128(writer, symbol.offset); | |
| 135 | ||
| 136 | if (symbol.dylib_ordinal > 15) { | |
| 137 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); | |
| 138 | try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); | |
| 139 | } else if (symbol.dylib_ordinal > 0) { | |
| 140 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); | |
| 141 | } else { | |
| 142 | try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); | |
| 143 | } | |
| 144 | ||
| 145 | try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. | |
| 146 | try writer.writeAll(entry.key); | |
| 147 | try writer.writeByte(0); | |
| 148 | ||
| 149 | try writer.writeByte(macho.BIND_OPCODE_DO_BIND); | |
| 150 | try writer.writeByte(macho.BIND_OPCODE_DONE); | |
| 151 | } | |
| 152 | } |
src/main.zig+62-17| ... | ... | @@ -557,7 +557,7 @@ fn buildOutputType( |
| 557 | 557 | var test_filter: ?[]const u8 = null; |
| 558 | 558 | var test_name_prefix: ?[]const u8 = null; |
| 559 | 559 | var override_local_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LOCAL_CACHE_DIR"); |
| 560 | var override_global_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_GLOBAL_CACHE_DIR"); | |
| 560 | var override_global_cache_dir: ?[]const u8 = null; | |
| 561 | 561 | var override_lib_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIB_DIR"); |
| 562 | 562 | var main_pkg_path: ?[]const u8 = null; |
| 563 | 563 | var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; |
| ... | ... | @@ -841,7 +841,11 @@ fn buildOutputType( |
| 841 | 841 | } else if (mem.eql(u8, arg, "--debug-log")) { |
| 842 | 842 | if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); |
| 843 | 843 | i += 1; |
| 844 | try log_scopes.append(gpa, args[i]); | |
| 844 | if (!build_options.enable_logging) { | |
| 845 | std.log.warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{}); | |
| 846 | } else { | |
| 847 | try log_scopes.append(gpa, args[i]); | |
| 848 | } | |
| 845 | 849 | } else if (mem.eql(u8, arg, "-fcompiler-rt")) { |
| 846 | 850 | want_compiler_rt = true; |
| 847 | 851 | } else if (mem.eql(u8, arg, "-fno-compiler-rt")) { |
| ... | ... | @@ -2633,6 +2637,50 @@ fn argvCmd(allocator: *Allocator, argv: []const []const u8) ![]u8 { |
| 2633 | 2637 | return cmd.toOwnedSlice(); |
| 2634 | 2638 | } |
| 2635 | 2639 | |
| 2640 | fn readSourceFileToEndAlloc(allocator: *mem.Allocator, input: *const fs.File, size_hint: ?usize) ![]const u8 { | |
| 2641 | const source_code = input.readToEndAllocOptions( | |
| 2642 | allocator, | |
| 2643 | max_src_size, | |
| 2644 | size_hint, | |
| 2645 | @alignOf(u16), | |
| 2646 | null, | |
| 2647 | ) catch |err| switch (err) { | |
| 2648 | error.ConnectionResetByPeer => unreachable, | |
| 2649 | error.ConnectionTimedOut => unreachable, | |
| 2650 | error.NotOpenForReading => unreachable, | |
| 2651 | else => |e| return e, | |
| 2652 | }; | |
| 2653 | errdefer allocator.free(source_code); | |
| 2654 | ||
| 2655 | // Detect unsupported file types with their Byte Order Mark | |
| 2656 | const unsupported_boms = [_][]const u8{ | |
| 2657 | "\xff\xfe\x00\x00", // UTF-32 little endian | |
| 2658 | "\xfe\xff\x00\x00", // UTF-32 big endian | |
| 2659 | "\xfe\xff", // UTF-16 big endian | |
| 2660 | }; | |
| 2661 | for (unsupported_boms) |bom| { | |
| 2662 | if (mem.startsWith(u8, source_code, bom)) { | |
| 2663 | return error.UnsupportedEncoding; | |
| 2664 | } | |
| 2665 | } | |
| 2666 | ||
| 2667 | // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8 | |
| 2668 | if (mem.startsWith(u8, source_code, "\xff\xfe")) { | |
| 2669 | const source_code_utf16_le = mem.bytesAsSlice(u16, source_code); | |
| 2670 | const source_code_utf8 = std.unicode.utf16leToUtf8Alloc(allocator, source_code_utf16_le) catch |err| switch (err) { | |
| 2671 | error.DanglingSurrogateHalf => error.UnsupportedEncoding, | |
| 2672 | error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding, | |
| 2673 | error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding, | |
| 2674 | else => |e| return e, | |
| 2675 | }; | |
| 2676 | ||
| 2677 | allocator.free(source_code); | |
| 2678 | return source_code_utf8; | |
| 2679 | } | |
| 2680 | ||
| 2681 | return source_code; | |
| 2682 | } | |
| 2683 | ||
| 2636 | 2684 | pub const usage_fmt = |
| 2637 | 2685 | \\Usage: zig fmt [file]... |
| 2638 | 2686 | \\ |
| ... | ... | @@ -2704,9 +2752,10 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { |
| 2704 | 2752 | fatal("cannot use --stdin with positional arguments", .{}); |
| 2705 | 2753 | } |
| 2706 | 2754 | |
| 2707 | const stdin = io.getStdIn().reader(); | |
| 2708 | ||
| 2709 | const source_code = try stdin.readAllAlloc(gpa, max_src_size); | |
| 2755 | const stdin = io.getStdIn(); | |
| 2756 | const source_code = readSourceFileToEndAlloc(gpa, &stdin, null) catch |err| { | |
| 2757 | fatal("unable to read stdin: {s}", .{err}); | |
| 2758 | }; | |
| 2710 | 2759 | defer gpa.free(source_code); |
| 2711 | 2760 | |
| 2712 | 2761 | var tree = std.zig.parse(gpa, source_code) catch |err| { |
| ... | ... | @@ -2781,6 +2830,7 @@ const FmtError = error{ |
| 2781 | 2830 | EndOfStream, |
| 2782 | 2831 | Unseekable, |
| 2783 | 2832 | NotOpenForWriting, |
| 2833 | UnsupportedEncoding, | |
| 2784 | 2834 | } || fs.File.OpenError; |
| 2785 | 2835 | |
| 2786 | 2836 | fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { |
| ... | ... | @@ -2846,21 +2896,15 @@ fn fmtPathFile( |
| 2846 | 2896 | if (stat.kind == .Directory) |
| 2847 | 2897 | return error.IsDir; |
| 2848 | 2898 | |
| 2849 | const source_code = source_file.readToEndAllocOptions( | |
| 2899 | const source_code = try readSourceFileToEndAlloc( | |
| 2850 | 2900 | fmt.gpa, |
| 2851 | max_src_size, | |
| 2901 | &source_file, | |
| 2852 | 2902 | std.math.cast(usize, stat.size) catch return error.FileTooBig, |
| 2853 | @alignOf(u8), | |
| 2854 | null, | |
| 2855 | ) catch |err| switch (err) { | |
| 2856 | error.ConnectionResetByPeer => unreachable, | |
| 2857 | error.ConnectionTimedOut => unreachable, | |
| 2858 | error.NotOpenForReading => unreachable, | |
| 2859 | else => |e| return e, | |
| 2860 | }; | |
| 2903 | ); | |
| 2904 | defer fmt.gpa.free(source_code); | |
| 2905 | ||
| 2861 | 2906 | source_file.close(); |
| 2862 | 2907 | file_closed = true; |
| 2863 | defer fmt.gpa.free(source_code); | |
| 2864 | 2908 | |
| 2865 | 2909 | // Add to set after no longer possible to get error.IsDir. |
| 2866 | 2910 | if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; |
| ... | ... | @@ -3237,7 +3281,8 @@ pub const ClangArgIterator = struct { |
| 3237 | 3281 | self.zig_equivalent = clang_arg.zig_equivalent; |
| 3238 | 3282 | break :find_clang_arg; |
| 3239 | 3283 | }, |
| 3240 | } else { | |
| 3284 | } | |
| 3285 | else { | |
| 3241 | 3286 | fatal("Unknown Clang option: '{s}'", .{arg}); |
| 3242 | 3287 | } |
| 3243 | 3288 | } |
src/stage1/all_types.hpp+21-8| ... | ... | @@ -391,6 +391,8 @@ enum LazyValueId { |
| 391 | 391 | LazyValueIdAlignOf, |
| 392 | 392 | LazyValueIdSizeOf, |
| 393 | 393 | LazyValueIdPtrType, |
| 394 | LazyValueIdPtrTypeSimple, | |
| 395 | LazyValueIdPtrTypeSimpleConst, | |
| 394 | 396 | LazyValueIdOptType, |
| 395 | 397 | LazyValueIdSliceType, |
| 396 | 398 | LazyValueIdFnType, |
| ... | ... | @@ -467,6 +469,13 @@ struct LazyValuePtrType { |
| 467 | 469 | bool is_allowzero; |
| 468 | 470 | }; |
| 469 | 471 | |
| 472 | struct LazyValuePtrTypeSimple { | |
| 473 | LazyValue base; | |
| 474 | ||
| 475 | IrAnalyze *ira; | |
| 476 | IrInstGen *elem_type; | |
| 477 | }; | |
| 478 | ||
| 470 | 479 | struct LazyValueOptType { |
| 471 | 480 | LazyValue base; |
| 472 | 481 | |
| ... | ... | @@ -2130,10 +2139,6 @@ struct CodeGen { |
| 2130 | 2139 | Buf llvm_ir_file_output_path; |
| 2131 | 2140 | Buf analysis_json_output_path; |
| 2132 | 2141 | Buf docs_output_path; |
| 2133 | Buf *cache_dir; | |
| 2134 | Buf *c_artifact_dir; | |
| 2135 | const char **libc_include_dir_list; | |
| 2136 | size_t libc_include_dir_len; | |
| 2137 | 2142 | |
| 2138 | 2143 | Buf *builtin_zig_path; |
| 2139 | 2144 | Buf *zig_std_special_dir; // Cannot be overridden; derived from zig_lib_dir. |
| ... | ... | @@ -2610,7 +2615,8 @@ enum IrInstSrcId { |
| 2610 | 2615 | IrInstSrcIdEnumToInt, |
| 2611 | 2616 | IrInstSrcIdIntToErr, |
| 2612 | 2617 | IrInstSrcIdErrToInt, |
| 2613 | IrInstSrcIdCheckSwitchProngs, | |
| 2618 | IrInstSrcIdCheckSwitchProngsUnderYes, | |
| 2619 | IrInstSrcIdCheckSwitchProngsUnderNo, | |
| 2614 | 2620 | IrInstSrcIdCheckStatementIsVoid, |
| 2615 | 2621 | IrInstSrcIdTypeName, |
| 2616 | 2622 | IrInstSrcIdDeclRef, |
| ... | ... | @@ -2624,12 +2630,15 @@ enum IrInstSrcId { |
| 2624 | 2630 | IrInstSrcIdHasField, |
| 2625 | 2631 | IrInstSrcIdSetEvalBranchQuota, |
| 2626 | 2632 | IrInstSrcIdPtrType, |
| 2633 | IrInstSrcIdPtrTypeSimple, | |
| 2634 | IrInstSrcIdPtrTypeSimpleConst, | |
| 2627 | 2635 | IrInstSrcIdAlignCast, |
| 2628 | 2636 | IrInstSrcIdImplicitCast, |
| 2629 | 2637 | IrInstSrcIdResolveResult, |
| 2630 | 2638 | IrInstSrcIdResetResult, |
| 2631 | 2639 | IrInstSrcIdSetAlignStack, |
| 2632 | IrInstSrcIdArgType, | |
| 2640 | IrInstSrcIdArgTypeAllowVarFalse, | |
| 2641 | IrInstSrcIdArgTypeAllowVarTrue, | |
| 2633 | 2642 | IrInstSrcIdExport, |
| 2634 | 2643 | IrInstSrcIdExtern, |
| 2635 | 2644 | IrInstSrcIdErrorReturnTrace, |
| ... | ... | @@ -3294,6 +3303,12 @@ struct IrInstSrcArrayType { |
| 3294 | 3303 | IrInstSrc *child_type; |
| 3295 | 3304 | }; |
| 3296 | 3305 | |
| 3306 | struct IrInstSrcPtrTypeSimple { | |
| 3307 | IrInstSrc base; | |
| 3308 | ||
| 3309 | IrInstSrc *child_type; | |
| 3310 | }; | |
| 3311 | ||
| 3297 | 3312 | struct IrInstSrcPtrType { |
| 3298 | 3313 | IrInstSrc base; |
| 3299 | 3314 | |
| ... | ... | @@ -4020,7 +4035,6 @@ struct IrInstSrcCheckSwitchProngs { |
| 4020 | 4035 | IrInstSrcCheckSwitchProngsRange *ranges; |
| 4021 | 4036 | size_t range_count; |
| 4022 | 4037 | AstNode* else_prong; |
| 4023 | bool have_underscore_prong; | |
| 4024 | 4038 | }; |
| 4025 | 4039 | |
| 4026 | 4040 | struct IrInstSrcCheckStatementIsVoid { |
| ... | ... | @@ -4144,7 +4158,6 @@ struct IrInstSrcArgType { |
| 4144 | 4158 | |
| 4145 | 4159 | IrInstSrc *fn_type; |
| 4146 | 4160 | IrInstSrc *arg_index; |
| 4147 | bool allow_var; | |
| 4148 | 4161 | }; |
| 4149 | 4162 | |
| 4150 | 4163 | struct IrInstSrcExport { |
src/stage1/analyze.cpp+48-1| ... | ... | @@ -1237,6 +1237,22 @@ Error type_val_resolve_zero_bits(CodeGen *g, ZigValue *type_val, ZigType *parent |
| 1237 | 1237 | parent_type_val, is_zero_bits); |
| 1238 | 1238 | } |
| 1239 | 1239 | } |
| 1240 | case LazyValueIdPtrTypeSimple: | |
| 1241 | case LazyValueIdPtrTypeSimpleConst: { | |
| 1242 | LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast<LazyValuePtrTypeSimple *>(type_val->data.x_lazy); | |
| 1243 | ||
| 1244 | if (parent_type_val == lazy_ptr_type->elem_type->value) { | |
| 1245 | // Does a struct which contains a pointer field to itself have bits? Yes. | |
| 1246 | *is_zero_bits = false; | |
| 1247 | return ErrorNone; | |
| 1248 | } else { | |
| 1249 | if (parent_type_val == nullptr) { | |
| 1250 | parent_type_val = type_val; | |
| 1251 | } | |
| 1252 | return type_val_resolve_zero_bits(g, lazy_ptr_type->elem_type->value, parent_type, | |
| 1253 | parent_type_val, is_zero_bits); | |
| 1254 | } | |
| 1255 | } | |
| 1240 | 1256 | case LazyValueIdArrayType: { |
| 1241 | 1257 | LazyValueArrayType *lazy_array_type = |
| 1242 | 1258 | reinterpret_cast<LazyValueArrayType *>(type_val->data.x_lazy); |
| ... | ... | @@ -1285,6 +1301,8 @@ Error type_val_resolve_is_opaque_type(CodeGen *g, ZigValue *type_val, bool *is_o |
| 1285 | 1301 | zig_unreachable(); |
| 1286 | 1302 | case LazyValueIdSliceType: |
| 1287 | 1303 | case LazyValueIdPtrType: |
| 1304 | case LazyValueIdPtrTypeSimple: | |
| 1305 | case LazyValueIdPtrTypeSimpleConst: | |
| 1288 | 1306 | case LazyValueIdFnType: |
| 1289 | 1307 | case LazyValueIdOptType: |
| 1290 | 1308 | case LazyValueIdErrUnionType: |
| ... | ... | @@ -1313,6 +1331,11 @@ static ReqCompTime type_val_resolve_requires_comptime(CodeGen *g, ZigValue *type |
| 1313 | 1331 | LazyValuePtrType *lazy_ptr_type = reinterpret_cast<LazyValuePtrType *>(type_val->data.x_lazy); |
| 1314 | 1332 | return type_val_resolve_requires_comptime(g, lazy_ptr_type->elem_type->value); |
| 1315 | 1333 | } |
| 1334 | case LazyValueIdPtrTypeSimple: | |
| 1335 | case LazyValueIdPtrTypeSimpleConst: { | |
| 1336 | LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast<LazyValuePtrTypeSimple *>(type_val->data.x_lazy); | |
| 1337 | return type_val_resolve_requires_comptime(g, lazy_ptr_type->elem_type->value); | |
| 1338 | } | |
| 1316 | 1339 | case LazyValueIdOptType: { |
| 1317 | 1340 | LazyValueOptType *lazy_opt_type = reinterpret_cast<LazyValueOptType *>(type_val->data.x_lazy); |
| 1318 | 1341 | return type_val_resolve_requires_comptime(g, lazy_opt_type->payload_type->value); |
| ... | ... | @@ -1413,6 +1436,24 @@ start_over: |
| 1413 | 1436 | } |
| 1414 | 1437 | return ErrorNone; |
| 1415 | 1438 | } |
| 1439 | case LazyValueIdPtrTypeSimple: | |
| 1440 | case LazyValueIdPtrTypeSimpleConst: { | |
| 1441 | LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast<LazyValuePtrTypeSimple *>(type_val->data.x_lazy); | |
| 1442 | bool is_zero_bits; | |
| 1443 | if ((err = type_val_resolve_zero_bits(g, lazy_ptr_type->elem_type->value, nullptr, | |
| 1444 | nullptr, &is_zero_bits))) | |
| 1445 | { | |
| 1446 | return err; | |
| 1447 | } | |
| 1448 | if (is_zero_bits) { | |
| 1449 | *abi_size = 0; | |
| 1450 | *size_in_bits = 0; | |
| 1451 | } else { | |
| 1452 | *abi_size = g->builtin_types.entry_usize->abi_size; | |
| 1453 | *size_in_bits = g->builtin_types.entry_usize->size_in_bits; | |
| 1454 | } | |
| 1455 | return ErrorNone; | |
| 1456 | } | |
| 1416 | 1457 | case LazyValueIdFnType: |
| 1417 | 1458 | *abi_size = g->builtin_types.entry_usize->abi_size; |
| 1418 | 1459 | *size_in_bits = g->builtin_types.entry_usize->size_in_bits; |
| ... | ... | @@ -1449,6 +1490,8 @@ Error type_val_resolve_abi_align(CodeGen *g, AstNode *source_node, ZigValue *typ |
| 1449 | 1490 | zig_unreachable(); |
| 1450 | 1491 | case LazyValueIdSliceType: |
| 1451 | 1492 | case LazyValueIdPtrType: |
| 1493 | case LazyValueIdPtrTypeSimple: | |
| 1494 | case LazyValueIdPtrTypeSimpleConst: | |
| 1452 | 1495 | case LazyValueIdFnType: |
| 1453 | 1496 | *abi_align = g->builtin_types.entry_usize->abi_align; |
| 1454 | 1497 | return ErrorNone; |
| ... | ... | @@ -1506,7 +1549,9 @@ static OnePossibleValue type_val_resolve_has_one_possible_value(CodeGen *g, ZigV |
| 1506 | 1549 | return OnePossibleValueYes; |
| 1507 | 1550 | return type_val_resolve_has_one_possible_value(g, lazy_array_type->elem_type->value); |
| 1508 | 1551 | } |
| 1509 | case LazyValueIdPtrType: { | |
| 1552 | case LazyValueIdPtrType: | |
| 1553 | case LazyValueIdPtrTypeSimple: | |
| 1554 | case LazyValueIdPtrTypeSimpleConst: { | |
| 1510 | 1555 | Error err; |
| 1511 | 1556 | bool zero_bits; |
| 1512 | 1557 | if ((err = type_val_resolve_zero_bits(g, type_val, nullptr, nullptr, &zero_bits))) { |
| ... | ... | @@ -5758,6 +5803,8 @@ static bool can_mutate_comptime_var_state(ZigValue *value) { |
| 5758 | 5803 | case LazyValueIdAlignOf: |
| 5759 | 5804 | case LazyValueIdSizeOf: |
| 5760 | 5805 | case LazyValueIdPtrType: |
| 5806 | case LazyValueIdPtrTypeSimple: | |
| 5807 | case LazyValueIdPtrTypeSimpleConst: | |
| 5761 | 5808 | case LazyValueIdOptType: |
| 5762 | 5809 | case LazyValueIdSliceType: |
| 5763 | 5810 | case LazyValueIdFnType: |
src/stage1/ir.cpp+140-26| ... | ... | @@ -476,7 +476,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { |
| 476 | 476 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcIntToErr *>(inst)); |
| 477 | 477 | case IrInstSrcIdErrToInt: |
| 478 | 478 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcErrToInt *>(inst)); |
| 479 | case IrInstSrcIdCheckSwitchProngs: | |
| 479 | case IrInstSrcIdCheckSwitchProngsUnderNo: | |
| 480 | case IrInstSrcIdCheckSwitchProngsUnderYes: | |
| 480 | 481 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcCheckSwitchProngs *>(inst)); |
| 481 | 482 | case IrInstSrcIdCheckStatementIsVoid: |
| 482 | 483 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcCheckStatementIsVoid *>(inst)); |
| ... | ... | @@ -486,6 +487,9 @@ static void destroy_instruction_src(IrInstSrc *inst) { |
| 486 | 487 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcTagName *>(inst)); |
| 487 | 488 | case IrInstSrcIdPtrType: |
| 488 | 489 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcPtrType *>(inst)); |
| 490 | case IrInstSrcIdPtrTypeSimple: | |
| 491 | case IrInstSrcIdPtrTypeSimpleConst: | |
| 492 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcPtrTypeSimple *>(inst)); | |
| 489 | 493 | case IrInstSrcIdDeclRef: |
| 490 | 494 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcDeclRef *>(inst)); |
| 491 | 495 | case IrInstSrcIdPanic: |
| ... | ... | @@ -514,7 +518,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { |
| 514 | 518 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcResetResult *>(inst)); |
| 515 | 519 | case IrInstSrcIdSetAlignStack: |
| 516 | 520 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcSetAlignStack *>(inst)); |
| 517 | case IrInstSrcIdArgType: | |
| 521 | case IrInstSrcIdArgTypeAllowVarFalse: | |
| 522 | case IrInstSrcIdArgTypeAllowVarTrue: | |
| 518 | 523 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcArgType *>(inst)); |
| 519 | 524 | case IrInstSrcIdExport: |
| 520 | 525 | return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcExport *>(inst)); |
| ... | ... | @@ -1470,10 +1475,6 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcErrToInt *) { |
| 1470 | 1475 | return IrInstSrcIdErrToInt; |
| 1471 | 1476 | } |
| 1472 | 1477 | |
| 1473 | static constexpr IrInstSrcId ir_inst_id(IrInstSrcCheckSwitchProngs *) { | |
| 1474 | return IrInstSrcIdCheckSwitchProngs; | |
| 1475 | } | |
| 1476 | ||
| 1477 | 1478 | static constexpr IrInstSrcId ir_inst_id(IrInstSrcCheckStatementIsVoid *) { |
| 1478 | 1479 | return IrInstSrcIdCheckStatementIsVoid; |
| 1479 | 1480 | } |
| ... | ... | @@ -1546,10 +1547,6 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcSetAlignStack *) { |
| 1546 | 1547 | return IrInstSrcIdSetAlignStack; |
| 1547 | 1548 | } |
| 1548 | 1549 | |
| 1549 | static constexpr IrInstSrcId ir_inst_id(IrInstSrcArgType *) { | |
| 1550 | return IrInstSrcIdArgType; | |
| 1551 | } | |
| 1552 | ||
| 1553 | 1550 | static constexpr IrInstSrcId ir_inst_id(IrInstSrcExport *) { |
| 1554 | 1551 | return IrInstSrcIdExport; |
| 1555 | 1552 | } |
| ... | ... | @@ -2615,11 +2612,35 @@ static IrInstGen *ir_build_br_gen(IrAnalyze *ira, IrInst *source_instr, IrBasicB |
| 2615 | 2612 | return &inst->base; |
| 2616 | 2613 | } |
| 2617 | 2614 | |
| 2615 | static IrInstSrc *ir_build_ptr_type_simple(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, | |
| 2616 | IrInstSrc *child_type, bool is_const) | |
| 2617 | { | |
| 2618 | IrInstSrcPtrTypeSimple *inst = heap::c_allocator.create<IrInstSrcPtrTypeSimple>(); | |
| 2619 | inst->base.id = is_const ? IrInstSrcIdPtrTypeSimpleConst : IrInstSrcIdPtrTypeSimple; | |
| 2620 | inst->base.base.scope = scope; | |
| 2621 | inst->base.base.source_node = source_node; | |
| 2622 | inst->base.base.debug_id = exec_next_debug_id(irb->exec); | |
| 2623 | inst->base.owner_bb = irb->current_basic_block; | |
| 2624 | ir_instruction_append(irb->current_basic_block, &inst->base); | |
| 2625 | ||
| 2626 | inst->child_type = child_type; | |
| 2627 | ||
| 2628 | ir_ref_instruction(child_type, irb->current_basic_block); | |
| 2629 | ||
| 2630 | return &inst->base; | |
| 2631 | } | |
| 2632 | ||
| 2618 | 2633 | static IrInstSrc *ir_build_ptr_type(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, |
| 2619 | 2634 | IrInstSrc *child_type, bool is_const, bool is_volatile, PtrLen ptr_len, |
| 2620 | 2635 | IrInstSrc *sentinel, IrInstSrc *align_value, |
| 2621 | 2636 | uint32_t bit_offset_start, uint32_t host_int_bytes, bool is_allow_zero) |
| 2622 | 2637 | { |
| 2638 | if (!is_volatile && ptr_len == PtrLenSingle && sentinel == nullptr && align_value == nullptr && | |
| 2639 | bit_offset_start == 0 && host_int_bytes == 0 && is_allow_zero == 0) | |
| 2640 | { | |
| 2641 | return ir_build_ptr_type_simple(irb, scope, source_node, child_type, is_const); | |
| 2642 | } | |
| 2643 | ||
| 2623 | 2644 | IrInstSrcPtrType *inst = ir_build_instruction<IrInstSrcPtrType>(irb, scope, source_node); |
| 2624 | 2645 | inst->sentinel = sentinel; |
| 2625 | 2646 | inst->align_value = align_value; |
| ... | ... | @@ -4354,13 +4375,19 @@ static IrInstSrc *ir_build_check_switch_prongs(IrBuilderSrc *irb, Scope *scope, |
| 4354 | 4375 | IrInstSrc *target_value, IrInstSrcCheckSwitchProngsRange *ranges, size_t range_count, |
| 4355 | 4376 | AstNode* else_prong, bool have_underscore_prong) |
| 4356 | 4377 | { |
| 4357 | IrInstSrcCheckSwitchProngs *instruction = ir_build_instruction<IrInstSrcCheckSwitchProngs>( | |
| 4358 | irb, scope, source_node); | |
| 4378 | IrInstSrcCheckSwitchProngs *instruction = heap::c_allocator.create<IrInstSrcCheckSwitchProngs>(); | |
| 4379 | instruction->base.id = have_underscore_prong ? | |
| 4380 | IrInstSrcIdCheckSwitchProngsUnderYes : IrInstSrcIdCheckSwitchProngsUnderNo; | |
| 4381 | instruction->base.base.scope = scope; | |
| 4382 | instruction->base.base.source_node = source_node; | |
| 4383 | instruction->base.base.debug_id = exec_next_debug_id(irb->exec); | |
| 4384 | instruction->base.owner_bb = irb->current_basic_block; | |
| 4385 | ir_instruction_append(irb->current_basic_block, &instruction->base); | |
| 4386 | ||
| 4359 | 4387 | instruction->target_value = target_value; |
| 4360 | 4388 | instruction->ranges = ranges; |
| 4361 | 4389 | instruction->range_count = range_count; |
| 4362 | 4390 | instruction->else_prong = else_prong; |
| 4363 | instruction->have_underscore_prong = have_underscore_prong; | |
| 4364 | 4391 | |
| 4365 | 4392 | ir_ref_instruction(target_value, irb->current_basic_block); |
| 4366 | 4393 | for (size_t i = 0; i < range_count; i += 1) { |
| ... | ... | @@ -4590,10 +4617,17 @@ static IrInstSrc *ir_build_set_align_stack(IrBuilderSrc *irb, Scope *scope, AstN |
| 4590 | 4617 | static IrInstSrc *ir_build_arg_type(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, |
| 4591 | 4618 | IrInstSrc *fn_type, IrInstSrc *arg_index, bool allow_var) |
| 4592 | 4619 | { |
| 4593 | IrInstSrcArgType *instruction = ir_build_instruction<IrInstSrcArgType>(irb, scope, source_node); | |
| 4620 | IrInstSrcArgType *instruction = heap::c_allocator.create<IrInstSrcArgType>(); | |
| 4621 | instruction->base.id = allow_var ? | |
| 4622 | IrInstSrcIdArgTypeAllowVarTrue : IrInstSrcIdArgTypeAllowVarFalse; | |
| 4623 | instruction->base.base.scope = scope; | |
| 4624 | instruction->base.base.source_node = source_node; | |
| 4625 | instruction->base.base.debug_id = exec_next_debug_id(irb->exec); | |
| 4626 | instruction->base.owner_bb = irb->current_basic_block; | |
| 4627 | ir_instruction_append(irb->current_basic_block, &instruction->base); | |
| 4628 | ||
| 4594 | 4629 | instruction->fn_type = fn_type; |
| 4595 | 4630 | instruction->arg_index = arg_index; |
| 4596 | instruction->allow_var = allow_var; | |
| 4597 | 4631 | |
| 4598 | 4632 | ir_ref_instruction(fn_type, irb->current_basic_block); |
| 4599 | 4633 | ir_ref_instruction(arg_index, irb->current_basic_block); |
| ... | ... | @@ -29702,7 +29736,7 @@ static IrInstGen *ir_analyze_instruction_test_comptime(IrAnalyze *ira, IrInstSrc |
| 29702 | 29736 | } |
| 29703 | 29737 | |
| 29704 | 29738 | static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, |
| 29705 | IrInstSrcCheckSwitchProngs *instruction) | |
| 29739 | IrInstSrcCheckSwitchProngs *instruction, bool have_underscore_prong) | |
| 29706 | 29740 | { |
| 29707 | 29741 | IrInstGen *target_value = instruction->target_value->child; |
| 29708 | 29742 | ZigType *switch_type = target_value->value->type; |
| ... | ... | @@ -29767,7 +29801,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, |
| 29767 | 29801 | bigint_incr(&field_index); |
| 29768 | 29802 | } |
| 29769 | 29803 | } |
| 29770 | if (instruction->have_underscore_prong) { | |
| 29804 | if (have_underscore_prong) { | |
| 29771 | 29805 | if (!switch_type->data.enumeration.non_exhaustive) { |
| 29772 | 29806 | ir_add_error(ira, &instruction->base.base, |
| 29773 | 29807 | buf_sprintf("switch on exhaustive enum has `_` prong")); |
| ... | ... | @@ -30871,6 +30905,24 @@ static IrInstGen *ir_analyze_instruction_ptr_to_int(IrAnalyze *ira, IrInstSrcPtr |
| 30871 | 30905 | return ir_build_ptr_to_int_gen(ira, &instruction->base.base, target); |
| 30872 | 30906 | } |
| 30873 | 30907 | |
| 30908 | static IrInstGen *ir_analyze_instruction_ptr_type_simple(IrAnalyze *ira, | |
| 30909 | IrInstSrcPtrTypeSimple *instruction, bool is_const) | |
| 30910 | { | |
| 30911 | IrInstGen *result = ir_const(ira, &instruction->base.base, ira->codegen->builtin_types.entry_type); | |
| 30912 | result->value->special = ConstValSpecialLazy; | |
| 30913 | ||
| 30914 | LazyValuePtrTypeSimple *lazy_ptr_type = heap::c_allocator.create<LazyValuePtrTypeSimple>(); | |
| 30915 | lazy_ptr_type->ira = ira; ira_ref(ira); | |
| 30916 | result->value->data.x_lazy = &lazy_ptr_type->base; | |
| 30917 | lazy_ptr_type->base.id = is_const ? LazyValueIdPtrTypeSimpleConst : LazyValueIdPtrTypeSimple; | |
| 30918 | ||
| 30919 | lazy_ptr_type->elem_type = instruction->child_type->child; | |
| 30920 | if (ir_resolve_type_lazy(ira, lazy_ptr_type->elem_type) == nullptr) | |
| 30921 | return ira->codegen->invalid_inst_gen; | |
| 30922 | ||
| 30923 | return result; | |
| 30924 | } | |
| 30925 | ||
| 30874 | 30926 | static IrInstGen *ir_analyze_instruction_ptr_type(IrAnalyze *ira, IrInstSrcPtrType *instruction) { |
| 30875 | 30927 | IrInstGen *result = ir_const(ira, &instruction->base.base, ira->codegen->builtin_types.entry_type); |
| 30876 | 30928 | result->value->special = ConstValSpecialLazy; |
| ... | ... | @@ -30976,7 +31028,9 @@ static IrInstGen *ir_analyze_instruction_set_align_stack(IrAnalyze *ira, IrInstS |
| 30976 | 31028 | return ir_const_void(ira, &instruction->base.base); |
| 30977 | 31029 | } |
| 30978 | 31030 | |
| 30979 | static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgType *instruction) { | |
| 31031 | static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgType *instruction, | |
| 31032 | bool allow_var) | |
| 31033 | { | |
| 30980 | 31034 | IrInstGen *fn_type_inst = instruction->fn_type->child; |
| 30981 | 31035 | ZigType *fn_type = ir_resolve_type(ira, fn_type_inst); |
| 30982 | 31036 | if (type_is_invalid(fn_type)) |
| ... | ... | @@ -30998,7 +31052,7 @@ static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgTy |
| 30998 | 31052 | |
| 30999 | 31053 | FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; |
| 31000 | 31054 | if (arg_index >= fn_type_id->param_count) { |
| 31001 | if (instruction->allow_var) { | |
| 31055 | if (allow_var) { | |
| 31002 | 31056 | // TODO remove this with var args |
| 31003 | 31057 | return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_anytype); |
| 31004 | 31058 | } |
| ... | ... | @@ -31013,7 +31067,7 @@ static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgTy |
| 31013 | 31067 | // Args are only unresolved if our function is generic. |
| 31014 | 31068 | ir_assert(fn_type->data.fn.is_generic, &instruction->base.base); |
| 31015 | 31069 | |
| 31016 | if (instruction->allow_var) { | |
| 31070 | if (allow_var) { | |
| 31017 | 31071 | return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_anytype); |
| 31018 | 31072 | } else { |
| 31019 | 31073 | ir_add_error(ira, &arg_index_inst->base, |
| ... | ... | @@ -32341,8 +32395,10 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc |
| 32341 | 32395 | return ir_analyze_instruction_fn_proto(ira, (IrInstSrcFnProto *)instruction); |
| 32342 | 32396 | case IrInstSrcIdTestComptime: |
| 32343 | 32397 | return ir_analyze_instruction_test_comptime(ira, (IrInstSrcTestComptime *)instruction); |
| 32344 | case IrInstSrcIdCheckSwitchProngs: | |
| 32345 | return ir_analyze_instruction_check_switch_prongs(ira, (IrInstSrcCheckSwitchProngs *)instruction); | |
| 32398 | case IrInstSrcIdCheckSwitchProngsUnderNo: | |
| 32399 | return ir_analyze_instruction_check_switch_prongs(ira, (IrInstSrcCheckSwitchProngs *)instruction, false); | |
| 32400 | case IrInstSrcIdCheckSwitchProngsUnderYes: | |
| 32401 | return ir_analyze_instruction_check_switch_prongs(ira, (IrInstSrcCheckSwitchProngs *)instruction, true); | |
| 32346 | 32402 | case IrInstSrcIdCheckStatementIsVoid: |
| 32347 | 32403 | return ir_analyze_instruction_check_statement_is_void(ira, (IrInstSrcCheckStatementIsVoid *)instruction); |
| 32348 | 32404 | case IrInstSrcIdDeclRef: |
| ... | ... | @@ -32373,6 +32429,10 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc |
| 32373 | 32429 | return ir_analyze_instruction_set_eval_branch_quota(ira, (IrInstSrcSetEvalBranchQuota *)instruction); |
| 32374 | 32430 | case IrInstSrcIdPtrType: |
| 32375 | 32431 | return ir_analyze_instruction_ptr_type(ira, (IrInstSrcPtrType *)instruction); |
| 32432 | case IrInstSrcIdPtrTypeSimple: | |
| 32433 | return ir_analyze_instruction_ptr_type_simple(ira, (IrInstSrcPtrTypeSimple *)instruction, false); | |
| 32434 | case IrInstSrcIdPtrTypeSimpleConst: | |
| 32435 | return ir_analyze_instruction_ptr_type_simple(ira, (IrInstSrcPtrTypeSimple *)instruction, true); | |
| 32376 | 32436 | case IrInstSrcIdAlignCast: |
| 32377 | 32437 | return ir_analyze_instruction_align_cast(ira, (IrInstSrcAlignCast *)instruction); |
| 32378 | 32438 | case IrInstSrcIdImplicitCast: |
| ... | ... | @@ -32383,8 +32443,10 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc |
| 32383 | 32443 | return ir_analyze_instruction_reset_result(ira, (IrInstSrcResetResult *)instruction); |
| 32384 | 32444 | case IrInstSrcIdSetAlignStack: |
| 32385 | 32445 | return ir_analyze_instruction_set_align_stack(ira, (IrInstSrcSetAlignStack *)instruction); |
| 32386 | case IrInstSrcIdArgType: | |
| 32387 | return ir_analyze_instruction_arg_type(ira, (IrInstSrcArgType *)instruction); | |
| 32446 | case IrInstSrcIdArgTypeAllowVarFalse: | |
| 32447 | return ir_analyze_instruction_arg_type(ira, (IrInstSrcArgType *)instruction, false); | |
| 32448 | case IrInstSrcIdArgTypeAllowVarTrue: | |
| 32449 | return ir_analyze_instruction_arg_type(ira, (IrInstSrcArgType *)instruction, true); | |
| 32388 | 32450 | case IrInstSrcIdExport: |
| 32389 | 32451 | return ir_analyze_instruction_export(ira, (IrInstSrcExport *)instruction); |
| 32390 | 32452 | case IrInstSrcIdExtern: |
| ... | ... | @@ -32737,12 +32799,15 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { |
| 32737 | 32799 | case IrInstSrcIdMemcpy: |
| 32738 | 32800 | case IrInstSrcIdBreakpoint: |
| 32739 | 32801 | case IrInstSrcIdOverflowOp: // TODO when we support multiple returns this can be side effect free |
| 32740 | case IrInstSrcIdCheckSwitchProngs: | |
| 32802 | case IrInstSrcIdCheckSwitchProngsUnderNo: | |
| 32803 | case IrInstSrcIdCheckSwitchProngsUnderYes: | |
| 32741 | 32804 | case IrInstSrcIdCheckStatementIsVoid: |
| 32742 | 32805 | case IrInstSrcIdCheckRuntimeScope: |
| 32743 | 32806 | case IrInstSrcIdPanic: |
| 32744 | 32807 | case IrInstSrcIdSetEvalBranchQuota: |
| 32745 | 32808 | case IrInstSrcIdPtrType: |
| 32809 | case IrInstSrcIdPtrTypeSimple: | |
| 32810 | case IrInstSrcIdPtrTypeSimpleConst: | |
| 32746 | 32811 | case IrInstSrcIdSetAlignStack: |
| 32747 | 32812 | case IrInstSrcIdExport: |
| 32748 | 32813 | case IrInstSrcIdExtern: |
| ... | ... | @@ -32826,7 +32891,8 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { |
| 32826 | 32891 | case IrInstSrcIdAlignCast: |
| 32827 | 32892 | case IrInstSrcIdImplicitCast: |
| 32828 | 32893 | case IrInstSrcIdResolveResult: |
| 32829 | case IrInstSrcIdArgType: | |
| 32894 | case IrInstSrcIdArgTypeAllowVarFalse: | |
| 32895 | case IrInstSrcIdArgTypeAllowVarTrue: | |
| 32830 | 32896 | case IrInstSrcIdErrorReturnTrace: |
| 32831 | 32897 | case IrInstSrcIdErrorUnion: |
| 32832 | 32898 | case IrInstSrcIdFloatOp: |
| ... | ... | @@ -33249,6 +33315,54 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { |
| 33249 | 33315 | // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. |
| 33250 | 33316 | return ErrorNone; |
| 33251 | 33317 | } |
| 33318 | case LazyValueIdPtrTypeSimple: { | |
| 33319 | LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast<LazyValuePtrTypeSimple *>(val->data.x_lazy); | |
| 33320 | IrAnalyze *ira = lazy_ptr_type->ira; | |
| 33321 | ||
| 33322 | ZigType *elem_type = ir_resolve_type(ira, lazy_ptr_type->elem_type); | |
| 33323 | if (type_is_invalid(elem_type)) | |
| 33324 | return ErrorSemanticAnalyzeFail; | |
| 33325 | ||
| 33326 | if (elem_type->id == ZigTypeIdUnreachable) { | |
| 33327 | ir_add_error(ira, &lazy_ptr_type->elem_type->base, | |
| 33328 | buf_create_from_str("pointer to noreturn not allowed")); | |
| 33329 | return ErrorSemanticAnalyzeFail; | |
| 33330 | } | |
| 33331 | ||
| 33332 | assert(val->type->id == ZigTypeIdMetaType); | |
| 33333 | val->data.x_type = get_pointer_to_type_extra2(ira->codegen, elem_type, | |
| 33334 | false, false, PtrLenSingle, 0, | |
| 33335 | 0, 0, | |
| 33336 | false, VECTOR_INDEX_NONE, nullptr, nullptr); | |
| 33337 | val->special = ConstValSpecialStatic; | |
| 33338 | ||
| 33339 | // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. | |
| 33340 | return ErrorNone; | |
| 33341 | } | |
| 33342 | case LazyValueIdPtrTypeSimpleConst: { | |
| 33343 | LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast<LazyValuePtrTypeSimple *>(val->data.x_lazy); | |
| 33344 | IrAnalyze *ira = lazy_ptr_type->ira; | |
| 33345 | ||
| 33346 | ZigType *elem_type = ir_resolve_type(ira, lazy_ptr_type->elem_type); | |
| 33347 | if (type_is_invalid(elem_type)) | |
| 33348 | return ErrorSemanticAnalyzeFail; | |
| 33349 | ||
| 33350 | if (elem_type->id == ZigTypeIdUnreachable) { | |
| 33351 | ir_add_error(ira, &lazy_ptr_type->elem_type->base, | |
| 33352 | buf_create_from_str("pointer to noreturn not allowed")); | |
| 33353 | return ErrorSemanticAnalyzeFail; | |
| 33354 | } | |
| 33355 | ||
| 33356 | assert(val->type->id == ZigTypeIdMetaType); | |
| 33357 | val->data.x_type = get_pointer_to_type_extra2(ira->codegen, elem_type, | |
| 33358 | true, false, PtrLenSingle, 0, | |
| 33359 | 0, 0, | |
| 33360 | false, VECTOR_INDEX_NONE, nullptr, nullptr); | |
| 33361 | val->special = ConstValSpecialStatic; | |
| 33362 | ||
| 33363 | // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. | |
| 33364 | return ErrorNone; | |
| 33365 | } | |
| 33252 | 33366 | case LazyValueIdArrayType: { |
| 33253 | 33367 | LazyValueArrayType *lazy_array_type = reinterpret_cast<LazyValueArrayType *>(val->data.x_lazy); |
| 33254 | 33368 | IrAnalyze *ira = lazy_array_type->ira; |
src/stage1/ir_print.cpp+49-10| ... | ... | @@ -270,8 +270,10 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { |
| 270 | 270 | return "SrcIntToErr"; |
| 271 | 271 | case IrInstSrcIdErrToInt: |
| 272 | 272 | return "SrcErrToInt"; |
| 273 | case IrInstSrcIdCheckSwitchProngs: | |
| 274 | return "SrcCheckSwitchProngs"; | |
| 273 | case IrInstSrcIdCheckSwitchProngsUnderNo: | |
| 274 | return "SrcCheckSwitchProngsUnderNo"; | |
| 275 | case IrInstSrcIdCheckSwitchProngsUnderYes: | |
| 276 | return "SrcCheckSwitchProngsUnderYes"; | |
| 275 | 277 | case IrInstSrcIdCheckStatementIsVoid: |
| 276 | 278 | return "SrcCheckStatementIsVoid"; |
| 277 | 279 | case IrInstSrcIdTypeName: |
| ... | ... | @@ -298,6 +300,10 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { |
| 298 | 300 | return "SrcSetEvalBranchQuota"; |
| 299 | 301 | case IrInstSrcIdPtrType: |
| 300 | 302 | return "SrcPtrType"; |
| 303 | case IrInstSrcIdPtrTypeSimple: | |
| 304 | return "SrcPtrTypeSimple"; | |
| 305 | case IrInstSrcIdPtrTypeSimpleConst: | |
| 306 | return "SrcPtrTypeSimpleConst"; | |
| 301 | 307 | case IrInstSrcIdAlignCast: |
| 302 | 308 | return "SrcAlignCast"; |
| 303 | 309 | case IrInstSrcIdImplicitCast: |
| ... | ... | @@ -308,8 +314,10 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { |
| 308 | 314 | return "SrcResetResult"; |
| 309 | 315 | case IrInstSrcIdSetAlignStack: |
| 310 | 316 | return "SrcSetAlignStack"; |
| 311 | case IrInstSrcIdArgType: | |
| 312 | return "SrcArgType"; | |
| 317 | case IrInstSrcIdArgTypeAllowVarFalse: | |
| 318 | return "SrcArgTypeAllowVarFalse"; | |
| 319 | case IrInstSrcIdArgTypeAllowVarTrue: | |
| 320 | return "SrcArgTypeAllowVarTrue"; | |
| 313 | 321 | case IrInstSrcIdExport: |
| 314 | 322 | return "SrcExport"; |
| 315 | 323 | case IrInstSrcIdExtern: |
| ... | ... | @@ -2187,7 +2195,9 @@ static void ir_print_err_to_int(IrPrintGen *irp, IrInstGenErrToInt *instruction) |
| 2187 | 2195 | ir_print_other_inst_gen(irp, instruction->target); |
| 2188 | 2196 | } |
| 2189 | 2197 | |
| 2190 | static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchProngs *instruction) { | |
| 2198 | static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchProngs *instruction, | |
| 2199 | bool have_underscore_prong) | |
| 2200 | { | |
| 2191 | 2201 | fprintf(irp->f, "@checkSwitchProngs("); |
| 2192 | 2202 | ir_print_other_inst_src(irp, instruction->target_value); |
| 2193 | 2203 | fprintf(irp->f, ","); |
| ... | ... | @@ -2200,6 +2210,8 @@ static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchPr |
| 2200 | 2210 | } |
| 2201 | 2211 | const char *have_else_str = instruction->else_prong != nullptr ? "yes" : "no"; |
| 2202 | 2212 | fprintf(irp->f, ")else:%s", have_else_str); |
| 2213 | const char *have_under_str = have_underscore_prong ? "yes" : "no"; | |
| 2214 | fprintf(irp->f, " _:%s", have_under_str); | |
| 2203 | 2215 | } |
| 2204 | 2216 | |
| 2205 | 2217 | static void ir_print_check_statement_is_void(IrPrintSrc *irp, IrInstSrcCheckStatementIsVoid *instruction) { |
| ... | ... | @@ -2237,6 +2249,15 @@ static void ir_print_ptr_type(IrPrintSrc *irp, IrInstSrcPtrType *instruction) { |
| 2237 | 2249 | ir_print_other_inst_src(irp, instruction->child_type); |
| 2238 | 2250 | } |
| 2239 | 2251 | |
| 2252 | static void ir_print_ptr_type_simple(IrPrintSrc *irp, IrInstSrcPtrTypeSimple *instruction, | |
| 2253 | bool is_const) | |
| 2254 | { | |
| 2255 | fprintf(irp->f, "&"); | |
| 2256 | const char *const_str = is_const ? "const " : ""; | |
| 2257 | fprintf(irp->f, "*%s", const_str); | |
| 2258 | ir_print_other_inst_src(irp, instruction->child_type); | |
| 2259 | } | |
| 2260 | ||
| 2240 | 2261 | static void ir_print_decl_ref(IrPrintSrc *irp, IrInstSrcDeclRef *instruction) { |
| 2241 | 2262 | const char *ptr_str = (instruction->lval != LValNone) ? "ptr " : ""; |
| 2242 | 2263 | fprintf(irp->f, "declref %s%s", ptr_str, buf_ptr(instruction->tld->name)); |
| ... | ... | @@ -2344,11 +2365,17 @@ static void ir_print_set_align_stack(IrPrintSrc *irp, IrInstSrcSetAlignStack *in |
| 2344 | 2365 | fprintf(irp->f, ")"); |
| 2345 | 2366 | } |
| 2346 | 2367 | |
| 2347 | static void ir_print_arg_type(IrPrintSrc *irp, IrInstSrcArgType *instruction) { | |
| 2368 | static void ir_print_arg_type(IrPrintSrc *irp, IrInstSrcArgType *instruction, bool allow_var) { | |
| 2348 | 2369 | fprintf(irp->f, "@ArgType("); |
| 2349 | 2370 | ir_print_other_inst_src(irp, instruction->fn_type); |
| 2350 | 2371 | fprintf(irp->f, ","); |
| 2351 | 2372 | ir_print_other_inst_src(irp, instruction->arg_index); |
| 2373 | fprintf(irp->f, ","); | |
| 2374 | if (allow_var) { | |
| 2375 | fprintf(irp->f, "allow_var=true"); | |
| 2376 | } else { | |
| 2377 | fprintf(irp->f, "allow_var=false"); | |
| 2378 | } | |
| 2352 | 2379 | fprintf(irp->f, ")"); |
| 2353 | 2380 | } |
| 2354 | 2381 | |
| ... | ... | @@ -2885,8 +2912,11 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai |
| 2885 | 2912 | case IrInstSrcIdErrToInt: |
| 2886 | 2913 | ir_print_err_to_int(irp, (IrInstSrcErrToInt *)instruction); |
| 2887 | 2914 | break; |
| 2888 | case IrInstSrcIdCheckSwitchProngs: | |
| 2889 | ir_print_check_switch_prongs(irp, (IrInstSrcCheckSwitchProngs *)instruction); | |
| 2915 | case IrInstSrcIdCheckSwitchProngsUnderNo: | |
| 2916 | ir_print_check_switch_prongs(irp, (IrInstSrcCheckSwitchProngs *)instruction, false); | |
| 2917 | break; | |
| 2918 | case IrInstSrcIdCheckSwitchProngsUnderYes: | |
| 2919 | ir_print_check_switch_prongs(irp, (IrInstSrcCheckSwitchProngs *)instruction, true); | |
| 2890 | 2920 | break; |
| 2891 | 2921 | case IrInstSrcIdCheckStatementIsVoid: |
| 2892 | 2922 | ir_print_check_statement_is_void(irp, (IrInstSrcCheckStatementIsVoid *)instruction); |
| ... | ... | @@ -2900,6 +2930,12 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai |
| 2900 | 2930 | case IrInstSrcIdPtrType: |
| 2901 | 2931 | ir_print_ptr_type(irp, (IrInstSrcPtrType *)instruction); |
| 2902 | 2932 | break; |
| 2933 | case IrInstSrcIdPtrTypeSimple: | |
| 2934 | ir_print_ptr_type_simple(irp, (IrInstSrcPtrTypeSimple *)instruction, false); | |
| 2935 | break; | |
| 2936 | case IrInstSrcIdPtrTypeSimpleConst: | |
| 2937 | ir_print_ptr_type_simple(irp, (IrInstSrcPtrTypeSimple *)instruction, true); | |
| 2938 | break; | |
| 2903 | 2939 | case IrInstSrcIdDeclRef: |
| 2904 | 2940 | ir_print_decl_ref(irp, (IrInstSrcDeclRef *)instruction); |
| 2905 | 2941 | break; |
| ... | ... | @@ -2942,8 +2978,11 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai |
| 2942 | 2978 | case IrInstSrcIdSetAlignStack: |
| 2943 | 2979 | ir_print_set_align_stack(irp, (IrInstSrcSetAlignStack *)instruction); |
| 2944 | 2980 | break; |
| 2945 | case IrInstSrcIdArgType: | |
| 2946 | ir_print_arg_type(irp, (IrInstSrcArgType *)instruction); | |
| 2981 | case IrInstSrcIdArgTypeAllowVarFalse: | |
| 2982 | ir_print_arg_type(irp, (IrInstSrcArgType *)instruction, false); | |
| 2983 | break; | |
| 2984 | case IrInstSrcIdArgTypeAllowVarTrue: | |
| 2985 | ir_print_arg_type(irp, (IrInstSrcArgType *)instruction, true); | |
| 2947 | 2986 | break; |
| 2948 | 2987 | case IrInstSrcIdExport: |
| 2949 | 2988 | ir_print_export(irp, (IrInstSrcExport *)instruction); |
src/translate_c.zig+254-115| ... | ... | @@ -11,6 +11,7 @@ const math = std.math; |
| 11 | 11 | const ast = @import("translate_c/ast.zig"); |
| 12 | 12 | const Node = ast.Node; |
| 13 | 13 | const Tag = Node.Tag; |
| 14 | const c_builtins = std.c.builtins; | |
| 14 | 15 | |
| 15 | 16 | const CallingConvention = std.builtin.CallingConvention; |
| 16 | 17 | |
| ... | ... | @@ -269,7 +270,10 @@ pub const Context = struct { |
| 269 | 270 | global_scope: *Scope.Root, |
| 270 | 271 | clang_context: *clang.ASTContext, |
| 271 | 272 | mangle_count: u32 = 0, |
| 273 | /// Table of record decls that have been demoted to opaques. | |
| 272 | 274 | opaque_demotes: std.AutoHashMapUnmanaged(usize, void) = .{}, |
| 275 | /// Table of unnamed enums and records that are child types of typedefs. | |
| 276 | unnamed_typedefs: std.AutoHashMapUnmanaged(usize, []const u8) = .{}, | |
| 273 | 277 | |
| 274 | 278 | /// This one is different than the root scope's name table. This contains |
| 275 | 279 | /// a list of names that we found by visiting all the top level decls without |
| ... | ... | @@ -337,6 +341,7 @@ pub fn translate( |
| 337 | 341 | context.alias_list.deinit(); |
| 338 | 342 | context.global_names.deinit(gpa); |
| 339 | 343 | context.opaque_demotes.deinit(gpa); |
| 344 | context.unnamed_typedefs.deinit(gpa); | |
| 340 | 345 | context.global_scope.deinit(); |
| 341 | 346 | } |
| 342 | 347 | |
| ... | ... | @@ -400,6 +405,51 @@ fn declVisitorNamesOnly(c: *Context, decl: *const clang.Decl) Error!void { |
| 400 | 405 | if (decl.castToNamedDecl()) |named_decl| { |
| 401 | 406 | const decl_name = try c.str(named_decl.getName_bytes_begin()); |
| 402 | 407 | try c.global_names.put(c.gpa, decl_name, {}); |
| 408 | ||
| 409 | // Check for typedefs with unnamed enum/record child types. | |
| 410 | if (decl.getKind() == .Typedef) { | |
| 411 | const typedef_decl = @ptrCast(*const clang.TypedefNameDecl, decl); | |
| 412 | var child_ty = typedef_decl.getUnderlyingType().getTypePtr(); | |
| 413 | const addr: usize = while (true) switch (child_ty.getTypeClass()) { | |
| 414 | .Enum => { | |
| 415 | const enum_ty = @ptrCast(*const clang.EnumType, child_ty); | |
| 416 | const enum_decl = enum_ty.getDecl(); | |
| 417 | // check if this decl is unnamed | |
| 418 | if (@ptrCast(*const clang.NamedDecl, enum_decl).getName_bytes_begin()[0] != 0) return; | |
| 419 | break @ptrToInt(enum_decl.getCanonicalDecl()); | |
| 420 | }, | |
| 421 | .Record => { | |
| 422 | const record_ty = @ptrCast(*const clang.RecordType, child_ty); | |
| 423 | const record_decl = record_ty.getDecl(); | |
| 424 | // check if this decl is unnamed | |
| 425 | if (@ptrCast(*const clang.NamedDecl, record_decl).getName_bytes_begin()[0] != 0) return; | |
| 426 | break @ptrToInt(record_decl.getCanonicalDecl()); | |
| 427 | }, | |
| 428 | .Elaborated => { | |
| 429 | const elaborated_ty = @ptrCast(*const clang.ElaboratedType, child_ty); | |
| 430 | child_ty = elaborated_ty.getNamedType().getTypePtr(); | |
| 431 | }, | |
| 432 | .Decayed => { | |
| 433 | const decayed_ty = @ptrCast(*const clang.DecayedType, child_ty); | |
| 434 | child_ty = decayed_ty.getDecayedType().getTypePtr(); | |
| 435 | }, | |
| 436 | .Attributed => { | |
| 437 | const attributed_ty = @ptrCast(*const clang.AttributedType, child_ty); | |
| 438 | child_ty = attributed_ty.getEquivalentType().getTypePtr(); | |
| 439 | }, | |
| 440 | .MacroQualified => { | |
| 441 | const macroqualified_ty = @ptrCast(*const clang.MacroQualifiedType, child_ty); | |
| 442 | child_ty = macroqualified_ty.getModifiedType().getTypePtr(); | |
| 443 | }, | |
| 444 | else => return, | |
| 445 | } else unreachable; | |
| 446 | // TODO https://github.com/ziglang/zig/issues/3756 | |
| 447 | // TODO https://github.com/ziglang/zig/issues/1802 | |
| 448 | const name = if (isZigPrimitiveType(decl_name)) try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ decl_name, c.getMangle() }) else decl_name; | |
| 449 | try c.unnamed_typedefs.putNoClobber(c.gpa, addr, name); | |
| 450 | // Put this typedef in the decl_table to avoid redefinitions. | |
| 451 | try c.decl_table.putNoClobber(c.gpa, @ptrToInt(typedef_decl.getCanonicalDecl()), name); | |
| 452 | } | |
| 403 | 453 | } |
| 404 | 454 | } |
| 405 | 455 | |
| ... | ... | @@ -635,7 +685,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co |
| 635 | 685 | if (has_init) trans_init: { |
| 636 | 686 | if (decl_init) |expr| { |
| 637 | 687 | const node_or_error = if (expr.getStmtClass() == .StringLiteralClass) |
| 638 | transStringLiteralAsArray(c, scope, @ptrCast(*const clang.StringLiteral, expr), zigArraySize(c, type_node) catch 0) | |
| 688 | transStringLiteralInitializer(c, scope, @ptrCast(*const clang.StringLiteral, expr), type_node) | |
| 639 | 689 | else |
| 640 | 690 | transExprCoercing(c, scope, expr, .used); |
| 641 | 691 | init_node = node_or_error catch |err| switch (err) { |
| ... | ... | @@ -751,17 +801,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD |
| 751 | 801 | const toplevel = scope.id == .root; |
| 752 | 802 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; |
| 753 | 803 | |
| 754 | var bare_name = try c.str(@ptrCast(*const clang.NamedDecl, record_decl).getName_bytes_begin()); | |
| 755 | var is_unnamed = false; | |
| 756 | // Record declarations such as `struct {...} x` have no name but they're not | |
| 757 | // anonymous hence here isAnonymousStructOrUnion is not needed | |
| 758 | if (bare_name.len == 0) { | |
| 759 | bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); | |
| 760 | is_unnamed = true; | |
| 761 | } | |
| 762 | ||
| 763 | var container_kind_name: []const u8 = undefined; | |
| 764 | 804 | var is_union = false; |
| 805 | var container_kind_name: []const u8 = undefined; | |
| 806 | var bare_name: []const u8 = try c.str(@ptrCast(*const clang.NamedDecl, record_decl).getName_bytes_begin()); | |
| 807 | ||
| 765 | 808 | if (record_decl.isUnion()) { |
| 766 | 809 | container_kind_name = "union"; |
| 767 | 810 | is_union = true; |
| ... | ... | @@ -772,7 +815,20 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD |
| 772 | 815 | return failDecl(c, record_loc, bare_name, "record {s} is not a struct or union", .{bare_name}); |
| 773 | 816 | } |
| 774 | 817 | |
| 775 | var name: []const u8 = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name }); | |
| 818 | var is_unnamed = false; | |
| 819 | var name = bare_name; | |
| 820 | if (c.unnamed_typedefs.get(@ptrToInt(record_decl.getCanonicalDecl()))) |typedef_name| { | |
| 821 | bare_name = typedef_name; | |
| 822 | name = typedef_name; | |
| 823 | } else { | |
| 824 | // Record declarations such as `struct {...} x` have no name but they're not | |
| 825 | // anonymous hence here isAnonymousStructOrUnion is not needed | |
| 826 | if (bare_name.len == 0) { | |
| 827 | bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); | |
| 828 | is_unnamed = true; | |
| 829 | } | |
| 830 | name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name }); | |
| 831 | } | |
| 776 | 832 | if (!toplevel) name = try bs.makeMangledName(c, name); |
| 777 | 833 | try c.decl_table.putNoClobber(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), name); |
| 778 | 834 | |
| ... | ... | @@ -873,14 +929,19 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) E |
| 873 | 929 | const toplevel = scope.id == .root; |
| 874 | 930 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; |
| 875 | 931 | |
| 876 | var bare_name = try c.str(@ptrCast(*const clang.NamedDecl, enum_decl).getName_bytes_begin()); | |
| 877 | 932 | var is_unnamed = false; |
| 878 | if (bare_name.len == 0) { | |
| 879 | bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); | |
| 880 | is_unnamed = true; | |
| 933 | var bare_name: []const u8 = try c.str(@ptrCast(*const clang.NamedDecl, enum_decl).getName_bytes_begin()); | |
| 934 | var name = bare_name; | |
| 935 | if (c.unnamed_typedefs.get(@ptrToInt(enum_decl.getCanonicalDecl()))) |typedef_name| { | |
| 936 | bare_name = typedef_name; | |
| 937 | name = typedef_name; | |
| 938 | } else { | |
| 939 | if (bare_name.len == 0) { | |
| 940 | bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); | |
| 941 | is_unnamed = true; | |
| 942 | } | |
| 943 | name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name}); | |
| 881 | 944 | } |
| 882 | ||
| 883 | var name: []const u8 = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name}); | |
| 884 | 945 | if (!toplevel) _ = try bs.makeMangledName(c, name); |
| 885 | 946 | try c.decl_table.putNoClobber(c.gpa, @ptrToInt(enum_decl.getCanonicalDecl()), name); |
| 886 | 947 | |
| ... | ... | @@ -1058,6 +1119,11 @@ fn transStmt( |
| 1058 | 1119 | const compound_literal = @ptrCast(*const clang.CompoundLiteralExpr, stmt); |
| 1059 | 1120 | return transExpr(c, scope, compound_literal.getInitializer(), result_used); |
| 1060 | 1121 | }, |
| 1122 | .GenericSelectionExprClass => { | |
| 1123 | const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, stmt); | |
| 1124 | return transExpr(c, scope, gen_sel.getResultExpr(), result_used); | |
| 1125 | }, | |
| 1126 | // When adding new cases here, see comment for maybeBlockify() | |
| 1061 | 1127 | else => { |
| 1062 | 1128 | return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)}); |
| 1063 | 1129 | }, |
| ... | ... | @@ -1407,7 +1473,7 @@ fn transDeclStmtOne( |
| 1407 | 1473 | |
| 1408 | 1474 | var init_node = if (decl_init) |expr| |
| 1409 | 1475 | if (expr.getStmtClass() == .StringLiteralClass) |
| 1410 | try transStringLiteralAsArray(c, scope, @ptrCast(*const clang.StringLiteral, expr), try zigArraySize(c, type_node)) | |
| 1476 | try transStringLiteralInitializer(c, scope, @ptrCast(*const clang.StringLiteral, expr), type_node) | |
| 1411 | 1477 | else |
| 1412 | 1478 | try transExprCoercing(c, scope, expr, .used) |
| 1413 | 1479 | else |
| ... | ... | @@ -1522,7 +1588,7 @@ fn transImplicitCastExpr( |
| 1522 | 1588 | return maybeSuppressResult(c, scope, result_used, ne); |
| 1523 | 1589 | }, |
| 1524 | 1590 | .BuiltinFnToFnPtr => { |
| 1525 | return transExpr(c, scope, sub_expr, result_used); | |
| 1591 | return transBuiltinFnExpr(c, scope, sub_expr, result_used); | |
| 1526 | 1592 | }, |
| 1527 | 1593 | .ToVoid => { |
| 1528 | 1594 | // Should only appear in the rhs and lhs of a ConditionalOperator |
| ... | ... | @@ -1538,6 +1604,22 @@ fn transImplicitCastExpr( |
| 1538 | 1604 | } |
| 1539 | 1605 | } |
| 1540 | 1606 | |
| 1607 | fn isBuiltinDefined(name: []const u8) bool { | |
| 1608 | inline for (std.meta.declarations(c_builtins)) |decl| { | |
| 1609 | if (std.mem.eql(u8, name, decl.name)) return true; | |
| 1610 | } | |
| 1611 | return false; | |
| 1612 | } | |
| 1613 | ||
| 1614 | fn transBuiltinFnExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node { | |
| 1615 | const node = try transExpr(c, scope, expr, used); | |
| 1616 | if (node.castTag(.identifier)) |ident| { | |
| 1617 | const name = ident.data; | |
| 1618 | if (!isBuiltinDefined(name)) return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "TODO implement function '{s}' in std.c.builtins", .{name}); | |
| 1619 | } | |
| 1620 | return node; | |
| 1621 | } | |
| 1622 | ||
| 1541 | 1623 | fn transBoolExpr( |
| 1542 | 1624 | c: *Context, |
| 1543 | 1625 | scope: *Scope, |
| ... | ... | @@ -1582,6 +1664,10 @@ fn exprIsNarrowStringLiteral(expr: *const clang.Expr) bool { |
| 1582 | 1664 | const op_expr = @ptrCast(*const clang.ParenExpr, expr).getSubExpr(); |
| 1583 | 1665 | return exprIsNarrowStringLiteral(op_expr); |
| 1584 | 1666 | }, |
| 1667 | .GenericSelectionExprClass => { | |
| 1668 | const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, expr); | |
| 1669 | return exprIsNarrowStringLiteral(gen_sel.getResultExpr()); | |
| 1670 | }, | |
| 1585 | 1671 | else => return false, |
| 1586 | 1672 | } |
| 1587 | 1673 | } |
| ... | ... | @@ -1733,6 +1819,20 @@ fn transReturnStmt( |
| 1733 | 1819 | return Tag.@"return".create(c.arena, rhs); |
| 1734 | 1820 | } |
| 1735 | 1821 | |
| 1822 | fn transNarrowStringLiteral( | |
| 1823 | c: *Context, | |
| 1824 | scope: *Scope, | |
| 1825 | stmt: *const clang.StringLiteral, | |
| 1826 | result_used: ResultUsed, | |
| 1827 | ) TransError!Node { | |
| 1828 | var len: usize = undefined; | |
| 1829 | const bytes_ptr = stmt.getString_bytes_begin_size(&len); | |
| 1830 | ||
| 1831 | const str = try std.fmt.allocPrint(c.arena, "\"{}\"", .{std.zig.fmtEscapes(bytes_ptr[0..len])}); | |
| 1832 | const node = try Tag.string_literal.create(c.arena, str); | |
| 1833 | return maybeSuppressResult(c, scope, result_used, node); | |
| 1834 | } | |
| 1835 | ||
| 1736 | 1836 | fn transStringLiteral( |
| 1737 | 1837 | c: *Context, |
| 1738 | 1838 | scope: *Scope, |
| ... | ... | @@ -1741,19 +1841,14 @@ fn transStringLiteral( |
| 1741 | 1841 | ) TransError!Node { |
| 1742 | 1842 | const kind = stmt.getKind(); |
| 1743 | 1843 | switch (kind) { |
| 1744 | .Ascii, .UTF8 => { | |
| 1745 | var len: usize = undefined; | |
| 1746 | const bytes_ptr = stmt.getString_bytes_begin_size(&len); | |
| 1747 | ||
| 1748 | const str = try std.fmt.allocPrint(c.arena, "\"{}\"", .{std.zig.fmtEscapes(bytes_ptr[0..len])}); | |
| 1749 | const node = try Tag.string_literal.create(c.arena, str); | |
| 1750 | return maybeSuppressResult(c, scope, result_used, node); | |
| 1751 | }, | |
| 1844 | .Ascii, .UTF8 => return transNarrowStringLiteral(c, scope, stmt, result_used), | |
| 1752 | 1845 | .UTF16, .UTF32, .Wide => { |
| 1753 | 1846 | const str_type = @tagName(stmt.getKind()); |
| 1754 | 1847 | const name = try std.fmt.allocPrint(c.arena, "zig.{s}_string_{d}", .{ str_type, c.getMangle() }); |
| 1755 | const lit_array = try transStringLiteralAsArray(c, scope, stmt, stmt.getLength() + 1); | |
| 1756 | 1848 | |
| 1849 | const expr_base = @ptrCast(*const clang.Expr, stmt); | |
| 1850 | const array_type = try transQualTypeInitialized(c, scope, expr_base.getType(), expr_base, expr_base.getBeginLoc()); | |
| 1851 | const lit_array = try transStringLiteralInitializer(c, scope, stmt, array_type); | |
| 1757 | 1852 | const decl = try Tag.var_simple.create(c.arena, .{ .name = name, .init = lit_array }); |
| 1758 | 1853 | try scope.appendNode(decl); |
| 1759 | 1854 | const node = try Tag.identifier.create(c.arena, name); |
| ... | ... | @@ -1762,52 +1857,67 @@ fn transStringLiteral( |
| 1762 | 1857 | } |
| 1763 | 1858 | } |
| 1764 | 1859 | |
| 1765 | /// Parse the size of an array back out from an ast Node. | |
| 1766 | fn zigArraySize(c: *Context, node: Node) TransError!usize { | |
| 1767 | if (node.castTag(.array_type)) |array| { | |
| 1768 | return array.data.len; | |
| 1769 | } | |
| 1770 | return error.UnsupportedTranslation; | |
| 1860 | fn getArrayPayload(array_type: Node) ast.Payload.Array.ArrayTypeInfo { | |
| 1861 | return (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data; | |
| 1771 | 1862 | } |
| 1772 | 1863 | |
| 1773 | /// Translate a string literal to an array of integers. Used when an | |
| 1774 | /// array is initialized from a string literal. `array_size` is the | |
| 1775 | /// size of the array being initialized. If the string literal is larger | |
| 1776 | /// than the array, truncate the string. If the array is larger than the | |
| 1777 | /// string literal, pad the array with 0's | |
| 1778 | fn transStringLiteralAsArray( | |
| 1864 | /// Translate a string literal that is initializing an array. In general narrow string | |
| 1865 | /// literals become `"<string>".*` or `"<string>"[0..<size>].*` if they need truncation. | |
| 1866 | /// Wide string literals become an array of integers. zero-fillers pad out the array to | |
| 1867 | /// the appropriate length, if necessary. | |
| 1868 | fn transStringLiteralInitializer( | |
| 1779 | 1869 | c: *Context, |
| 1780 | 1870 | scope: *Scope, |
| 1781 | 1871 | stmt: *const clang.StringLiteral, |
| 1782 | array_size: usize, | |
| 1872 | array_type: Node, | |
| 1783 | 1873 | ) TransError!Node { |
| 1784 | if (array_size == 0) return error.UnsupportedType; | |
| 1874 | assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type); | |
| 1875 | ||
| 1876 | const is_narrow = stmt.getKind() == .Ascii or stmt.getKind() == .UTF8; | |
| 1785 | 1877 | |
| 1786 | 1878 | const str_length = stmt.getLength(); |
| 1879 | const payload = getArrayPayload(array_type); | |
| 1880 | const array_size = payload.len; | |
| 1881 | const elem_type = payload.elem_type; | |
| 1882 | ||
| 1883 | if (array_size == 0) return Tag.empty_array.create(c.arena, elem_type); | |
| 1884 | ||
| 1885 | const num_inits = math.min(str_length, array_size); | |
| 1886 | const init_node = if (num_inits > 0) blk: { | |
| 1887 | if (is_narrow) { | |
| 1888 | // "string literal".* or string literal"[0..num_inits].* | |
| 1889 | var str = try transNarrowStringLiteral(c, scope, stmt, .used); | |
| 1890 | if (str_length != array_size) str = try Tag.string_slice.create(c.arena, .{ .string = str, .end = num_inits }); | |
| 1891 | break :blk try Tag.deref.create(c.arena, str); | |
| 1892 | } else { | |
| 1893 | const init_list = try c.arena.alloc(Node, num_inits); | |
| 1894 | var i: c_uint = 0; | |
| 1895 | while (i < num_inits) : (i += 1) { | |
| 1896 | init_list[i] = try transCreateCharLitNode(c, false, stmt.getCodeUnit(i)); | |
| 1897 | } | |
| 1898 | const init_args = .{ .len = num_inits, .elem_type = elem_type }; | |
| 1899 | const init_array_type = try if (array_type.tag() == .array_type) Tag.array_type.create(c.arena, init_args) else Tag.null_sentinel_array_type.create(c.arena, init_args); | |
| 1900 | break :blk try Tag.array_init.create(c.arena, .{ | |
| 1901 | .cond = init_array_type, | |
| 1902 | .cases = init_list, | |
| 1903 | }); | |
| 1904 | } | |
| 1905 | } else null; | |
| 1787 | 1906 | |
| 1788 | const expr_base = @ptrCast(*const clang.Expr, stmt); | |
| 1789 | const ty = expr_base.getType().getTypePtr(); | |
| 1790 | const const_arr_ty = @ptrCast(*const clang.ConstantArrayType, ty); | |
| 1907 | if (num_inits == array_size) return init_node.?; // init_node is only null if num_inits == 0; but if num_inits == array_size == 0 we've already returned | |
| 1908 | assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned. | |
| 1791 | 1909 | |
| 1792 | const elem_type = try transQualType(c, scope, const_arr_ty.getElementType(), expr_base.getBeginLoc()); | |
| 1793 | const arr_type = try Tag.array_type.create(c.arena, .{ .len = array_size, .elem_type = elem_type }); | |
| 1794 | const init_list = try c.arena.alloc(Node, array_size); | |
| 1910 | const filler_node = try Tag.array_filler.create(c.arena, .{ | |
| 1911 | .type = elem_type, | |
| 1912 | .filler = Tag.zero_literal.init(), | |
| 1913 | .count = array_size - str_length, | |
| 1914 | }); | |
| 1795 | 1915 | |
| 1796 | var i: c_uint = 0; | |
| 1797 | const kind = stmt.getKind(); | |
| 1798 | const narrow = kind == .Ascii or kind == .UTF8; | |
| 1799 | while (i < str_length and i < array_size) : (i += 1) { | |
| 1800 | const code_unit = stmt.getCodeUnit(i); | |
| 1801 | init_list[i] = try transCreateCharLitNode(c, narrow, code_unit); | |
| 1802 | } | |
| 1803 | while (i < array_size) : (i += 1) { | |
| 1804 | init_list[i] = try transCreateNodeNumber(c, 0, .int); | |
| 1916 | if (init_node) |some| { | |
| 1917 | return Tag.array_cat.create(c.arena, .{ .lhs = some, .rhs = filler_node }); | |
| 1918 | } else { | |
| 1919 | return filler_node; | |
| 1805 | 1920 | } |
| 1806 | ||
| 1807 | return Tag.array_init.create(c.arena, .{ | |
| 1808 | .cond = arr_type, | |
| 1809 | .cases = init_list, | |
| 1810 | }); | |
| 1811 | 1921 | } |
| 1812 | 1922 | |
| 1813 | 1923 | /// determine whether `stmt` is a "pointer subtraction expression" - a subtraction where |
| ... | ... | @@ -1836,6 +1946,7 @@ fn cIntTypeForEnum(enum_qt: clang.QualType) clang.QualType { |
| 1836 | 1946 | return enum_decl.getIntegerType(); |
| 1837 | 1947 | } |
| 1838 | 1948 | |
| 1949 | // when modifying this function, make sure to also update std.meta.cast | |
| 1839 | 1950 | fn transCCast( |
| 1840 | 1951 | c: *Context, |
| 1841 | 1952 | scope: *Scope, |
| ... | ... | @@ -2192,6 +2303,35 @@ fn transImplicitValueInitExpr( |
| 2192 | 2303 | return transZeroInitExpr(c, scope, source_loc, ty); |
| 2193 | 2304 | } |
| 2194 | 2305 | |
| 2306 | /// If a statement can possibly translate to a Zig assignment (either directly because it's | |
| 2307 | /// an assignment in C or indirectly via result assignment to `_`) AND it's the sole statement | |
| 2308 | /// in the body of an if statement or loop, then we need to put the statement into its own block. | |
| 2309 | /// The `else` case here corresponds to statements that could result in an assignment. If a statement | |
| 2310 | /// class never needs a block, add its enum to the top prong. | |
| 2311 | fn maybeBlockify(c: *Context, scope: *Scope, stmt: *const clang.Stmt) TransError!Node { | |
| 2312 | switch (stmt.getStmtClass()) { | |
| 2313 | .BreakStmtClass, | |
| 2314 | .CompoundStmtClass, | |
| 2315 | .ContinueStmtClass, | |
| 2316 | .DeclRefExprClass, | |
| 2317 | .DeclStmtClass, | |
| 2318 | .DoStmtClass, | |
| 2319 | .ForStmtClass, | |
| 2320 | .IfStmtClass, | |
| 2321 | .ReturnStmtClass, | |
| 2322 | .NullStmtClass, | |
| 2323 | .WhileStmtClass, | |
| 2324 | => return transStmt(c, scope, stmt, .unused), | |
| 2325 | else => { | |
| 2326 | var block_scope = try Scope.Block.init(c, scope, false); | |
| 2327 | defer block_scope.deinit(); | |
| 2328 | const result = try transStmt(c, &block_scope.base, stmt, .unused); | |
| 2329 | try block_scope.statements.append(result); | |
| 2330 | return block_scope.complete(c); | |
| 2331 | }, | |
| 2332 | } | |
| 2333 | } | |
| 2334 | ||
| 2195 | 2335 | fn transIfStmt( |
| 2196 | 2336 | c: *Context, |
| 2197 | 2337 | scope: *Scope, |
| ... | ... | @@ -2209,9 +2349,10 @@ fn transIfStmt( |
| 2209 | 2349 | const cond_expr = @ptrCast(*const clang.Expr, stmt.getCond()); |
| 2210 | 2350 | const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used); |
| 2211 | 2351 | |
| 2212 | const then_body = try transStmt(c, scope, stmt.getThen(), .unused); | |
| 2352 | const then_body = try maybeBlockify(c, scope, stmt.getThen()); | |
| 2353 | ||
| 2213 | 2354 | const else_body = if (stmt.getElse()) |expr| |
| 2214 | try transStmt(c, scope, expr, .unused) | |
| 2355 | try maybeBlockify(c, scope, expr) | |
| 2215 | 2356 | else |
| 2216 | 2357 | null; |
| 2217 | 2358 | return Tag.@"if".create(c.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); |
| ... | ... | @@ -2236,7 +2377,7 @@ fn transWhileLoop( |
| 2236 | 2377 | .parent = scope, |
| 2237 | 2378 | .id = .loop, |
| 2238 | 2379 | }; |
| 2239 | const body = try transStmt(c, &loop_scope, stmt.getBody(), .unused); | |
| 2380 | const body = try maybeBlockify(c, &loop_scope, stmt.getBody()); | |
| 2240 | 2381 | return Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = null }); |
| 2241 | 2382 | } |
| 2242 | 2383 | |
| ... | ... | @@ -2262,7 +2403,7 @@ fn transDoWhileLoop( |
| 2262 | 2403 | const if_not_break = switch (cond.tag()) { |
| 2263 | 2404 | .false_literal => return transStmt(c, scope, stmt.getBody(), .unused), |
| 2264 | 2405 | .true_literal => { |
| 2265 | const body_node = try transStmt(c, scope, stmt.getBody(), .unused); | |
| 2406 | const body_node = try maybeBlockify(c, scope, stmt.getBody()); | |
| 2266 | 2407 | return Tag.while_true.create(c.arena, body_node); |
| 2267 | 2408 | }, |
| 2268 | 2409 | else => try Tag.if_not_break.create(c.arena, cond), |
| ... | ... | @@ -2338,7 +2479,7 @@ fn transForLoop( |
| 2338 | 2479 | else |
| 2339 | 2480 | null; |
| 2340 | 2481 | |
| 2341 | const body = try transStmt(c, &loop_scope, stmt.getBody(), .unused); | |
| 2482 | const body = try maybeBlockify(c, &loop_scope, stmt.getBody()); | |
| 2342 | 2483 | const while_node = try Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr }); |
| 2343 | 2484 | if (block_scope) |*bs| { |
| 2344 | 2485 | try bs.statements.append(while_node); |
| ... | ... | @@ -2725,6 +2866,10 @@ fn cIsFunctionDeclRef(expr: *const clang.Expr) bool { |
| 2725 | 2866 | const opcode = un_op.getOpcode(); |
| 2726 | 2867 | return (opcode == .AddrOf or opcode == .Deref) and cIsFunctionDeclRef(un_op.getSubExpr()); |
| 2727 | 2868 | }, |
| 2869 | .GenericSelectionExprClass => { | |
| 2870 | const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, expr); | |
| 2871 | return cIsFunctionDeclRef(gen_sel.getResultExpr()); | |
| 2872 | }, | |
| 2728 | 2873 | else => return false, |
| 2729 | 2874 | } |
| 2730 | 2875 | } |
| ... | ... | @@ -3052,43 +3197,34 @@ fn transCreateCompoundAssign( |
| 3052 | 3197 | const requires_int_cast = blk: { |
| 3053 | 3198 | const are_integers = cIsInteger(lhs_qt) and cIsInteger(rhs_qt); |
| 3054 | 3199 | const are_same_sign = cIsSignedInteger(lhs_qt) == cIsSignedInteger(rhs_qt); |
| 3055 | break :blk are_integers and !are_same_sign; | |
| 3200 | break :blk are_integers and !(are_same_sign and cIntTypeCmp(lhs_qt, rhs_qt) == .eq); | |
| 3056 | 3201 | }; |
| 3202 | ||
| 3057 | 3203 | if (used == .unused) { |
| 3058 | 3204 | // common case |
| 3059 | 3205 | // c: lhs += rhs |
| 3060 | 3206 | // zig: lhs += rhs |
| 3207 | const lhs_node = try transExpr(c, scope, lhs, .used); | |
| 3208 | var rhs_node = try transExpr(c, scope, rhs, .used); | |
| 3209 | if (is_ptr_op_signed) rhs_node = try usizeCastForWrappingPtrArithmetic(c.arena, rhs_node); | |
| 3210 | ||
| 3061 | 3211 | if ((is_mod or is_div) and is_signed) { |
| 3062 | const lhs_node = try transExpr(c, scope, lhs, .used); | |
| 3063 | const rhs_node = try transExpr(c, scope, rhs, .used); | |
| 3212 | if (requires_int_cast) rhs_node = try transCCast(c, scope, loc, lhs_qt, rhs_qt, rhs_node); | |
| 3213 | const operands = .{ .lhs = lhs_node, .rhs = rhs_node }; | |
| 3064 | 3214 | const builtin = if (is_mod) |
| 3065 | try Tag.rem.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node }) | |
| 3215 | try Tag.rem.create(c.arena, operands) | |
| 3066 | 3216 | else |
| 3067 | try Tag.div_trunc.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node }); | |
| 3217 | try Tag.div_trunc.create(c.arena, operands); | |
| 3068 | 3218 | |
| 3069 | 3219 | return transCreateNodeInfixOp(c, scope, .assign, lhs_node, builtin, .used); |
| 3070 | 3220 | } |
| 3071 | 3221 | |
| 3072 | const lhs_node = try transExpr(c, scope, lhs, .used); | |
| 3073 | var rhs_node = if (is_shift or requires_int_cast) | |
| 3074 | try transExprCoercing(c, scope, rhs, .used) | |
| 3075 | else | |
| 3076 | try transExpr(c, scope, rhs, .used); | |
| 3077 | ||
| 3078 | if (is_ptr_op_signed) { | |
| 3079 | rhs_node = try usizeCastForWrappingPtrArithmetic(c.arena, rhs_node); | |
| 3080 | } | |
| 3081 | ||
| 3082 | if (is_shift or requires_int_cast) { | |
| 3083 | // @intCast(rhs) | |
| 3084 | const cast_to_type = if (is_shift) | |
| 3085 | try qualTypeToLog2IntRef(c, scope, getExprQualType(c, rhs), loc) | |
| 3086 | else | |
| 3087 | try transQualType(c, scope, getExprQualType(c, lhs), loc); | |
| 3088 | ||
| 3222 | if (is_shift) { | |
| 3223 | const cast_to_type = try qualTypeToLog2IntRef(c, scope, rhs_qt, loc); | |
| 3089 | 3224 | rhs_node = try Tag.int_cast.create(c.arena, .{ .lhs = cast_to_type, .rhs = rhs_node }); |
| 3225 | } else if (requires_int_cast) { | |
| 3226 | rhs_node = try transCCast(c, scope, loc, lhs_qt, rhs_qt, rhs_node); | |
| 3090 | 3227 | } |
| 3091 | ||
| 3092 | 3228 | return transCreateNodeInfixOp(c, scope, op, lhs_node, rhs_node, .used); |
| 3093 | 3229 | } |
| 3094 | 3230 | // worst case |
| ... | ... | @@ -3110,29 +3246,24 @@ fn transCreateCompoundAssign( |
| 3110 | 3246 | const lhs_node = try Tag.identifier.create(c.arena, ref); |
| 3111 | 3247 | const ref_node = try Tag.deref.create(c.arena, lhs_node); |
| 3112 | 3248 | |
| 3249 | var rhs_node = try transExpr(c, &block_scope.base, rhs, .used); | |
| 3250 | if (is_ptr_op_signed) rhs_node = try usizeCastForWrappingPtrArithmetic(c.arena, rhs_node); | |
| 3113 | 3251 | if ((is_mod or is_div) and is_signed) { |
| 3114 | const rhs_node = try transExpr(c, &block_scope.base, rhs, .used); | |
| 3252 | if (requires_int_cast) rhs_node = try transCCast(c, scope, loc, lhs_qt, rhs_qt, rhs_node); | |
| 3253 | const operands = .{ .lhs = ref_node, .rhs = rhs_node }; | |
| 3115 | 3254 | const builtin = if (is_mod) |
| 3116 | try Tag.rem.create(c.arena, .{ .lhs = ref_node, .rhs = rhs_node }) | |
| 3255 | try Tag.rem.create(c.arena, operands) | |
| 3117 | 3256 | else |
| 3118 | try Tag.div_trunc.create(c.arena, .{ .lhs = ref_node, .rhs = rhs_node }); | |
| 3257 | try Tag.div_trunc.create(c.arena, operands); | |
| 3119 | 3258 | |
| 3120 | 3259 | const assign = try transCreateNodeInfixOp(c, &block_scope.base, .assign, ref_node, builtin, .used); |
| 3121 | 3260 | try block_scope.statements.append(assign); |
| 3122 | 3261 | } else { |
| 3123 | var rhs_node = try transExpr(c, &block_scope.base, rhs, .used); | |
| 3124 | ||
| 3125 | if (is_shift or requires_int_cast) { | |
| 3126 | // @intCast(rhs) | |
| 3127 | const cast_to_type = if (is_shift) | |
| 3128 | try qualTypeToLog2IntRef(c, scope, getExprQualType(c, rhs), loc) | |
| 3129 | else | |
| 3130 | try transQualType(c, scope, getExprQualType(c, lhs), loc); | |
| 3131 | ||
| 3262 | if (is_shift) { | |
| 3263 | const cast_to_type = try qualTypeToLog2IntRef(c, &block_scope.base, rhs_qt, loc); | |
| 3132 | 3264 | rhs_node = try Tag.int_cast.create(c.arena, .{ .lhs = cast_to_type, .rhs = rhs_node }); |
| 3133 | } | |
| 3134 | if (is_ptr_op_signed) { | |
| 3135 | rhs_node = try usizeCastForWrappingPtrArithmetic(c.arena, rhs_node); | |
| 3265 | } else if (requires_int_cast) { | |
| 3266 | rhs_node = try transCCast(c, &block_scope.base, loc, lhs_qt, rhs_qt, rhs_node); | |
| 3136 | 3267 | } |
| 3137 | 3268 | |
| 3138 | 3269 | const assign = try transCreateNodeInfixOp(c, &block_scope.base, op, ref_node, rhs_node, .used); |
| ... | ... | @@ -3194,11 +3325,11 @@ fn transFloatingLiteral(c: *Context, scope: *Scope, stmt: *const clang.FloatingL |
| 3194 | 3325 | var dbl = stmt.getValueAsApproximateDouble(); |
| 3195 | 3326 | const is_negative = dbl < 0; |
| 3196 | 3327 | if (is_negative) dbl = -dbl; |
| 3197 | const str = try std.fmt.allocPrint(c.arena, "{d}", .{dbl}); | |
| 3198 | var node = if (dbl == std.math.floor(dbl)) | |
| 3199 | try Tag.integer_literal.create(c.arena, str) | |
| 3328 | const str = if (dbl == std.math.floor(dbl)) | |
| 3329 | try std.fmt.allocPrint(c.arena, "{d}.0", .{dbl}) | |
| 3200 | 3330 | else |
| 3201 | try Tag.float_literal.create(c.arena, str); | |
| 3331 | try std.fmt.allocPrint(c.arena, "{d}", .{dbl}); | |
| 3332 | var node = try Tag.float_literal.create(c.arena, str); | |
| 3202 | 3333 | if (is_negative) node = try Tag.negate.create(c.arena, node); |
| 3203 | 3334 | return maybeSuppressResult(c, scope, used, node); |
| 3204 | 3335 | } |
| ... | ... | @@ -3312,9 +3443,8 @@ fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: Node) !void { |
| 3312 | 3443 | try c.global_scope.nodes.append(decl_node); |
| 3313 | 3444 | } |
| 3314 | 3445 | |
| 3315 | /// Translate a qual type for a variable with an initializer. The initializer | |
| 3316 | /// only matters for incomplete arrays, since the size of the array is determined | |
| 3317 | /// by the size of the initializer | |
| 3446 | /// Translate a qualtype for a variable with an initializer. This only matters | |
| 3447 | /// for incomplete arrays, since the initializer determines the size of the array. | |
| 3318 | 3448 | fn transQualTypeInitialized( |
| 3319 | 3449 | c: *Context, |
| 3320 | 3450 | scope: *Scope, |
| ... | ... | @@ -3330,9 +3460,14 @@ fn transQualTypeInitialized( |
| 3330 | 3460 | switch (decl_init.getStmtClass()) { |
| 3331 | 3461 | .StringLiteralClass => { |
| 3332 | 3462 | const string_lit = @ptrCast(*const clang.StringLiteral, decl_init); |
| 3333 | const string_lit_size = string_lit.getLength() + 1; // +1 for null terminator | |
| 3463 | const string_lit_size = string_lit.getLength(); | |
| 3334 | 3464 | const array_size = @intCast(usize, string_lit_size); |
| 3335 | return Tag.array_type.create(c.arena, .{ .len = array_size, .elem_type = elem_ty }); | |
| 3465 | ||
| 3466 | // incomplete array initialized with empty string, will be translated as [1]T{0} | |
| 3467 | // see https://github.com/ziglang/zig/issues/8256 | |
| 3468 | if (array_size == 0) return Tag.array_type.create(c.arena, .{ .len = 1, .elem_type = elem_ty }); | |
| 3469 | ||
| 3470 | return Tag.null_sentinel_array_type.create(c.arena, .{ .len = array_size, .elem_type = elem_ty }); | |
| 3336 | 3471 | }, |
| 3337 | 3472 | .InitListExprClass => { |
| 3338 | 3473 | const init_expr = @ptrCast(*const clang.InitListExpr, decl_init); |
| ... | ... | @@ -4746,6 +4881,10 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!N |
| 4746 | 4881 | }, |
| 4747 | 4882 | .Identifier => { |
| 4748 | 4883 | const mangled_name = scope.getAlias(slice); |
| 4884 | if (mem.startsWith(u8, mangled_name, "__builtin_") and !isBuiltinDefined(mangled_name)) { | |
| 4885 | try m.fail(c, "TODO implement function '{s}' in std.c.builtins", .{mangled_name}); | |
| 4886 | return error.ParseError; | |
| 4887 | } | |
| 4749 | 4888 | return Tag.identifier.create(c.arena, builtin_typedef_map.get(mangled_name) orelse mangled_name); |
| 4750 | 4889 | }, |
| 4751 | 4890 | .LParen => { |
src/translate_c/ast.zig+83-3| ... | ... | @@ -40,6 +40,8 @@ pub const Node = extern union { |
| 40 | 40 | string_literal, |
| 41 | 41 | char_literal, |
| 42 | 42 | enum_literal, |
| 43 | /// "string"[0..end] | |
| 44 | string_slice, | |
| 43 | 45 | identifier, |
| 44 | 46 | @"if", |
| 45 | 47 | /// if (!operand) break; |
| ... | ... | @@ -176,6 +178,7 @@ pub const Node = extern union { |
| 176 | 178 | c_pointer, |
| 177 | 179 | single_pointer, |
| 178 | 180 | array_type, |
| 181 | null_sentinel_array_type, | |
| 179 | 182 | |
| 180 | 183 | /// @import("std").meta.sizeof(operand) |
| 181 | 184 | std_meta_sizeof, |
| ... | ... | @@ -334,7 +337,7 @@ pub const Node = extern union { |
| 334 | 337 | .std_meta_promoteIntLiteral => Payload.PromoteIntLiteral, |
| 335 | 338 | .block => Payload.Block, |
| 336 | 339 | .c_pointer, .single_pointer => Payload.Pointer, |
| 337 | .array_type => Payload.Array, | |
| 340 | .array_type, .null_sentinel_array_type => Payload.Array, | |
| 338 | 341 | .arg_redecl, .alias, .fail_decl => Payload.ArgRedecl, |
| 339 | 342 | .log2_int_type => Payload.Log2IntType, |
| 340 | 343 | .var_simple, .pub_var_simple => Payload.SimpleVarDecl, |
| ... | ... | @@ -342,6 +345,7 @@ pub const Node = extern union { |
| 342 | 345 | .array_filler => Payload.ArrayFiller, |
| 343 | 346 | .pub_inline_fn => Payload.PubInlineFn, |
| 344 | 347 | .field_access => Payload.FieldAccess, |
| 348 | .string_slice => Payload.StringSlice, | |
| 345 | 349 | }; |
| 346 | 350 | } |
| 347 | 351 | |
| ... | ... | @@ -584,10 +588,12 @@ pub const Payload = struct { |
| 584 | 588 | |
| 585 | 589 | pub const Array = struct { |
| 586 | 590 | base: Payload, |
| 587 | data: struct { | |
| 591 | data: ArrayTypeInfo, | |
| 592 | ||
| 593 | pub const ArrayTypeInfo = struct { | |
| 588 | 594 | elem_type: Node, |
| 589 | 595 | len: usize, |
| 590 | }, | |
| 596 | }; | |
| 591 | 597 | }; |
| 592 | 598 | |
| 593 | 599 | pub const Pointer = struct { |
| ... | ... | @@ -664,6 +670,14 @@ pub const Payload = struct { |
| 664 | 670 | radix: Node, |
| 665 | 671 | }, |
| 666 | 672 | }; |
| 673 | ||
| 674 | pub const StringSlice = struct { | |
| 675 | base: Payload, | |
| 676 | data: struct { | |
| 677 | string: Node, | |
| 678 | end: usize, | |
| 679 | }, | |
| 680 | }; | |
| 667 | 681 | }; |
| 668 | 682 | |
| 669 | 683 | /// Converts the nodes into a Zig ast. |
| ... | ... | @@ -1015,6 +1029,36 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { |
| 1015 | 1029 | .data = undefined, |
| 1016 | 1030 | }); |
| 1017 | 1031 | }, |
| 1032 | .string_slice => { | |
| 1033 | const payload = node.castTag(.string_slice).?.data; | |
| 1034 | ||
| 1035 | const string = try renderNode(c, payload.string); | |
| 1036 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 1037 | const start = try c.addNode(.{ | |
| 1038 | .tag = .integer_literal, | |
| 1039 | .main_token = try c.addToken(.integer_literal, "0"), | |
| 1040 | .data = undefined, | |
| 1041 | }); | |
| 1042 | _ = try c.addToken(.ellipsis2, ".."); | |
| 1043 | const end = try c.addNode(.{ | |
| 1044 | .tag = .integer_literal, | |
| 1045 | .main_token = try c.addTokenFmt(.integer_literal, "{d}", .{payload.end}), | |
| 1046 | .data = undefined, | |
| 1047 | }); | |
| 1048 | _ = try c.addToken(.r_bracket, "]"); | |
| 1049 | ||
| 1050 | return c.addNode(.{ | |
| 1051 | .tag = .slice, | |
| 1052 | .main_token = l_bracket, | |
| 1053 | .data = .{ | |
| 1054 | .lhs = string, | |
| 1055 | .rhs = try c.addExtra(std.zig.ast.Node.Slice{ | |
| 1056 | .start = start, | |
| 1057 | .end = end, | |
| 1058 | }), | |
| 1059 | }, | |
| 1060 | }); | |
| 1061 | }, | |
| 1018 | 1062 | .fail_decl => { |
| 1019 | 1063 | const payload = node.castTag(.fail_decl).?.data; |
| 1020 | 1064 | // pub const name = @compileError(msg); |
| ... | ... | @@ -1581,6 +1625,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { |
| 1581 | 1625 | const payload = node.castTag(.array_type).?.data; |
| 1582 | 1626 | return renderArrayType(c, payload.len, payload.elem_type); |
| 1583 | 1627 | }, |
| 1628 | .null_sentinel_array_type => { | |
| 1629 | const payload = node.castTag(.null_sentinel_array_type).?.data; | |
| 1630 | return renderNullSentinelArrayType(c, payload.len, payload.elem_type); | |
| 1631 | }, | |
| 1584 | 1632 | .array_filler => { |
| 1585 | 1633 | const payload = node.castTag(.array_filler).?.data; |
| 1586 | 1634 | |
| ... | ... | @@ -1946,6 +1994,36 @@ fn renderArrayType(c: *Context, len: usize, elem_type: Node) !NodeIndex { |
| 1946 | 1994 | }); |
| 1947 | 1995 | } |
| 1948 | 1996 | |
| 1997 | fn renderNullSentinelArrayType(c: *Context, len: usize, elem_type: Node) !NodeIndex { | |
| 1998 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 1999 | const len_expr = try c.addNode(.{ | |
| 2000 | .tag = .integer_literal, | |
| 2001 | .main_token = try c.addTokenFmt(.integer_literal, "{d}", .{len}), | |
| 2002 | .data = undefined, | |
| 2003 | }); | |
| 2004 | _ = try c.addToken(.colon, ":"); | |
| 2005 | ||
| 2006 | const sentinel_expr = try c.addNode(.{ | |
| 2007 | .tag = .integer_literal, | |
| 2008 | .main_token = try c.addToken(.integer_literal, "0"), | |
| 2009 | .data = undefined, | |
| 2010 | }); | |
| 2011 | ||
| 2012 | _ = try c.addToken(.r_bracket, "]"); | |
| 2013 | const elem_type_expr = try renderNode(c, elem_type); | |
| 2014 | return c.addNode(.{ | |
| 2015 | .tag = .array_type_sentinel, | |
| 2016 | .main_token = l_bracket, | |
| 2017 | .data = .{ | |
| 2018 | .lhs = len_expr, | |
| 2019 | .rhs = try c.addExtra(std.zig.ast.Node.ArrayTypeSentinel { | |
| 2020 | .sentinel = sentinel_expr, | |
| 2021 | .elem_type = elem_type_expr, | |
| 2022 | }), | |
| 2023 | }, | |
| 2024 | }); | |
| 2025 | } | |
| 2026 | ||
| 1949 | 2027 | fn addSemicolonIfNeeded(c: *Context, node: Node) !void { |
| 1950 | 2028 | switch (node.tag()) { |
| 1951 | 2029 | .warning => unreachable, |
| ... | ... | @@ -2014,6 +2092,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { |
| 2014 | 2092 | .integer_literal, |
| 2015 | 2093 | .float_literal, |
| 2016 | 2094 | .string_literal, |
| 2095 | .string_slice, | |
| 2017 | 2096 | .char_literal, |
| 2018 | 2097 | .enum_literal, |
| 2019 | 2098 | .identifier, |
| ... | ... | @@ -2035,6 +2114,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { |
| 2035 | 2114 | .func, |
| 2036 | 2115 | .call, |
| 2037 | 2116 | .array_type, |
| 2117 | .null_sentinel_array_type, | |
| 2038 | 2118 | .bool_to_int, |
| 2039 | 2119 | .div_exact, |
| 2040 | 2120 | .byte_offset_of, |
src/zig_clang.cpp+5| ... | ... | @@ -2459,6 +2459,11 @@ struct ZigClangQualType ZigClangFunctionType_getReturnType(const struct ZigClang |
| 2459 | 2459 | return bitcast(casted->getReturnType()); |
| 2460 | 2460 | } |
| 2461 | 2461 | |
| 2462 | const struct ZigClangExpr *ZigClangGenericSelectionExpr_getResultExpr(const struct ZigClangGenericSelectionExpr *self) { | |
| 2463 | auto casted = reinterpret_cast<const clang::GenericSelectionExpr *>(self); | |
| 2464 | return reinterpret_cast<const struct ZigClangExpr *>(casted->getResultExpr()); | |
| 2465 | } | |
| 2466 | ||
| 2462 | 2467 | bool ZigClangFunctionProtoType_isVariadic(const struct ZigClangFunctionProtoType *self) { |
| 2463 | 2468 | auto casted = reinterpret_cast<const clang::FunctionProtoType *>(self); |
| 2464 | 2469 | return casted->isVariadic(); |
src/zig_clang.h+2| ... | ... | @@ -1123,6 +1123,8 @@ ZIG_EXTERN_C bool ZigClangFunctionType_getNoReturnAttr(const struct ZigClangFunc |
| 1123 | 1123 | ZIG_EXTERN_C enum ZigClangCallingConv ZigClangFunctionType_getCallConv(const struct ZigClangFunctionType *self); |
| 1124 | 1124 | ZIG_EXTERN_C struct ZigClangQualType ZigClangFunctionType_getReturnType(const struct ZigClangFunctionType *self); |
| 1125 | 1125 | |
| 1126 | ZIG_EXTERN_C const struct ZigClangExpr *ZigClangGenericSelectionExpr_getResultExpr(const struct ZigClangGenericSelectionExpr *self); | |
| 1127 | ||
| 1126 | 1128 | ZIG_EXTERN_C bool ZigClangFunctionProtoType_isVariadic(const struct ZigClangFunctionProtoType *self); |
| 1127 | 1129 | ZIG_EXTERN_C unsigned ZigClangFunctionProtoType_getNumParams(const struct ZigClangFunctionProtoType *self); |
| 1128 | 1130 | ZIG_EXTERN_C struct ZigClangQualType ZigClangFunctionProtoType_getParamType(const struct ZigClangFunctionProtoType *self, unsigned i); |
test/cli.zig+11| ... | ... | @@ -28,6 +28,8 @@ pub fn main() !void { |
| 28 | 28 | const zig_exe = try fs.path.resolve(a, &[_][]const u8{zig_exe_rel}); |
| 29 | 29 | |
| 30 | 30 | const dir_path = try fs.path.join(a, &[_][]const u8{ cache_root, "clitest" }); |
| 31 | defer fs.cwd().deleteTree(dir_path) catch {}; | |
| 32 | ||
| 31 | 33 | const TestFn = fn ([]const u8, []const u8) anyerror!void; |
| 32 | 34 | const test_fns = [_]TestFn{ |
| 33 | 35 | testZigInitLib, |
| ... | ... | @@ -174,4 +176,13 @@ fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void { |
| 174 | 176 | const run_result3 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path }); |
| 175 | 177 | // both files have been formatted, nothing should change now |
| 176 | 178 | testing.expect(run_result3.stdout.len == 0); |
| 179 | ||
| 180 | // Check UTF-16 decoding | |
| 181 | const fmt4_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt4.zig" }); | |
| 182 | var unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00"; | |
| 183 | try fs.cwd().writeFile(fmt4_zig_path, unformatted_code_utf16); | |
| 184 | ||
| 185 | const run_result4 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path }); | |
| 186 | testing.expect(std.mem.startsWith(u8, run_result4.stdout, fmt4_zig_path)); | |
| 187 | testing.expect(run_result4.stdout.len == fmt4_zig_path.len + 1 and run_result4.stdout[run_result4.stdout.len - 1] == '\n'); | |
| 177 | 188 | } |
test/run_translated_c.zig+121| ... | ... | @@ -3,6 +3,17 @@ const tests = @import("tests.zig"); |
| 3 | 3 | const nl = std.cstr.line_sep; |
| 4 | 4 | |
| 5 | 5 | pub fn addCases(cases: *tests.RunTranslatedCContext) void { |
| 6 | cases.add("division of floating literals", | |
| 7 | \\#define _NO_CRT_STDIO_INLINE 1 | |
| 8 | \\#include <stdio.h> | |
| 9 | \\#define PI 3.14159265358979323846f | |
| 10 | \\#define DEG2RAD (PI/180.0f) | |
| 11 | \\int main(void) { | |
| 12 | \\ printf("DEG2RAD is: %f\n", DEG2RAD); | |
| 13 | \\ return 0; | |
| 14 | \\} | |
| 15 | , "DEG2RAD is: 0.017453" ++ nl); | |
| 16 | ||
| 6 | 17 | cases.add("use global scope for record/enum/typedef type transalation if needed", |
| 7 | 18 | \\void bar(void); |
| 8 | 19 | \\void baz(void); |
| ... | ... | @@ -1187,4 +1198,114 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { |
| 1187 | 1198 | \\ return 0; |
| 1188 | 1199 | \\} |
| 1189 | 1200 | , ""); |
| 1201 | ||
| 1202 | cases.add("Generic selections", | |
| 1203 | \\#include <stdlib.h> | |
| 1204 | \\#include <string.h> | |
| 1205 | \\#include <stdint.h> | |
| 1206 | \\#define my_generic_fn(X) _Generic((X), \ | |
| 1207 | \\ int: abs, \ | |
| 1208 | \\ char *: strlen, \ | |
| 1209 | \\ size_t: malloc, \ | |
| 1210 | \\ default: free \ | |
| 1211 | \\)(X) | |
| 1212 | \\#define my_generic_val(X) _Generic((X), \ | |
| 1213 | \\ int: 1, \ | |
| 1214 | \\ const char *: "bar" \ | |
| 1215 | \\) | |
| 1216 | \\int main(void) { | |
| 1217 | \\ if (my_generic_val(100) != 1) abort(); | |
| 1218 | \\ | |
| 1219 | \\ const char *foo = "foo"; | |
| 1220 | \\ const char *bar = my_generic_val(foo); | |
| 1221 | \\ if (strcmp(bar, "bar") != 0) abort(); | |
| 1222 | \\ | |
| 1223 | \\ if (my_generic_fn(-42) != 42) abort(); | |
| 1224 | \\ if (my_generic_fn("hello") != 5) abort(); | |
| 1225 | \\ | |
| 1226 | \\ size_t size = 8192; | |
| 1227 | \\ uint8_t *mem = my_generic_fn(size); | |
| 1228 | \\ memset(mem, 42, size); | |
| 1229 | \\ if (mem[size - 1] != 42) abort(); | |
| 1230 | \\ my_generic_fn(mem); | |
| 1231 | \\ | |
| 1232 | \\ return 0; | |
| 1233 | \\} | |
| 1234 | , ""); | |
| 1235 | ||
| 1236 | // See __builtin_alloca_with_align comment in std.c.builtins | |
| 1237 | cases.add("use of unimplemented builtin in unused function does not prevent compilation", | |
| 1238 | \\#include <stdlib.h> | |
| 1239 | \\void unused() { | |
| 1240 | \\ __builtin_alloca_with_align(1, 8); | |
| 1241 | \\} | |
| 1242 | \\int main(void) { | |
| 1243 | \\ if (__builtin_sqrt(1.0) != 1.0) abort(); | |
| 1244 | \\ return 0; | |
| 1245 | \\} | |
| 1246 | , ""); | |
| 1247 | ||
| 1248 | cases.add("convert single-statement bodies into blocks for if/else/for/while. issue #8159", | |
| 1249 | \\#include <stdlib.h> | |
| 1250 | \\int foo() { return 1; } | |
| 1251 | \\int main(void) { | |
| 1252 | \\ int i = 0; | |
| 1253 | \\ if (i == 0) if (i == 0) if (i != 0) i = 1; | |
| 1254 | \\ if (i != 0) i = 1; else if (i == 0) if (i == 0) i += 1; | |
| 1255 | \\ for (; i < 10;) for (; i < 10;) i++; | |
| 1256 | \\ while (i == 100) while (i == 100) foo(); | |
| 1257 | \\ if (0) do do "string"; while(1); while(1); | |
| 1258 | \\ return 0; | |
| 1259 | \\} | |
| 1260 | , ""); | |
| 1261 | ||
| 1262 | cases.add("cast RHS of compound assignment if necessary, unused result", | |
| 1263 | \\#include <stdlib.h> | |
| 1264 | \\int main(void) { | |
| 1265 | \\ signed short val = -1; | |
| 1266 | \\ val += 1; if (val != 0) abort(); | |
| 1267 | \\ val -= 1; if (val != -1) abort(); | |
| 1268 | \\ val *= 2; if (val != -2) abort(); | |
| 1269 | \\ val /= 2; if (val != -1) abort(); | |
| 1270 | \\ val %= 2; if (val != -1) abort(); | |
| 1271 | \\ val <<= 1; if (val != -2) abort(); | |
| 1272 | \\ val >>= 1; if (val != -1) abort(); | |
| 1273 | \\ val += 100000000; // compile error if @truncate() not inserted | |
| 1274 | \\ unsigned short uval = 1; | |
| 1275 | \\ uval += 1; if (uval != 2) abort(); | |
| 1276 | \\ uval -= 1; if (uval != 1) abort(); | |
| 1277 | \\ uval *= 2; if (uval != 2) abort(); | |
| 1278 | \\ uval /= 2; if (uval != 1) abort(); | |
| 1279 | \\ uval %= 2; if (uval != 1) abort(); | |
| 1280 | \\ uval <<= 1; if (uval != 2) abort(); | |
| 1281 | \\ uval >>= 1; if (uval != 1) abort(); | |
| 1282 | \\ uval += 100000000; // compile error if @truncate() not inserted | |
| 1283 | \\} | |
| 1284 | , ""); | |
| 1285 | ||
| 1286 | cases.add("cast RHS of compound assignment if necessary, used result", | |
| 1287 | \\#include <stdlib.h> | |
| 1288 | \\int main(void) { | |
| 1289 | \\ signed short foo; | |
| 1290 | \\ signed short val = -1; | |
| 1291 | \\ foo = (val += 1); if (foo != 0) abort(); | |
| 1292 | \\ foo = (val -= 1); if (foo != -1) abort(); | |
| 1293 | \\ foo = (val *= 2); if (foo != -2) abort(); | |
| 1294 | \\ foo = (val /= 2); if (foo != -1) abort(); | |
| 1295 | \\ foo = (val %= 2); if (foo != -1) abort(); | |
| 1296 | \\ foo = (val <<= 1); if (foo != -2) abort(); | |
| 1297 | \\ foo = (val >>= 1); if (foo != -1) abort(); | |
| 1298 | \\ foo = (val += 100000000); // compile error if @truncate() not inserted | |
| 1299 | \\ unsigned short ufoo; | |
| 1300 | \\ unsigned short uval = 1; | |
| 1301 | \\ ufoo = (uval += 1); if (ufoo != 2) abort(); | |
| 1302 | \\ ufoo = (uval -= 1); if (ufoo != 1) abort(); | |
| 1303 | \\ ufoo = (uval *= 2); if (ufoo != 2) abort(); | |
| 1304 | \\ ufoo = (uval /= 2); if (ufoo != 1) abort(); | |
| 1305 | \\ ufoo = (uval %= 2); if (ufoo != 1) abort(); | |
| 1306 | \\ ufoo = (uval <<= 1); if (ufoo != 2) abort(); | |
| 1307 | \\ ufoo = (uval >>= 1); if (ufoo != 1) abort(); | |
| 1308 | \\ ufoo = (uval += 100000000); // compile error if @truncate() not inserted | |
| 1309 | \\} | |
| 1310 | , ""); | |
| 1190 | 1311 | } |
test/stage1/behavior/vector.zig+8-4| ... | ... | @@ -4,7 +4,7 @@ const mem = std.mem; |
| 4 | 4 | const math = std.math; |
| 5 | 5 | const expect = std.testing.expect; |
| 6 | 6 | const expectEqual = std.testing.expectEqual; |
| 7 | const expectWithinEpsilon = std.testing.expectWithinEpsilon; | |
| 7 | const expectApproxEqRel = std.testing.expectApproxEqRel; | |
| 8 | 8 | const Vector = std.meta.Vector; |
| 9 | 9 | |
| 10 | 10 | test "implicit cast vector to array - bool" { |
| ... | ... | @@ -527,10 +527,14 @@ test "vector reduce operation" { |
| 527 | 527 | switch (@typeInfo(TX)) { |
| 528 | 528 | .Int, .Bool => expectEqual(expected, r), |
| 529 | 529 | .Float => { |
| 530 | if (math.isNan(expected) != math.isNan(r)) { | |
| 531 | std.debug.panic("unexpected NaN value!\n", .{}); | |
| 530 | const expected_nan = math.isNan(expected); | |
| 531 | const got_nan = math.isNan(r); | |
| 532 | ||
| 533 | if (expected_nan and got_nan) { | |
| 534 | // Do this check explicitly as two NaN values are never | |
| 535 | // equal. | |
| 532 | 536 | } else { |
| 533 | expectWithinEpsilon(expected, r, 0.001); | |
| 537 | expectApproxEqRel(expected, r, math.sqrt(math.epsilon(TX))); | |
| 534 | 538 | } |
| 535 | 539 | }, |
| 536 | 540 | else => unreachable, |
test/stage2/cbe.zig+1-1| ... | ... | @@ -51,7 +51,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 51 | 51 | \\ _ = printf("Hello, %s!\n", "world"); |
| 52 | 52 | \\ return 0; |
| 53 | 53 | \\} |
| 54 | , "Hello, world!\n"); | |
| 54 | , "Hello, world!" ++ std.cstr.line_sep); | |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | 57 | { |
test/stage2/wasm.zig+35| ... | ... | @@ -175,6 +175,41 @@ pub fn addCases(ctx: *TestContext) !void { |
| 175 | 175 | \\ return i; |
| 176 | 176 | \\} |
| 177 | 177 | , "31\n"); |
| 178 | ||
| 179 | case.addCompareOutput( | |
| 180 | \\export fn _start() void { | |
| 181 | \\ assert(foo(true) != @as(i32, 30)); | |
| 182 | \\} | |
| 183 | \\ | |
| 184 | \\fn assert(ok: bool) void { | |
| 185 | \\ if (!ok) unreachable; | |
| 186 | \\} | |
| 187 | \\ | |
| 188 | \\fn foo(ok: bool) i32 { | |
| 189 | \\ const x = if(ok) @as(i32, 20) else @as(i32, 10); | |
| 190 | \\ return x; | |
| 191 | \\} | |
| 192 | , ""); | |
| 193 | ||
| 194 | case.addCompareOutput( | |
| 195 | \\export fn _start() void { | |
| 196 | \\ assert(foo(false) == @as(i32, 20)); | |
| 197 | \\ assert(foo(true) == @as(i32, 30)); | |
| 198 | \\} | |
| 199 | \\ | |
| 200 | \\fn assert(ok: bool) void { | |
| 201 | \\ if (!ok) unreachable; | |
| 202 | \\} | |
| 203 | \\ | |
| 204 | \\fn foo(ok: bool) i32 { | |
| 205 | \\ const val: i32 = blk: { | |
| 206 | \\ var x: i32 = 1; | |
| 207 | \\ if (!ok) break :blk x + @as(i32, 9); | |
| 208 | \\ break :blk x + @as(i32, 19); | |
| 209 | \\ }; | |
| 210 | \\ return val + 10; | |
| 211 | \\} | |
| 212 | , ""); | |
| 178 | 213 | } |
| 179 | 214 | |
| 180 | 215 | { |
test/standalone.zig+4-1| ... | ... | @@ -9,7 +9,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void { |
| 9 | 9 | cases.add("test/standalone/main_return_error/error_u8.zig"); |
| 10 | 10 | cases.add("test/standalone/main_return_error/error_u8_non_zero.zig"); |
| 11 | 11 | cases.addBuildFile("test/standalone/main_pkg_path/build.zig"); |
| 12 | cases.addBuildFile("test/standalone/shared_library/build.zig"); | |
| 12 | if (std.Target.current.os.tag != .macos) { | |
| 13 | // TODO zld cannot link shared libraries yet. | |
| 14 | cases.addBuildFile("test/standalone/shared_library/build.zig"); | |
| 15 | } | |
| 13 | 16 | cases.addBuildFile("test/standalone/mix_o_files/build.zig"); |
| 14 | 17 | cases.addBuildFile("test/standalone/global_linkage/build.zig"); |
| 15 | 18 | cases.addBuildFile("test/standalone/static_c_lib/build.zig"); |
test/standalone/mix_o_files/base64.zig+3-3| ... | ... | @@ -3,9 +3,9 @@ const base64 = @import("std").base64; |
| 3 | 3 | export fn decode_base_64(dest_ptr: [*]u8, dest_len: usize, source_ptr: [*]const u8, source_len: usize) usize { |
| 4 | 4 | const src = source_ptr[0..source_len]; |
| 5 | 5 | const dest = dest_ptr[0..dest_len]; |
| 6 | const base64_decoder = base64.standard_decoder_unsafe; | |
| 7 | const decoded_size = base64_decoder.calcSize(src); | |
| 8 | base64_decoder.decode(dest[0..decoded_size], src); | |
| 6 | const base64_decoder = base64.standard.Decoder; | |
| 7 | const decoded_size = base64_decoder.calcSizeForSlice(src) catch unreachable; | |
| 8 | base64_decoder.decode(dest[0..decoded_size], src) catch unreachable; | |
| 9 | 9 | return decoded_size; |
| 10 | 10 | } |
| 11 | 11 |
test/translate_c.zig+126-76| ... | ... | @@ -3,6 +3,28 @@ const std = @import("std"); |
| 3 | 3 | const CrossTarget = std.zig.CrossTarget; |
| 4 | 4 | |
| 5 | 5 | pub fn addCases(cases: *tests.TranslateCContext) void { |
| 6 | cases.add("unnamed child types of typedef receive typedef's name", | |
| 7 | \\typedef enum { | |
| 8 | \\ FooA, | |
| 9 | \\ FooB, | |
| 10 | \\} Foo; | |
| 11 | \\typedef struct { | |
| 12 | \\ int a, b; | |
| 13 | \\} Bar; | |
| 14 | , &[_][]const u8{ | |
| 15 | \\pub const Foo = extern enum(c_int) { | |
| 16 | \\ A, | |
| 17 | \\ B, | |
| 18 | \\ _, | |
| 19 | \\}; | |
| 20 | \\pub const FooA = @enumToInt(Foo.A); | |
| 21 | \\pub const FooB = @enumToInt(Foo.B); | |
| 22 | \\pub const Bar = extern struct { | |
| 23 | \\ a: c_int, | |
| 24 | \\ b: c_int, | |
| 25 | \\}; | |
| 26 | }); | |
| 27 | ||
| 6 | 28 | cases.add("if as while stmt has semicolon", |
| 7 | 29 | \\void foo() { |
| 8 | 30 | \\ while (1) if (1) { |
| ... | ... | @@ -218,9 +240,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 218 | 240 | \\} Bar; |
| 219 | 241 | , &[_][]const u8{ |
| 220 | 242 | \\source.h:1:9: warning: struct demoted to opaque type - unable to translate type of field foo |
| 221 | \\const struct_unnamed_1 = opaque {}; | |
| 222 | \\pub const Foo = struct_unnamed_1; | |
| 223 | \\const struct_unnamed_2 = extern struct { | |
| 243 | \\pub const Foo = opaque {}; | |
| 244 | \\pub const Bar = extern struct { | |
| 224 | 245 | \\ bar: ?*Foo, |
| 225 | 246 | \\}; |
| 226 | 247 | }); |
| ... | ... | @@ -519,17 +540,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 519 | 540 | \\} outer; |
| 520 | 541 | \\void foo(outer *x) { x->y = x->x; } |
| 521 | 542 | , &[_][]const u8{ |
| 522 | \\const struct_unnamed_3 = extern struct { | |
| 543 | \\const struct_unnamed_2 = extern struct { | |
| 523 | 544 | \\ y: c_int, |
| 524 | 545 | \\}; |
| 525 | \\const union_unnamed_2 = extern union { | |
| 546 | \\const union_unnamed_1 = extern union { | |
| 526 | 547 | \\ x: u8, |
| 527 | \\ unnamed_0: struct_unnamed_3, | |
| 548 | \\ unnamed_0: struct_unnamed_2, | |
| 528 | 549 | \\}; |
| 529 | \\const struct_unnamed_1 = extern struct { | |
| 530 | \\ unnamed_0: union_unnamed_2, | |
| 550 | \\pub const outer = extern struct { | |
| 551 | \\ unnamed_0: union_unnamed_1, | |
| 531 | 552 | \\}; |
| 532 | \\pub const outer = struct_unnamed_1; | |
| 533 | 553 | \\pub export fn foo(arg_x: [*c]outer) void { |
| 534 | 554 | \\ var x = arg_x; |
| 535 | 555 | \\ x.*.unnamed_0.unnamed_0.y = @bitCast(c_int, @as(c_uint, x.*.unnamed_0.x)); |
| ... | ... | @@ -565,21 +585,20 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 565 | 585 | \\struct {int x,y;} s2 = {.y = 2, .x=1}; |
| 566 | 586 | \\foo s3 = { 123 }; |
| 567 | 587 | , &[_][]const u8{ |
| 568 | \\const struct_unnamed_1 = extern struct { | |
| 588 | \\pub const foo = extern struct { | |
| 569 | 589 | \\ x: c_int, |
| 570 | 590 | \\}; |
| 571 | \\pub const foo = struct_unnamed_1; | |
| 572 | \\const struct_unnamed_2 = extern struct { | |
| 591 | \\const struct_unnamed_1 = extern struct { | |
| 573 | 592 | \\ x: f64, |
| 574 | 593 | \\ y: f64, |
| 575 | 594 | \\ z: f64, |
| 576 | 595 | \\}; |
| 577 | \\pub export var s0: struct_unnamed_2 = struct_unnamed_2{ | |
| 596 | \\pub export var s0: struct_unnamed_1 = struct_unnamed_1{ | |
| 578 | 597 | \\ .x = 1.2, |
| 579 | 598 | \\ .y = 1.3, |
| 580 | 599 | \\ .z = 0, |
| 581 | 600 | \\}; |
| 582 | \\const struct_unnamed_3 = extern struct { | |
| 601 | \\const struct_unnamed_2 = extern struct { | |
| 583 | 602 | \\ sec: c_int, |
| 584 | 603 | \\ min: c_int, |
| 585 | 604 | \\ hour: c_int, |
| ... | ... | @@ -587,7 +606,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 587 | 606 | \\ mon: c_int, |
| 588 | 607 | \\ year: c_int, |
| 589 | 608 | \\}; |
| 590 | \\pub export var s1: struct_unnamed_3 = struct_unnamed_3{ | |
| 609 | \\pub export var s1: struct_unnamed_2 = struct_unnamed_2{ | |
| 591 | 610 | \\ .sec = @as(c_int, 30), |
| 592 | 611 | \\ .min = @as(c_int, 15), |
| 593 | 612 | \\ .hour = @as(c_int, 17), |
| ... | ... | @@ -595,11 +614,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 595 | 614 | \\ .mon = @as(c_int, 12), |
| 596 | 615 | \\ .year = @as(c_int, 2014), |
| 597 | 616 | \\}; |
| 598 | \\const struct_unnamed_4 = extern struct { | |
| 617 | \\const struct_unnamed_3 = extern struct { | |
| 599 | 618 | \\ x: c_int, |
| 600 | 619 | \\ y: c_int, |
| 601 | 620 | \\}; |
| 602 | \\pub export var s2: struct_unnamed_4 = struct_unnamed_4{ | |
| 621 | \\pub export var s2: struct_unnamed_3 = struct_unnamed_3{ | |
| 603 | 622 | \\ .x = @as(c_int, 1), |
| 604 | 623 | \\ .y = @as(c_int, 2), |
| 605 | 624 | \\}; |
| ... | ... | @@ -745,14 +764,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 745 | 764 | \\ static const char v2[] = "2.2.2"; |
| 746 | 765 | \\} |
| 747 | 766 | , &[_][]const u8{ |
| 748 | \\const v2: [6]u8 = [6]u8{ | |
| 749 | \\ '2', | |
| 750 | \\ '.', | |
| 751 | \\ '2', | |
| 752 | \\ '.', | |
| 753 | \\ '2', | |
| 754 | \\ 0, | |
| 755 | \\}; | |
| 767 | \\const v2: [5:0]u8 = "2.2.2".*; | |
| 756 | 768 | \\pub export fn foo() void {} |
| 757 | 769 | }); |
| 758 | 770 | |
| ... | ... | @@ -1600,30 +1612,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 1600 | 1612 | \\static char arr1[] = "hello"; |
| 1601 | 1613 | \\char arr2[] = "hello"; |
| 1602 | 1614 | , &[_][]const u8{ |
| 1603 | \\pub export var arr0: [6]u8 = [6]u8{ | |
| 1604 | \\ 'h', | |
| 1605 | \\ 'e', | |
| 1606 | \\ 'l', | |
| 1607 | \\ 'l', | |
| 1608 | \\ 'o', | |
| 1609 | \\ 0, | |
| 1610 | \\}; | |
| 1611 | \\pub var arr1: [6]u8 = [6]u8{ | |
| 1612 | \\ 'h', | |
| 1613 | \\ 'e', | |
| 1614 | \\ 'l', | |
| 1615 | \\ 'l', | |
| 1616 | \\ 'o', | |
| 1617 | \\ 0, | |
| 1618 | \\}; | |
| 1619 | \\pub export var arr2: [6]u8 = [6]u8{ | |
| 1620 | \\ 'h', | |
| 1621 | \\ 'e', | |
| 1622 | \\ 'l', | |
| 1623 | \\ 'l', | |
| 1624 | \\ 'o', | |
| 1625 | \\ 0, | |
| 1626 | \\}; | |
| 1615 | \\pub export var arr0: [5:0]u8 = "hello".*; | |
| 1616 | \\pub var arr1: [5:0]u8 = "hello".*; | |
| 1617 | \\pub export var arr2: [5:0]u8 = "hello".*; | |
| 1627 | 1618 | }); |
| 1628 | 1619 | |
| 1629 | 1620 | cases.add("array initializer expr", |
| ... | ... | @@ -1667,37 +1658,36 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 1667 | 1658 | \\ p, |
| 1668 | 1659 | \\}; |
| 1669 | 1660 | , &[_][]const u8{ |
| 1670 | \\const enum_unnamed_1 = extern enum(c_int) { | |
| 1661 | \\pub const d = extern enum(c_int) { | |
| 1671 | 1662 | \\ a, |
| 1672 | 1663 | \\ b, |
| 1673 | 1664 | \\ c, |
| 1674 | 1665 | \\ _, |
| 1675 | 1666 | \\}; |
| 1676 | \\pub const a = @enumToInt(enum_unnamed_1.a); | |
| 1677 | \\pub const b = @enumToInt(enum_unnamed_1.b); | |
| 1678 | \\pub const c = @enumToInt(enum_unnamed_1.c); | |
| 1679 | \\pub const d = enum_unnamed_1; | |
| 1680 | \\const enum_unnamed_2 = extern enum(c_int) { | |
| 1667 | \\pub const a = @enumToInt(d.a); | |
| 1668 | \\pub const b = @enumToInt(d.b); | |
| 1669 | \\pub const c = @enumToInt(d.c); | |
| 1670 | \\const enum_unnamed_1 = extern enum(c_int) { | |
| 1681 | 1671 | \\ e = 0, |
| 1682 | 1672 | \\ f = 4, |
| 1683 | 1673 | \\ g = 5, |
| 1684 | 1674 | \\ _, |
| 1685 | 1675 | \\}; |
| 1686 | \\pub const e = @enumToInt(enum_unnamed_2.e); | |
| 1687 | \\pub const f = @enumToInt(enum_unnamed_2.f); | |
| 1688 | \\pub const g = @enumToInt(enum_unnamed_2.g); | |
| 1689 | \\pub export var h: enum_unnamed_2 = @intToEnum(enum_unnamed_2, e); | |
| 1690 | \\const enum_unnamed_3 = extern enum(c_int) { | |
| 1676 | \\pub const e = @enumToInt(enum_unnamed_1.e); | |
| 1677 | \\pub const f = @enumToInt(enum_unnamed_1.f); | |
| 1678 | \\pub const g = @enumToInt(enum_unnamed_1.g); | |
| 1679 | \\pub export var h: enum_unnamed_1 = @intToEnum(enum_unnamed_1, e); | |
| 1680 | \\const enum_unnamed_2 = extern enum(c_int) { | |
| 1691 | 1681 | \\ i, |
| 1692 | 1682 | \\ j, |
| 1693 | 1683 | \\ k, |
| 1694 | 1684 | \\ _, |
| 1695 | 1685 | \\}; |
| 1696 | \\pub const i = @enumToInt(enum_unnamed_3.i); | |
| 1697 | \\pub const j = @enumToInt(enum_unnamed_3.j); | |
| 1698 | \\pub const k = @enumToInt(enum_unnamed_3.k); | |
| 1686 | \\pub const i = @enumToInt(enum_unnamed_2.i); | |
| 1687 | \\pub const j = @enumToInt(enum_unnamed_2.j); | |
| 1688 | \\pub const k = @enumToInt(enum_unnamed_2.k); | |
| 1699 | 1689 | \\pub const struct_Baz = extern struct { |
| 1700 | \\ l: enum_unnamed_3, | |
| 1690 | \\ l: enum_unnamed_2, | |
| 1701 | 1691 | \\ m: d, |
| 1702 | 1692 | \\}; |
| 1703 | 1693 | \\pub const enum_i = extern enum(c_int) { |
| ... | ... | @@ -1962,7 +1952,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 1962 | 1952 | , &[_][]const u8{ |
| 1963 | 1953 | \\pub export fn foo() c_int { |
| 1964 | 1954 | \\ var a: c_int = 5; |
| 1965 | \\ while (true) a = 2; | |
| 1955 | \\ while (true) { | |
| 1956 | \\ a = 2; | |
| 1957 | \\ } | |
| 1966 | 1958 | \\ while (true) { |
| 1967 | 1959 | \\ var a_1: c_int = 4; |
| 1968 | 1960 | \\ a_1 = 9; |
| ... | ... | @@ -1975,7 +1967,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 1975 | 1967 | \\ var a_1: c_int = 2; |
| 1976 | 1968 | \\ a_1 = 12; |
| 1977 | 1969 | \\ } |
| 1978 | \\ while (true) a = 7; | |
| 1970 | \\ while (true) { | |
| 1971 | \\ a = 7; | |
| 1972 | \\ } | |
| 1979 | 1973 | \\ return 0; |
| 1980 | 1974 | \\} |
| 1981 | 1975 | }); |
| ... | ... | @@ -2036,7 +2030,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 2036 | 2030 | \\} |
| 2037 | 2031 | , &[_][]const u8{ |
| 2038 | 2032 | \\pub export fn bar() c_int { |
| 2039 | \\ if ((if (true) @as(c_int, 5) else if (true) @as(c_int, 4) else @as(c_int, 6)) != 0) _ = @as(c_int, 2); | |
| 2033 | \\ if ((if (true) @as(c_int, 5) else if (true) @as(c_int, 4) else @as(c_int, 6)) != 0) { | |
| 2034 | \\ _ = @as(c_int, 2); | |
| 2035 | \\ } | |
| 2040 | 2036 | \\ return if (true) @as(c_int, 5) else if (true) @as(c_int, 4) else @as(c_int, 6); |
| 2041 | 2037 | \\} |
| 2042 | 2038 | }); |
| ... | ... | @@ -2417,7 +2413,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 2417 | 2413 | \\pub const yes = [*c]u8; |
| 2418 | 2414 | \\pub export fn foo() void { |
| 2419 | 2415 | \\ var a: yes = undefined; |
| 2420 | \\ if (a != null) _ = @as(c_int, 2); | |
| 2416 | \\ if (a != null) { | |
| 2417 | \\ _ = @as(c_int, 2); | |
| 2418 | \\ } | |
| 2421 | 2419 | \\} |
| 2422 | 2420 | }); |
| 2423 | 2421 | |
| ... | ... | @@ -2456,7 +2454,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 2456 | 2454 | \\ b: c_int, |
| 2457 | 2455 | \\}; |
| 2458 | 2456 | \\pub extern var a: struct_Foo; |
| 2459 | \\pub export var b: f32 = 2; | |
| 2457 | \\pub export var b: f32 = 2.0; | |
| 2460 | 2458 | \\pub export fn foo() void { |
| 2461 | 2459 | \\ var c: [*c]struct_Foo = undefined; |
| 2462 | 2460 | \\ _ = a.b; |
| ... | ... | @@ -2768,7 +2766,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 2768 | 2766 | \\ var a = arg_a; |
| 2769 | 2767 | \\ var i: c_int = 0; |
| 2770 | 2768 | \\ while (a > @bitCast(c_uint, @as(c_int, 0))) { |
| 2771 | \\ a >>= @intCast(@import("std").math.Log2Int(c_int), 1); | |
| 2769 | \\ a >>= @intCast(@import("std").math.Log2Int(c_int), @as(c_int, 1)); | |
| 2772 | 2770 | \\ } |
| 2773 | 2771 | \\ return i; |
| 2774 | 2772 | \\} |
| ... | ... | @@ -2788,7 +2786,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 2788 | 2786 | \\ var a = arg_a; |
| 2789 | 2787 | \\ var i: c_int = 0; |
| 2790 | 2788 | \\ while (a > @bitCast(c_uint, @as(c_int, 0))) { |
| 2791 | \\ a >>= @intCast(@import("std").math.Log2Int(c_int), 1); | |
| 2789 | \\ a >>= @intCast(@import("std").math.Log2Int(c_int), @as(c_int, 1)); | |
| 2792 | 2790 | \\ } |
| 2793 | 2791 | \\ return i; |
| 2794 | 2792 | \\} |
| ... | ... | @@ -3020,17 +3018,17 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 3020 | 3018 | \\pub extern fn fn_bool(x: bool) void; |
| 3021 | 3019 | \\pub extern fn fn_ptr(x: ?*c_void) void; |
| 3022 | 3020 | \\pub export fn call() void { |
| 3023 | \\ fn_int(@floatToInt(c_int, 3)); | |
| 3024 | \\ fn_int(@floatToInt(c_int, 3)); | |
| 3025 | \\ fn_int(@floatToInt(c_int, 3)); | |
| 3021 | \\ fn_int(@floatToInt(c_int, 3.0)); | |
| 3022 | \\ fn_int(@floatToInt(c_int, 3.0)); | |
| 3023 | \\ fn_int(@floatToInt(c_int, 3.0)); | |
| 3026 | 3024 | \\ fn_int(@as(c_int, 1094861636)); |
| 3027 | 3025 | \\ fn_f32(@intToFloat(f32, @as(c_int, 3))); |
| 3028 | 3026 | \\ fn_f64(@intToFloat(f64, @as(c_int, 3))); |
| 3029 | 3027 | \\ fn_char(@bitCast(u8, @truncate(i8, @as(c_int, '3')))); |
| 3030 | 3028 | \\ fn_char(@bitCast(u8, @truncate(i8, @as(c_int, '\x01')))); |
| 3031 | 3029 | \\ fn_char(@bitCast(u8, @truncate(i8, @as(c_int, 0)))); |
| 3032 | \\ fn_f32(3); | |
| 3033 | \\ fn_f64(3); | |
| 3030 | \\ fn_f32(3.0); | |
| 3031 | \\ fn_f64(3.0); | |
| 3034 | 3032 | \\ fn_bool(@as(c_int, 123) != 0); |
| 3035 | 3033 | \\ fn_bool(@as(c_int, 0) != 0); |
| 3036 | 3034 | \\ fn_bool(@ptrToInt(fn_int) != 0); |
| ... | ... | @@ -3418,4 +3416,56 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 3418 | 3416 | \\pub const MAY_NEED_PROMOTION_HEX = @import("std").meta.promoteIntLiteral(c_int, 0x80000000, .hexadecimal); |
| 3419 | 3417 | \\pub const MAY_NEED_PROMOTION_OCT = @import("std").meta.promoteIntLiteral(c_int, 0o20000000000, .octal); |
| 3420 | 3418 | }); |
| 3419 | ||
| 3420 | // See __builtin_alloca_with_align comment in std.c.builtins | |
| 3421 | cases.add("demote un-implemented builtins", | |
| 3422 | \\#define FOO(X) __builtin_alloca_with_align((X), 8) | |
| 3423 | , &[_][]const u8{ | |
| 3424 | \\pub const FOO = @compileError("TODO implement function '__builtin_alloca_with_align' in std.c.builtins"); | |
| 3425 | }); | |
| 3426 | ||
| 3427 | cases.add("null sentinel arrays when initialized from string literal. Issue #8256", | |
| 3428 | \\#include <stdint.h> | |
| 3429 | \\char zero[0] = "abc"; | |
| 3430 | \\uint32_t zero_w[0] = U"💯💯💯"; | |
| 3431 | \\char empty_incomplete[] = ""; | |
| 3432 | \\uint32_t empty_incomplete_w[] = U""; | |
| 3433 | \\char empty_constant[100] = ""; | |
| 3434 | \\uint32_t empty_constant_w[100] = U""; | |
| 3435 | \\char incomplete[] = "abc"; | |
| 3436 | \\uint32_t incomplete_w[] = U"💯💯💯"; | |
| 3437 | \\char truncated[1] = "abc"; | |
| 3438 | \\uint32_t truncated_w[1] = U"💯💯💯"; | |
| 3439 | \\char extend[5] = "a"; | |
| 3440 | \\uint32_t extend_w[5] = U"💯"; | |
| 3441 | \\char no_null[3] = "abc"; | |
| 3442 | \\uint32_t no_null_w[3] = U"💯💯💯"; | |
| 3443 | , &[_][]const u8{ | |
| 3444 | \\pub export var zero: [0]u8 = [0]u8{}; | |
| 3445 | \\pub export var zero_w: [0]u32 = [0]u32{}; | |
| 3446 | \\pub export var empty_incomplete: [1]u8 = [1]u8{0} ** 1; | |
| 3447 | \\pub export var empty_incomplete_w: [1]u32 = [1]u32{0} ** 1; | |
| 3448 | \\pub export var empty_constant: [100]u8 = [1]u8{0} ** 100; | |
| 3449 | \\pub export var empty_constant_w: [100]u32 = [1]u32{0} ** 100; | |
| 3450 | \\pub export var incomplete: [3:0]u8 = "abc".*; | |
| 3451 | \\pub export var incomplete_w: [3:0]u32 = [3:0]u32{ | |
| 3452 | \\ '\u{1f4af}', | |
| 3453 | \\ '\u{1f4af}', | |
| 3454 | \\ '\u{1f4af}', | |
| 3455 | \\}; | |
| 3456 | \\pub export var truncated: [1]u8 = "abc"[0..1].*; | |
| 3457 | \\pub export var truncated_w: [1]u32 = [1]u32{ | |
| 3458 | \\ '\u{1f4af}', | |
| 3459 | \\}; | |
| 3460 | \\pub export var extend: [5]u8 = "a"[0..1].* ++ [1]u8{0} ** 4; | |
| 3461 | \\pub export var extend_w: [5]u32 = [1]u32{ | |
| 3462 | \\ '\u{1f4af}', | |
| 3463 | \\} ++ [1]u32{0} ** 4; | |
| 3464 | \\pub export var no_null: [3]u8 = "abc".*; | |
| 3465 | \\pub export var no_null_w: [3]u32 = [3]u32{ | |
| 3466 | \\ '\u{1f4af}', | |
| 3467 | \\ '\u{1f4af}', | |
| 3468 | \\ '\u{1f4af}', | |
| 3469 | \\}; | |
| 3470 | }); | |
| 3421 | 3471 | } |
tools/update_clang_options.zig+4| ... | ... | @@ -332,6 +332,10 @@ const known_options = [_]KnownOpt{ |
| 332 | 332 | .name = "s", |
| 333 | 333 | .ident = "strip", |
| 334 | 334 | }, |
| 335 | .{ | |
| 336 | .name = "dynamiclib", | |
| 337 | .ident = "shared", | |
| 338 | }, | |
| 335 | 339 | }; |
| 336 | 340 | |
| 337 | 341 | const blacklisted_options = [_][]const u8{}; |