zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit b5a36f676b1fd69f195d9f1eb6e35f3eb2f15946 (tree)
parent d6d05fc84d33c71434a1f8bae51ca5956e08cdf0
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Wed,  7 Oct 2020 00:46:05 -0700

Merge remote-tracking branch 'origin/master' into llvm11

Conflicts:
  cmake/Findllvm.cmake

The llvm11 branch changed 10's to 11's and master branch added the
"using LLVM_CONFIG_EXE" help message, so the resolution was to merge
these changes together.

I also added a check to make sure LLVM is built with AVR enabled, which
is no longer an experimental target.

Diffstat:
MCMakeLists.txt | 42++++++++++++++++++++++++------------------
MREADME.md | 1-
Mci/azure/pipelines.yml | 14++------------
Dci/azure/windows_mingw_script | 28----------------------------
Mci/azure/windows_msvc_install | 7+------
Mcmake/Findllvm.cmake | 14+++++++-------
Ddeps/dbg-macro/LICENSE | 21---------------------
Ddeps/dbg-macro/README.md | 172-------------------------------------------------------------------------------
Ddeps/dbg-macro/dbg.h | 711-------------------------------------------------------------------------------
Alib/libc/mingw/lib32/user32.def | 998+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/array_list.zig | 574++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mlib/std/build.zig | 116+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mlib/std/builtin.zig | 13+++++++++++++
Mlib/std/c.zig | 3+++
Mlib/std/c/darwin.zig | 2+-
Mlib/std/c/linux.zig | 2++
Mlib/std/child_process.zig | 4++--
Mlib/std/crypto.zig | 88+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mlib/std/crypto/25519/field.zig | 14++++++++------
Alib/std/crypto/aes_gcm.zig | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/crypto/benchmark.zig | 3+++
Alib/std/crypto/ghash.zig | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/std/crypto/hkdf.zig | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/crypto/poly1305.zig | 26+++++++++++++++++---------
Mlib/std/event/loop.zig | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/fmt.zig | 10++++++++++
Mlib/std/fs.zig | 18+++++++++++++-----
Mlib/std/fs/file.zig | 16+++++++++-------
Mlib/std/macho.zig | 44++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/math/big/int.zig | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/math/big/int_test.zig | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/meta.zig | 2++
Mlib/std/meta/trailer_flags.zig | 1+
Mlib/std/os.zig | 32+++++++++++++++++++++++++++++++-
Mlib/std/os/bits/linux.zig | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/os/linux.zig | 20++++++++++++++++++++
Mlib/std/os/test.zig | 10++++++++++
Mlib/std/packed_int_array.zig | 15+++++++++++++++
Mlib/std/zig/system.zig | 2++
Msrc/Compilation.zig | 64+++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/Module.zig | 56++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/astgen.zig | 11+++++++++++
Msrc/codegen.zig | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/codegen/arm.zig | 397++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/glibc.zig | 27++++++++++++++++++---------
Msrc/link.zig | 2++
Msrc/link/Coff.zig | 30+++++++++++++++++-------------
Msrc/link/Elf.zig | 117+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/link/MachO.zig | 778++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/main.zig | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/mingw.zig | 11++++++-----
Msrc/stage1.zig | 6+++++-
Msrc/stage1/all_types.hpp | 32+++++++++++++++++++++++++++-----
Msrc/stage1/analyze.cpp | 24++++++++++++++++--------
Msrc/stage1/codegen.cpp | 133+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/stage1/config.h.in | 1+
Msrc/stage1/ir.cpp | 420+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/stage1/ir_print.cpp | 35+++++++++++++++++++++++++++++++++++
Msrc/stage1/os.cpp | 1074++-----------------------------------------------------------------------------
Msrc/stage1/os.hpp | 52----------------------------------------------------
Msrc/stage1/zig0.cpp | 10++++++++++
Msrc/target.zig | 2+-
Msrc/test.zig | 15+++++++++++++++
Msrc/translate_c.zig | 545+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/type.zig | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/value.zig | 17++++++++++++++++-
Msrc/zig_llvm.cpp | 28++++++++++++++++++++++++++++
Msrc/zig_llvm.h | 8++++++++
Msrc/zir.zig | 4++++
Msrc/zir_sema.zig | 32++++++++++++++++++++++++++++++++
Mtest/compile_errors.zig | 229++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Atest/stage1/behavior/bugs/1467.zig | 7+++++++
Mtest/stage1/behavior/type.zig | 43+++++++++++++++++++++++++++++++++++--------
Mtest/stage1/behavior/type_info.zig | 21+++++++++++++++++++--
Mtest/stage1/behavior/vector.zig | 40++++++++++++++++++++++++++++++++++++++++
Atest/stage2/arm.zig | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/stage2/test.zig | 154+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mtest/translate_c.zig | 18+++++++++++++++++-
78 files changed, 5719 insertions(+), 3111 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -27,23 +27,26 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) set(ZIG_VERSION_MAJOR 0) set(ZIG_VERSION_MINOR 6) set(ZIG_VERSION_PATCH 0) -set(ZIG_VERSION "${ZIG_VERSION_MAJOR}.${ZIG_VERSION_MINOR}.${ZIG_VERSION_PATCH}") - -find_program(GIT_EXE NAMES git) -if(GIT_EXE) - execute_process( - COMMAND ${GIT_EXE} -C ${CMAKE_SOURCE_DIR} name-rev HEAD --tags --name-only --no-undefined --always - RESULT_VARIABLE EXIT_STATUS - OUTPUT_VARIABLE ZIG_GIT_REV - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET) - if(EXIT_STATUS EQUAL "0") - if(ZIG_GIT_REV MATCHES "\\^0$") - if(NOT("${ZIG_GIT_REV}" STREQUAL "${ZIG_VERSION}^0")) - message("WARNING: Tag does not match configured Zig version") +set(ZIG_VERSION "" CACHE STRING "Override Zig version string. Default is to find out with git.") + +if("${ZIG_VERSION}" STREQUAL "") + set(ZIG_VERSION "${ZIG_VERSION_MAJOR}.${ZIG_VERSION_MINOR}.${ZIG_VERSION_PATCH}") + find_program(GIT_EXE NAMES git) + if(GIT_EXE) + execute_process( + COMMAND ${GIT_EXE} -C ${CMAKE_SOURCE_DIR} name-rev HEAD --tags --name-only --no-undefined --always + RESULT_VARIABLE EXIT_STATUS + OUTPUT_VARIABLE ZIG_GIT_REV + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(EXIT_STATUS EQUAL "0") + if(ZIG_GIT_REV MATCHES "\\^0$") + if(NOT("${ZIG_GIT_REV}" STREQUAL "${ZIG_VERSION}^0")) + message("WARNING: Tag does not match configured Zig version") + endif() + else() + set(ZIG_VERSION "${ZIG_VERSION}+${ZIG_GIT_REV}") endif() - else() - set(ZIG_VERSION "${ZIG_VERSION}+${ZIG_GIT_REV}") endif() endif() endif() @@ -63,6 +66,9 @@ endif() if(ZIG_STATIC) set(ZIG_STATIC_LLVM "on") + set(ZIG_LINK_MODE "Static") +else() + set(ZIG_LINK_MODE "Dynamic") endif() string(REGEX REPLACE "\\\\" "\\\\\\\\" ZIG_LIBC_LIB_DIR_ESCAPED "${ZIG_LIBC_LIB_DIR}") @@ -74,6 +80,7 @@ option(ZIG_TEST_COVERAGE "Build Zig with test coverage instrumentation" OFF) set(ZIG_TARGET_TRIPLE "native" CACHE STRING "arch-os-abi to output binaries for") set(ZIG_TARGET_MCPU "baseline" CACHE STRING "-mcpu parameter to output binaries for") set(ZIG_EXECUTABLE "" CACHE STRING "(when cross compiling) path to already-built zig binary") +set(ZIG_PREFER_LLVM_CONFIG off CACHE BOOL "(when cross compiling) use llvm-config to find target llvm dependencies if needed") find_package(llvm) find_package(clang) @@ -257,7 +264,6 @@ target_include_directories(embedded_softfloat PUBLIC ) include_directories("${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/include") set(SOFTFLOAT_LIBRARIES embedded_softfloat) -include_directories("${CMAKE_SOURCE_DIR}/deps/dbg-macro") find_package(Threads) @@ -487,7 +493,7 @@ if("${ZIG_TARGET_TRIPLE}" STREQUAL "native") endif() else() add_custom_target(zig_build_zig1 ALL - COMMAND "${ZIG_EXECUTABLE}" ${BUILD_ZIG1_ARGS} + COMMAND "${ZIG_EXECUTABLE}" "build-obj" ${BUILD_ZIG1_ARGS} BYPRODUCTS "${ZIG1_OBJECT}" COMMENT STATUS "Building self-hosted component ${ZIG1_OBJECT}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" diff --git a/README.md b/README.md @@ -37,7 +37,6 @@ This step must be repeated when you make changes to any of the C++ source code. * cmake >= 3.15.3 * Microsoft Visual Studio. Supported versions: - - 2015 (version 14) - 2017 (version 15.8) - 2019 (version 16) * LLVM, Clang, LLD development libraries == 11.x diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml @@ -28,20 +28,10 @@ jobs: - job: BuildWindows pool: vmImage: 'windows-2019' - strategy: - matrix: - mingw64: - CHERE_INVOKING: yes - MSYSTEM: MINGW64 - SCRIPT: '%CD:~0,2%\msys64\usr\bin\bash -lc "bash ci/azure/windows_mingw_script"' - msvc: - SCRIPT: ci/azure/windows_msvc_script.bat - timeoutInMinutes: 360 - steps: - powershell: | - (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2020-07-20/msys2-base-x86_64-20200720.sfx.exe", "sfx.exe") + (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2020-09-03/msys2-base-x86_64-20200903.sfx.exe", "sfx.exe") .\sfx.exe -y -o\ del sfx.exe displayName: Download/Extract/Install MSYS2 @@ -57,7 +47,7 @@ jobs: - task: DownloadSecureFile@1 inputs: secureFile: s3cfg - - script: $(SCRIPT) + - script: ci/azure/windows_msvc_script.bat name: main displayName: 'Build and test' - job: OnMasterSuccess diff --git a/ci/azure/windows_mingw_script b/ci/azure/windows_mingw_script @@ -1,28 +0,0 @@ -#!/bin/sh - -set -x -set -e - -pacman --noconfirm --needed -S git base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-clang mingw-w64-x86_64-lld mingw-w64-x86_64-llvm - -git config core.abbrev 9 - -# Git is wrong for autocrlf being enabled by default on Windows. -# git is mangling files on Windows by default. -# This is the second bug I've tracked down to being caused by autocrlf. -git config core.autocrlf false -# Too late; the files are already mangled. -git checkout . - -ZIGBUILDDIR="$(pwd)/build" -PREFIX="$ZIGBUILDDIR/dist" -CMAKEFLAGS="-DCMAKE_COLOR_MAKEFILE=OFF -DCMAKE_INSTALL_PREFIX=$PREFIX -DZIG_STATIC=ON" - -mkdir $ZIGBUILDDIR -cd $ZIGBUILDDIR - -cmake .. -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo $CMAKEFLAGS -DCMAKE_EXE_LINKER_FLAGS='-fuse-ld=lld -Wl,/debug,/pdb:zig.pdb' - -make -j$(nproc) install - -./zig build test-behavior -Dskip-non-native -Dskip-release diff --git a/ci/azure/windows_msvc_install b/ci/azure/windows_msvc_install @@ -4,12 +4,7 @@ set -x set -e pacman -Su --needed --noconfirm - -# Uncomment when https://github.com/msys2/MSYS2-packages/issues/2050 is fixed -#pacman -S --needed --noconfirm wget p7zip python3-pip tar xz -pacman -S --needed --noconfirm wget p7zip tar xz -pacman -U --noconfirm http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-python-3.8.4-1-any.pkg.tar.zst -pacman -U --noconfirm http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-python-pip-20.0.2-1-any.pkg.tar.xz +pacman -S --needed --noconfirm wget p7zip python3-pip tar xz pip install s3cmd wget -nv "https://ziglang.org/deps/llvm%2bclang%2blld-10.0.0-x86_64-windows-msvc-release-mt.tar.xz" diff --git a/cmake/Findllvm.cmake b/cmake/Findllvm.cmake @@ -32,7 +32,7 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB) /usr/local/llvm11/lib /usr/local/llvm110/lib ) -elseif("${ZIG_TARGET_TRIPLE}" STREQUAL "native") +elseif(("${ZIG_TARGET_TRIPLE}" STREQUAL "native") OR ZIG_PREFER_LLVM_CONFIG) find_program(LLVM_CONFIG_EXE NAMES llvm-config-11 llvm-config-11.0 llvm-config110 llvm-config11 llvm-config PATHS @@ -55,13 +55,13 @@ elseif("${ZIG_TARGET_TRIPLE}" STREQUAL "native") OUTPUT_STRIP_TRAILING_WHITESPACE) if("${LLVM_CONFIG_VERSION}" VERSION_LESS 11) - message(FATAL_ERROR "expected LLVM 11.x but found ${LLVM_CONFIG_VERSION}") + message(FATAL_ERROR "expected LLVM 11.x but found ${LLVM_CONFIG_VERSION} using ${LLVM_CONFIG_EXE}") endif() if("${LLVM_CONFIG_VERSION}" VERSION_EQUAL 12) - message(FATAL_ERROR "expected LLVM 11.x but found ${LLVM_CONFIG_VERSION}") + message(FATAL_ERROR "expected LLVM 11.x but found ${LLVM_CONFIG_VERSION} using ${LLVM_CONFIG_EXE}") endif() if("${LLVM_CONFIG_VERSION}" VERSION_GREATER 11) - message(FATAL_ERROR "expected LLVM 11.x but found ${LLVM_CONFIG_VERSION}") + message(FATAL_ERROR "expected LLVM 11.x but found ${LLVM_CONFIG_VERSION} using ${LLVM_CONFIG_EXE}") endif() execute_process( @@ -72,12 +72,13 @@ elseif("${ZIG_TARGET_TRIPLE}" STREQUAL "native") function(NEED_TARGET TARGET_NAME) list (FIND LLVM_TARGETS_BUILT "${TARGET_NAME}" _index) if (${_index} EQUAL -1) - message(FATAL_ERROR "LLVM is missing target ${TARGET_NAME}. Zig requires LLVM to be built with all default targets enabled.") + message(FATAL_ERROR "LLVM (according to ${LLVM_CONFIG_EXE}) is missing target ${TARGET_NAME}. Zig requires LLVM to be built with all default targets enabled.") endif() endfunction(NEED_TARGET) NEED_TARGET("AArch64") NEED_TARGET("AMDGPU") NEED_TARGET("ARM") + NEED_TARGET("AVR") NEED_TARGET("BPF") NEED_TARGET("Hexagon") NEED_TARGET("Lanai") @@ -141,8 +142,7 @@ elseif("${ZIG_TARGET_TRIPLE}" STREQUAL "native") link_directories("${LLVM_LIBDIRS}") else() # Here we assume that we're cross compiling with Zig, of course. No reason - # to support more complicated setups. We also assume the experimental target - # AVR is enabled. + # to support more complicated setups. macro(FIND_AND_ADD_LLVM_LIB _libname_) string(TOUPPER ${_libname_} _prettylibname_) diff --git a/deps/dbg-macro/LICENSE b/deps/dbg-macro/LICENSE @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 David Peter <mail@david-peter.de> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/deps/dbg-macro/README.md b/deps/dbg-macro/README.md @@ -1,172 +0,0 @@ -# `dbg(…)` - -[![Build Status](https://travis-ci.org/sharkdp/dbg-macro.svg?branch=master)](https://travis-ci.org/sharkdp/dbg-macro) [![Build status](https://ci.appveyor.com/api/projects/status/vmo9rw4te2wifkul/branch/master?svg=true)](https://ci.appveyor.com/project/sharkdp/dbg-macro) [![Try it online](https://img.shields.io/badge/try-online-f34b7d.svg)](https://repl.it/@sharkdp/dbg-macro-demo) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](dbg.h) - -*A macro for `printf`-style debugging fans.* - -Debuggers are great. But sometimes you just don't have the time or patience to set -up everything correctly and just want a quick way to inspect some values at runtime. - -This projects provides a [single header file](dbg.h) with a `dbg(…)` -macro that can be used in all circumstances where you would typically write -`printf("…", …)` or `std::cout << …`. But it comes with a few extras. - -## Examples - -``` c++ -#include <vector> -#include <dbg.h> - -// You can use "dbg(..)" in expressions: -int factorial(int n) { - if (dbg(n <= 1)) { - return dbg(1); - } else { - return dbg(n * factorial(n - 1)); - } -} - -int main() { - std::string message = "hello"; - dbg(message); // [example.cpp:15 (main)] message = "hello" (std::string) - - const int a = 2; - const int b = dbg(3 * a) + 1; // [example.cpp:18 (main)] 3 * a = 6 (int) - - std::vector<int> numbers{b, 13, 42}; - dbg(numbers); // [example.cpp:21 (main)] numbers = {7, 13, 42} (size: 3) (std::vector<int>) - - dbg("this line is executed"); // [example.cpp:23 (main)] this line is executed - - factorial(4); - - return 0; -} -``` - -The code above produces this output ([try it yourself](https://repl.it/@sharkdp/dbg-macro-demo)): - -![dbg(…) macro output](https://i.imgur.com/NHEYk9A.png) - -## Features - - * Easy to read, colorized output (colors auto-disable when the output is not an interactive terminal) - * Prints file name, line number, function name and the original expression - * Adds type information for the printed-out value - * Specialized pretty-printers for containers, pointers, string literals, enums, `std::optional`, etc. - * Can be used inside expressions (passing through the original value) - * The `dbg.h` header issues a compiler warning when included (so you don't forget to remove it). - * Compatible and tested with C++11, C++14 and C++17. - -## Installation - -To make this practical, the `dbg.h` header should to be readily available from all kinds of different -places and in all kinds of environments. The quick & dirty way is to actually copy the header file -to `/usr/include` or to clone the repository and symlink `dbg.h` to `/usr/include/dbg.h`. -``` bash -git clone https://github.com/sharkdp/dbg-macro -sudo ln -s $(readlink -f dbg-macro/dbg.h) /usr/include/dbg.h -``` -If you don't want to make untracked changes to your filesystem, check below if there is a package for -your operating system or package manager. - -### On Arch Linux - -You can install [`dbg-macro` from the AUR](https://aur.archlinux.org/packages/dbg-macro/): -``` bash -yay -S dbg-macro -``` - -### With vcpkg - -You can install the [`dbg-macro` port](https://github.com/microsoft/vcpkg/tree/master/ports/dbg-macro) via: -``` bash -vcpkg install dbg-macro -``` - -## Configuration - -* Set the `DBG_MACRO_DISABLE` flag to disable the `dbg(…)` macro (i.e. to make it a no-op). -* Set the `DBG_MACRO_NO_WARNING` flag to disable the *"'dbg.h' header is included in your code base"* warnings. - -## Advanced features - -### Hexadecimal, octal and binary format - -If you want to format integers in hexadecimal, octal or binary representation, you can -simply wrap them in `dbg::hex(…)`, `dbg::oct(…)` or `dbg::bin(…)`: -```c++ -const uint32_t secret = 12648430; -dbg(dbg::hex(secret)); -``` - -### Printing type names - -`dbg(…)` already prints the type for each value in parenthesis (see screenshot above). But -sometimes you just want to print a type (maybe because you don't have a value for that type). -In this case, you can use the `dbg::type<T>()` helper to pretty-print a given type `T`. -For example: -```c++ -template <typename T> -void my_function_template() { - using MyDependentType = typename std::remove_reference<T>::type&&; - dbg(dbg::type<MyDependentType>()); -} -``` - -### Print the current time - -To print a timestamp, you can use the `dbg::time()` helper: -```c++ -dbg(dbg::time()); -``` - -### Customization - -If you want `dbg(…)` to work for your custom datatype, you can simply overload `operator<<` for -`std::ostream&`: -```c++ -std::ostream& operator<<(std::ostream& out, const user_defined_type& v) { - out << "…"; - return out; -} -``` - -If you want to modify the type name that is printed by `dbg(…)`, you can add a custom -`get_type_name` overload: -```c++ -// Customization point for type information -namespace dbg { - std::string get_type_name(type_tag<bool>) { - return "truth value"; - } -} -``` - -## Development - -If you want to contribute to `dbg-macro`, here is how you can build the tests and demos: - -Make sure that the submodule(s) are up to date: -```bash -git submodule update --init -``` - -Then, use the typical `cmake` workflow. Usage of `-DCMAKE_CXX_STANDARD=17` is optional, -but recommended in order to have the largest set of features enabled: -```bash -mkdir build -cd build -cmake .. -DCMAKE_CXX_STANDARD=17 -make -``` - -To run the tests, simply call: -```bash -make test -``` -You can find the unit tests in `tests/basic.cpp`. - -## Acknowledgement - -This project is inspired by Rusts [`dbg!(…)` macro](https://doc.rust-lang.org/std/macro.dbg.html). diff --git a/deps/dbg-macro/dbg.h b/deps/dbg-macro/dbg.h @@ -1,711 +0,0 @@ -/***************************************************************************** - - dbg(...) macro - -License (MIT): - - Copyright (c) 2019 David Peter <mail@david-peter.de> - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to - deal in the Software without restriction, including without limitation the - rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - -*****************************************************************************/ - -#ifndef DBG_MACRO_DBG_H -#define DBG_MACRO_DBG_H - -#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) -#define DBG_MACRO_UNIX -#elif defined(_MSC_VER) -#define DBG_MACRO_WINDOWS -#endif - -#ifndef DBG_MACRO_NO_WARNING -#pragma message("WARNING: the 'dbg.h' header is included in your code base") -#endif // DBG_MACRO_NO_WARNING - -#include <algorithm> -#include <chrono> -#include <ctime> -#include <iomanip> -#include <ios> -#include <iostream> -#include <memory> -#include <sstream> -#include <string> -#include <tuple> -#include <type_traits> -#include <vector> - -#ifdef DBG_MACRO_UNIX -#include <unistd.h> -#endif - -#if __cplusplus >= 201703L || defined(_MSC_VER) -#define DBG_MACRO_CXX_STANDARD 17 -#elif __cplusplus >= 201402L -#define DBG_MACRO_CXX_STANDARD 14 -#else -#define DBG_MACRO_CXX_STANDARD 11 -#endif - -#if DBG_MACRO_CXX_STANDARD >= 17 -#include <optional> -#include <variant> -#endif - -namespace dbg { - -#ifdef DBG_MACRO_UNIX -inline bool isColorizedOutputEnabled() { - return isatty(fileno(stderr)); -} -#else -inline bool isColorizedOutputEnabled() { - return true; -} -#endif - -struct time {}; - -namespace pretty_function { - -// Compiler-agnostic version of __PRETTY_FUNCTION__ and constants to -// extract the template argument in `type_name_impl` - -#if defined(__clang__) -#define DBG_MACRO_PRETTY_FUNCTION __PRETTY_FUNCTION__ -static constexpr size_t PREFIX_LENGTH = - sizeof("const char *dbg::type_name_impl() [T = ") - 1; -static constexpr size_t SUFFIX_LENGTH = sizeof("]") - 1; -#elif defined(__GNUC__) && !defined(__clang__) -#define DBG_MACRO_PRETTY_FUNCTION __PRETTY_FUNCTION__ -static constexpr size_t PREFIX_LENGTH = - sizeof("const char* dbg::type_name_impl() [with T = ") - 1; -static constexpr size_t SUFFIX_LENGTH = sizeof("]") - 1; -#elif defined(_MSC_VER) -#define DBG_MACRO_PRETTY_FUNCTION __FUNCSIG__ -static constexpr size_t PREFIX_LENGTH = - sizeof("const char *__cdecl dbg::type_name_impl<") - 1; -static constexpr size_t SUFFIX_LENGTH = sizeof(">(void)") - 1; -#else -#error "This compiler is currently not supported by dbg_macro." -#endif - -} // namespace pretty_function - -// Formatting helpers - -template <typename T> -struct print_formatted { - static_assert(std::is_integral<T>::value, - "Only integral types are supported."); - - print_formatted(T value, int numeric_base) - : inner(value), base(numeric_base) {} - - operator T() const { return inner; } - - const char* prefix() const { - switch (base) { - case 8: - return "0o"; - case 16: - return "0x"; - case 2: - return "0b"; - default: - return ""; - } - } - - T inner; - int base; -}; - -template <typename T> -print_formatted<T> hex(T value) { - return print_formatted<T>{value, 16}; -} - -template <typename T> -print_formatted<T> oct(T value) { - return print_formatted<T>{value, 8}; -} - -template <typename T> -print_formatted<T> bin(T value) { - return print_formatted<T>{value, 2}; -} - -// Implementation of 'type_name<T>()' - -template <typename T> -const char* type_name_impl() { - return DBG_MACRO_PRETTY_FUNCTION; -} - -template <typename T> -struct type_tag {}; - -template <int&... ExplicitArgumentBarrier, typename T> -std::string get_type_name(type_tag<T>) { - namespace pf = pretty_function; - - std::string type = type_name_impl<T>(); - return type.substr(pf::PREFIX_LENGTH, - type.size() - pf::PREFIX_LENGTH - pf::SUFFIX_LENGTH); -} - -template <typename T> -std::string type_name() { - if (std::is_volatile<T>::value) { - if (std::is_pointer<T>::value) { - return type_name<typename std::remove_volatile<T>::type>() + " volatile"; - } else { - return "volatile " + type_name<typename std::remove_volatile<T>::type>(); - } - } - if (std::is_const<T>::value) { - if (std::is_pointer<T>::value) { - return type_name<typename std::remove_const<T>::type>() + " const"; - } else { - return "const " + type_name<typename std::remove_const<T>::type>(); - } - } - if (std::is_pointer<T>::value) { - return type_name<typename std::remove_pointer<T>::type>() + "*"; - } - if (std::is_lvalue_reference<T>::value) { - return type_name<typename std::remove_reference<T>::type>() + "&"; - } - if (std::is_rvalue_reference<T>::value) { - return type_name<typename std::remove_reference<T>::type>() + "&&"; - } - return get_type_name(type_tag<T>{}); -} - -inline std::string get_type_name(type_tag<short>) { - return "short"; -} - -inline std::string get_type_name(type_tag<unsigned short>) { - return "unsigned short"; -} - -inline std::string get_type_name(type_tag<long>) { - return "long"; -} - -inline std::string get_type_name(type_tag<unsigned long>) { - return "unsigned long"; -} - -inline std::string get_type_name(type_tag<std::string>) { - return "std::string"; -} - -template <typename T> -std::string get_type_name(type_tag<std::vector<T, std::allocator<T>>>) { - return "std::vector<" + type_name<T>() + ">"; -} - -template <typename T1, typename T2> -std::string get_type_name(type_tag<std::pair<T1, T2>>) { - return "std::pair<" + type_name<T1>() + ", " + type_name<T2>() + ">"; -} - -template <typename... T> -std::string type_list_to_string() { - std::string result; - auto unused = {(result += type_name<T>() + ", ", 0)..., 0}; - static_cast<void>(unused); - - if (sizeof...(T) > 0) { - result.pop_back(); - result.pop_back(); - } - return result; -} - -template <typename... T> -std::string get_type_name(type_tag<std::tuple<T...>>) { - return "std::tuple<" + type_list_to_string<T...>() + ">"; -} - -template <typename T> -inline std::string get_type_name(type_tag<print_formatted<T>>) { - return type_name<T>(); -} - -// Implementation of 'is_detected' to specialize for container-like types - -namespace detail_detector { - -struct nonesuch { - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(nonesuch const&) = delete; - void operator=(nonesuch const&) = delete; -}; - -template <typename...> -using void_t = void; - -template <class Default, - class AlwaysVoid, - template <class...> - class Op, - class... Args> -struct detector { - using value_t = std::false_type; - using type = Default; -}; - -template <class Default, template <class...> class Op, class... Args> -struct detector<Default, void_t<Op<Args...>>, Op, Args...> { - using value_t = std::true_type; - using type = Op<Args...>; -}; - -} // namespace detail_detector - -template <template <class...> class Op, class... Args> -using is_detected = typename detail_detector:: - detector<detail_detector::nonesuch, void, Op, Args...>::value_t; - -namespace detail { - -namespace { -using std::begin; -using std::end; -#if DBG_MACRO_CXX_STANDARD < 17 -template <typename T> -constexpr auto size(const T& c) -> decltype(c.size()) { - return c.size(); -} -template <typename T, std::size_t N> -constexpr std::size_t size(const T (&)[N]) { - return N; -} -#else -using std::size; -#endif -} // namespace - -template <typename T> -using detect_begin_t = decltype(detail::begin(std::declval<T>())); - -template <typename T> -using detect_end_t = decltype(detail::end(std::declval<T>())); - -template <typename T> -using detect_size_t = decltype(detail::size(std::declval<T>())); - -template <typename T> -struct is_container { - static constexpr bool value = - is_detected<detect_begin_t, T>::value && - is_detected<detect_end_t, T>::value && - is_detected<detect_size_t, T>::value && - !std::is_same<std::string, - typename std::remove_cv< - typename std::remove_reference<T>::type>::type>::value; -}; - -template <typename T> -using ostream_operator_t = - decltype(std::declval<std::ostream&>() << std::declval<T>()); - -template <typename T> -struct has_ostream_operator : is_detected<ostream_operator_t, T> {}; - -} // namespace detail - -// Helper to dbg(…)-print types -template <typename T> -struct print_type {}; - -template <typename T> -print_type<T> type() { - return print_type<T>{}; -} - -// Specializations of "pretty_print" - -template <typename T> -inline void pretty_print(std::ostream& stream, const T& value, std::true_type) { - stream << value; -} - -template <typename T> -inline void pretty_print(std::ostream&, const T&, std::false_type) { - static_assert(detail::has_ostream_operator<const T&>::value, - "Type does not support the << ostream operator"); -} - -template <typename T> -inline typename std::enable_if<!detail::is_container<const T&>::value && - !std::is_enum<T>::value, - bool>::type -pretty_print(std::ostream& stream, const T& value) { - pretty_print(stream, value, - typename detail::has_ostream_operator<const T&>::type{}); - return true; -} - -inline bool pretty_print(std::ostream& stream, const bool& value) { - stream << std::boolalpha << value; - return true; -} - -inline bool pretty_print(std::ostream& stream, const char& value) { - const bool printable = value >= 0x20 && value <= 0x7E; - - if (printable) { - stream << "'" << value << "'"; - } else { - stream << "'\\x" << std::setw(2) << std::setfill('0') << std::hex - << std::uppercase << (0xFF & value) << "'"; - } - return true; -} - -template <typename P> -inline bool pretty_print(std::ostream& stream, P* const& value) { - if (value == nullptr) { - stream << "nullptr"; - } else { - stream << value; - } - return true; -} - -template <typename T, typename Deleter> -inline bool pretty_print(std::ostream& stream, - std::unique_ptr<T, Deleter>& value) { - pretty_print(stream, value.get()); - return true; -} - -template <typename T> -inline bool pretty_print(std::ostream& stream, std::shared_ptr<T>& value) { - pretty_print(stream, value.get()); - stream << " (use_count = " << value.use_count() << ")"; - - return true; -} - -template <size_t N> -inline bool pretty_print(std::ostream& stream, const char (&value)[N]) { - stream << value; - return false; -} - -template <> -inline bool pretty_print(std::ostream& stream, const char* const& value) { - stream << '"' << value << '"'; - return true; -} - -template <size_t Idx> -struct pretty_print_tuple { - template <typename... Ts> - static void print(std::ostream& stream, const std::tuple<Ts...>& tuple) { - pretty_print_tuple<Idx - 1>::print(stream, tuple); - stream << ", "; - pretty_print(stream, std::get<Idx>(tuple)); - } -}; - -template <> -struct pretty_print_tuple<0> { - template <typename... Ts> - static void print(std::ostream& stream, const std::tuple<Ts...>& tuple) { - pretty_print(stream, std::get<0>(tuple)); - } -}; - -template <typename... Ts> -inline bool pretty_print(std::ostream& stream, const std::tuple<Ts...>& value) { - stream << "{"; - pretty_print_tuple<sizeof...(Ts) - 1>::print(stream, value); - stream << "}"; - - return true; -} - -template <> -inline bool pretty_print(std::ostream& stream, const std::tuple<>&) { - stream << "{}"; - - return true; -} - -template <> -inline bool pretty_print(std::ostream& stream, const time&) { - using namespace std::chrono; - - const auto now = system_clock::now(); - const auto us = - duration_cast<microseconds>(now.time_since_epoch()).count() % 1000000; - const auto hms = system_clock::to_time_t(now); - const std::tm* tm = std::localtime(&hms); - stream << "current time = " << std::put_time(tm, "%H:%M:%S") << '.' - << std::setw(6) << std::setfill('0') << us; - - return false; -} - -// Converts decimal integer to binary string -template <typename T> -std::string decimalToBinary(T n) { - const size_t length = 8 * sizeof(T); - std::string toRet; - toRet.resize(length); - - for (size_t i = 0; i < length; ++i) { - const auto bit_at_index_i = (n >> i) & 1; - toRet[length - 1 - i] = bit_at_index_i + '0'; - } - - return toRet; -} - -template <typename T> -inline bool pretty_print(std::ostream& stream, - const print_formatted<T>& value) { - if (value.inner < 0) { - stream << "-"; - } - stream << value.prefix(); - - // Print using setbase - if (value.base != 2) { - stream << std::setw(sizeof(T)) << std::setfill('0') - << std::setbase(value.base) << std::uppercase; - - if (value.inner >= 0) { - // The '+' sign makes sure that a uint_8 is printed as a number - stream << +value.inner; - } else { - using unsigned_type = typename std::make_unsigned<T>::type; - stream << +(static_cast<unsigned_type>(-(value.inner + 1)) + 1); - } - } else { - // Print for binary - if (value.inner >= 0) { - stream << decimalToBinary(value.inner); - } else { - using unsigned_type = typename std::make_unsigned<T>::type; - stream << decimalToBinary<unsigned_type>( - static_cast<unsigned_type>(-(value.inner + 1)) + 1); - } - } - - return true; -} - -template <typename T> -inline bool pretty_print(std::ostream& stream, const print_type<T>&) { - stream << type_name<T>(); - - stream << " [sizeof: " << sizeof(T) << " byte, "; - - stream << "trivial: "; - if (std::is_trivial<T>::value) { - stream << "yes"; - } else { - stream << "no"; - } - - stream << ", standard layout: "; - if (std::is_standard_layout<T>::value) { - stream << "yes"; - } else { - stream << "no"; - } - stream << "]"; - - return false; -} - -template <typename Container> -inline typename std::enable_if<detail::is_container<const Container&>::value, - bool>::type -pretty_print(std::ostream& stream, const Container& value) { - stream << "{"; - const size_t size = detail::size(value); - const size_t n = std::min(size_t{10}, size); - size_t i = 0; - using std::begin; - using std::end; - for (auto it = begin(value); it != end(value) && i < n; ++it, ++i) { - pretty_print(stream, *it); - if (i != n - 1) { - stream << ", "; - } - } - - if (size > n) { - stream << ", ..."; - stream << " size:" << size; - } - - stream << "}"; - return true; -} - -template <typename Enum> -inline typename std::enable_if<std::is_enum<Enum>::value, bool>::type -pretty_print(std::ostream& stream, Enum const& value) { - using UnderlyingType = typename std::underlying_type<Enum>::type; - stream << static_cast<UnderlyingType>(value); - - return true; -} - -inline bool pretty_print(std::ostream& stream, const std::string& value) { - stream << '"' << value << '"'; - return true; -} - -template <typename T1, typename T2> -inline bool pretty_print(std::ostream& stream, const std::pair<T1, T2>& value) { - stream << "{"; - pretty_print(stream, value.first); - stream << ", "; - pretty_print(stream, value.second); - stream << "}"; - return true; -} - -#if DBG_MACRO_CXX_STANDARD >= 17 - -template <typename T> -inline bool pretty_print(std::ostream& stream, const std::optional<T>& value) { - if (value) { - stream << '{'; - pretty_print(stream, *value); - stream << '}'; - } else { - stream << "nullopt"; - } - - return true; -} - -template <typename... Ts> -inline bool pretty_print(std::ostream& stream, - const std::variant<Ts...>& value) { - stream << "{"; - std::visit([&stream](auto&& arg) { pretty_print(stream, arg); }, value); - stream << "}"; - - return true; -} - -#endif - -class DebugOutput { - public: - DebugOutput(const char* filepath, - int line, - const char* function_name, - const char* expression) - : m_use_colorized_output(isColorizedOutputEnabled()), - m_filepath(filepath), - m_line(line), - m_function_name(function_name), - m_expression(expression) { - const std::size_t path_length = m_filepath.length(); - if (path_length > MAX_PATH_LENGTH) { - m_filepath = ".." + m_filepath.substr(path_length - MAX_PATH_LENGTH, - MAX_PATH_LENGTH); - } - } - - template <typename T> - T&& print(const std::string& type, T&& value) const { - const T& ref = value; - std::stringstream stream_value; - const bool print_expr_and_type = pretty_print(stream_value, ref); - - std::stringstream output; - output << ansi(ANSI_DEBUG) << "[" << m_filepath << ":" << m_line << " (" - << m_function_name << ")] " << ansi(ANSI_RESET); - if (print_expr_and_type) { - output << ansi(ANSI_EXPRESSION) << m_expression << ansi(ANSI_RESET) - << " = "; - } - output << ansi(ANSI_VALUE) << stream_value.str() << ansi(ANSI_RESET); - if (print_expr_and_type) { - output << " (" << ansi(ANSI_TYPE) << type << ansi(ANSI_RESET) << ")"; - } - output << std::endl; - std::cerr << output.str(); - - return std::forward<T>(value); - } - - private: - const char* ansi(const char* code) const { - if (m_use_colorized_output) { - return code; - } else { - return ANSI_EMPTY; - } - } - - const bool m_use_colorized_output; - - std::string m_filepath; - const int m_line; - const std::string m_function_name; - const std::string m_expression; - - static constexpr std::size_t MAX_PATH_LENGTH = 20; - - static constexpr const char* const ANSI_EMPTY = ""; - static constexpr const char* const ANSI_DEBUG = "\x1b[02m"; - static constexpr const char* const ANSI_EXPRESSION = "\x1b[36m"; - static constexpr const char* const ANSI_VALUE = "\x1b[01m"; - static constexpr const char* const ANSI_TYPE = "\x1b[32m"; - static constexpr const char* const ANSI_RESET = "\x1b[0m"; -}; - -// Identity function to suppress "-Wunused-value" warnings in DBG_MACRO_DISABLE -// mode -template <typename T> -T&& identity(T&& t) { - return std::forward<T>(t); -} - -} // namespace dbg - -#ifndef DBG_MACRO_DISABLE -// We use a variadic macro to support commas inside expressions (e.g. -// initializer lists): -#define dbg(...) \ - dbg::DebugOutput(__FILE__, __LINE__, __func__, #__VA_ARGS__) \ - .print(dbg::type_name<decltype(__VA_ARGS__)>(), (__VA_ARGS__)) -#else -#define dbg(...) dbg::identity(__VA_ARGS__) -#endif // DBG_MACRO_DISABLE - -#endif // DBG_MACRO_DBG_H diff --git a/lib/libc/mingw/lib32/user32.def b/lib/libc/mingw/lib32/user32.def @@ -0,0 +1,998 @@ +LIBRARY USER32.dll +EXPORTS +;ord_1500@16 @1500 +;ord_1501@4 @1501 +;ord_1502@12 @1502 +ActivateKeyboardLayout@8 +AddClipboardFormatListener@4 +AdjustWindowRect@12 +AdjustWindowRectEx@16 +AlignRects@16 +AllowForegroundActivation@0 +AllowSetForegroundWindow@4 +AnimateWindow@12 +AnyPopup@0 +AppendMenuA@16 +AppendMenuW@16 +ArrangeIconicWindows@4 +AttachThreadInput@12 +BeginDeferWindowPos@4 +BeginPaint@8 +BlockInput@4 +BringWindowToTop@4 +BroadcastSystemMessage@20 +BroadcastSystemMessageA@20 +BroadcastSystemMessageExA@24 +BroadcastSystemMessageExW@24 +BroadcastSystemMessageW@20 +BuildReasonArray@12 +CalcChildScroll@8 +CalcMenuBar@20 +CalculatePopupWindowPosition@20 +CallMsgFilter@8 +CallMsgFilterA@8 +CallMsgFilterW@8 +CallNextHookEx@16 +CallWindowProcA@20 +CallWindowProcW@20 +CancelShutdown@0 +CascadeChildWindows@8 +CascadeWindows@20 +ChangeClipboardChain@8 +ChangeDisplaySettingsA@8 +ChangeDisplaySettingsExA@20 +ChangeDisplaySettingsExW@20 +ChangeDisplaySettingsW@8 +ChangeMenuA@20 +ChangeMenuW@20 +ChangeWindowMessageFilter@8 +ChangeWindowMessageFilterEx@16 +CharLowerA@4 +CharLowerBuffA@8 +CharLowerBuffW@8 +CharLowerW@4 +CharNextA@4 +;ord_1550@12 @1550 +;ord_1551@8 @1551 +;ord_1552@8 @1552 +;ord_1553@12 @1553 +;ord_1554@8 @1554 +;ord_1555@16 @1555 +;ord_1556@4 @1556 +CharNextExA@12 +CharNextW@4 +CharPrevA@8 +CharPrevExA@16 +CharPrevW@8 +CharToOemA@8 +CharToOemBuffA@12 +CharToOemBuffW@12 +CharToOemW@8 +CharUpperA@4 +CharUpperBuffA@8 +CharUpperBuffW@8 +CharUpperW@4 +CheckDesktopByThreadId@4 +CheckDBCSEnabledExt@0 +CheckDlgButton@12 +CheckMenuItem@12 +CheckMenuRadioItem@20 +CheckProcessForClipboardAccess@8 +CheckProcessSession@4 +CheckRadioButton@16 +CheckWindowThreadDesktop@8 +ChildWindowFromPoint@12 +ChildWindowFromPointEx@16 +CliImmSetHotKey@16 +ClientThreadSetup@0 +ClientToScreen@8 +ClipCursor@4 +CloseClipboard@0 +CloseDesktop@4 +CloseGestureInfoHandle@4 +CloseTouchInputHandle@4 +CloseWindow@4 +CloseWindowStation@4 +ConsoleControl@12 +ControlMagnification@8 +CopyAcceleratorTableA@12 +CopyAcceleratorTableW@12 +CopyIcon@4 +CopyImage@20 +CopyRect@8 +CountClipboardFormats@0 +CreateAcceleratorTableA@8 +CreateAcceleratorTableW@8 +CreateCaret@16 +CreateCursor@28 +CreateDCompositionHwndTarget@12 +CreateDesktopA@24 +CreateDesktopExA@32 +CreateDesktopExW@32 +CreateDesktopW@24 +CreateDialogIndirectParamA@20 +CreateDialogIndirectParamAorW@24 +CreateDialogIndirectParamW@20 +CreateDialogParamA@20 +CreateDialogParamW@20 +CreateIcon@28 +CreateIconFromResource@16 +CreateIconFromResourceEx@28 +CreateIconIndirect@4 +CreateMDIWindowA@40 +CreateMDIWindowW@40 +CreateMenu@0 +CreatePopupMenu@0 +CreateSystemThreads@16 ; ReactOS has the @8 variant +CreateWindowExA@48 +CreateWindowExW@48 +CreateWindowInBand@52 +CreateWindowIndirect@4 +CreateWindowStationA@16 +CreateWindowStationW@16 +CsrBroadcastSystemMessageExW@24 +CtxInitUser32@0 +DdeAbandonTransaction@12 +DdeAccessData@8 +DdeAddData@16 +DdeClientTransaction@32 +DdeCmpStringHandles@8 +DdeConnect@16 +DdeConnectList@20 +DdeCreateDataHandle@28 +DdeCreateStringHandleA@12 +DdeCreateStringHandleW@12 +DdeDisconnect@4 +DdeDisconnectList@4 +DdeEnableCallback@12 +DdeFreeDataHandle@4 +DdeFreeStringHandle@8 +DdeGetData@16 +DdeGetLastError@4 +DdeGetQualityOfService@12 +DdeImpersonateClient@4 +DdeInitializeA@16 +DdeInitializeW@16 +DdeKeepStringHandle@8 +DdeNameService@16 +DdePostAdvise@12 +DdeQueryConvInfo@12 +DdeQueryNextServer@8 +DdeQueryStringA@20 +DdeQueryStringW@20 +DdeReconnect@4 +DdeSetQualityOfService@12 +DdeSetUserHandle@12 +DdeUnaccessData@4 +DdeUninitialize@4 +DefDlgProcA@16 +DefDlgProcW@16 +DefFrameProcA@20 +DefFrameProcW@20 +DefMDIChildProcA@16 +DefMDIChildProcW@16 +DefRawInputProc@12 +DefWindowProcA@16 +DefWindowProcW@16 +DeferWindowPos@32 +DeferWindowPosAndBand@36 +DeleteMenu@12 +DeregisterShellHookWindow@4 +DestroyAcceleratorTable@4 +DestroyCaret@0 +DestroyCursor@4 +DestroyDCompositionHwndTarget@8 +DestroyIcon@4 +DestroyMenu@4 +DestroyReasons@4 +DestroyWindow@4 +DeviceEventWorker@24 ; No documentation whatsoever, ReactOS has a stub with @20 - https://www.reactos.org/archives/public/ros-diffs/2011-February/040308.html +DialogBoxIndirectParamA@20 +DialogBoxIndirectParamAorW@24 +DialogBoxIndirectParamW@20 +DialogBoxParamA@20 +DialogBoxParamW@20 +DisableProcessWindowsGhosting@0 +DispatchMessageA@4 +DispatchMessageW@4 +DisplayConfigGetDeviceInfo@4 +DisplayConfigSetDeviceInfo@4 +DisplayExitWindowsWarnings@4 +DlgDirListA@20 +DlgDirListComboBoxA@20 +DlgDirListComboBoxW@20 +DlgDirListW@20 +DlgDirSelectComboBoxExA@16 +DlgDirSelectComboBoxExW@16 +DlgDirSelectExA@16 +DlgDirSelectExW@16 +DoSoundConnect@0 +DoSoundDisconnect@0 +DragDetect@12 +DragObject@20 +DrawAnimatedRects@16 +DrawCaption@16 +DrawCaptionTempA@28 +DrawCaptionTempW@28 +DrawEdge@16 +DrawFocusRect@8 +DrawFrame@16 +DrawFrameControl@16 +DrawIcon@16 +DrawIconEx@36 +DrawMenuBar@4 +DrawMenuBarTemp@20 +DrawStateA@40 +DrawStateW@40 +DrawTextA@20 +DrawTextExA@24 +DrawTextExW@24 +DrawTextW@20 +DwmGetDxSharedSurface@24 +DwmGetRemoteSessionOcclusionEvent@0 +DwmGetRemoteSessionOcclusionState@0 +DwmLockScreenUpdates@4 +DwmStartRedirection@8 ; Mentioned on http://habrahabr.ru/post/145174/ , enables GDI virtualization (for security purposes) +DwmStopRedirection@0 +DwmValidateWindow@8 +EditWndProc@16 +EmptyClipboard@0 +EnableMenuItem@12 +EnableMouseInPointer@4 +EnableScrollBar@12 +EnableSessionForMMCSS@4 +EnableWindow@8 +EndDeferWindowPos@4 +EndDeferWindowPosEx@8 +EndDialog@8 +EndMenu@0 +EndPaint@8 +EndTask@12 +EnterReaderModeHelper@4 +EnumChildWindows@12 +EnumClipboardFormats@4 +EnumDesktopWindows@12 +EnumDesktopsA@12 +EnumDesktopsW@12 +EnumDisplayDevicesA@16 +EnumDisplayDevicesW@16 +EnumDisplayMonitors@16 +EnumDisplaySettingsA@12 +EnumDisplaySettingsExA@16 +EnumDisplaySettingsExW@16 +EnumDisplaySettingsW@12 +EnumPropsA@8 +EnumPropsExA@12 +EnumPropsExW@12 +EnumPropsW@8 +EnumThreadWindows@12 +EnumWindowStationsA@8 +EnumWindowStationsW@8 +EnumWindows@8 +EqualRect@8 +EvaluateProximityToPolygon@16 +EvaluateProximityToRect@12 +ExcludeUpdateRgn@8 +ExitWindowsEx@8 +FillRect@12 +FindWindowA@8 +FindWindowExA@16 +FindWindowExW@16 +FindWindowW@8 +FlashWindow@8 +FlashWindowEx@4 +FrameRect@12 +FreeDDElParam@8 +FrostCrashedWindow@8 +GetActiveWindow@0 +GetAltTabInfo@20 +GetAltTabInfoA@20 +GetAltTabInfoW@20 +GetAncestor@8 +GetAppCompatFlags2@4 +GetAppCompatFlags@8 ; ReactOS has @4 version http://doxygen.reactos.org/d9/d71/undocuser_8h_a9b76cdc68c523a061c86a40367049ed2.html +GetAsyncKeyState@4 +GetAutoRotationState@4 +GetCIMSSM@4 +GetCapture@0 +GetCaretBlinkTime@0 +GetCaretPos@4 +GetClassInfoA@12 +GetClassInfoExA@12 +GetClassInfoExW@12 +GetClassInfoW@12 +GetClassLongA@8 +GetClassLongW@8 +GetClassNameA@12 +GetClassNameW@12 +GetClassWord@8 +GetClientRect@8 +GetClipCursor@4 +GetClipboardAccessToken@8 +GetClipboardData@4 +GetClipboardFormatNameA@12 +GetClipboardFormatNameW@12 +GetClipboardOwner@0 +GetClipboardSequenceNumber@0 +GetClipboardViewer@0 +GetComboBoxInfo@8 +GetCurrentInputMessageSource@4 +GetCursor@0 +GetCursorFrameInfo@20 +GetCursorInfo@4 +GetCursorPos@4 +GetDC@4 +GetDCEx@12 +GetDesktopID@8 +GetDesktopWindow@0 +GetDialogBaseUnits@0 +GetDisplayAutoRotationPreferences@4 +GetDisplayConfigBufferSizes@12 +GetDlgCtrlID@4 +GetDlgItem@8 +GetDlgItemInt@16 +GetDlgItemTextA@16 +GetDlgItemTextW@16 +GetDoubleClickTime@0 +GetDpiForMonitorInternal@16 +GetFocus@0 +GetForegroundWindow@0 +GetGUIThreadInfo@8 +GetGestureConfig@24 +GetGestureExtraArgs@12 +GetGestureInfo@8 +GetGuiResources@8 +GetIconInfo@8 +GetIconInfoExA@8 +GetIconInfoExW@8 +GetInputDesktop@0 +GetInputLocaleInfo@8 +GetInputState@0 +GetInternalWindowPos@12 +GetKBCodePage@0 +GetKeyNameTextA@12 +GetKeyNameTextW@12 +GetKeyState@4 +GetKeyboardLayout@4 +GetKeyboardLayoutList@8 +GetKeyboardLayoutNameA@4 +GetKeyboardLayoutNameW@4 +GetKeyboardState@4 +GetKeyboardType@4 +GetLastActivePopup@4 +GetLastInputInfo@4 +GetLayeredWindowAttributes@16 +GetListBoxInfo@4 +GetMagnificationDesktopColorEffect@4 +GetMagnificationDesktopMagnification@12 +GetMagnificationLensCtxInformation@16 +GetMenu@4 +GetMenuBarInfo@16 +GetMenuCheckMarkDimensions@0 +GetMenuContextHelpId@4 +GetMenuDefaultItem@12 +GetMenuInfo@8 +GetMenuItemCount@4 +GetMenuItemID@8 +GetMenuItemInfoA@16 +GetMenuItemInfoW@16 +GetMenuItemRect@16 +GetMenuState@12 +GetMenuStringA@20 +GetMenuStringW@20 +GetMessageA@16 +GetMessageExtraInfo@0 +GetMessagePos@0 +GetMessageTime@0 +GetMessageW@16 +GetMonitorInfoA@8 +GetMonitorInfoW@8 +GetMouseMovePointsEx@20 +GetNextDlgGroupItem@12 +GetNextDlgTabItem@12 +GetOpenClipboardWindow@0 +GetParent@4 +GetPhysicalCursorPos@4 +GetPointerCursorId@8 +GetPointerDevice@8 +GetPointerDeviceCursors@12 +GetPointerDeviceProperties@12 +GetPointerDeviceRects@12 +GetPointerDevices@8 +GetPointerFrameInfo@12 +GetPointerFrameInfoHistory@16 +GetPointerFramePenInfo@12 +GetPointerFramePenInfoHistory@16 +GetPointerFrameTouchInfo@12 +GetPointerFrameTouchInfoHistory@16 +GetPointerInfo@8 +GetPointerInfoHistory@12 +GetPointerInputTransform@12 +GetPointerPenInfo@8 +GetPointerPenInfoHistory@12 +GetPointerTouchInfo@8 +GetPointerTouchInfoHistory@12 +GetPointerType@8 +GetPriorityClipboardFormat@8 +GetProcessDefaultLayout@4 +GetProcessDpiAwarenessInternal@8 +GetProcessWindowStation@0 +GetProgmanWindow@0 +GetPropA@8 +GetPropW@8 +GetQueueStatus@4 +GetRawInputBuffer@12 +GetRawInputData@20 +GetRawInputDeviceInfoA@16 +GetRawInputDeviceInfoW@16 +GetRawInputDeviceList@12 +GetRawPointerDeviceData@20 +GetReasonTitleFromReasonCode@12 +GetRegisteredRawInputDevices@12 +GetQueueStatus@4 +GetScrollBarInfo@12 +GetScrollInfo@12 +GetScrollPos@8 +GetScrollRange@16 +GetSendMessageReceiver@4 +GetShellWindow@0 +GetSubMenu@8 +GetSysColor@4 +GetSysColorBrush@4 +GetSystemMenu@8 +GetSystemMetrics@4 +GetTabbedTextExtentA@20 +GetTabbedTextExtentW@20 +GetTaskmanWindow@0 +GetThreadDesktop@4 +GetTitleBarInfo@8 +GetTopLevelWindow@4 +GetTopWindow@4 +GetTouchInputInfo@16 +GetUnpredictedMessagePos@0 +GetUpdateRect@12 +GetUpdateRgn@12 +GetUpdatedClipboardFormats@12 +GetUserObjectInformationA@20 +GetUserObjectInformationW@20 +GetUserObjectSecurity@20 +GetWinStationInfo@4 +GetWindow@8 +GetWindowBand@8 +GetWindowCompositionAttribute@8 +GetWindowCompositionInfo@8 +GetWindowContextHelpId@4 +GetWindowDC@4 +GetWindowDisplayAffinity@8 +GetWindowFeedbackSetting@20 +GetWindowInfo@8 +GetWindowLongA@8 +GetWindowLongW@8 +GetWindowMinimizeRect@8 +GetWindowModuleFileName@12 +GetWindowModuleFileNameA@12 +GetWindowModuleFileNameW@12 +GetWindowPlacement@8 +GetWindowRect@8 +GetWindowRgn@8 +GetWindowRgnBox@8 +GetWindowRgnEx@12 +GetWindowTextA@12 +GetWindowTextLengthA@4 +GetWindowTextLengthW@4 +GetWindowTextW@12 +GetWindowThreadProcessId@8 +GetWindowWord@8 +GhostWindowFromHungWindow@4 +GrayStringA@36 +GrayStringW@36 +HideCaret@4 +HiliteMenuItem@16 +HungWindowFromGhostWindow@4 +IMPGetIMEA@8 +IMPGetIMEW@8 +IMPQueryIMEA@4 +IMPQueryIMEW@4 +IMPSetIMEA@8 +IMPSetIMEW@8 +ImpersonateDdeClientWindow@8 +InSendMessage@0 +InSendMessageEx@4 +InflateRect@12 +InitializeLpkHooks@4 +InitializeWin32EntryTable@4 +InitializeTouchInjection@8 +InjectTouchInput@8 +InsertMenuA@20 +InsertMenuItemA@16 +InsertMenuItemW@16 +InsertMenuW@20 +InternalGetWindowIcon@8 +;ord_2001@4 @2001 +;ord_2002@4 @2002 +InternalGetWindowText@12 +IntersectRect@12 +;ord_2005@4 @2005 +InvalidateRect@12 +InvalidateRgn@12 +InvertRect@8 +IsCharAlphaA@4 +;ord_2010@16 @2010 +IsCharAlphaNumericA@4 +IsCharAlphaNumericW@4 +IsCharAlphaW@4 +IsCharLowerA@4 +IsCharLowerW@4 +IsCharUpperA@4 +IsCharUpperW@4 +IsChild@8 +IsClipboardFormatAvailable@4 +IsDialogMessage@8 +IsDialogMessageA@8 +IsDialogMessageW@8 +IsDlgButtonChecked@8 +IsGUIThread@4 +IsHungAppWindow@4 +IsIconic@4 +IsImmersiveProcess@4 +IsInDesktopWindowBand@4 +IsMenu@4 +IsProcess16Bit@0 +IsMouseInPointerEnabled@0 +IsProcessDPIAware@0 +IsQueueAttached@0 +IsRectEmpty@4 +IsSETEnabled@0 +IsServerSideWindow@4 +IsThreadDesktopComposited@0 +IsTopLevelWindow@4 +IsTouchWindow@8 +IsWinEventHookInstalled@4 +IsWindow@4 +IsWindowEnabled@4 +IsWindowInDestroy@4 +IsWindowRedirectedForPrint@4 +IsWindowUnicode@4 +IsWindowVisible@4 +IsWow64Message@0 +IsZoomed@4 +KillSystemTimer@8 +KillTimer@8 +LoadAcceleratorsA@8 +LoadAcceleratorsW@8 +LoadBitmapA@8 +LoadBitmapW@8 +LoadCursorA@8 +LoadCursorFromFileA@4 +LoadCursorFromFileW@4 +;ord_2000@0 @2000 +;ord_2001@4 @2001 +;ord_2002@4 @2002 +LoadCursorW@8 +LoadIconA@8 +;ord_2005@4 @2005 +LoadIconW@8 +LoadImageA@24 +LoadImageW@24 +LoadKeyboardLayoutA@8 +LoadKeyboardLayoutEx@12 +LoadKeyboardLayoutW@8 +LoadLocalFonts@0 +LoadMenuA@8 +LoadMenuIndirectA@4 +LoadMenuIndirectW@4 +LoadMenuW@8 +LoadRemoteFonts@0 +LoadStringA@16 +LoadStringW@16 +LockSetForegroundWindow@4 +LockWindowStation@4 +LockWindowUpdate@4 +LockWorkStation@0 +LogicalToPhysicalPoint@8 +LogicalToPhysicalPointForPerMonitorDPI@8 +LookupIconIdFromDirectory@8 +LookupIconIdFromDirectoryEx@20 +MBToWCSEx@24 +MBToWCSExt@20 +MB_GetString@4 +MapDialogRect@8 +MapVirtualKeyA@8 +MapVirtualKeyExA@12 +MapVirtualKeyExW@12 +MapVirtualKeyW@8 +MapWindowPoints@16 +MenuItemFromPoint@16 +MenuWindowProcA@20 +MenuWindowProcW@20 +MessageBeep@4 +MessageBoxA@16 +MessageBoxExA@20 +MessageBoxExW@20 +MessageBoxIndirectA@4 +MessageBoxIndirectW@4 +MessageBoxTimeoutA@24 +MessageBoxTimeoutW@24 +MessageBoxW@16 +ModifyMenuA@20 +ModifyMenuW@20 +MonitorFromPoint@12 +MonitorFromRect@8 +MonitorFromWindow@8 +MoveWindow@24 +MsgWaitForMultipleObjects@20 +MsgWaitForMultipleObjectsEx@20 +NotifyOverlayWindow@8 +NotifyWinEvent@16 +OemKeyScan@4 +OemToCharA@8 +OemToCharBuffA@12 +OemToCharBuffW@12 +OemToCharW@8 +OffsetRect@12 +OpenClipboard@4 +OpenDesktopA@16 +OpenDesktopW@16 +OpenIcon@4 +OpenInputDesktop@12 +OpenThreadDesktop@16 +OpenWindowStationA@12 +OpenWindowStationW@12 +PackDDElParam@12 +PackTouchHitTestingProximityEvaluation@8 +PaintDesktop@4 +PaintMenuBar@24 +PaintMonitor@12 +PeekMessageA@20 +PeekMessageW@20 +PhysicalToLogicalPoint@8 +PhysicalToLogicalPointForPerMonitorDPI@8 +PostMessageA@16 +PostMessageW@16 +PostQuitMessage@4 +PostThreadMessageA@16 +PostThreadMessageW@16 +PrintWindow@12 +PrivateExtractIconExA@20 +PrivateExtractIconExW@20 +PrivateExtractIconsA@32 +PrivateExtractIconsW@32 +PrivateSetDbgTag@8 +PrivateSetRipFlags@8 +PrivateRegisterICSProc@4 +PtInRect@12 +QueryBSDRWindow@0 +QueryDisplayConfig@24 +QuerySendMessage@4 +QueryUserCounters@20 +RealChildWindowFromPoint@12 +RealGetWindowClass@12 +RealGetWindowClassA@12 +RealGetWindowClassW@12 +ReasonCodeNeedsBugID@4 +ReasonCodeNeedsComment@4 +RecordShutdownReason@4 +RedrawWindow@16 +RegisterBSDRWindow@8 +RegisterClassA@4 +RegisterClassExA@4 +RegisterClassExW@4 +RegisterClassW@4 +RegisterClipboardFormatA@4 +RegisterClipboardFormatW@4 +RegisterDeviceNotificationA@12 +RegisterDeviceNotificationW@12 +RegisterErrorReportingDialog@8 +RegisterFrostWindow@8 +RegisterGhostWindow@8 +RegisterHotKey@16 +RegisterPowerSettingNotification@12 +RegisterLogonProcess@8 +RegisterMessagePumpHook@4 +RegisterPointerDeviceNotifications@8 +RegisterPointerInputTarget@8 +RegisterPowerSettingNotification@12 +RegisterRawInputDevices@12 +RegisterServicesProcess@4 +RegisterSessionPort@4 ; Undocumented, rumored to be related to ALPC - http://blogs.msdn.com/b/ntdebugging/archive/2007/07/26/lpc-local-procedure-calls-part-1-architecture.aspx +RegisterShellHookWindow@4 +RegisterSuspendResumeNotification@8 +RegisterSystemThread@8 +RegisterTasklist@4 +RegisterTouchHitTestingWindow@8 +RegisterTouchWindow@8 +RegisterUserApiHook@4 ; Prototype changed in 2003 - https://www.reactos.org/wiki/Techwiki:RegisterUserApiHook +RegisterWindowMessageA@4 +RegisterWindowMessageW@4 +ReleaseCapture@0 +ReleaseDC@8 +RemoveClipboardFormatListener@4 +RemoveMenu@12 +RemovePropA@8 +RemovePropW@8 +ReplyMessage@4 +ResolveDesktopForWOW@4 +ReuseDDElParam@20 +ScreenToClient@8 +ScrollChildren@12 +ScrollDC@28 +ScrollWindow@20 +ScrollWindowEx@32 +SendDlgItemMessageA@20 +SendDlgItemMessageW@20 +SendIMEMessageExA@8 +SendIMEMessageExW@8 +SendInput@12 +SendMessageA@16 +SendMessageCallbackA@24 +SendMessageCallbackW@24 +SendMessageTimeoutA@28 +SendMessageTimeoutW@28 +SendMessageW@16 +SendNotifyMessageA@16 +SendNotifyMessageW@16 +SetActiveWindow@4 +SetCapture@4 +SetCaretBlinkTime@4 +SetCaretPos@8 +SetClassLongA@12 +SetClassLongW@12 +SetClassWord@12 +SetClipboardData@8 +SetClipboardViewer@4 +SetConsoleReserveKeys@8 +SetCoalescableTimer@20 +SetCursor@4 +SetCursorContents@8 +SetCursorPos@8 +SetDebugErrorLevel@4 +SetDeskWallpaper@4 +SetDisplayAutoRotationPreferences@4 +SetDisplayConfig@20 +SetDlgItemInt@16 +SetDlgItemTextA@12 +SetDlgItemTextW@12 +SetDoubleClickTime@4 +SetFocus@4 +SetForegroundWindow@4 +SetGestureConfig@20 +SetImmersiveBackgroundWindow@4 +SetInternalWindowPos@16 +SetKeyboardState@4 +SetLastErrorEx@8 +SetLayeredWindowAttributes@16 +SetLogonNotifyWindow@4 +SetMagnificationDesktopColorEffect@4 +SetMagnificationDesktopMagnification@16 +SetMagnificationLensCtxInformation@16 +SetMenu@8 +SetMenuContextHelpId@8 +SetMenuDefaultItem@12 +SetMenuInfo@8 +SetMenuItemBitmaps@20 +SetMenuItemInfoA@16 +SetMenuItemInfoW@16 +SetMessageExtraInfo@4 +SetMessageQueue@4 +SetMirrorRendering@8 +SetParent@8 +SetPhysicalCursorPos@8 +SetProcessDPIAware@0 +SetProcessDefaultLayout@4 +SetProcessDpiAwarenessInternal@4 +SetProcessRestrictionExemption@4 +SetProcessWindowStation@4 +SetProgmanWindow@4 +SetPropA@12 +SetPropW@12 +SetRect@20 +SetRectEmpty@4 +SetScrollInfo@16 +SetScrollPos@16 +SetScrollRange@20 +SetShellWindow@4 +SetShellWindowEx@8 +SetSysColors@12 +SetSysColorsTemp@12 +SetSystemCursor@8 +SetSystemMenu@8 +SetSystemTimer@16 +SetTaskmanWindow@4 +SetThreadDesktop@4 +SetThreadInputBlocked@8 +SetTimer@16 +SetUserObjectInformationA@16 +SetUserObjectInformationW@16 +SetUserObjectSecurity@12 +SetWinEventHook@28 +SetWindowBand@12 +SetWindowCompositionAttribute@8 +SetWindowCompositionTransition@28 +SetWindowContextHelpId@8 +SetWindowDisplayAffinity@8 +SetWindowFeedbackSetting@20 +SetWindowLongA@12 +SetWindowLongW@12 +SetWindowPlacement@8 +SetWindowPos@28 +SetWindowRgn@12 +SetWindowRgnEx@12 +SetWindowStationUser@16 +SetWindowTextA@8 +SetWindowTextW@8 +SetWindowWord@12 +SetWindowsHookA@8 +SetWindowsHookExA@16 +SetWindowsHookExW@16 +SetWindowsHookW@8 +SfmDxBindSwapChain@12 +SfmDxGetSwapChainStats@8 +SfmDxOpenSwapChain@16 +SfmDxQuerySwapChainBindingStatus@12 +SfmDxReleaseSwapChain@8 +SfmDxReportPendingBindingsToDwm@0 +SfmDxSetSwapChainBindingStatus@8 +SfmDxSetSwapChainStats@8 +ShowCaret@4 +ShowCursor@4 +ShowOwnedPopups@8 +ShowScrollBar@12 +ShowStartGlass@4 +ShowSystemCursor@4 +ShowWindow@8 +ShowWindowAsync@8 +ShutdownBlockReasonCreate@8 +ShutdownBlockReasonDestroy@4 +ShutdownBlockReasonQuery@12 +SignalRedirectionStartComplete@0 +SkipPointerFrameMessages@4 +SoftModalMessageBox@4 +SoundSentry@0 +SubtractRect@12 +SwapMouseButton@4 +SwitchDesktop@4 +SwitchDesktopWithFade@12 ; Same as SwithDesktop(), only with fade (done at log-in), only usable by winlogon - http://blog.airesoft.co.uk/2010/08/things-microsoft-can-do-that-you-cant/ +SwitchToThisWindow@8 +SystemParametersInfoA@16 +SystemParametersInfoW@16 +TabbedTextOutA@32 +TabbedTextOutW@32 +TileChildWindows@8 +TileWindows@20 +ToAscii@20 +ToAsciiEx@24 +ToUnicode@24 +ToUnicodeEx@28 +TrackMouseEvent@4 +TrackPopupMenu@28 +TrackPopupMenuEx@24 +TranslateAccelerator@12 +TranslateAcceleratorA@12 +TranslateAcceleratorW@12 +TranslateMDISysAccel@8 +TranslateMessage@4 +TranslateMessageEx@8 +UnhookWinEvent@4 +UnhookWindowsHook@8 +UnhookWindowsHookEx@4 +UnionRect@12 +UnloadKeyboardLayout@4 +UnlockWindowStation@4 +UnpackDDElParam@16 +UnregisterClassA@8 +UnregisterClassW@8 +UnregisterDeviceNotification@4 +UnregisterHotKey@8 +UnregisterMessagePumpHook@0 +UnregisterPointerInputTarget@8 +UnregisterPowerSettingNotification@4 +UnregisterSessionPort@0 +UnregisterSuspendResumeNotification@4 +UnregisterTouchWindow@4 +UnregisterUserApiHook@0 +UpdateDefaultDesktopThumbnail@20 +UpdateLayeredWindow@36 +UpdateLayeredWindowIndirect@8 +UpdatePerUserSystemParameters@4 ; Undocumented, seems to apply certain registry settings to desktop, etc. ReactOS has @8 version - http://doxygen.reactos.org/d0/d92/win32ss_2user_2user32_2misc_2misc_8c_a1ff565f0af6bac6dce604f9f4473fe79.html ; @4 is rumored to be without the first DWORD +UpdateWindow@4 +UpdateWindowInputSinkHints@8 +UpdateWindowTransform@12 +User32InitializeImmEntryTable@4 +UserClientDllInitialize@12 +UserHandleGrantAccess@12 +UserLpkPSMTextOut@24 +UserLpkTabbedTextOut@48 +UserRealizePalette@4 +UserRegisterWowHandlers@8 +VRipOutput@0 +VTagOutput@0 +ValidateRect@8 +ValidateRgn@8 +VkKeyScanA@4 +VkKeyScanExA@8 +VkKeyScanExW@8 +VkKeyScanW@4 +WCSToMBEx@24 +WINNLSEnableIME@8 +WINNLSGetEnableStatus@4 +WINNLSGetIMEHotkey@4 +WaitForInputIdle@8 +WaitForRedirectionStartComplete@0 +WaitMessage@0 +Win32PoolAllocationStats@24 +WinHelpA@16 +WinHelpW@16 +WindowFromDC@4 +WindowFromPhysicalPoint@8 +WindowFromPoint@8 +_UserTestTokenForInteractive@8 +gSharedInfo DATA +gapfnScSendMessage DATA +keybd_event@16 +mouse_event@20 +wsprintfA +wsprintfW +wvsprintfA@12 +wvsprintfW@12 +;ord_2500@16 @2500 +;ord_2501@12 @2501 +;ord_2502@8 @2502 +;ord_2503@24 @2503 +;ord_2504@8 @2504 +;ord_2505@8 @2505 +;ord_2506@12 @2506 +;ord_2507@4 @2507 +;ord_2508@8 @2508 +;ord_2509@4 @2509 +;ord_2510@12 @2510 +;ord_2511@8 @2511 +;ord_2512@12 @2512 +;ord_2513@4 @2513 +;ord_2514@8 @2514 +;ord_2515@8 @2515 +;ord_2516@12 @2516 +;ord_2517@4 @2517 +;ord_2518@0 @2518 +;ord_2519@4 @2519 +;ord_2520@0 @2520 +;ord_2521@8 @2521 +;ord_2522@4 @2522 +;ord_2523@8 @2523 +;ord_2524@8 @2524 +;ord_2525@12 @2525 +;ord_2526@12 @2526 +;ord_2527@12 @2527 +IsThreadMessageQueueAttached@4 +;ord_2529@4 @2529 +;ord_2530@8 @2530 +;ord_2531@16 @2531 +;ord_2532@8 @2532 +;ord_2533@4 @2533 +;ord_2534@8 @2534 +;ord_2535@0 @2535 +;ord_2536@8 @2536 +;ord_2537@16 @2537 +;ord_2538@4 @2538 +;ord_2539@4 @2539 +;ord_2540@4 @2540 +;ord_2541@0 @2541 +;ord_2544@4 @2544 +;ord_2545@8 @2545 +;ord_2546@4 @2546 +;ord_2547@4 @2547 +;ord_2548@4 @2548 +;ord_2549@4 @2549 +;ord_2550@8 @2550 +;ord_2551@20 @2551 +;ord_2552@8 @2552 +;ord_2553@32 @2553 +;ord_2554@12 @2554 +;ord_2555@16 @2555 +;ord_2556@8 @2556 +;ord_2557@12 @2557 +;ord_2558@12 @2558 +;ord_2559@16 @2559 +;ord_2560@20 @2560 +;ord_2561@0 @2561 +;ord_2562@0 @2562 +;ord_2563@0 @2563 diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig @@ -371,7 +371,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ pub fn initCapacity(allocator: *Allocator, num: usize) !Self { var self = Self{}; - const new_memory = try self.allocator.allocAdvanced(T, alignment, num, .at_least); + const new_memory = try allocator.allocAdvanced(T, alignment, num, .at_least); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; @@ -419,7 +419,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Replace range of elements `list[start..start+len]` with `new_items` /// grows list if `len < new_items.len`. may allocate /// shrinks list if `len > new_items.len` - pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void { + pub fn replaceRange(self: *Self, allocator: *Allocator, start: usize, len: usize, new_items: SliceConst) !void { var managed = self.toManaged(allocator); try managed.replaceRange(start, len, new_items); self.* = managed.toUnmanaged(); @@ -617,201 +617,414 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ }; } -test "std.ArrayList.init" { - var list = ArrayList(i32).init(testing.allocator); - defer list.deinit(); +test "std.ArrayList/ArrayListUnmanaged.init" { + { + var list = ArrayList(i32).init(testing.allocator); + defer list.deinit(); - testing.expect(list.items.len == 0); - testing.expect(list.capacity == 0); -} + testing.expect(list.items.len == 0); + testing.expect(list.capacity == 0); + } -test "std.ArrayList.initCapacity" { - var list = try ArrayList(i8).initCapacity(testing.allocator, 200); - defer list.deinit(); - testing.expect(list.items.len == 0); - testing.expect(list.capacity >= 200); -} + { + var list = ArrayListUnmanaged(i32){}; -test "std.ArrayList.basic" { - var list = ArrayList(i32).init(testing.allocator); - defer list.deinit(); + testing.expect(list.items.len == 0); + testing.expect(list.capacity == 0); + } +} +test "std.ArrayList/ArrayListUnmanaged.initCapacity" { + const a = testing.allocator; { - var i: usize = 0; - while (i < 10) : (i += 1) { - list.append(@intCast(i32, i + 1)) catch unreachable; - } + var list = try ArrayList(i8).initCapacity(a, 200); + defer list.deinit(); + testing.expect(list.items.len == 0); + testing.expect(list.capacity >= 200); + } + { + var list = try ArrayListUnmanaged(i8).initCapacity(a, 200); + defer list.deinit(a); + testing.expect(list.items.len == 0); + testing.expect(list.capacity >= 200); } +} +test "std.ArrayList/ArrayListUnmanaged.basic" { + const a = testing.allocator; { - var i: usize = 0; - while (i < 10) : (i += 1) { - testing.expect(list.items[i] == @intCast(i32, i + 1)); + var list = ArrayList(i32).init(a); + defer list.deinit(); + + { + var i: usize = 0; + while (i < 10) : (i += 1) { + list.append(@intCast(i32, i + 1)) catch unreachable; + } + } + + { + var i: usize = 0; + while (i < 10) : (i += 1) { + testing.expect(list.items[i] == @intCast(i32, i + 1)); + } + } + + for (list.items) |v, i| { + testing.expect(v == @intCast(i32, i + 1)); } - } - for (list.items) |v, i| { - testing.expect(v == @intCast(i32, i + 1)); + testing.expect(list.pop() == 10); + testing.expect(list.items.len == 9); + + list.appendSlice(&[_]i32{ 1, 2, 3 }) catch unreachable; + testing.expect(list.items.len == 12); + testing.expect(list.pop() == 3); + testing.expect(list.pop() == 2); + testing.expect(list.pop() == 1); + testing.expect(list.items.len == 9); + + list.appendSlice(&[_]i32{}) catch unreachable; + testing.expect(list.items.len == 9); + + // can only set on indices < self.items.len + list.items[7] = 33; + list.items[8] = 42; + + testing.expect(list.pop() == 42); + testing.expect(list.pop() == 33); } + { + var list = ArrayListUnmanaged(i32){}; + defer list.deinit(a); + + { + var i: usize = 0; + while (i < 10) : (i += 1) { + list.append(a, @intCast(i32, i + 1)) catch unreachable; + } + } + + { + var i: usize = 0; + while (i < 10) : (i += 1) { + testing.expect(list.items[i] == @intCast(i32, i + 1)); + } + } + + for (list.items) |v, i| { + testing.expect(v == @intCast(i32, i + 1)); + } - testing.expect(list.pop() == 10); - testing.expect(list.items.len == 9); + testing.expect(list.pop() == 10); + testing.expect(list.items.len == 9); - list.appendSlice(&[_]i32{ 1, 2, 3 }) catch unreachable; - testing.expect(list.items.len == 12); - testing.expect(list.pop() == 3); - testing.expect(list.pop() == 2); - testing.expect(list.pop() == 1); - testing.expect(list.items.len == 9); + list.appendSlice(a, &[_]i32{ 1, 2, 3 }) catch unreachable; + testing.expect(list.items.len == 12); + testing.expect(list.pop() == 3); + testing.expect(list.pop() == 2); + testing.expect(list.pop() == 1); + testing.expect(list.items.len == 9); - list.appendSlice(&[_]i32{}) catch unreachable; - testing.expect(list.items.len == 9); + list.appendSlice(a, &[_]i32{}) catch unreachable; + testing.expect(list.items.len == 9); - // can only set on indices < self.items.len - list.items[7] = 33; - list.items[8] = 42; + // can only set on indices < self.items.len + list.items[7] = 33; + list.items[8] = 42; - testing.expect(list.pop() == 42); - testing.expect(list.pop() == 33); + testing.expect(list.pop() == 42); + testing.expect(list.pop() == 33); + } } -test "std.ArrayList.appendNTimes" { - var list = ArrayList(i32).init(testing.allocator); - defer list.deinit(); +test "std.ArrayList/ArrayListUnmanaged.appendNTimes" { + const a = testing.allocator; + { + var list = ArrayList(i32).init(a); + defer list.deinit(); + + try list.appendNTimes(2, 10); + testing.expectEqual(@as(usize, 10), list.items.len); + for (list.items) |element| { + testing.expectEqual(@as(i32, 2), element); + } + } + { + var list = ArrayListUnmanaged(i32){}; + defer list.deinit(a); - try list.appendNTimes(2, 10); - testing.expectEqual(@as(usize, 10), list.items.len); - for (list.items) |element| { - testing.expectEqual(@as(i32, 2), element); + try list.appendNTimes(a, 2, 10); + testing.expectEqual(@as(usize, 10), list.items.len); + for (list.items) |element| { + testing.expectEqual(@as(i32, 2), element); + } } } -test "std.ArrayList.appendNTimes with failing allocator" { - var list = ArrayList(i32).init(testing.failing_allocator); - defer list.deinit(); - testing.expectError(error.OutOfMemory, list.appendNTimes(2, 10)); +test "std.ArrayList/ArrayListUnmanaged.appendNTimes with failing allocator" { + const a = testing.failing_allocator; + { + var list = ArrayList(i32).init(a); + defer list.deinit(); + testing.expectError(error.OutOfMemory, list.appendNTimes(2, 10)); + } + { + var list = ArrayListUnmanaged(i32){}; + defer list.deinit(a); + testing.expectError(error.OutOfMemory, list.appendNTimes(a, 2, 10)); + } } -test "std.ArrayList.orderedRemove" { - var list = ArrayList(i32).init(testing.allocator); - defer list.deinit(); +test "std.ArrayList/ArrayListUnmanaged.orderedRemove" { + const a = testing.allocator; + { + var list = ArrayList(i32).init(a); + defer list.deinit(); + + try list.append(1); + try list.append(2); + try list.append(3); + try list.append(4); + try list.append(5); + try list.append(6); + try list.append(7); + + //remove from middle + testing.expectEqual(@as(i32, 4), list.orderedRemove(3)); + testing.expectEqual(@as(i32, 5), list.items[3]); + testing.expectEqual(@as(usize, 6), list.items.len); + + //remove from end + testing.expectEqual(@as(i32, 7), list.orderedRemove(5)); + testing.expectEqual(@as(usize, 5), list.items.len); + + //remove from front + testing.expectEqual(@as(i32, 1), list.orderedRemove(0)); + testing.expectEqual(@as(i32, 2), list.items[0]); + testing.expectEqual(@as(usize, 4), list.items.len); + } + { + var list = ArrayListUnmanaged(i32){}; + defer list.deinit(a); - try list.append(1); - try list.append(2); - try list.append(3); - try list.append(4); - try list.append(5); - try list.append(6); - try list.append(7); - - //remove from middle - testing.expectEqual(@as(i32, 4), list.orderedRemove(3)); - testing.expectEqual(@as(i32, 5), list.items[3]); - testing.expectEqual(@as(usize, 6), list.items.len); - - //remove from end - testing.expectEqual(@as(i32, 7), list.orderedRemove(5)); - testing.expectEqual(@as(usize, 5), list.items.len); - - //remove from front - testing.expectEqual(@as(i32, 1), list.orderedRemove(0)); - testing.expectEqual(@as(i32, 2), list.items[0]); - testing.expectEqual(@as(usize, 4), list.items.len); + try list.append(a, 1); + try list.append(a, 2); + try list.append(a, 3); + try list.append(a, 4); + try list.append(a, 5); + try list.append(a, 6); + try list.append(a, 7); + + //remove from middle + testing.expectEqual(@as(i32, 4), list.orderedRemove(3)); + testing.expectEqual(@as(i32, 5), list.items[3]); + testing.expectEqual(@as(usize, 6), list.items.len); + + //remove from end + testing.expectEqual(@as(i32, 7), list.orderedRemove(5)); + testing.expectEqual(@as(usize, 5), list.items.len); + + //remove from front + testing.expectEqual(@as(i32, 1), list.orderedRemove(0)); + testing.expectEqual(@as(i32, 2), list.items[0]); + testing.expectEqual(@as(usize, 4), list.items.len); + } } -test "std.ArrayList.swapRemove" { - var list = ArrayList(i32).init(testing.allocator); - defer list.deinit(); +test "std.ArrayList/ArrayListUnmanaged.swapRemove" { + const a = testing.allocator; + { + var list = ArrayList(i32).init(a); + defer list.deinit(); - try list.append(1); - try list.append(2); - try list.append(3); - try list.append(4); - try list.append(5); - try list.append(6); - try list.append(7); - - //remove from middle - testing.expect(list.swapRemove(3) == 4); - testing.expect(list.items[3] == 7); - testing.expect(list.items.len == 6); - - //remove from end - testing.expect(list.swapRemove(5) == 6); - testing.expect(list.items.len == 5); - - //remove from front - testing.expect(list.swapRemove(0) == 1); - testing.expect(list.items[0] == 5); - testing.expect(list.items.len == 4); + try list.append(1); + try list.append(2); + try list.append(3); + try list.append(4); + try list.append(5); + try list.append(6); + try list.append(7); + + //remove from middle + testing.expect(list.swapRemove(3) == 4); + testing.expect(list.items[3] == 7); + testing.expect(list.items.len == 6); + + //remove from end + testing.expect(list.swapRemove(5) == 6); + testing.expect(list.items.len == 5); + + //remove from front + testing.expect(list.swapRemove(0) == 1); + testing.expect(list.items[0] == 5); + testing.expect(list.items.len == 4); + } + { + var list = ArrayListUnmanaged(i32){}; + defer list.deinit(a); + + try list.append(a, 1); + try list.append(a, 2); + try list.append(a, 3); + try list.append(a, 4); + try list.append(a, 5); + try list.append(a, 6); + try list.append(a, 7); + + //remove from middle + testing.expect(list.swapRemove(3) == 4); + testing.expect(list.items[3] == 7); + testing.expect(list.items.len == 6); + + //remove from end + testing.expect(list.swapRemove(5) == 6); + testing.expect(list.items.len == 5); + + //remove from front + testing.expect(list.swapRemove(0) == 1); + testing.expect(list.items[0] == 5); + testing.expect(list.items.len == 4); + } } -test "std.ArrayList.insert" { - var list = ArrayList(i32).init(testing.allocator); - defer list.deinit(); +test "std.ArrayList/ArrayListUnmanaged.insert" { + const a = testing.allocator; + { + var list = ArrayList(i32).init(a); + defer list.deinit(); - try list.append(1); - try list.append(2); - try list.append(3); - try list.insert(0, 5); - testing.expect(list.items[0] == 5); - testing.expect(list.items[1] == 1); - testing.expect(list.items[2] == 2); - testing.expect(list.items[3] == 3); + try list.append(1); + try list.append(2); + try list.append(3); + try list.insert(0, 5); + testing.expect(list.items[0] == 5); + testing.expect(list.items[1] == 1); + testing.expect(list.items[2] == 2); + testing.expect(list.items[3] == 3); + } + { + var list = ArrayListUnmanaged(i32){}; + defer list.deinit(a); + + try list.append(a, 1); + try list.append(a, 2); + try list.append(a, 3); + try list.insert(a, 0, 5); + testing.expect(list.items[0] == 5); + testing.expect(list.items[1] == 1); + testing.expect(list.items[2] == 2); + testing.expect(list.items[3] == 3); + } } -test "std.ArrayList.insertSlice" { - var list = ArrayList(i32).init(testing.allocator); - defer list.deinit(); +test "std.ArrayList/ArrayListUnmanaged.insertSlice" { + const a = testing.allocator; + { + var list = ArrayList(i32).init(a); + defer list.deinit(); - try list.append(1); - try list.append(2); - try list.append(3); - try list.append(4); - try list.insertSlice(1, &[_]i32{ 9, 8 }); - testing.expect(list.items[0] == 1); - testing.expect(list.items[1] == 9); - testing.expect(list.items[2] == 8); - testing.expect(list.items[3] == 2); - testing.expect(list.items[4] == 3); - testing.expect(list.items[5] == 4); - - const items = [_]i32{1}; - try list.insertSlice(0, items[0..0]); - testing.expect(list.items.len == 6); - testing.expect(list.items[0] == 1); + try list.append(1); + try list.append(2); + try list.append(3); + try list.append(4); + try list.insertSlice(1, &[_]i32{ 9, 8 }); + testing.expect(list.items[0] == 1); + testing.expect(list.items[1] == 9); + testing.expect(list.items[2] == 8); + testing.expect(list.items[3] == 2); + testing.expect(list.items[4] == 3); + testing.expect(list.items[5] == 4); + + const items = [_]i32{1}; + try list.insertSlice(0, items[0..0]); + testing.expect(list.items.len == 6); + testing.expect(list.items[0] == 1); + } + { + var list = ArrayListUnmanaged(i32){}; + defer list.deinit(a); + + try list.append(a, 1); + try list.append(a, 2); + try list.append(a, 3); + try list.append(a, 4); + try list.insertSlice(a, 1, &[_]i32{ 9, 8 }); + testing.expect(list.items[0] == 1); + testing.expect(list.items[1] == 9); + testing.expect(list.items[2] == 8); + testing.expect(list.items[3] == 2); + testing.expect(list.items[4] == 3); + testing.expect(list.items[5] == 4); + + const items = [_]i32{1}; + try list.insertSlice(a, 0, items[0..0]); + testing.expect(list.items.len == 6); + testing.expect(list.items[0] == 1); + } } -test "std.ArrayList.replaceRange" { +test "std.ArrayList/ArrayListUnmanaged.replaceRange" { var arena = std.heap.ArenaAllocator.init(testing.allocator); defer arena.deinit(); + const a = &arena.allocator; - const alloc = &arena.allocator; const init = [_]i32{ 1, 2, 3, 4, 5 }; const new = [_]i32{ 0, 0, 0 }; - var list_zero = ArrayList(i32).init(alloc); - var list_eq = ArrayList(i32).init(alloc); - var list_lt = ArrayList(i32).init(alloc); - var list_gt = ArrayList(i32).init(alloc); + const result_zero = [_]i32{ 1, 0, 0, 0, 2, 3, 4, 5 }; + const result_eq = [_]i32{ 1, 0, 0, 0, 5 }; + const result_le = [_]i32{ 1, 0, 0, 0, 4, 5 }; + const result_gt = [_]i32{ 1, 0, 0, 0 }; - try list_zero.appendSlice(&init); - try list_eq.appendSlice(&init); - try list_lt.appendSlice(&init); - try list_gt.appendSlice(&init); - - try list_zero.replaceRange(1, 0, &new); - try list_eq.replaceRange(1, 3, &new); - try list_lt.replaceRange(1, 2, &new); - - // after_range > new_items.len in function body - testing.expect(1 + 4 > new.len); - try list_gt.replaceRange(1, 4, &new); - - testing.expectEqualSlices(i32, list_zero.items, &[_]i32{ 1, 0, 0, 0, 2, 3, 4, 5 }); - testing.expectEqualSlices(i32, list_eq.items, &[_]i32{ 1, 0, 0, 0, 5 }); - testing.expectEqualSlices(i32, list_lt.items, &[_]i32{ 1, 0, 0, 0, 4, 5 }); - testing.expectEqualSlices(i32, list_gt.items, &[_]i32{ 1, 0, 0, 0 }); + { + var list_zero = ArrayList(i32).init(a); + var list_eq = ArrayList(i32).init(a); + var list_lt = ArrayList(i32).init(a); + var list_gt = ArrayList(i32).init(a); + + try list_zero.appendSlice(&init); + try list_eq.appendSlice(&init); + try list_lt.appendSlice(&init); + try list_gt.appendSlice(&init); + + try list_zero.replaceRange(1, 0, &new); + try list_eq.replaceRange(1, 3, &new); + try list_lt.replaceRange(1, 2, &new); + + // after_range > new_items.len in function body + testing.expect(1 + 4 > new.len); + try list_gt.replaceRange(1, 4, &new); + + testing.expectEqualSlices(i32, list_zero.items, &result_zero); + testing.expectEqualSlices(i32, list_eq.items, &result_eq); + testing.expectEqualSlices(i32, list_lt.items, &result_le); + testing.expectEqualSlices(i32, list_gt.items, &result_gt); + } + { + var list_zero = ArrayListUnmanaged(i32){}; + var list_eq = ArrayListUnmanaged(i32){}; + var list_lt = ArrayListUnmanaged(i32){}; + var list_gt = ArrayListUnmanaged(i32){}; + + try list_zero.appendSlice(a, &init); + try list_eq.appendSlice(a, &init); + try list_lt.appendSlice(a, &init); + try list_gt.appendSlice(a, &init); + + try list_zero.replaceRange(a, 1, 0, &new); + try list_eq.replaceRange(a, 1, 3, &new); + try list_lt.replaceRange(a, 1, 2, &new); + + // after_range > new_items.len in function body + testing.expect(1 + 4 > new.len); + try list_gt.replaceRange(a, 1, 4, &new); + + testing.expectEqualSlices(i32, list_zero.items, &result_zero); + testing.expectEqualSlices(i32, list_eq.items, &result_eq); + testing.expectEqualSlices(i32, list_lt.items, &result_le); + testing.expectEqualSlices(i32, list_gt.items, &result_gt); + } } const Item = struct { @@ -819,11 +1032,25 @@ const Item = struct { sub_items: ArrayList(Item), }; -test "std.ArrayList: ArrayList(T) of struct T" { - var root = Item{ .integer = 1, .sub_items = ArrayList(Item).init(testing.allocator) }; - defer root.sub_items.deinit(); - try root.sub_items.append(Item{ .integer = 42, .sub_items = ArrayList(Item).init(testing.allocator) }); - testing.expect(root.sub_items.items[0].integer == 42); +const ItemUnmanaged = struct { + integer: i32, + sub_items: ArrayListUnmanaged(ItemUnmanaged), +}; + +test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" { + const a = std.testing.allocator; + { + var root = Item{ .integer = 1, .sub_items = ArrayList(Item).init(a) }; + defer root.sub_items.deinit(); + try root.sub_items.append(Item{ .integer = 42, .sub_items = ArrayList(Item).init(a) }); + testing.expect(root.sub_items.items[0].integer == 42); + } + { + var root = ItemUnmanaged{ .integer = 1, .sub_items = ArrayListUnmanaged(ItemUnmanaged){} }; + defer root.sub_items.deinit(a); + try root.sub_items.append(a, ItemUnmanaged{ .integer = 42, .sub_items = ArrayListUnmanaged(ItemUnmanaged){} }); + testing.expect(root.sub_items.items[0].integer == 42); + } } test "std.ArrayList(u8) implements outStream" { @@ -837,19 +1064,32 @@ test "std.ArrayList(u8) implements outStream" { testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.span()); } -test "std.ArrayList.shrink still sets length on error.OutOfMemory" { +test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMemory" { // use an arena allocator to make sure realloc returns error.OutOfMemory var arena = std.heap.ArenaAllocator.init(testing.allocator); defer arena.deinit(); + const a = &arena.allocator; - var list = ArrayList(i32).init(&arena.allocator); + { + var list = ArrayList(i32).init(a); - try list.append(1); - try list.append(2); - try list.append(3); + try list.append(1); + try list.append(2); + try list.append(3); - list.shrink(1); - testing.expect(list.items.len == 1); + list.shrink(1); + testing.expect(list.items.len == 1); + } + { + var list = ArrayListUnmanaged(i32){}; + + try list.append(a, 1); + try list.append(a, 2); + try list.append(a, 3); + + list.shrink(a, 1); + testing.expect(list.items.len == 1); + } } test "std.ArrayList.writer" { @@ -864,7 +1104,7 @@ test "std.ArrayList.writer" { testing.expectEqualSlices(u8, list.items, "abcdefg"); } -test "addManyAsArray" { +test "std.ArrayList/ArrayListUnmanaged.addManyAsArray" { const a = std.testing.allocator; { var list = ArrayList(u8).init(a); diff --git a/lib/std/build.zig b/lib/std/build.zig @@ -1232,6 +1232,9 @@ pub const LibExeObjStep = struct { installed_path: ?[]const u8, install_step: ?*InstallArtifactStep, + /// Base address for an executable image. + image_base: ?u64 = null, + libc_file: ?[]const u8 = null, valgrind_support: ?bool = null, @@ -1239,6 +1242,7 @@ pub const LibExeObjStep = struct { /// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF /// file. link_eh_frame_hdr: bool = false, + link_emit_relocs: bool = false, /// Place every function in its own section so that unused ones may be /// safely garbage-collected during the linking phase. @@ -1384,66 +1388,50 @@ pub const LibExeObjStep = struct { } fn computeOutFileNames(self: *LibExeObjStep) void { - // TODO make this call std.zig.binNameAlloc - switch (self.kind) { - .Obj => { - self.out_filename = self.builder.fmt("{}{}", .{ self.name, self.target.oFileExt() }); - }, - .Exe => { - self.out_filename = self.builder.fmt("{}{}", .{ self.name, self.target.exeFileExt() }); - }, - .Test => { - self.out_filename = self.builder.fmt("test{}", .{self.target.exeFileExt()}); + const target_info = std.zig.system.NativeTargetInfo.detect( + self.builder.allocator, + self.target, + ) catch unreachable; + const target = target_info.target; + self.out_filename = std.zig.binNameAlloc(self.builder.allocator, .{ + .root_name = self.name, + .target = target, + .output_mode = switch (self.kind) { + .Lib => .Lib, + .Obj => .Obj, + .Exe, .Test => .Exe, }, - .Lib => { - if (!self.is_dynamic) { - self.out_filename = self.builder.fmt("{}{}{}", .{ - self.target.libPrefix(), + .link_mode = if (self.is_dynamic) .Dynamic else .Static, + .version = self.version, + }) catch unreachable; + + if (self.kind == .Lib) { + if (!self.is_dynamic) { + self.out_lib_filename = self.out_filename; + } else if (self.version) |version| { + if (target.isDarwin()) { + self.major_only_filename = self.builder.fmt("lib{}.{d}.dylib", .{ self.name, - self.target.staticLibSuffix(), + version.major, }); + self.name_only_filename = self.builder.fmt("lib{}.dylib", .{self.name}); self.out_lib_filename = self.out_filename; - } else if (self.version) |version| { - if (self.target.isDarwin()) { - self.out_filename = self.builder.fmt("lib{}.{d}.{d}.{d}.dylib", .{ - self.name, - version.major, - version.minor, - version.patch, - }); - self.major_only_filename = self.builder.fmt("lib{}.{d}.dylib", .{ - self.name, - version.major, - }); - self.name_only_filename = self.builder.fmt("lib{}.dylib", .{self.name}); - self.out_lib_filename = self.out_filename; - } else if (self.target.isWindows()) { - self.out_filename = self.builder.fmt("{}.dll", .{self.name}); - self.out_lib_filename = self.builder.fmt("{}.lib", .{self.name}); - } else { - self.out_filename = self.builder.fmt("lib{}.so.{d}.{d}.{d}", .{ - self.name, - version.major, - version.minor, - version.patch, - }); - self.major_only_filename = self.builder.fmt("lib{}.so.{d}", .{ self.name, version.major }); - self.name_only_filename = self.builder.fmt("lib{}.so", .{self.name}); - self.out_lib_filename = self.out_filename; - } + } else if (target.os.tag == .windows) { + self.out_lib_filename = self.builder.fmt("{}.lib", .{self.name}); } else { - if (self.target.isDarwin()) { - self.out_filename = self.builder.fmt("lib{}.dylib", .{self.name}); - self.out_lib_filename = self.out_filename; - } else if (self.target.isWindows()) { - self.out_filename = self.builder.fmt("{}.dll", .{self.name}); - self.out_lib_filename = self.builder.fmt("{}.lib", .{self.name}); - } else { - self.out_filename = self.builder.fmt("lib{}.so", .{self.name}); - self.out_lib_filename = self.out_filename; - } + self.major_only_filename = self.builder.fmt("lib{}.so.{d}", .{ self.name, version.major }); + self.name_only_filename = self.builder.fmt("lib{}.so", .{self.name}); + self.out_lib_filename = self.out_filename; } - }, + } else { + if (target.isDarwin()) { + self.out_lib_filename = self.out_filename; + } else if (target.os.tag == .windows) { + self.out_lib_filename = self.builder.fmt("{}.lib", .{self.name}); + } else { + self.out_lib_filename = self.out_filename; + } + } } } @@ -2040,6 +2028,11 @@ pub const LibExeObjStep = struct { try zig_args.append("--pkg-end"); } + if (self.image_base) |image_base| { + try zig_args.append("--image-base"); + try zig_args.append(builder.fmt("0x{x}", .{image_base})); + } + if (self.filter) |filter| { try zig_args.append("--test-filter"); try zig_args.append(filter); @@ -2075,6 +2068,9 @@ pub const LibExeObjStep = struct { if (self.link_eh_frame_hdr) { try zig_args.append("--eh-frame-hdr"); } + if (self.link_emit_relocs) { + try zig_args.append("--emit-relocs"); + } if (self.link_function_sections) { try zig_args.append("-ffunction-sections"); } @@ -2168,8 +2164,8 @@ pub const LibExeObjStep = struct { } if (self.linker_script) |linker_script| { - zig_args.append("--linker-script") catch unreachable; - zig_args.append(builder.pathFromRoot(linker_script)) catch unreachable; + try zig_args.append("--script"); + try zig_args.append(builder.pathFromRoot(linker_script)); } if (self.version_script) |version_script| { @@ -2335,6 +2331,14 @@ pub const LibExeObjStep = struct { var it = src_dir.iterate(); while (try it.next()) |entry| { + // The compiler can put these files into the same directory, but we don't + // want to copy them over. + if (mem.eql(u8, entry.name, "stage1.id") or + mem.eql(u8, entry.name, "llvm-ar.id") or + mem.eql(u8, entry.name, "libs.txt") or + mem.eql(u8, entry.name, "builtin.zig") or + mem.eql(u8, entry.name, "lld.id")) continue; + _ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{}); } } else { diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig @@ -100,6 +100,16 @@ pub const AtomicOrder = enum { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. +pub const ReduceOp = enum { + And, + Or, + Xor, + Min, + Max, +}; + +/// This data structure is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. pub const AtomicRmwOp = enum { Xchg, Add, @@ -262,6 +272,7 @@ pub const TypeInfo = union(enum) { field_type: type, default_value: anytype, is_comptime: bool, + alignment: comptime_int, }; /// This data structure is used by the Zig language code generation and @@ -318,6 +329,7 @@ pub const TypeInfo = union(enum) { pub const UnionField = struct { name: []const u8, field_type: type, + alignment: comptime_int, }; /// This data structure is used by the Zig language code generation and @@ -341,6 +353,7 @@ pub const TypeInfo = union(enum) { /// therefore must be kept in sync with the compiler implementation. pub const Fn = struct { calling_convention: CallingConvention, + alignment: comptime_int, is_generic: bool, is_var_args: bool, return_type: ?type, diff --git a/lib/std/c.zig b/lib/std/c.zig @@ -342,3 +342,6 @@ pub extern "c" fn fsync(fd: c_int) c_int; pub extern "c" fn fdatasync(fd: c_int) c_int; pub extern "c" fn prctl(option: c_int, ...) c_int; + +pub extern "c" fn getrlimit(resource: rlimit_resource, rlim: *rlimit) c_int; +pub extern "c" fn setrlimit(resource: rlimit_resource, rlim: *const rlimit) c_int; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig @@ -12,7 +12,7 @@ usingnamespace @import("../os/bits.zig"); extern "c" fn __error() *c_int; pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; -pub extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int; +pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int; pub extern "c" fn _dyld_image_count() u32; pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header; pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize; diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig @@ -100,6 +100,8 @@ pub extern "c" fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_ pub extern "c" fn signalfd(fd: fd_t, mask: *const sigset_t, flags: c_uint) c_int; +pub extern "c" fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: *const rlimit, old_limit: *rlimit) c_int; + pub const pthread_attr_t = extern struct { __size: [56]u8, __align: c_long, diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig @@ -105,8 +105,8 @@ pub const ChildProcess = struct { .term = null, .env_map = null, .cwd = null, - .uid = if (builtin.os.tag == .windows) {} else null, - .gid = if (builtin.os.tag == .windows) {} else null, + .uid = if (builtin.os.tag == .windows or builtin.os.tag == .wasi) {} else null, + .gid = if (builtin.os.tag == .windows or builtin.os.tag == .wasi) {} else null, .stdin = null, .stdout = null, .stderr = null, diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig @@ -4,6 +4,50 @@ // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. +/// Authenticated Encryption with Associated Data +pub const aead = struct { + const chacha20 = @import("crypto/chacha20.zig"); + + pub const Gimli = @import("crypto/gimli.zig").Aead; + pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305; + pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305; + pub const AEGIS128L = @import("crypto/aegis.zig").AEGIS128L; + pub const AEGIS256 = @import("crypto/aegis.zig").AEGIS256; + pub const AES128GCM = @import("crypto/aes_gcm.zig").AES128GCM; + pub const AES256GCM = @import("crypto/aes_gcm.zig").AES256GCM; +}; + +/// Authentication (MAC) functions. +pub const auth = struct { + pub const hmac = @import("crypto/hmac.zig"); + pub const siphash = @import("crypto/siphash.zig"); +}; + +/// Core functions, that should rarely be used directly by applications. +pub const core = struct { + pub const aes = @import("crypto/aes.zig"); + pub const Gimli = @import("crypto/gimli.zig").State; + + /// Modes are generic compositions to construct encryption/decryption functions from block ciphers and permutations. + /// + /// These modes are designed to be building blocks for higher-level constructions, and should generally not be used directly by applications, as they may not provide the expected properties and security guarantees. + /// + /// Most applications may want to use AEADs instead. + pub const modes = @import("crypto/modes.zig"); +}; + +/// Diffie-Hellman key exchange functions. +pub const dh = struct { + pub const X25519 = @import("crypto/25519/x25519.zig").X25519; +}; + +/// Elliptic-curve arithmetic. +pub const ecc = struct { + pub const Curve25519 = @import("crypto/25519/curve25519.zig").Curve25519; + pub const Edwards25519 = @import("crypto/25519/edwards25519.zig").Edwards25519; + pub const Ristretto255 = @import("crypto/25519/ristretto255.zig").Ristretto255; +}; + /// Hash functions. pub const hash = struct { pub const Md5 = @import("crypto/md5.zig").Md5; @@ -15,26 +59,15 @@ pub const hash = struct { pub const Gimli = @import("crypto/gimli.zig").Hash; }; -/// Authentication (MAC) functions. -pub const auth = struct { - pub const hmac = @import("crypto/hmac.zig"); - pub const siphash = @import("crypto/siphash.zig"); -}; - -/// Authenticated Encryption with Associated Data -pub const aead = struct { - const chacha20 = @import("crypto/chacha20.zig"); - - pub const Gimli = @import("crypto/gimli.zig").Aead; - pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305; - pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305; - pub const AEGIS128L = @import("crypto/aegis.zig").AEGIS128L; - pub const AEGIS256 = @import("crypto/aegis.zig").AEGIS256; +/// Key derivation functions. +pub const kdf = struct { + pub const hkdf = @import("crypto/hkdf.zig"); }; /// MAC functions requiring single-use secret keys. pub const onetimeauth = struct { pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; + pub const Ghash = @import("crypto/ghash.zig").Ghash; }; /// A password hashing function derives a uniform key from low-entropy input material such as passwords. @@ -57,31 +90,6 @@ pub const pwhash = struct { pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2; }; -/// Core functions, that should rarely be used directly by applications. -pub const core = struct { - pub const aes = @import("crypto/aes.zig"); - pub const Gimli = @import("crypto/gimli.zig").State; - - /// Modes are generic compositions to construct encryption/decryption functions from block ciphers and permutations. - /// - /// These modes are designed to be building blocks for higher-level constructions, and should generally not be used directly by applications, as they may not provide the expected properties and security guarantees. - /// - /// Most applications may want to use AEADs instead. - pub const modes = @import("crypto/modes.zig"); -}; - -/// Elliptic-curve arithmetic. -pub const ecc = struct { - pub const Curve25519 = @import("crypto/25519/curve25519.zig").Curve25519; - pub const Edwards25519 = @import("crypto/25519/edwards25519.zig").Edwards25519; - pub const Ristretto255 = @import("crypto/25519/ristretto255.zig").Ristretto255; -}; - -/// Diffie-Hellman key exchange functions. -pub const dh = struct { - pub const X25519 = @import("crypto/25519/x25519.zig").X25519; -}; - /// Digital signature functions. pub const sign = struct { pub const Ed25519 = @import("crypto/25519/ed25519.zig").Ed25519; diff --git a/lib/std/crypto/25519/field.zig b/lib/std/crypto/25519/field.zig @@ -307,12 +307,14 @@ pub const Fe = struct { } pub fn pow2523(a: Fe) Fe { - var c = a; - var i: usize = 0; - while (i < 249) : (i += 1) { - c = c.sq().mul(a); - } - return c.sq().sq().mul(a); + var t0 = a.mul(a.sq()); + var t1 = t0.mul(t0.sqn(2)).sq().mul(a); + t0 = t1.sqn(5).mul(t1); + var t2 = t0.sqn(5).mul(t1); + t1 = t2.sqn(15).mul(t2); + t2 = t1.sqn(30).mul(t1); + t1 = t2.sqn(60).mul(t2); + return t1.sqn(120).mul(t1).sqn(10).mul(t0).sqn(2).mul(a); } pub fn abs(a: Fe) Fe { diff --git a/lib/std/crypto/aes_gcm.zig b/lib/std/crypto/aes_gcm.zig @@ -0,0 +1,161 @@ +const std = @import("std"); +const assert = std.debug.assert; +const builtin = std.builtin; +const crypto = std.crypto; +const debug = std.debug; +const Ghash = std.crypto.onetimeauth.Ghash; +const mem = std.mem; +const modes = crypto.core.modes; + +pub const AES128GCM = AESGCM(crypto.core.aes.AES128); +pub const AES256GCM = AESGCM(crypto.core.aes.AES256); + +fn AESGCM(comptime AES: anytype) type { + debug.assert(AES.block.block_size == 16); + + return struct { + pub const tag_length = 16; + pub const nonce_length = 12; + pub const key_length = AES.key_bits / 8; + + const zeros = [_]u8{0} ** 16; + + pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { + debug.assert(c.len == m.len); + debug.assert(m.len <= 16 * ((1 << 32) - 2)); + + const aes = AES.initEnc(key); + var h: [16]u8 = undefined; + aes.encrypt(&h, &zeros); + + var t: [16]u8 = undefined; + var j: [16]u8 = undefined; + mem.copy(u8, j[0..nonce_length], npub[0..]); + mem.writeIntBig(u32, j[nonce_length..][0..4], 1); + aes.encrypt(&t, &j); + + var mac = Ghash.init(&h); + mac.update(ad); + mac.pad(); + + mem.writeIntBig(u32, j[nonce_length..][0..4], 2); + modes.ctr(@TypeOf(aes), aes, c, m, j, builtin.Endian.Big); + mac.update(c[0..m.len][0..]); + mac.pad(); + + var final_block = h; + mem.writeIntBig(u64, final_block[0..8], ad.len * 8); + mem.writeIntBig(u64, final_block[8..16], m.len * 8); + mac.update(&final_block); + mac.final(tag); + for (t) |x, i| { + tag[i] ^= x; + } + } + + pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { + assert(c.len == m.len); + + const aes = AES.initEnc(key); + var h: [16]u8 = undefined; + aes.encrypt(&h, &zeros); + + var t: [16]u8 = undefined; + var j: [16]u8 = undefined; + mem.copy(u8, j[0..nonce_length], npub[0..]); + mem.writeIntBig(u32, j[nonce_length..][0..4], 1); + aes.encrypt(&t, &j); + + var mac = Ghash.init(&h); + mac.update(ad); + mac.pad(); + + mac.update(c); + mac.pad(); + + var final_block = h; + mem.writeIntBig(u64, final_block[0..8], ad.len * 8); + mem.writeIntBig(u64, final_block[8..16], m.len * 8); + mac.update(&final_block); + var computed_tag: [Ghash.mac_length]u8 = undefined; + mac.final(&computed_tag); + for (t) |x, i| { + computed_tag[i] ^= x; + } + + var acc: u8 = 0; + for (computed_tag) |_, p| { + acc |= (computed_tag[p] ^ tag[p]); + } + if (acc != 0) { + mem.set(u8, m, 0xaa); + return error.AuthenticationFailed; + } + + mem.writeIntBig(u32, j[nonce_length..][0..4], 2); + modes.ctr(@TypeOf(aes), aes, m, c, j, builtin.Endian.Big); + } + }; +} + +const htest = @import("test.zig"); +const testing = std.testing; + +test "AES256GCM - Empty message and no associated data" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const ad = ""; + const m = ""; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + htest.assertEqual("6b6ff610a16fa4cd59f1fb7903154e92", &tag); +} + +test "AES256GCM - Associated data only" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const m = ""; + const ad = "Test with associated data"; + var c: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + htest.assertEqual("262ed164c2dfb26e080a9d108dd9dd4c", &tag); +} + +test "AES256GCM - Message only" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const m = "Test with message only"; + const ad = ""; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + try AES256GCM.decrypt(&m2, &c, tag, ad, nonce, key); + testing.expectEqualSlices(u8, m[0..], m2[0..]); + + htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01d539472f7c", &c); + htest.assertEqual("07cd7fc9103e2f9e9bf2dfaa319caff4", &tag); +} + +test "AES256GCM - Message and associated data" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const m = "Test with message"; + const ad = "Test with associated data"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + try AES256GCM.decrypt(&m2, &c, tag, ad, nonce, key); + testing.expectEqualSlices(u8, m[0..], m2[0..]); + + htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01", &c); + htest.assertEqual("64accec679d444e2373bd9f6796c0d2c", &tag); +} diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig @@ -57,6 +57,7 @@ pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 } const macs = [_]Crypto{ + Crypto{ .ty = crypto.onetimeauth.Ghash, .name = "ghash" }, Crypto{ .ty = crypto.onetimeauth.Poly1305, .name = "poly1305" }, Crypto{ .ty = crypto.auth.hmac.HmacMd5, .name = "hmac-md5" }, Crypto{ .ty = crypto.auth.hmac.HmacSha1, .name = "hmac-sha1" }, @@ -151,6 +152,8 @@ const aeads = [_]Crypto{ Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" }, Crypto{ .ty = crypto.aead.AEGIS128L, .name = "aegis-128l" }, Crypto{ .ty = crypto.aead.AEGIS256, .name = "aegis-256" }, + Crypto{ .ty = crypto.aead.AES128GCM, .name = "aes128-gcm" }, + Crypto{ .ty = crypto.aead.AES256GCM, .name = "aes256-gcm" }, }; pub fn benchmarkAead(comptime Aead: anytype, comptime bytes: comptime_int) !u64 { diff --git a/lib/std/crypto/ghash.zig b/lib/std/crypto/ghash.zig @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +// +// Adapted from BearSSL's ctmul64 implementation originally written by Thomas Pornin <pornin@bolet.org> + +const std = @import("../std.zig"); +const assert = std.debug.assert; +const math = std.math; +const mem = std.mem; + +/// GHASH is a universal hash function that features multiplication +/// by a fixed parameter within a Galois field. +/// +/// It is not a general purpose hash function - The key must be secret, unpredictable and never reused. +/// +/// GHASH is typically used to compute the authentication tag in the AES-GCM construction. +pub const Ghash = struct { + pub const block_size: usize = 16; + pub const mac_length = 16; + pub const minimum_key_length = 16; + + y0: u64 = 0, + y1: u64 = 0, + h0: u64, + h1: u64, + h2: u64, + h0r: u64, + h1r: u64, + h2r: u64, + + hh0: u64 = undefined, + hh1: u64 = undefined, + hh2: u64 = undefined, + hh0r: u64 = undefined, + hh1r: u64 = undefined, + hh2r: u64 = undefined, + + leftover: usize = 0, + buf: [block_size]u8 align(16) = undefined, + + pub fn init(key: *const [minimum_key_length]u8) Ghash { + const h1 = mem.readIntBig(u64, key[0..8]); + const h0 = mem.readIntBig(u64, key[8..16]); + const h1r = @bitReverse(u64, h1); + const h0r = @bitReverse(u64, h0); + const h2 = h0 ^ h1; + const h2r = h0r ^ h1r; + + if (std.builtin.mode == .ReleaseSmall) { + return Ghash{ + .h0 = h0, + .h1 = h1, + .h2 = h2, + .h0r = h0r, + .h1r = h1r, + .h2r = h2r, + }; + } else { + // Precompute H^2 + var hh = Ghash{ + .h0 = h0, + .h1 = h1, + .h2 = h2, + .h0r = h0r, + .h1r = h1r, + .h2r = h2r, + }; + hh.update(key); + const hh1 = hh.y1; + const hh0 = hh.y0; + const hh1r = @bitReverse(u64, hh1); + const hh0r = @bitReverse(u64, hh0); + const hh2 = hh0 ^ hh1; + const hh2r = hh0r ^ hh1r; + + return Ghash{ + .h0 = h0, + .h1 = h1, + .h2 = h2, + .h0r = h0r, + .h1r = h1r, + .h2r = h2r, + + .hh0 = hh0, + .hh1 = hh1, + .hh2 = hh2, + .hh0r = hh0r, + .hh1r = hh1r, + .hh2r = hh2r, + }; + } + } + + inline fn clmul_pclmul(x: u64, y: u64) u64 { + const Vector = std.meta.Vector; + const product = asm ( + \\ vpclmulqdq $0x00, %[x], %[y], %[out] + : [out] "=x" (-> Vector(2, u64)) + : [x] "x" (@bitCast(Vector(2, u64), @as(u128, x))), + [y] "x" (@bitCast(Vector(2, u64), @as(u128, y))) + ); + return product[0]; + } + + fn clmul_soft(x: u64, y: u64) u64 { + const x0 = x & 0x1111111111111111; + const x1 = x & 0x2222222222222222; + const x2 = x & 0x4444444444444444; + const x3 = x & 0x8888888888888888; + const y0 = y & 0x1111111111111111; + const y1 = y & 0x2222222222222222; + const y2 = y & 0x4444444444444444; + const y3 = y & 0x8888888888888888; + var z0 = (x0 *% y0) ^ (x1 *% y3) ^ (x2 *% y2) ^ (x3 *% y1); + var z1 = (x0 *% y1) ^ (x1 *% y0) ^ (x2 *% y3) ^ (x3 *% y2); + var z2 = (x0 *% y2) ^ (x1 *% y1) ^ (x2 *% y0) ^ (x3 *% y3); + var z3 = (x0 *% y3) ^ (x1 *% y2) ^ (x2 *% y1) ^ (x3 *% y0); + z0 &= 0x1111111111111111; + z1 &= 0x2222222222222222; + z2 &= 0x4444444444444444; + z3 &= 0x8888888888888888; + return z0 | z1 | z2 | z3; + } + + const has_pclmul = comptime std.Target.x86.featureSetHas(std.Target.current.cpu.features, .pclmul); + const has_avx = comptime std.Target.x86.featureSetHas(std.Target.current.cpu.features, .avx); + const clmul = if (std.Target.current.cpu.arch == .x86_64 and has_pclmul and has_avx) clmul_pclmul else clmul_soft; + + fn blocks(st: *Ghash, msg: []const u8) void { + assert(msg.len % 16 == 0); // GHASH blocks() expects full blocks + var y1 = st.y1; + var y0 = st.y0; + + var i: usize = 0; + + // 2-blocks aggregated reduction + if (std.builtin.mode != .ReleaseSmall) { + while (i + 32 <= msg.len) : (i += 32) { + // B0 * H^2 unreduced + y1 ^= mem.readIntBig(u64, msg[i..][0..8]); + y0 ^= mem.readIntBig(u64, msg[i..][8..16]); + + const y1r = @bitReverse(u64, y1); + const y0r = @bitReverse(u64, y0); + const y2 = y0 ^ y1; + const y2r = y0r ^ y1r; + + var z0 = clmul(y0, st.hh0); + var z1 = clmul(y1, st.hh1); + var z2 = clmul(y2, st.hh2) ^ z0 ^ z1; + var z0h = clmul(y0r, st.hh0r); + var z1h = clmul(y1r, st.hh1r); + var z2h = clmul(y2r, st.hh2r) ^ z0h ^ z1h; + + // B1 * H unreduced + const sy1 = mem.readIntBig(u64, msg[i..][16..24]); + const sy0 = mem.readIntBig(u64, msg[i..][24..32]); + + const sy1r = @bitReverse(u64, sy1); + const sy0r = @bitReverse(u64, sy0); + const sy2 = sy0 ^ sy1; + const sy2r = sy0r ^ sy1r; + + const sz0 = clmul(sy0, st.h0); + const sz1 = clmul(sy1, st.h1); + const sz2 = clmul(sy2, st.h2) ^ sz0 ^ sz1; + const sz0h = clmul(sy0r, st.h0r); + const sz1h = clmul(sy1r, st.h1r); + const sz2h = clmul(sy2r, st.h2r) ^ sz0h ^ sz1h; + + // ((B0 * H^2) + B1 * H) (mod M) + z0 ^= sz0; + z1 ^= sz1; + z2 ^= sz2; + z0h ^= sz0h; + z1h ^= sz1h; + z2h ^= sz2h; + z0h = @bitReverse(u64, z0h) >> 1; + z1h = @bitReverse(u64, z1h) >> 1; + z2h = @bitReverse(u64, z2h) >> 1; + + var v3 = z1h; + var v2 = z1 ^ z2h; + var v1 = z0h ^ z2; + var v0 = z0; + + v3 = (v3 << 1) | (v2 >> 63); + v2 = (v2 << 1) | (v1 >> 63); + v1 = (v1 << 1) | (v0 >> 63); + v0 = (v0 << 1); + + v2 ^= v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7); + v1 ^= (v0 << 63) ^ (v0 << 62) ^ (v0 << 57); + y1 = v3 ^ v1 ^ (v1 >> 1) ^ (v1 >> 2) ^ (v1 >> 7); + y0 = v2 ^ (v1 << 63) ^ (v1 << 62) ^ (v1 << 57); + } + } + + // single block + while (i + 16 <= msg.len) : (i += 16) { + y1 ^= mem.readIntBig(u64, msg[i..][0..8]); + y0 ^= mem.readIntBig(u64, msg[i..][8..16]); + + const y1r = @bitReverse(u64, y1); + const y0r = @bitReverse(u64, y0); + const y2 = y0 ^ y1; + const y2r = y0r ^ y1r; + + const z0 = clmul(y0, st.h0); + const z1 = clmul(y1, st.h1); + var z2 = clmul(y2, st.h2) ^ z0 ^ z1; + var z0h = clmul(y0r, st.h0r); + var z1h = clmul(y1r, st.h1r); + var z2h = clmul(y2r, st.h2r) ^ z0h ^ z1h; + z0h = @bitReverse(u64, z0h) >> 1; + z1h = @bitReverse(u64, z1h) >> 1; + z2h = @bitReverse(u64, z2h) >> 1; + + // shift & reduce + var v3 = z1h; + var v2 = z1 ^ z2h; + var v1 = z0h ^ z2; + var v0 = z0; + + v3 = (v3 << 1) | (v2 >> 63); + v2 = (v2 << 1) | (v1 >> 63); + v1 = (v1 << 1) | (v0 >> 63); + v0 = (v0 << 1); + + v2 ^= v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7); + v1 ^= (v0 << 63) ^ (v0 << 62) ^ (v0 << 57); + y1 = v3 ^ v1 ^ (v1 >> 1) ^ (v1 >> 2) ^ (v1 >> 7); + y0 = v2 ^ (v1 << 63) ^ (v1 << 62) ^ (v1 << 57); + } + st.y1 = y1; + st.y0 = y0; + } + + pub fn update(st: *Ghash, m: []const u8) void { + var mb = m; + + if (st.leftover > 0) { + const want = math.min(block_size - st.leftover, mb.len); + const mc = mb[0..want]; + for (mc) |x, i| { + st.buf[st.leftover + i] = x; + } + mb = mb[want..]; + st.leftover += want; + if (st.leftover < block_size) { + return; + } + st.blocks(&st.buf); + st.leftover = 0; + } + if (mb.len >= block_size) { + const want = mb.len & ~(block_size - 1); + st.blocks(mb[0..want]); + mb = mb[want..]; + } + if (mb.len > 0) { + for (mb) |x, i| { + st.buf[st.leftover + i] = x; + } + st.leftover += mb.len; + } + } + + /// Zero-pad to align the next input to the first byte of a block + pub fn pad(st: *Ghash) void { + if (st.leftover == 0) { + return; + } + var i = st.leftover; + while (i < block_size) : (i += 1) { + st.buf[i] = 0; + } + st.blocks(&st.buf); + st.leftover = 0; + } + + pub fn final(st: *Ghash, out: *[mac_length]u8) void { + st.pad(); + mem.writeIntBig(u64, out[0..8], st.y1); + mem.writeIntBig(u64, out[8..16], st.y0); + + mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Ghash)]); + } + + pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [minimum_key_length]u8) void { + var st = Ghash.init(key); + st.update(msg); + st.final(out); + } +}; + +const htest = @import("test.zig"); + +test "ghash" { + const key = [_]u8{0x42} ** 16; + const m = [_]u8{0x69} ** 256; + + var st = Ghash.init(&key); + st.update(&m); + var out: [16]u8 = undefined; + st.final(&out); + htest.assertEqual("889295fa746e8b174bf4ec80a65dea41", &out); + + st = Ghash.init(&key); + st.update(m[0..100]); + st.update(m[100..]); + st.final(&out); + htest.assertEqual("889295fa746e8b174bf4ec80a65dea41", &out); +} diff --git a/lib/std/crypto/hkdf.zig b/lib/std/crypto/hkdf.zig @@ -0,0 +1,66 @@ +const std = @import("../std.zig"); +const assert = std.debug.assert; +const hmac = std.crypto.auth.hmac; +const mem = std.mem; + +/// HKDF-SHA256 +pub const HkdfSha256 = Hkdf(hmac.sha2.HmacSha256); + +/// HKDF-SHA512 +pub const HkdfSha512 = Hkdf(hmac.sha2.HmacSha512); + +/// The Hkdf construction takes some source of initial keying material and +/// derives one or more uniform keys from it. +pub fn Hkdf(comptime Hmac: type) type { + return struct { + /// Return a master key from a salt and initial keying material. + fn extract(salt: []const u8, ikm: []const u8) [Hmac.mac_length]u8 { + var prk: [Hmac.mac_length]u8 = undefined; + Hmac.create(&prk, ikm, salt); + return prk; + } + + /// Derive a subkey from a master key `prk` and a subkey description `ctx`. + fn expand(out: []u8, ctx: []const u8, prk: [Hmac.mac_length]u8) void { + assert(out.len < Hmac.mac_length * 255); // output size is too large for the Hkdf construction + var i: usize = 0; + var counter = [1]u8{1}; + while (i + Hmac.mac_length <= out.len) : (i += Hmac.mac_length) { + var st = Hmac.init(&prk); + if (i != 0) { + st.update(out[i - Hmac.mac_length ..][0..Hmac.mac_length]); + } + st.update(ctx); + st.update(&counter); + st.final(out[i..][0..Hmac.mac_length]); + counter[0] += 1; + } + const left = out.len % Hmac.mac_length; + if (left > 0) { + var st = Hmac.init(&prk); + if (i != 0) { + st.update(out[i - Hmac.mac_length ..][0..Hmac.mac_length]); + } + st.update(ctx); + st.update(&counter); + var tmp: [Hmac.mac_length]u8 = undefined; + st.final(tmp[0..Hmac.mac_length]); + mem.copy(u8, out[i..][0..left], tmp[0..left]); + } + } + }; +} + +const htest = @import("test.zig"); + +test "Hkdf" { + const ikm = [_]u8{0x0b} ** 22; + const salt = [_]u8{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c }; + const context = [_]u8{ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9 }; + const kdf = HkdfSha256; + const prk = kdf.extract(&salt, &ikm); + htest.assertEqual("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5", &prk); + var out: [42]u8 = undefined; + kdf.expand(&out, &context, prk); + htest.assertEqual("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", &out); +} diff --git a/lib/std/crypto/poly1305.zig b/lib/std/crypto/poly1305.zig @@ -22,8 +22,7 @@ pub const Poly1305 = struct { // partial block buffer buf: [block_size]u8 align(16) = undefined, - pub fn init(key: []const u8) Poly1305 { - std.debug.assert(key.len >= minimum_key_length); + pub fn init(key: *const [minimum_key_length]u8) Poly1305 { const t0 = mem.readIntLittle(u64, key[0..8]); const t1 = mem.readIntLittle(u64, key[8..16]); return Poly1305{ @@ -92,7 +91,7 @@ pub const Poly1305 = struct { } mb = mb[want..]; st.leftover += want; - if (st.leftover > block_size) { + if (st.leftover < block_size) { return; } st.blocks(&st.buf, false); @@ -115,8 +114,20 @@ pub const Poly1305 = struct { } } - pub fn final(st: *Poly1305, out: []u8) void { - std.debug.assert(out.len >= mac_length); + /// Zero-pad to align the next input to the first byte of a block + pub fn pad(st: *Poly1305) void { + if (st.leftover == 0) { + return; + } + var i = st.leftover; + while (i < block_size) : (i += 1) { + st.buf[i] = 0; + } + st.blocks(&st.buf); + st.leftover = 0; + } + + pub fn final(st: *Poly1305, out: *[mac_length]u8) void { if (st.leftover > 0) { var i = st.leftover; st.buf[i] = 1; @@ -187,10 +198,7 @@ pub const Poly1305 = struct { std.mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Poly1305)]); } - pub fn create(out: []u8, msg: []const u8, key: []const u8) void { - std.debug.assert(out.len >= mac_length); - std.debug.assert(key.len >= minimum_key_length); - + pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [minimum_key_length]u8) void { var st = Poly1305.init(key); st.update(msg); st.final(out); diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig @@ -647,6 +647,31 @@ pub const Loop = struct { } } + /// Runs the provided function asynchronously. The function's frame is allocated + /// with `allocator` and freed when the function returns. + /// `func` must return void and it can be an async function. + /// Yields to the event loop, running the function on the next tick. + pub fn runDetached(self: *Loop, alloc: *mem.Allocator, comptime func: anytype, args: anytype) error{OutOfMemory}!void { + if (!std.io.is_async) @compileError("Can't use runDetached in non-async mode!"); + if (@TypeOf(@call(.{}, func, args)) != void) { + @compileError("`func` must not have a return value"); + } + + const Wrapper = struct { + const Args = @TypeOf(args); + fn run(func_args: Args, loop: *Loop, allocator: *mem.Allocator) void { + loop.yield(); + const result = @call(.{}, func, func_args); + suspend { + allocator.destroy(@frame()); + } + } + }; + + var run_frame = try alloc.create(@Frame(Wrapper.run)); + run_frame.* = async Wrapper.run(args, self, alloc); + } + /// Yielding lets the event loop run, starting any unstarted async operations. /// Note that async operations automatically start when a function yields for any other reason, /// for example, when async I/O is performed. This function is intended to be used only when @@ -1493,3 +1518,33 @@ fn testEventLoop2(h: anyframe->i32, did_it: *bool) void { testing.expect(value == 1234); did_it.* = true; } + +var testRunDetachedData: usize = 0; +test "std.event.Loop - runDetached" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + if (!std.io.is_async) return error.SkipZigTest; + if (true) { + // https://github.com/ziglang/zig/issues/4922 + return error.SkipZigTest; + } + + var loop: Loop = undefined; + try loop.initMultiThreaded(); + defer loop.deinit(); + + // Schedule the execution, won't actually start until we start the + // event loop. + try loop.runDetached(std.testing.allocator, testRunDetached, .{}); + + // Now we can start the event loop. The function will return only + // after all tasks have been completed, allowing us to synchonize + // with the previous runDetached. + loop.run(); + + testing.expect(testRunDetachedData == 1); +} + +fn testRunDetached() void { + testRunDetachedData += 1; +} diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig @@ -1181,6 +1181,16 @@ fn bufPrintIntToSlice(buf: []u8, value: anytype, base: u8, uppercase: bool, opti return buf[0..formatIntBuf(buf, value, base, uppercase, options)]; } +pub fn comptimePrint(comptime fmt: []const u8, args: anytype) *const [count(fmt, args)]u8 { + comptime var buf: [count(fmt, args)]u8 = undefined; + _ = bufPrint(&buf, fmt, args) catch unreachable; + return &buf; +} + +test "comptimePrint" { + std.testing.expectEqualSlices(u8, "100", comptime comptimePrint("{}", .{100})); +} + test "parse u64 digit too big" { _ = parseUnsigned(u64, "123a", 10) catch |err| { if (err == error.InvalidCharacter) return; diff --git a/lib/std/fs.zig b/lib/std/fs.zig @@ -1856,7 +1856,7 @@ pub const Dir = struct { } }; -/// Returns an handle to the current working directory. It is not opened with iteration capability. +/// Returns a handle to the current working directory. It is not opened with iteration capability. /// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior. /// On POSIX targets, this function is comptime-callable. pub fn cwd() Dir { @@ -2162,7 +2162,7 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags); } -pub const SelfExePathError = os.ReadLinkError || os.SysCtlError; +pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError; /// `selfExePath` except allocates the result on the heap. /// Caller owns returned memory. @@ -2190,10 +2190,18 @@ pub fn selfExePathAlloc(allocator: *Allocator) ![]u8 { /// TODO make the return type of this a null terminated pointer pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { if (is_darwin) { - var u32_len: u32 = @intCast(u32, math.min(out_buffer.len, math.maxInt(u32))); - const rc = std.c._NSGetExecutablePath(out_buffer.ptr, &u32_len); + // Note that _NSGetExecutablePath() will return "a path" to + // the executable not a "real path" to the executable. + var symlink_path_buf: [MAX_PATH_BYTES:0]u8 = undefined; + var u32_len: u32 = MAX_PATH_BYTES + 1; // include the sentinel + const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); if (rc != 0) return error.NameTooLong; - return mem.spanZ(@ptrCast([*:0]u8, out_buffer)); + + var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; + const real_path = try std.os.realpathZ(&symlink_path_buf, &real_path_buf); + if (real_path.len > out_buffer.len) return error.NameTooLong; + std.mem.copy(u8, out_buffer, real_path); + return out_buffer[0..real_path.len]; } switch (builtin.os.tag) { .linux => return os.readlinkZ("/proc/self/exe", out_buffer), diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig @@ -615,7 +615,7 @@ pub const File = struct { } } - pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!usize { + pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter if (iovecs.len == 0) return @as(usize, 0); @@ -632,11 +632,11 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. - pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void { + pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void { if (iovecs.len == 0) return; var i: usize = 0; - var off: usize = 0; + var off: u64 = 0; while (true) { var amt = try self.pwritev(iovecs[i..], offset + off); off += amt; @@ -652,14 +652,16 @@ pub const File = struct { pub const CopyRangeError = os.CopyFileRangeError; - pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize { - return os.copy_file_range(in.handle, in_offset, out.handle, out_offset, len, 0); + pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { + const adjusted_len = math.cast(usize, len) catch math.maxInt(usize); + const result = try os.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0); + return result; } /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it /// means the in file reached the end. Reaching the end of a file is not an error condition. - pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize { - var total_bytes_copied: usize = 0; + pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { + var total_bytes_copied: u64 = 0; var in_off = in_offset; var out_off = out_offset; while (total_bytes_copied < len) { diff --git a/lib/std/macho.zig b/lib/std/macho.zig @@ -1257,3 +1257,47 @@ pub const reloc_type_x86_64 = packed enum(u4) { /// for thread local variables X86_64_RELOC_TLV, }; + +/// This symbol is a reference to an external non-lazy (data) symbol. +pub const REFERENCE_FLAG_UNDEFINED_NON_LAZY: u16 = 0x0; + +/// This symbol is a reference to an external lazy symbol—that is, to a function call. +pub const REFERENCE_FLAG_UNDEFINED_LAZY: u16 = 0x1; + +/// This symbol is defined in this module. +pub const REFERENCE_FLAG_DEFINED: u16 = 0x2; + +/// This symbol is defined in this module and is visible only to modules within this shared library. +pub const REFERENCE_FLAG_PRIVATE_DEFINED: u16 = 3; + +/// This symbol is defined in another module in this file, is a non-lazy (data) symbol, and is visible +/// only to modules within this shared library. +pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY: u16 = 4; + +/// This symbol is defined in another module in this file, is a lazy (function) symbol, and is visible +/// only to modules within this shared library. +pub const REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY: u16 = 5; + +/// Must be set for any defined symbol that is referenced by dynamic-loader APIs (such as dlsym and +/// NSLookupSymbolInImage) and not ordinary undefined symbol references. The strip tool uses this bit +/// to avoid removing symbols that must exist: If the symbol has this bit set, strip does not strip it. +pub const REFERENCED_DYNAMICALLY: u16 = 0x10; + +/// Used by the dynamic linker at runtime. Do not set this bit. +pub const N_DESC_DISCARDED: u16 = 0x20; + +/// Indicates that this symbol is a weak reference. If the dynamic linker cannot find a definition +/// for this symbol, it sets the address of this symbol to 0. The static linker sets this symbol given +/// the appropriate weak-linking flags. +pub const N_WEAK_REF: u16 = 0x40; + +/// Indicates that this symbol is a weak definition. If the static linker or the dynamic linker finds +/// another (non-weak) definition for this symbol, the weak definition is ignored. Only symbols in a +/// coalesced section (page 23) can be marked as a weak definition. +pub const N_WEAK_DEF: u16 = 0x80; + +/// The N_SYMBOL_RESOLVER bit of the n_desc field indicates that the +/// that the function is actually a resolver function and should +/// be called to get the address of the real function to use. +/// This bit is only available in .o files (MH_OBJECT filetype) +pub const N_SYMBOL_RESOLVER: u16 = 0x100; diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig @@ -58,6 +58,11 @@ pub fn calcSetStringLimbCount(base: u8, string_len: usize) usize { return (string_len + (limb_bits / base - 1)) / (limb_bits / base); } +pub fn calcPowLimbsBufferLen(a_bit_count: usize, y: usize) usize { + // The 2 accounts for the minimum space requirement for llmulacc + return 2 + (a_bit_count * y + (limb_bits - 1)) / limb_bits; +} + /// a + b * c + *carry, sets carry to the overflow bits pub fn addMulLimbWithCarry(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb { @setRuntimeSafety(debug_safety); @@ -597,6 +602,52 @@ pub const Mutable = struct { return gcdLehmer(rma, x_copy, y_copy, limbs_buffer); } + /// q = a ^ b + /// + /// r may not alias a. + /// + /// Asserts that `r` has enough limbs to store the result. Upper bound is + /// `calcPowLimbsBufferLen(a.bitCountAbs(), b)`. + /// + /// `limbs_buffer` is used for temporary storage. + /// The amount required is given by `calcPowLimbsBufferLen`. + pub fn pow(r: *Mutable, a: Const, b: u32, limbs_buffer: []Limb) !void { + assert(r.limbs.ptr != a.limbs.ptr); // illegal aliasing + + // Handle all the trivial cases first + switch (b) { + 0 => { + // a^0 = 1 + return r.set(1); + }, + 1 => { + // a^1 = a + return r.copy(a); + }, + else => {}, + } + + if (a.eqZero()) { + // 0^b = 0 + return r.set(0); + } else if (a.limbs.len == 1 and a.limbs[0] == 1) { + // 1^b = 1 and -1^b = ±1 + r.set(1); + r.positive = a.positive or (b & 1) == 0; + return; + } + + // Here a>1 and b>1 + const needed_limbs = calcPowLimbsBufferLen(a.bitCountAbs(), b); + assert(r.limbs.len >= needed_limbs); + assert(limbs_buffer.len >= needed_limbs); + + llpow(r.limbs, a.limbs, b, limbs_buffer); + + r.normalize(needed_limbs); + r.positive = a.positive or (b & 1) == 0; + } + /// rma may not alias x or y. /// x and y may alias each other. /// Asserts that `rma` has enough limbs to store the result. Upper bound is given by `calcGcdNoAliasLimbLen`. @@ -1775,6 +1826,29 @@ pub const Managed = struct { try m.gcd(x.toConst(), y.toConst(), &limbs_buffer); rma.setMetadata(m.positive, m.len); } + + pub fn pow(rma: *Managed, a: Managed, b: u32) !void { + const needed_limbs = calcPowLimbsBufferLen(a.bitCountAbs(), b); + + const limbs_buffer = try rma.allocator.alloc(Limb, needed_limbs); + defer rma.allocator.free(limbs_buffer); + + if (rma.limbs.ptr == a.limbs.ptr) { + var m = try Managed.initCapacity(rma.allocator, needed_limbs); + errdefer m.deinit(); + var m_mut = m.toMutable(); + try m_mut.pow(a.toConst(), b, limbs_buffer); + m.setMetadata(m_mut.positive, m_mut.len); + + rma.deinit(); + rma.swap(&m); + } else { + try rma.ensureCapacity(needed_limbs); + var rma_mut = rma.toMutable(); + try rma_mut.pow(a.toConst(), b, limbs_buffer); + rma.setMetadata(rma_mut.positive, rma_mut.len); + } + } }; /// Knuth 4.3.1, Algorithm M. @@ -2129,6 +2203,56 @@ fn llxor(r: []Limb, a: []const Limb, b: []const Limb) void { } } +/// Knuth 4.6.3 +fn llpow(r: []Limb, a: []const Limb, b: u32, tmp_limbs: []Limb) void { + var tmp1: []Limb = undefined; + var tmp2: []Limb = undefined; + + // Multiplication requires no aliasing between the operand and the result + // variable, use the output limbs and another temporary set to overcome this + // limitation. + // The initial assignment makes the result end in `r` so an extra memory + // copy is saved, each 1 flips the index twice so it's a no-op so count the + // 0. + const b_leading_zeros = @intCast(u5, @clz(u32, b)); + const exp_zeros = @popCount(u32, ~b) - b_leading_zeros; + if (exp_zeros & 1 != 0) { + tmp1 = tmp_limbs; + tmp2 = r; + } else { + tmp1 = r; + tmp2 = tmp_limbs; + } + + const a_norm = a[0..llnormalize(a)]; + + mem.copy(Limb, tmp1, a_norm); + mem.set(Limb, tmp1[a_norm.len..], 0); + + // Scan the exponent as a binary number, from left to right, dropping the + // most significant bit set. + const exp_bits = @intCast(u5, 31 - b_leading_zeros); + var exp = @bitReverse(u32, b) >> 1 + b_leading_zeros; + + var i: u5 = 0; + while (i < exp_bits) : (i += 1) { + // Square + { + mem.set(Limb, tmp2, 0); + const op = tmp1[0..llnormalize(tmp1)]; + llmulacc(null, tmp2, op, op); + mem.swap([]Limb, &tmp1, &tmp2); + } + // Multiply by a + if (exp & 1 != 0) { + mem.set(Limb, tmp2, 0); + llmulacc(null, tmp2, tmp1[0..llnormalize(tmp1)], a_norm); + mem.swap([]Limb, &tmp1, &tmp2); + } + exp >>= 1; + } +} + // Storage must live for the lifetime of the returned value fn fixedIntFromSignedDoubleLimb(A: SignedDoubleLimb, storage: []Limb) Mutable { assert(storage.len >= 2); diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig @@ -1480,3 +1480,55 @@ test "big.int const to managed" { testing.expect(a.toConst().eq(b.toConst())); } + +test "big.int pow" { + { + var a = try Managed.initSet(testing.allocator, 10); + defer a.deinit(); + + try a.pow(a, 8); + testing.expectEqual(@as(u32, 100000000), try a.to(u32)); + } + { + var a = try Managed.initSet(testing.allocator, 10); + defer a.deinit(); + + var y = try Managed.init(testing.allocator); + defer y.deinit(); + + // y and a are not aliased + try y.pow(a, 123); + // y and a are aliased + try a.pow(a, 123); + + testing.expect(a.eq(y)); + + const ys = try y.toString(testing.allocator, 16, false); + defer testing.allocator.free(ys); + testing.expectEqualSlices( + u8, + "183425a5f872f126e00a5ad62c839075cd6846c6fb0230887c7ad7a9dc530fcb" ++ + "4933f60e8000000000000000000000000000000", + ys, + ); + } + // Special cases + { + var a = try Managed.initSet(testing.allocator, 0); + defer a.deinit(); + + try a.pow(a, 100); + testing.expectEqual(@as(i32, 0), try a.to(i32)); + + try a.set(1); + try a.pow(a, 0); + testing.expectEqual(@as(i32, 1), try a.to(i32)); + try a.pow(a, 100); + testing.expectEqual(@as(i32, 1), try a.to(i32)); + try a.set(-1); + try a.pow(a, 15); + testing.expectEqual(@as(i32, -1), try a.to(i32)); + try a.pow(a, 16); + testing.expectEqual(@as(i32, 1), try a.to(i32)); + } +} diff --git a/lib/std/meta.zig b/lib/std/meta.zig @@ -854,6 +854,7 @@ pub fn ArgsTuple(comptime Function: type) type { .field_type = arg.arg_type.?, .default_value = @as(?(arg.arg_type.?), null), .is_comptime = false, + .alignment = @alignOf(arg.arg_type.?), }; } @@ -884,6 +885,7 @@ pub fn Tuple(comptime types: []const type) type { .field_type = T, .default_value = @as(?T, null), .is_comptime = false, + .alignment = @alignOf(T), }; } diff --git a/lib/std/meta/trailer_flags.zig b/lib/std/meta/trailer_flags.zig @@ -47,6 +47,7 @@ pub fn TrailerFlags(comptime Fields: type) type { @as(?struct_field.field_type, null), ), .is_comptime = false, + .alignment = @alignOf(?struct_field.field_type), }; } break :blk @Type(.{ diff --git a/lib/std/os.zig b/lib/std/os.zig @@ -3993,7 +3993,7 @@ pub const RealPathError = error{ /// Expands all symbolic links and resolves references to `.`, `..`, and /// extra `/` characters in `pathname`. /// The return value is a slice of `out_buffer`, but not necessarily from the beginning. -/// See also `realpathC` and `realpathW`. +/// See also `realpathZ` and `realpathW`. pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { if (builtin.os.tag == .windows) { const pathname_w = try windows.sliceToPrefixedFileW(pathname); @@ -5410,3 +5410,33 @@ pub fn prctl(option: i32, args: anytype) PrctlError!u31 { else => |err| return std.os.unexpectedErrno(err), } } + +pub const GetrlimitError = UnexpectedError; + +pub fn getrlimit(resource: rlimit_resource) GetrlimitError!rlimit { + // TODO implement for systems other than linux and enable test + var limits: rlimit = undefined; + const rc = system.getrlimit(resource, &limits); + switch (errno(rc)) { + 0 => return limits, + EFAULT => unreachable, // bogus pointer + EINVAL => unreachable, + else => |err| return std.os.unexpectedErrno(err), + } +} + +pub const SetrlimitError = error{ + PermissionDenied, +} || UnexpectedError; + +pub fn setrlimit(resource: rlimit_resource, limits: rlimit) SetrlimitError!void { + // TODO implement for systems other than linux and enable test + const rc = system.setrlimit(resource, &limits); + switch (errno(rc)) { + 0 => return, + EFAULT => unreachable, // bogus pointer + EINVAL => unreachable, + EPERM => return error.PermissionDenied, + else => |err| return std.os.unexpectedErrno(err), + } +} diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig @@ -1890,3 +1890,79 @@ pub const ifreq = extern struct { data: ?[*]u8, }, }; + +// doc comments copied from musl +pub const rlimit_resource = extern enum(c_int) { + /// Per-process CPU limit, in seconds. + CPU, + + /// Largest file that can be created, in bytes. + FSIZE, + + /// Maximum size of data segment, in bytes. + DATA, + + /// Maximum size of stack segment, in bytes. + STACK, + + /// Largest core file that can be created, in bytes. + CORE, + + /// Largest resident set size, in bytes. + /// This affects swapping; processes that are exceeding their + /// resident set size will be more likely to have physical memory + /// taken from them. + RSS, + + /// Number of processes. + NPROC, + + /// Number of open files. + NOFILE, + + /// Locked-in-memory address space. + MEMLOCK, + + /// Address space limit. + AS, + + /// Maximum number of file locks. + LOCKS, + + /// Maximum number of pending signals. + SIGPENDING, + + /// Maximum bytes in POSIX message queues. + MSGQUEUE, + + /// Maximum nice priority allowed to raise to. + /// Nice levels 19 .. -20 correspond to 0 .. 39 + /// values of this resource limit. + NICE, + + /// Maximum realtime priority allowed for non-priviledged + /// processes. + RTPRIO, + + /// Maximum CPU time in µs that a process scheduled under a real-time + /// scheduling policy may consume without making a blocking system + /// call before being forcibly descheduled. + RTTIME, + + _, +}; + +pub const rlim_t = u64; + +/// No limit +pub const RLIM_INFINITY = ~@as(rlim_t, 0); + +pub const RLIM_SAVED_MAX = RLIM_INFINITY; +pub const RLIM_SAVED_CUR = RLIM_INFINITY; + +pub const rlimit = extern struct { + /// Soft limit + cur: rlim_t, + /// Hard limit + max: rlim_t, +}; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig @@ -1263,6 +1263,26 @@ pub fn prctl(option: i32, arg2: usize, arg3: usize, arg4: usize, arg5: usize) us return syscall5(.prctl, @bitCast(usize, @as(isize, option)), arg2, arg3, arg4, arg5); } +pub fn getrlimit(resource: rlimit_resource, rlim: *rlimit) usize { + // use prlimit64 to have 64 bit limits on 32 bit platforms + return prlimit(0, resource, null, rlim); +} + +pub fn setrlimit(resource: rlimit_resource, rlim: *const rlimit) usize { + // use prlimit64 to have 64 bit limits on 32 bit platforms + return prlimit(0, resource, rlim, null); +} + +pub fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: ?*const rlimit, old_limit: ?*rlimit) usize { + return syscall4( + .prlimit64, + @bitCast(usize, @as(isize, pid)), + @bitCast(usize, @as(isize, @enumToInt(resource))), + @ptrToInt(new_limit), + @ptrToInt(old_limit) + ); +} + test "" { if (builtin.os.tag == .linux) { _ = @import("linux/test.zig"); diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig @@ -591,3 +591,13 @@ test "fsync" { try os.fsync(file.handle); try os.fdatasync(file.handle); } + +test "getrlimit and setrlimit" { + // TODO enable for other systems when implemented + if(builtin.os.tag != .linux){ + return error.SkipZigTest; + } + + const cpuLimit = try os.getrlimit(.CPU); + try os.setrlimit(.CPU, cpuLimit); +} diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig @@ -318,9 +318,12 @@ pub fn PackedIntSliceEndian(comptime Int: type, comptime endian: builtin.Endian) }; } +const we_are_testing_this_with_stage1_which_leaks_comptime_memory = true; + test "PackedIntArray" { // TODO @setEvalBranchQuota generates panics in wasm32. Investigate. if (builtin.arch == .wasm32) return error.SkipZigTest; + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; @setEvalBranchQuota(10000); const max_bits = 256; @@ -358,6 +361,7 @@ test "PackedIntArray" { } test "PackedIntArray init" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; const PackedArray = PackedIntArray(u3, 8); var packed_array = PackedArray.init([_]u3{ 0, 1, 2, 3, 4, 5, 6, 7 }); var i = @as(usize, 0); @@ -367,6 +371,7 @@ test "PackedIntArray init" { test "PackedIntSlice" { // TODO @setEvalBranchQuota generates panics in wasm32. Investigate. if (builtin.arch == .wasm32) return error.SkipZigTest; + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; @setEvalBranchQuota(10000); const max_bits = 256; @@ -405,6 +410,7 @@ test "PackedIntSlice" { } test "PackedIntSlice of PackedInt(Array/Slice)" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; const max_bits = 16; const int_count = 19; @@ -470,6 +476,7 @@ test "PackedIntSlice of PackedInt(Array/Slice)" { } test "PackedIntSlice accumulating bit offsets" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; //bit_offset is u3, so standard debugging asserts should catch // anything { @@ -497,6 +504,8 @@ test "PackedIntSlice accumulating bit offsets" { //@NOTE: As I do not have a big endian system to test this on, // big endian values were not tested test "PackedInt(Array/Slice) sliceCast" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; + const PackedArray = PackedIntArray(u1, 16); var packed_array = PackedArray.init([_]u1{ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 }); const packed_slice_cast_2 = packed_array.sliceCast(u2); @@ -537,6 +546,8 @@ test "PackedInt(Array/Slice) sliceCast" { } test "PackedInt(Array/Slice)Endian" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; + { const PackedArrayBe = PackedIntArrayEndian(u4, .Big, 8); var packed_array_be = PackedArrayBe.init([_]u4{ 0, 1, 2, 3, 4, 5, 6, 7 }); @@ -604,6 +615,8 @@ test "PackedInt(Array/Slice)Endian" { // after this one is not mapped and will cause a segfault if we // don't account for the bounds. test "PackedIntArray at end of available memory" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; + switch (builtin.os.tag) { .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {}, else => return, @@ -623,6 +636,8 @@ test "PackedIntArray at end of available memory" { } test "PackedIntSlice at end of available memory" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; + switch (builtin.os.tag) { .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {}, else => return, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig @@ -213,6 +213,8 @@ pub const NativeTargetInfo = struct { // kernel version const kernel_version = if (mem.indexOfScalar(u8, release, '-')) |pos| release[0..pos] + else if (mem.indexOfScalar(u8, release, '_')) |pos| + release[0..pos] else release; diff --git a/src/Compilation.zig b/src/Compilation.zig @@ -8,6 +8,7 @@ const log = std.log.scoped(.compilation); const Target = std.Target; const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; const target_util = @import("target.zig"); const Package = @import("Package.zig"); const link = @import("link.zig"); @@ -352,6 +353,7 @@ pub const InitOptions = struct { time_report: bool = false, stack_report: bool = false, link_eh_frame_hdr: bool = false, + link_emit_relocs: bool = false, linker_script: ?[]const u8 = null, version_script: ?[]const u8 = null, override_soname: ?[]const u8 = null, @@ -376,6 +378,7 @@ pub const InitOptions = struct { is_compiler_rt_or_libc: bool = false, parent_compilation_link_libc: bool = false, stack_size_override: ?u64 = null, + image_base_override: ?u64 = null, self_exe_path: ?[]const u8 = null, version: ?std.builtin.Version = null, libc_installation: ?*const LibCInstallation = null, @@ -447,8 +450,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { options.system_libs.len != 0 or options.link_libc or options.link_libcpp or options.link_eh_frame_hdr or + options.link_emit_relocs or options.output_mode == .Lib or options.lld_argv.len != 0 or + options.image_base_override != null or options.linker_script != null or options.version_script != null) { break :blk true; @@ -473,8 +478,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { { break :dl true; } - if (options.system_libs.len != 0) - break :dl true; + if (options.system_libs.len != 0) { + // when creating a executable that links to system libraries, + // we require dynamic linking, but we must not link static libraries + // or object files dynamically! + break :dl (options.output_mode == .Exe); + } break :dl false; }; @@ -638,15 +647,19 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const root_scope = rs: { if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) { + const struct_payload = try gpa.create(Type.Payload.EmptyStruct); const root_scope = try gpa.create(Module.Scope.File); + struct_payload.* = .{ .scope = &root_scope.root_container }; root_scope.* = .{ - .sub_file_path = root_pkg.root_src_path, + // TODO this is duped so it can be freed in Container.deinit + .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path), .source = .{ .unloaded = {} }, .contents = .{ .not_available = {} }, .status = .never_loaded, .root_container = .{ .file_scope = root_scope, .decls = .{}, + .ty = Type.initPayload(&struct_payload.base), }, }; break :rs &root_scope.base; @@ -765,10 +778,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .z_nodelete = options.linker_z_nodelete, .z_defs = options.linker_z_defs, .stack_size_override = options.stack_size_override, + .image_base_override = options.image_base_override, .linker_script = options.linker_script, .version_script = options.version_script, .gc_sections = options.linker_gc_sections, .eh_frame_hdr = options.link_eh_frame_hdr, + .emit_relocs = options.link_emit_relocs, .rdynamic = options.rdynamic, .extra_lld_args = options.lld_argv, .override_soname = options.override_soname, @@ -785,7 +800,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .llvm_cpu_features = llvm_cpu_features, .is_compiler_rt_or_libc = options.is_compiler_rt_or_libc, .parent_compilation_link_libc = options.parent_compilation_link_libc, - .each_lib_rpath = options.each_lib_rpath orelse false, + .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os, .disable_lld_caching = options.disable_lld_caching, .subsystem = options.subsystem, .is_test = options.is_test, @@ -1022,6 +1037,17 @@ pub fn update(self: *Compilation) !void { else => |e| return e, }; } + + // TODO only analyze imports if they are still referenced + for (module.import_table.items()) |entry| { + entry.value.unload(module.gpa); + module.analyzeContainer(&entry.value.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + else => |e| return e, + }; + } } } @@ -1146,7 +1172,15 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }; } -pub fn performAllTheWork(self: *Compilation) error{OutOfMemory}!void { +pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void { + var progress: std.Progress = .{}; + var main_progress_node = try progress.start("", null); + defer main_progress_node.end(); + if (self.color == .Off) progress.terminal = null; + + var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len); + defer c_comp_progress_node.end(); + while (self.work_queue.readItem()) |work_item| switch (work_item) { .codegen_decl => |decl| switch (decl.analysis) { .unreferenced => unreachable, @@ -1223,7 +1257,7 @@ pub fn performAllTheWork(self: *Compilation) error{OutOfMemory}!void { }; }, .c_object => |c_object| { - self.updateCObject(c_object) catch |err| switch (err) { + self.updateCObject(c_object, &c_comp_progress_node) catch |err| switch (err) { error.AnalysisFail => continue, else => { try self.failed_c_objects.ensureCapacity(self.gpa, self.failed_c_objects.items().len + 1); @@ -1309,7 +1343,7 @@ pub fn performAllTheWork(self: *Compilation) error{OutOfMemory}!void { if (!build_options.is_stage1) unreachable; - self.updateStage1Module() catch |err| { + self.updateStage1Module(main_progress_node) catch |err| { fatal("unable to build stage1 zig object: {}", .{@errorName(err)}); }; }, @@ -1470,7 +1504,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { }; } -fn updateCObject(comp: *Compilation, c_object: *CObject) !void { +fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return comp.failCObj(c_object, "clang not available: compiler built without LLVM extensions", .{}); } @@ -1513,6 +1547,12 @@ fn updateCObject(comp: *Compilation, c_object: *CObject) !void { const arena = &arena_allocator.allocator; const c_source_basename = std.fs.path.basename(c_object.src.src_path); + + c_comp_progress_node.activate(); + var child_progress_node = c_comp_progress_node.start(c_source_basename, null); + child_progress_node.activate(); + defer child_progress_node.end(); + // Special case when doing build-obj for just one C file. When there are more than one object // file and building an object we need to link them together, but with just one it should go // directly to the output file. @@ -2506,7 +2546,7 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR }; } -fn updateStage1Module(comp: *Compilation) !void { +fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node) !void { const tracy = trace(@src()); defer tracy.end(); @@ -2550,6 +2590,8 @@ fn updateStage1Module(comp: *Compilation) !void { man.hash.add(comp.emit_llvm_ir != null); man.hash.add(comp.emit_analysis != null); man.hash.add(comp.emit_docs != null); + man.hash.addOptionalBytes(comp.test_filter); + man.hash.addOptionalBytes(comp.test_name_prefix); // Capture the state in case we come back from this branch where the hash doesn't match. const prev_hash_state = man.hash.peekBin(); @@ -2612,10 +2654,6 @@ fn updateStage1Module(comp: *Compilation) !void { .llvm_cpu_name = if (target.cpu.model.llvm_name) |s| s.ptr else null, .llvm_cpu_features = comp.bin_file.options.llvm_cpu_features.?, }; - var progress: std.Progress = .{}; - var main_progress_node = try progress.start("", null); - defer main_progress_node.end(); - if (comp.color == .Off) progress.terminal = null; comp.stage1_cache_manifest = &man; diff --git a/src/Module.zig b/src/Module.zig @@ -70,6 +70,9 @@ deletion_set: ArrayListUnmanaged(*Decl) = .{}, /// Error tags and their values, tag names are duped with mod.gpa. global_error_set: std.StringHashMapUnmanaged(u16) = .{}, +/// Keys are fully qualified paths +import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{}, + /// Incrementing integer used to compare against the corresponding Decl /// field to determine whether a Decl's status applies to an ongoing update, or a /// previous analysis. @@ -208,7 +211,7 @@ pub const Decl = struct { .container => { const container = @fieldParentPtr(Scope.Container, "base", self.scope); const tree = container.file_scope.contents.tree; - // TODO Container should have it's own decls() + // TODO Container should have its own decls() const decl_node = tree.root_node.decls()[self.src_index]; return tree.token_locs[decl_node.firstToken()].start; }, @@ -532,12 +535,13 @@ pub const Scope = struct { /// Direct children of the file. decls: std.AutoArrayHashMapUnmanaged(*Decl, void), - - // TODO implement container types and put this in a status union - // ty: Type + ty: Type, pub fn deinit(self: *Container, gpa: *Allocator) void { self.decls.deinit(gpa); + // TODO either Container of File should have an arena for sub_file_path and ty + gpa.destroy(self.ty.cast(Type.Payload.EmptyStruct).?); + gpa.free(self.file_scope.sub_file_path); self.* = undefined; } @@ -854,6 +858,11 @@ pub fn deinit(self: *Module) void { gpa.free(entry.key); } self.global_error_set.deinit(gpa); + + for (self.import_table.items()) |entry| { + entry.value.base.destroy(gpa); + } + self.import_table.deinit(gpa); } fn freeExportList(gpa: *Allocator, export_list: []*Export) void { @@ -2381,6 +2390,45 @@ pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, return self.fail(scope, src, "TODO implement analysis of slice", .{}); } +pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: []const u8) !*Scope.File { + // TODO if (package_table.get(target_string)) |pkg| + if (self.import_table.get(target_string)) |some| { + return some; + } + + // TODO check for imports outside of pkg path + if (false) return error.ImportOutsidePkgPath; + + // TODO Scope.Container arena for ty and sub_file_path + const struct_payload = try self.gpa.create(Type.Payload.EmptyStruct); + errdefer self.gpa.destroy(struct_payload); + const file_scope = try self.gpa.create(Scope.File); + errdefer self.gpa.destroy(file_scope); + const file_path = try self.gpa.dupe(u8, target_string); + errdefer self.gpa.free(file_path); + + struct_payload.* = .{ .scope = &file_scope.root_container }; + file_scope.* = .{ + .sub_file_path = file_path, + .source = .{ .unloaded = {} }, + .contents = .{ .not_available = {} }, + .status = .never_loaded, + .root_container = .{ + .file_scope = file_scope, + .decls = .{}, + .ty = Type.initPayload(&struct_payload.base), + }, + }; + self.analyzeContainer(&file_scope.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.comp.totalErrorCount() != 0); + }, + else => |e| return e, + }; + try self.import_table.put(self.gpa, file_scope.sub_file_path, file_scope); + return file_scope; +} + /// Asserts that lhs and rhs types are both numeric. pub fn cmpNumeric( self: *Module, diff --git a/src/astgen.zig b/src/astgen.zig @@ -1973,6 +1973,15 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa } } +fn import(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + try ensureBuiltinParamCount(mod, scope, call, 1); + const tree = scope.tree(); + const src = tree.token_locs[call.builtin_token].start; + const params = call.params(); + const target = try expr(mod, scope, .none, params[0]); + return addZIRUnOp(mod, scope, src, .import, target); +} + fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); const builtin_name = tree.tokenSlice(call.builtin_token); @@ -1995,6 +2004,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built } else if (mem.eql(u8, builtin_name, "@breakpoint")) { const src = tree.token_locs[call.builtin_token].start; return rlWrap(mod, scope, rl, try addZIRNoOp(mod, scope, src, .breakpoint)); + } else if (mem.eql(u8, builtin_name, "@import")) { + return rlWrap(mod, scope, rl, try import(mod, scope, call)); } else { return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{}'", .{builtin_name}); } diff --git a/src/codegen.zig b/src/codegen.zig @@ -17,9 +17,6 @@ const DW = std.dwarf; const leb128 = std.debug.leb; const log = std.log.scoped(.codegen); -// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. -// zig fmt: off - /// The codegen-related data that is stored in `ir.Inst.Block` instructions. pub const BlockData = struct { relocs: std.ArrayListUnmanaged(Reloc) = undefined, @@ -35,7 +32,7 @@ pub const BlockData = struct { /// comptime assert that makes sure we guessed correctly about the size. This only /// exists so that we can bitcast an arch-independent field to and from the real MCValue. pub const AnyMCValue = extern struct { - a: u64, + a: usize, b: u64, }; @@ -170,7 +167,6 @@ pub fn generateSymbol( }, .Pointer => { // TODO populate .debug_info for the pointer - if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { const decl = payload.decl; if (decl.analysis != .complete) return error.AnalysisFail; @@ -206,7 +202,6 @@ pub fn generateSymbol( }, .Int => { // TODO populate .debug_info for the integer - const info = typed_value.ty.intInfo(bin_file.options.target); if (info.bits == 8 and !info.signed) { const x = typed_value.val.toUnsignedInt(); @@ -399,7 +394,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); const reg = callee_preserved_regs[free_index]; self.registers.putAssumeCapacityNoClobber(reg, inst); - log.debug("alloc {} => {*}", .{reg, inst}); + log.debug("alloc {} => {*}", .{ reg, inst }); return reg; } @@ -439,7 +434,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } try branch_stack.append(.{}); - const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: { + const src_data: struct { lbrace_src: usize, rbrace_src: usize, source: []const u8 } = blk: { if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| { const tree = container_scope.file_scope.contents.tree; const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; @@ -570,6 +565,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.dbgSetEpilogueBegin(); } }, + .arm => { + const cc = self.fn_type.fnCallingConvention(); + if (cc != .Naked) { + // push {fp, lr} + // mov fp, sp + // sub sp, sp, #reloc + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.push(.al, .{ .fp, .lr }).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .fp, Instruction.Operand.reg(.sp, Instruction.Operand.Shift.none)).toU32()); + // TODO: prepare stack for local variables + // const backpatch_reloc = try self.code.addManyAsArray(4); + + try self.dbgSetPrologueEnd(); + + try self.genBody(self.mod_fn.analysis.success); + + // Backpatch stack offset + // const stack_end = self.max_end_stack; + // const aligned_stack_end = mem.alignForward(stack_end, self.stack_align); + // mem.writeIntLittle(u32, backpatch_reloc, Instruction.sub(.al, .sp, .sp, Instruction.Operand.imm())); + + try self.dbgSetEpilogueBegin(); + + // mov sp, fp + // pop {fp, pc} + // TODO: return by jumping to this code, use relocations + // mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .sp, Instruction.Operand.reg(.fp, Instruction.Operand.Shift.none)).toU32()); + // mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.pop(.al, .{ .fp, .pc }).toU32()); + } else { + try self.dbgSetPrologueEnd(); + try self.genBody(self.mod_fn.analysis.success); + try self.dbgSetEpilogueBegin(); + } + }, else => { try self.dbgSetPrologueEnd(); try self.genBody(self.mod_fn.analysis.success); @@ -586,7 +614,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const mcv = try self.genFuncInst(inst); if (!inst.isUnused()) { - log.debug("{*} => {}", .{inst, mcv}); + log.debug("{*} => {}", .{ inst, mcv }); const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; try branch.inst_table.putNoClobber(self.gpa, inst, mcv); } @@ -851,7 +879,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue.dead; - + const operand = try self.resolveInst(inst.operand); const info_a = inst.operand.ty.intInfo(self.target.*); const info_b = inst.base.ty.intInfo(self.target.*); @@ -972,10 +1000,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (self.registers.getEntry(toCanonicalReg(reg))) |entry| { entry.value = inst; } - log.debug("reusing {} => {*}", .{reg, inst}); + log.debug("reusing {} => {*}", .{ reg, inst }); }, .stack_offset => |off| { - log.debug("reusing stack offset {} => {*}", .{off, inst}); + log.debug("reusing stack offset {} => {*}", .{ off, inst }); return true; }, else => return false, @@ -1274,7 +1302,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const result = self.args[self.arg_index]; self.arg_index += 1; - const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1]; + const name_with_null = inst.name[0 .. mem.lenZ(inst.name) + 1]; switch (result) { .register => |reg| { self.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), &inst.base); @@ -1461,7 +1489,35 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } }, .arm => { - if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch}); + for (info.args) |mc_arg, arg_i| { + const arg = inst.args[arg_i]; + const arg_mcv = try self.resolveInst(inst.args[arg_i]); + + switch (mc_arg) { + .none => continue, + .undef => unreachable, + .immediate => unreachable, + .unreach => unreachable, + .dead => unreachable, + .embedded_in_code => unreachable, + .memory => unreachable, + .compare_flags_signed => unreachable, + .compare_flags_unsigned => unreachable, + .register => |reg| { + try self.genSetReg(arg.src, reg, arg_mcv); + // TODO interact with the register allocator to mark the instruction as moved. + }, + .stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); + }, + .ptr_stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); + }, + .ptr_embedded_in_code => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); + }, + } + } if (inst.func.cast(ir.Inst.Constant)) |func_inst| { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { @@ -1476,13 +1532,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else unreachable; - // TODO only works with leaf functions - // at the moment, which works fine for - // Hello World, but not for real code - // of course. Add pushing lr to stack - // and popping after call try self.genSetReg(inst.base.src, .lr, .{ .memory = got_addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.blx(.al, .lr).toU32()); + + // TODO: add Instruction.supportedOn + // function for ARM + if (Target.arm.featureSetHas(self.target.cpu.features, .has_v5t)) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.blx(.al, .lr).toU32()); + } else { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .lr, Instruction.Operand.reg(.pc, Instruction.Operand.Shift.none)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bx(.al, .lr).toU32()); + } } else { return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); } @@ -1532,12 +1591,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { const func = func_val.func; const got = &macho_file.sections.items[macho_file.got_section_index.?]; - const ptr_bytes = 8; - const got_addr = @intCast(u32, got.addr + func.owner_decl.link.macho.offset_table_index.? * ptr_bytes); - // ff 14 25 xx xx xx xx call [addr] - try self.code.ensureCapacity(self.code.items.len + 7); - self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); - mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr); + const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); + // Here, we store the got address in %rax, and then call %rax + // movabsq [addr], %rax + try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr }); + // callq *%rax + try self.code.ensureCapacity(self.code.items.len + 2); + self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 }); } else { return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); } @@ -1601,7 +1661,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.zero, 0, .ra).toU32()); }, .arm => { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bx(.al, .lr).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .sp, Instruction.Operand.reg(.fp, Instruction.Operand.Shift.none)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.pop(.al, .{ .fp, .pc }).toU32()); + // TODO: jump to the end with relocation + // // Just add space for an instruction, patch this later + // try self.code.resize(self.code.items.len + 4); + // try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); }, else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), } @@ -1709,7 +1774,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.code.items.len += 4; break :reloc reloc; }, - else => return self.fail(inst.base.src, "TODO implement condbr {}", .{ self.target.cpu.arch }), + else => return self.fail(inst.base.src, "TODO implement condbr {}", .{self.target.cpu.arch}), }; // Capture the state of register and stack allocation state so that we can revert to it. @@ -1789,7 +1854,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } }; - log.debug("consolidating else_entry {*} {}=>{}", .{else_entry.key, else_entry.value, canon_mcv}); + log.debug("consolidating else_entry {*} {}=>{}", .{ else_entry.key, else_entry.value, canon_mcv }); // TODO make sure the destination stack offset / register does not already have something // going on there. try self.setRegOrMem(inst.base.src, else_entry.key.ty, canon_mcv, else_entry.value); @@ -1813,7 +1878,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } }; - log.debug("consolidating then_entry {*} {}=>{}", .{then_entry.key, parent_mcv, then_entry.value}); + log.debug("consolidating then_entry {*} {}=>{}", .{ then_entry.key, parent_mcv, then_entry.value }); // TODO make sure the destination stack offset / register does not already have something // going on there. try self.setRegOrMem(inst.base.src, then_entry.key.ty, parent_mcv, then_entry.value); @@ -1880,7 +1945,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // break instruction will choose a MCValue for the block result and overwrite // this field. Following break instructions will use that MCValue to put their // block results. - .mcv = @bitCast(AnyMCValue, MCValue { .none = {} }), + .mcv = @bitCast(AnyMCValue, MCValue{ .none = {} }), }; defer inst.codegen.relocs.deinit(self.gpa); @@ -2162,7 +2227,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { mem.writeIntLittle(u64, &buf, x_big); // mov DWORD PTR [rbp+offset+4], immediate - self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp + 4}); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp + 4 }); self.code.appendSliceAssumeCapacity(buf[4..8]); // mov DWORD PTR [rbp+offset], immediate @@ -2213,14 +2278,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // least amount of necessary instructions (use // more intelligent rotating) if (x <= math.maxInt(u8)) { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); return; } else if (x <= math.maxInt(u16)) { // TODO Use movw Note: Not supported on // all ARM targets! - - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); } else if (x <= math.maxInt(u32)) { // TODO Use movw and movt Note: Not // supported on all ARM targets! Also TODO @@ -2232,20 +2296,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // orr reg, reg, #0xbb, 24 // orr reg, reg, #0xcc, 16 // orr reg, reg, #0xdd, 8 - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 16), 8)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 24), 4)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 16), 8)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 24), 4)).toU32()); return; } else { return self.fail(src, "ARM registers are 32-bit wide", .{}); } }, + .register => |src_reg| { + // If the registers are the same, nothing to do. + if (src_reg.id() == reg.id()) + return; + + // mov reg, src_reg + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.reg(src_reg, Instruction.Operand.Shift.none)).toU32()); + }, .memory => |addr| { // The value is in memory at a hard-coded address. // If the type is a pointer, it means the pointer address is at this memory location. try self.genSetReg(src, reg, .{ .immediate = addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, reg, Instruction.Offset.none).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, reg, .{ .offset = Instruction.Offset.none }).toU32()); }, else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}), }, @@ -2590,7 +2662,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const decl = payload.decl; const got = &macho_file.sections.items[macho_file.got_section_index.?]; - const got_addr = got.addr + decl.link.macho.offset_table_index.? * ptr_bytes; + const got_addr = got.addr + decl.link.macho.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const decl = payload.decl; @@ -2701,6 +2773,55 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else => return self.fail(src, "TODO implement function parameters for {} on x86_64", .{cc}), } }, + .arm => { + switch (cc) { + .Naked => { + assert(result.args.len == 0); + result.return_value = .{ .unreach = {} }; + result.stack_byte_count = 0; + result.stack_align = 1; + return result; + }, + .Unspecified, .C => { + // ARM Procedure Call Standard, Chapter 6.5 + var ncrn: usize = 0; // Next Core Register Number + var nsaa: u32 = 0; // Next stacked argument address + + for (param_types) |ty, i| { + if (ty.abiAlignment(self.target.*) == 8) { + // Round up NCRN to the next even number + ncrn += ncrn % 2; + } + + const param_size = @intCast(u32, ty.abiSize(self.target.*)); + if (std.math.divCeil(u32, param_size, 4) catch unreachable <= 4 - ncrn) { + if (param_size <= 4) { + result.args[i] = .{ .register = c_abi_int_param_regs[ncrn] }; + ncrn += 1; + } else { + return self.fail(src, "TODO MCValues with multiple registers", .{}); + } + } else if (ncrn < 4 and nsaa == 0) { + return self.fail(src, "TODO MCValues split between registers and stack", .{}); + } else { + ncrn = 4; + if (ty.abiAlignment(self.target.*) == 8) { + if (nsaa % 8 != 0) { + nsaa += 8 - (nsaa % 8); + } + } + + result.args[i] = .{ .stack_offset = nsaa }; + nsaa += param_size; + } + } + + result.stack_byte_count = nsaa; + result.stack_align = 4; + }, + else => return self.fail(src, "TODO implement function parameters for {} on arm", .{cc}), + } + }, else => if (param_types.len != 0) return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}), } @@ -2719,6 +2840,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => return self.fail(src, "TODO implement function return values for {}", .{cc}), }, + .arm => switch (cc) { + .Naked => unreachable, + .Unspecified, .C => { + const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); + if (ret_ty_size <= 4) { + result.return_value = .{ .register = c_abi_int_return_regs[0] }; + } else { + return self.fail(src, "TODO support more return types for ARM backend", .{}); + } + }, + else => return self.fail(src, "TODO implement function return values for {}", .{cc}), + }, else => return self.fail(src, "TODO implement codegen return values for {}", .{self.target.cpu.arch}), } return result; diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig @@ -113,6 +113,13 @@ test "Register.id" { testing.expectEqual(@as(u4, 15), Register.pc.id()); } +/// Program status registers containing flags, mode bits and other +/// vital information +pub const Psr = enum { + cpsr, + spsr, +}; + pub const callee_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3, .r4, .r5, .r6, .r7, .r8, .r10 }; pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 }; pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 }; @@ -135,15 +142,26 @@ pub const Instruction = union(enum) { offset: u12, rd: u4, rn: u4, - l: u1, - w: u1, - b: u1, - u: u1, - p: u1, - i: u1, + load_store: u1, + write_back: u1, + byte_word: u1, + up_down: u1, + pre_post: u1, + imm: u1, fixed: u2 = 0b01, cond: u4, }, + BlockDataTransfer: packed struct { + register_list: u16, + rn: u4, + load_store: u1, + write_back: u1, + psr_or_user: u1, + up_down: u1, + pre_post: u1, + fixed: u3 = 0b100, + cond: u4, + }, Branch: packed struct { offset: u24, link: u1, @@ -235,14 +253,14 @@ pub const Instruction = union(enum) { rs: u4, }, - const Type = enum(u2) { - LogicalLeft, - LogicalRight, - ArithmeticRight, - RotateRight, + pub const Type = enum(u2) { + logical_left, + logical_right, + arithmetic_right, + rotate_right, }; - const none = Shift{ + pub const none = Shift{ .Immediate = .{ .amount = 0, .typ = 0, @@ -338,10 +356,32 @@ pub const Instruction = union(enum) { } }; + /// Represents the register list operand to a block data transfer + /// instruction + pub const RegisterList = packed struct { + r0: bool = false, + r1: bool = false, + r2: bool = false, + r3: bool = false, + r4: bool = false, + r5: bool = false, + r6: bool = false, + r7: bool = false, + r8: bool = false, + r9: bool = false, + r10: bool = false, + r11: bool = false, + r12: bool = false, + r13: bool = false, + r14: bool = false, + r15: bool = false, + }; + pub fn toU32(self: Instruction) u32 { return switch (self) { .DataProcessing => |v| @bitCast(u32, v), .SingleDataTransfer => |v| @bitCast(u32, v), + .BlockDataTransfer => |v| @bitCast(u32, v), .Branch => |v| @bitCast(u32, v), .BranchExchange => |v| @bitCast(u32, v), .SupervisorCall => |v| @bitCast(u32, v), @@ -362,7 +402,7 @@ pub const Instruction = union(enum) { return Instruction{ .DataProcessing = .{ .cond = @enumToInt(cond), - .i = if (op2 == .Immediate) 1 else 0, + .i = @boolToInt(op2 == .Immediate), .opcode = @enumToInt(opcode), .s = s, .rn = rn.id(), @@ -377,10 +417,10 @@ pub const Instruction = union(enum) { rd: Register, rn: Register, offset: Offset, - pre_post: u1, - up_down: u1, + pre_index: bool, + positive: bool, byte_word: u1, - writeback: u1, + write_back: bool, load_store: u1, ) Instruction { return Instruction{ @@ -389,12 +429,36 @@ pub const Instruction = union(enum) { .rn = rn.id(), .rd = rd.id(), .offset = offset.toU12(), - .l = load_store, - .w = writeback, - .b = byte_word, - .u = up_down, - .p = pre_post, - .i = if (offset == .Immediate) 0 else 1, + .load_store = load_store, + .write_back = @boolToInt(write_back), + .byte_word = byte_word, + .up_down = @boolToInt(positive), + .pre_post = @boolToInt(pre_index), + .imm = @boolToInt(offset != .Immediate), + }, + }; + } + + fn blockDataTransfer( + cond: Condition, + rn: Register, + reg_list: RegisterList, + pre_post: u1, + up_down: u1, + psr_or_user: u1, + write_back: bool, + load_store: u1, + ) Instruction { + return Instruction{ + .BlockDataTransfer = .{ + .register_list = @bitCast(u16, reg_list), + .rn = rn.id(), + .load_store = load_store, + .write_back = @boolToInt(write_back), + .psr_or_user = psr_or_user, + .up_down = up_down, + .pre_post = pre_post, + .cond = @enumToInt(cond), }, }; } @@ -442,36 +506,68 @@ pub const Instruction = union(enum) { // Data processing - pub fn @"and"(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .@"and", s, rd, rn, op2); + pub fn @"and"(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", 0, rd, rn, op2); + } + + pub fn ands(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", 1, rd, rn, op2); + } + + pub fn eor(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, 0, rd, rn, op2); + } + + pub fn eors(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, 1, rd, rn, op2); + } + + pub fn sub(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, 0, rd, rn, op2); + } + + pub fn subs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, 1, rd, rn, op2); + } + + pub fn rsb(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsb, 0, rd, rn, op2); + } + + pub fn rsbs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsb, 1, rd, rn, op2); + } + + pub fn add(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .add, 0, rd, rn, op2); } - pub fn eor(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .eor, s, rd, rn, op2); + pub fn adds(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .add, 1, rd, rn, op2); } - pub fn sub(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .sub, s, rd, rn, op2); + pub fn adc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .adc, 0, rd, rn, op2); } - pub fn rsb(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .rsb, s, rd, rn, op2); + pub fn adcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .adc, 1, rd, rn, op2); } - pub fn add(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .add, s, rd, rn, op2); + pub fn sbc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sbc, 0, rd, rn, op2); } - pub fn adc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .adc, s, rd, rn, op2); + pub fn sbcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sbc, 1, rd, rn, op2); } - pub fn sbc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .sbc, s, rd, rn, op2); + pub fn rsc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, 0, rd, rn, op2); } - pub fn rsc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .rsc, s, rd, rn, op2); + pub fn rscs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, 1, rd, rn, op2); } pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction { @@ -490,32 +586,115 @@ pub const Instruction = union(enum) { return dataProcessing(cond, .cmn, 1, .r0, rn, op2); } - pub fn orr(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .orr, s, rd, rn, op2); + pub fn orr(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, 0, rd, rn, op2); + } + + pub fn orrs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, 1, rd, rn, op2); + } + + pub fn mov(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, 0, rd, .r0, op2); } - pub fn mov(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mov, s, rd, .r0, op2); + pub fn movs(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, 1, rd, .r0, op2); } - pub fn bic(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .bic, s, rd, rn, op2); + pub fn bic(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, 0, rd, rn, op2); } - pub fn mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mvn, s, rd, .r0, op2); + pub fn bics(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, 1, rd, rn, op2); + } + + pub fn mvn(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mvn, 0, rd, .r0, op2); + } + + pub fn mvns(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mvn, 1, rd, .r0, op2); + } + + // PSR transfer + + pub fn mrs(cond: Condition, rd: Register, psr: Psr) Instruction { + return dataProcessing(cond, if (psr == .cpsr) .tst else .cmp, 0, rd, .r15, Operand.reg(.r0, Operand.Shift.none)); } // Single data transfer - pub fn ldr(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { - return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 1); + pub const OffsetArgs = struct { + pre_index: bool = true, + positive: bool = true, + offset: Offset, + write_back: bool = false, + }; + + pub fn ldr(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 1); + } + + pub fn ldrb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 1); + } + + pub fn str(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 0); + } + + pub fn strb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 0); + } + + // Block data transfer + + pub fn ldmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 1); + } + + pub fn ldmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 1); } - pub fn str(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { - return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 0); + pub fn ldmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 1); } + pub fn ldmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 1); + } + + pub const ldmfa = ldmda; + pub const ldmea = ldmdb; + pub const ldmed = ldmib; + pub const ldmfd = ldmia; + pub const ldm = ldmia; + + pub fn stmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 0); + } + + pub fn stmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 0); + } + + pub fn stmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 0); + } + + pub fn stmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 0); + } + + pub const stmed = stmda; + pub const stmfd = stmdb; + pub const stmfa = stmib; + pub const stmea = stmia; + pub const stm = stmia; + // Branch pub fn b(cond: Condition, offset: i24) Instruction { @@ -549,6 +728,58 @@ pub const Instruction = union(enum) { pub fn bkpt(imm: u16) Instruction { return breakpoint(imm); } + + // Aliases + + pub fn pop(cond: Condition, args: anytype) Instruction { + if (@typeInfo(@TypeOf(args)) != .Struct) { + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); + } + + if (args.len < 1) { + @compileError("Expected at least one register"); + } else if (args.len == 1) { + const reg = args[0]; + return ldr(cond, reg, .sp, .{ + .pre_index = false, + .positive = true, + .offset = Offset.imm(4), + .write_back = false, + }); + } else { + var register_list: u16 = 0; + inline for (args) |arg| { + const reg = @as(Register, arg); + register_list |= @as(u16, 1) << reg.id(); + } + return ldm(cond, .sp, true, @bitCast(RegisterList, register_list)); + } + } + + pub fn push(cond: Condition, args: anytype) Instruction { + if (@typeInfo(@TypeOf(args)) != .Struct) { + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); + } + + if (args.len < 1) { + @compileError("Expected at least one register"); + } else if (args.len == 1) { + const reg = args[0]; + return str(cond, reg, .sp, .{ + .pre_index = true, + .positive = false, + .offset = Offset.imm(4), + .write_back = true, + }); + } else { + var register_list: u16 = 0; + inline for (args) |arg| { + const reg = @as(Register, arg); + register_list |= @as(u16, 1) << reg.id(); + } + return stmdb(cond, .sp, true, @bitCast(RegisterList, register_list)); + } + } }; test "serialize instructions" { @@ -559,23 +790,31 @@ test "serialize instructions" { const testcases = [_]Testcase{ .{ // add r0, r0, r0 - .inst = Instruction.add(.al, 0, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)), + .inst = Instruction.add(.al, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)), .expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000, }, .{ // mov r4, r2 - .inst = Instruction.mov(.al, 0, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)), + .inst = Instruction.mov(.al, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)), .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010, }, .{ // mov r0, #42 - .inst = Instruction.mov(.al, 0, .r0, Instruction.Operand.imm(42, 0)), + .inst = Instruction.mov(.al, .r0, Instruction.Operand.imm(42, 0)), .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010, }, + .{ // mrs r5, cpsr + .inst = Instruction.mrs(.al, .r5, .cpsr), + .expected = 0b1110_00010_0_001111_0101_000000000000, + }, .{ // ldr r0, [r2, #42] - .inst = Instruction.ldr(.al, .r0, .r2, Instruction.Offset.imm(42)), + .inst = Instruction.ldr(.al, .r0, .r2, .{ + .offset = Instruction.Offset.imm(42), + }), .expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010, }, .{ // str r0, [r3] - .inst = Instruction.str(.al, .r0, .r3, Instruction.Offset.none), + .inst = Instruction.str(.al, .r0, .r3, .{ + .offset = Instruction.Offset.none, + }), .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000, }, .{ // b #12 @@ -598,6 +837,14 @@ test "serialize instructions" { .inst = Instruction.bkpt(42), .expected = 0b1110_0001_0010_000000000010_0111_1010, }, + .{ // stmdb r9, {r0} + .inst = Instruction.stmdb(.al, .r9, false, .{ .r0 = true }), + .expected = 0b1110_100_1_0_0_0_0_1001_0000000000000001, + }, + .{ // ldmea r4!, {r2, r5} + .inst = Instruction.ldmea(.al, .r4, true, .{ .r2 = true, .r5 = true }), + .expected = 0b1110_100_1_0_0_1_1_0100_0000000000100100, + }, }; for (testcases) |case| { @@ -605,3 +852,43 @@ test "serialize instructions" { testing.expectEqual(case.expected, actual); } } + +test "aliases" { + const Testcase = struct { + expected: Instruction, + actual: Instruction, + }; + + const testcases = [_]Testcase{ + .{ // pop { r6 } + .actual = Instruction.pop(.al, .{.r6}), + .expected = Instruction.ldr(.al, .r6, .sp, .{ + .pre_index = false, + .positive = true, + .offset = Instruction.Offset.imm(4), + .write_back = false, + }), + }, + .{ // pop { r1, r5 } + .actual = Instruction.pop(.al, .{ .r1, .r5 }), + .expected = Instruction.ldm(.al, .sp, true, .{ .r1 = true, .r5 = true }), + }, + .{ // push { r3 } + .actual = Instruction.push(.al, .{.r3}), + .expected = Instruction.str(.al, .r3, .sp, .{ + .pre_index = true, + .positive = false, + .offset = Instruction.Offset.imm(4), + .write_back = true, + }), + }, + .{ // push { r0, r2 } + .actual = Instruction.push(.al, .{ .r0, .r2 }), + .expected = Instruction.stmdb(.al, .sp, true, .{ .r0 = true, .r2 = true }), + }, + }; + + for (testcases) |case| { + testing.expectEqual(case.expected.toU32(), case.actual.toU32()); + } +} diff --git a/src/glibc.zig b/src/glibc.zig @@ -689,9 +689,6 @@ pub const BuiltSharedObjects = struct { const all_map_basename = "all.map"; -// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. -// zig fmt: off - pub fn buildSharedObjects(comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); @@ -827,8 +824,9 @@ pub fn buildSharedObjects(comp: *Compilation) !void { if (ver.patch == 0) { const sym_plus_ver = try std.fmt.allocPrint( - arena, "{s}_{d}_{d}", - .{sym_name, ver.major, ver.minor}, + arena, + "{s}_{d}_{d}", + .{ sym_name, ver.major, ver.minor }, ); try zig_body.writer().print( \\.globl {s} @@ -840,13 +838,19 @@ pub fn buildSharedObjects(comp: *Compilation) !void { , .{ sym_plus_ver, sym_plus_ver, - sym_plus_ver, sym_name, at_sign_str, ver.major, ver.minor, + sym_plus_ver, + sym_name, + at_sign_str, + ver.major, + ver.minor, sym_plus_ver, sym_plus_ver, }); } else { - const sym_plus_ver = try std.fmt.allocPrint(arena, "{s}_{d}_{d}_{d}", - .{sym_name, ver.major, ver.minor, ver.patch}, + const sym_plus_ver = try std.fmt.allocPrint( + arena, + "{s}_{d}_{d}_{d}", + .{ sym_name, ver.major, ver.minor, ver.patch }, ); try zig_body.writer().print( \\.globl {s} @@ -858,7 +862,12 @@ pub fn buildSharedObjects(comp: *Compilation) !void { , .{ sym_plus_ver, sym_plus_ver, - sym_plus_ver, sym_name, at_sign_str, ver.major, ver.minor, ver.patch, + sym_plus_ver, + sym_name, + at_sign_str, + ver.major, + ver.minor, + ver.patch, sym_plus_ver, sym_plus_ver, }); diff --git a/src/link.zig b/src/link.zig @@ -45,6 +45,7 @@ pub const Options = struct { program_code_size_hint: u64 = 256 * 1024, entry_addr: ?u64 = null, stack_size_override: ?u64, + image_base_override: ?u64, /// Set to `true` to omit debug info. strip: bool, /// If this is true then this link code is responsible for outputting an object @@ -60,6 +61,7 @@ pub const Options = struct { link_libcpp: bool, function_sections: bool, eh_frame_hdr: bool, + emit_relocs: bool, rdynamic: bool, z_nodelete: bool, z_defs: bool, diff --git a/src/link/Coff.zig b/src/link/Coff.zig @@ -22,10 +22,10 @@ const minimum_text_block_size = 64 * allocation_padding; const section_alignment = 4096; const file_alignment = 512; -const image_base = 0x400_000; +const default_image_base = 0x400_000; const section_table_size = 2 * 40; comptime { - assert(mem.isAligned(image_base, section_alignment)); + assert(mem.isAligned(default_image_base, section_alignment)); } pub const base_tag: link.File.Tag = .coff; @@ -55,7 +55,7 @@ offset_table: std.ArrayListUnmanaged(u64) = .{}, /// Free list of offset table indices offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, -/// Virtual address of the entry point procedure relative to `image_base` +/// Virtual address of the entry point procedure relative to image base. entry_addr: ?u32 = null, /// Absolute virtual address of the text section when the executable is loaded in memory. @@ -183,14 +183,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio self.section_data_offset = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, file_alignment); const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, section_alignment); - self.offset_table_virtual_address = image_base + section_data_relative_virtual_address; + self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address; self.offset_table_size = default_offset_table_size; self.section_table_offset = section_table_offset; - self.text_section_virtual_address = image_base + section_data_relative_virtual_address + section_alignment; + self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment; self.text_section_size = default_size_of_code; // Size of file when loaded in memory - const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - image_base + default_size_of_code, section_alignment); + const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); index += 2; @@ -234,11 +234,11 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio index += 4; // Image base address - mem.writeIntLittle(u32, hdr_data[index..][0..4], image_base); + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); index += 4; } else { // Image base address - mem.writeIntLittle(u64, hdr_data[index..][0..8], image_base); + mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); index += 8; } @@ -328,7 +328,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); index += 4; // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - image_base); + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); index += 4; } else { mem.set(u8, hdr_data[index..][0..8], 0); @@ -354,7 +354,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); index += 4; // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - image_base); + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); index += 4; } else { mem.set(u8, hdr_data[index..][0..8], 0); @@ -601,7 +601,7 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { // Write .text new virtual address self.text_section_virtual_address = self.text_section_virtual_address + va_offset; - mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - image_base); + mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - default_image_base); try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 12); // Fix the VAs in the offset table @@ -716,7 +716,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, } } if (mem.eql(u8, exp.options.name, "_start")) { - self.entry_addr = decl.link.coff.getVAddr(self.*) - image_base; + self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; } else { try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( @@ -754,7 +754,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation) !void { } if (self.base.options.output_mode == .Exe and self.size_of_image_dirty) { - const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - image_base + self.text_section_size, section_alignment); + const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + self.text_section_size, section_alignment); var buf: [4]u8 = undefined; mem.writeIntLittle(u32, &buf, new_size_of_image); try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 56); @@ -832,6 +832,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { } try man.addOptionalFile(module_obj_path); man.hash.addOptional(self.base.options.stack_size_override); + man.hash.addOptional(self.base.options.image_base_override); man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.addListOfBytes(self.base.options.lib_dirs); man.hash.add(self.base.options.is_compiler_rt_or_libc); @@ -914,6 +915,9 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { const stack_size = self.base.options.stack_size_override orelse 16777216; try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); + } if (target.cpu.arch == .i386) { try argv.append("-MACHINE:X86"); diff --git a/src/link/Elf.zig b/src/link/Elf.zig @@ -27,9 +27,6 @@ const Cache = @import("../Cache.zig"); const default_entry_addr = 0x8000000; -// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. -// zig fmt: off - pub const base_tag: File.Tag = .elf; base: File, @@ -273,8 +270,8 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Elf { const ptr_width: PtrWidth = switch (options.target.cpu.arch.ptrBitWidth()) { - 0 ... 32 => .p32, - 33 ... 64 => .p64, + 0...32 => .p32, + 33...64 => .p64, else => return error.UnsupportedELFArchitecture, }; const self = try gpa.create(Elf); @@ -752,40 +749,52 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { // These are LEB encoded but since the values are all less than 127 // we can simply append these bytes. const abbrev_buf = [_]u8{ - abbrev_compile_unit, DW.TAG_compile_unit, DW.CHILDREN_yes, // header - DW.AT_stmt_list, DW.FORM_sec_offset, DW.AT_low_pc, - DW.FORM_addr, DW.AT_high_pc, DW.FORM_addr, - DW.AT_name, DW.FORM_strp, DW.AT_comp_dir, - DW.FORM_strp, DW.AT_producer, DW.FORM_strp, - DW.AT_language, DW.FORM_data2, 0, + abbrev_compile_unit, DW.TAG_compile_unit, DW.CHILDREN_yes, // header + DW.AT_stmt_list, DW.FORM_sec_offset, DW.AT_low_pc, + DW.FORM_addr, DW.AT_high_pc, DW.FORM_addr, + DW.AT_name, DW.FORM_strp, DW.AT_comp_dir, + DW.FORM_strp, DW.AT_producer, DW.FORM_strp, + DW.AT_language, DW.FORM_data2, 0, 0, // table sentinel - abbrev_subprogram, DW.TAG_subprogram, + abbrev_subprogram, + DW.TAG_subprogram, DW.CHILDREN_yes, // header - DW.AT_low_pc, DW.FORM_addr, - DW.AT_high_pc, DW.FORM_data4, DW.AT_type, - DW.FORM_ref4, DW.AT_name, DW.FORM_string, - 0, 0, // table sentinel - abbrev_subprogram_retvoid, - DW.TAG_subprogram, DW.CHILDREN_yes, // header - DW.AT_low_pc, - DW.FORM_addr, DW.AT_high_pc, DW.FORM_data4, - DW.AT_name, DW.FORM_string, 0, + DW.AT_low_pc, + DW.FORM_addr, + DW.AT_high_pc, + DW.FORM_data4, + DW.AT_type, + DW.FORM_ref4, + DW.AT_name, + DW.FORM_string, + 0, 0, // table sentinel + abbrev_subprogram_retvoid, + DW.TAG_subprogram, DW.CHILDREN_yes, // header + DW.AT_low_pc, DW.FORM_addr, + DW.AT_high_pc, DW.FORM_data4, + DW.AT_name, DW.FORM_string, + 0, 0, // table sentinel - abbrev_base_type, DW.TAG_base_type, + abbrev_base_type, + DW.TAG_base_type, DW.CHILDREN_no, // header - DW.AT_encoding, DW.FORM_data1, - DW.AT_byte_size, DW.FORM_data1, DW.AT_name, - DW.FORM_string, 0, 0, // table sentinel - - abbrev_pad1, DW.TAG_unspecified_type, DW.CHILDREN_no, // header - 0, 0, // table sentinel - abbrev_parameter, + DW.AT_encoding, + DW.FORM_data1, + DW.AT_byte_size, + DW.FORM_data1, + DW.AT_name, + DW.FORM_string, 0, 0, // table sentinel + abbrev_pad1, DW.TAG_unspecified_type, DW.CHILDREN_no, // header + 0, 0, // table sentinel + abbrev_parameter, DW.TAG_formal_parameter, DW.CHILDREN_no, // header - DW.AT_location, - DW.FORM_exprloc, DW.AT_type, DW.FORM_ref4, - DW.AT_name, DW.FORM_string, 0, + DW.AT_location, DW.FORM_exprloc, + DW.AT_type, DW.FORM_ref4, + DW.AT_name, DW.FORM_string, + 0, 0, // table sentinel - 0, 0, + 0, + 0, 0, // section sentinel }; @@ -1021,7 +1030,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { 0, // `DW.LNS_set_prologue_end` 0, // `DW.LNS_set_epilogue_begin` 1, // `DW.LNS_set_isa` - 0, // include_directories (none except the compilation unit cwd) }); // file_names[0] @@ -1284,8 +1292,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // We can skip hashing libc and libc++ components that we are in charge of building from Zig // installation sources because they are always a product of the compiler version + target information. man.hash.add(stack_size); + man.hash.addOptional(self.base.options.image_base_override); man.hash.add(gc_sections); man.hash.add(self.base.options.eh_frame_hdr); + man.hash.add(self.base.options.emit_relocs); man.hash.add(self.base.options.rdynamic); man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.addListOfBytes(self.base.options.lib_dirs); @@ -1317,7 +1327,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { var prev_digest_buf: [digest.len]u8 = undefined; const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { - log.debug("ELF LLD new_digest={} readlink error: {}", .{digest, @errorName(err)}); + log.debug("ELF LLD new_digest={} readlink error: {}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -1327,7 +1337,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { self.base.lock = man.toOwnedLock(); return; } - log.debug("ELF LLD prev_digest={} new_digest={}", .{prev_digest, digest}); + log.debug("ELF LLD prev_digest={} new_digest={}", .{ prev_digest, digest }); // We are about to change the output file to be different, so we invalidate the build hash now. directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { @@ -1352,6 +1362,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { try argv.append(try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size})); } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base})); + } + if (self.base.options.linker_script) |linker_script| { try argv.append("-T"); try argv.append(linker_script); @@ -1365,6 +1379,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { try argv.append("--eh-frame-hdr"); } + if (self.base.options.emit_relocs) { + try argv.append("--emit-relocs"); + } + if (self.base.options.rdynamic) { try argv.append("--export-dynamic"); } @@ -1443,10 +1461,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { var test_path = std.ArrayList(u8).init(self.base.allocator); defer test_path.deinit(); for (self.base.options.lib_dirs) |lib_dir_path| { - for (self.base.options.system_libs.items()) |link_lib| { + for (self.base.options.system_libs.items()) |entry| { + const link_lib = entry.key; test_path.shrinkRetainingCapacity(0); const sep = fs.path.sep_str; - try test_path.writer().print("{}" ++ sep ++ "lib{}.so", .{ lib_dir_path, link_lib }); + try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, link_lib }); fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, @@ -1480,9 +1499,9 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (is_dyn_lib) { const soname = self.base.options.override_soname orelse if (self.base.options.version) |ver| - try std.fmt.allocPrint(arena, "lib{}.so.{}", .{self.base.options.root_name, ver.major}) - else - try std.fmt.allocPrint(arena, "lib{}.so", .{self.base.options.root_name}); + try std.fmt.allocPrint(arena, "lib{}.so.{}", .{ self.base.options.root_name, ver.major }) + else + try std.fmt.allocPrint(arena, "lib{}.so", .{self.base.options.root_name}); try argv.append("-soname"); try argv.append(soname); @@ -1605,7 +1624,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { }; defer stdout_context.data.deinit(); const llvm = @import("../llvm.zig"); - const ok = llvm.Link(.ELF, new_argv.ptr, new_argv.len, append_diagnostic, + const ok = llvm.Link( + .ELF, + new_argv.ptr, + new_argv.len, + append_diagnostic, @ptrToInt(&stdout_context), @ptrToInt(&stderr_context), ); @@ -1631,7 +1654,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { - std.log.warn("failed to write cache manifest when linking: {}", .{ @errorName(err) }); + std.log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); }; // We hang on to this lock so that the output file path can be used without // other processes clobbering it. @@ -2866,8 +2889,8 @@ fn dbgLineNeededHeaderBytes(self: Elf) u32 { const root_src_dir_path_len = if (self.base.options.module.?.root_pkg.root_src_directory.path) |p| p.len else 1; // "." return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 + directory_count * 8 + file_name_count * 8 + - // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like - // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. + // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like + // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. root_src_dir_path_len + self.base.options.module.?.root_pkg.root_src_path.len); } @@ -2888,7 +2911,7 @@ fn pwriteDbgLineNops( prev_padding_size: usize, buf: []const u8, next_padding_size: usize, - offset: usize, + offset: u64, ) !void { const tracy = trace(@src()); defer tracy.end(); @@ -2967,7 +2990,7 @@ fn pwriteDbgInfoNops( buf: []const u8, next_padding_size: usize, trailing_zero: bool, - offset: usize, + offset: u64, ) !void { const tracy = trace(@src()); defer tracy.end(); diff --git a/src/link/MachO.zig b/src/link/MachO.zig @@ -27,6 +27,10 @@ const LoadCommand = union(enum) { LinkeditData: macho.linkedit_data_command, Symtab: macho.symtab_command, Dysymtab: macho.dysymtab_command, + DyldInfo: macho.dyld_info_command, + Dylinker: macho.dylinker_command, + Dylib: macho.dylib_command, + EntryPoint: macho.entry_point_command, pub fn cmdsize(self: LoadCommand) u32 { return switch (self) { @@ -34,6 +38,10 @@ const LoadCommand = union(enum) { .LinkeditData => |x| x.cmdsize, .Symtab => |x| x.cmdsize, .Dysymtab => |x| x.cmdsize, + .DyldInfo => |x| x.cmdsize, + .Dylinker => |x| x.cmdsize, + .Dylib => |x| x.cmdsize, + .EntryPoint => |x| x.cmdsize, }; } @@ -43,6 +51,10 @@ const LoadCommand = union(enum) { .LinkeditData => |cmd| writeGeneric(cmd, file, offset), .Symtab => |cmd| writeGeneric(cmd, file, offset), .Dysymtab => |cmd| writeGeneric(cmd, file, offset), + .DyldInfo => |cmd| writeGeneric(cmd, file, offset), + .Dylinker => |cmd| writeGeneric(cmd, file, offset), + .Dylib => |cmd| writeGeneric(cmd, file, offset), + .EntryPoint => |cmd| writeGeneric(cmd, file, offset), }; } @@ -56,30 +68,52 @@ base: File, /// Table of all load commands load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, -segment_cmd_index: ?u16 = null, +/// __PAGEZERO segment +pagezero_segment_cmd_index: ?u16 = null, +/// __TEXT segment +text_segment_cmd_index: ?u16 = null, +/// __DATA segment +data_segment_cmd_index: ?u16 = null, +/// __LINKEDIT segment +linkedit_segment_cmd_index: ?u16 = null, +/// Dyld info +dyld_info_cmd_index: ?u16 = null, +/// Symbol table symtab_cmd_index: ?u16 = null, +/// Dynamic symbol table dysymtab_cmd_index: ?u16 = null, +/// Path to dyld linker +dylinker_cmd_index: ?u16 = null, +/// Path to libSystem +libsystem_cmd_index: ?u16 = null, +/// Data-in-code section of __LINKEDIT segment data_in_code_cmd_index: ?u16 = null, +/// Address to entry point function +function_starts_cmd_index: ?u16 = null, +/// Main/entry point +/// Specifies offset wrt __TEXT segment start address to the main entry point +/// of the binary. +main_cmd_index: ?u16 = null, /// Table of all sections sections: std.ArrayListUnmanaged(macho.section_64) = .{}, -/// __TEXT segment sections +/// __TEXT,__text section text_section_index: ?u16 = null, -cstring_section_index: ?u16 = null, -const_text_section_index: ?u16 = null, -stubs_section_index: ?u16 = null, -stub_helper_section_index: ?u16 = null, -/// __DATA segment sections +/// __DATA,__got section got_section_index: ?u16 = null, -const_data_section_index: ?u16 = null, entry_addr: ?u64 = null, -/// Table of all symbols used. +/// Table of all local symbols /// Internally references string table for names (which are optional). -symbol_table: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +/// Table of all defined global symbols +global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +/// Table of all undefined symbols +undef_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +dyld_stub_binder_index: ?u16 = null, /// Table of symbol names aka the string table. string_table: std.ArrayListUnmanaged(u8) = .{}, @@ -115,19 +149,27 @@ const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; pub const TextBlock = struct { - /// Index into the symbol table - symbol_table_index: ?u32, + /// Each decl always gets a local symbol with the fully qualified name. + /// The vaddr and size are found here directly. + /// The file offset is found by computing the vaddr offset from the section vaddr + /// the symbol references, and adding that to the file offset of the section. + /// If this field is 0, it means the codegen size = 0 and there is no symbol or + /// offset table entry. + local_sym_index: u32, /// Index into offset table - offset_table_index: ?u32, + /// This field is undefined for symbols with size = 0. + offset_table_index: u32, /// Size of this text block + /// Unlike in Elf, we need to store the size of this symbol as part of + /// the TextBlock since macho.nlist_64 lacks this information. size: u64, /// Points to the previous and next neighbours prev: ?*TextBlock, next: ?*TextBlock, pub const empty = TextBlock{ - .symbol_table_index = null, - .offset_table_index = null, + .local_sym_index = 0, + .offset_table_index = undefined, .size = 0, .prev = null, .next = null, @@ -156,6 +198,15 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio self.base.file = file; + // Index 0 is always a null symbol. + try self.local_symbols.append(allocator, .{ + .n_strx = 0, + .n_type = 0, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + switch (options.output_mode) { .Exe => {}, .Obj => {}, @@ -196,88 +247,83 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); + // Unfortunately these have to be buffered and done at the end because MachO does not allow + // mixing local, global and undefined symbols within a symbol table. + try self.writeAllGlobalSymbols(); + try self.writeAllUndefSymbols(); + + try self.writeStringTable(); + switch (self.base.options.output_mode) { .Exe => { - var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); - { - // Specify path to dynamic linker dyld - const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)); - const load_dylinker = [1]macho.dylinker_command{ - .{ - .cmd = macho.LC_LOAD_DYLINKER, - .cmdsize = cmdsize, - .name = @sizeOf(macho.dylinker_command), - }, - }; - - try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), last_cmd_offset); - - const file_offset = last_cmd_offset + @sizeOf(macho.dylinker_command); - try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); - - try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); - last_cmd_offset += cmdsize; + if (self.entry_addr) |addr| { + // Write export trie. + try self.writeExportTrie(); + + // Update LC_MAIN with entry offset + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const main_cmd = &self.load_commands.items[self.main_cmd_index.?].EntryPoint; + main_cmd.entryoff = addr - text_segment.vmaddr; } { - // Link against libSystem - const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)); - // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. - // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0. - const min_version = 0x10000; - const dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files - .current_version = min_version, - .compatibility_version = min_version, - }; - const load_dylib = [1]macho.dylib_command{ - .{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = dylib, - }, - }; - - try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), last_cmd_offset); - - const file_offset = last_cmd_offset + @sizeOf(macho.dylib_command); - try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); - - try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); - last_cmd_offset += cmdsize; + // Update dynamic symbol table. + const nlocals = @intCast(u32, self.local_symbols.items.len); + const nglobals = @intCast(u32, self.global_symbols.items.len); + const nundefs = @intCast(u32, self.undef_symbols.items.len); + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym = nlocals; + dysymtab.iextdefsym = nlocals; + dysymtab.nextdefsym = nglobals; + dysymtab.iundefsym = nlocals + nglobals; + dysymtab.nundefsym = nundefs; } - }, - .Obj => { { - const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - symtab.nsyms = @intCast(u32, self.symbol_table.items.len); - const allocated_size = self.allocatedSize(symtab.stroff); - const needed_size = self.string_table.items.len; - log.debug("allocated_size = 0x{x}, needed_size = 0x{x}\n", .{ allocated_size, needed_size }); - - if (needed_size > allocated_size) { - symtab.strsize = 0; - symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); + // Write path to dyld loader. + var off: usize = @sizeOf(macho.mach_header_64); + for (self.load_commands.items) |cmd| { + if (cmd == .Dylinker) break; + off += cmd.cmdsize(); } - symtab.strsize = @intCast(u32, needed_size); - - log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); - - try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); + const cmd = &self.load_commands.items[self.dylinker_cmd_index.?].Dylinker; + off += cmd.name; + const padding = cmd.cmdsize - @sizeOf(macho.dylinker_command); + log.debug("writing LC_LOAD_DYLINKER padding of size {} at 0x{x}\n", .{ padding, off }); + try self.addPadding(padding, off); + log.debug("writing LC_LOAD_DYLINKER path to dyld at 0x{x}\n", .{off}); + try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), off); } - - var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); - for (self.load_commands.items) |cmd| { - try cmd.write(&self.base.file.?, last_cmd_offset); - last_cmd_offset += cmd.cmdsize(); + { + // Write path to libSystem. + var off: usize = @sizeOf(macho.mach_header_64); + for (self.load_commands.items) |cmd| { + if (cmd == .Dylib) break; + off += cmd.cmdsize(); + } + const cmd = &self.load_commands.items[self.libsystem_cmd_index.?].Dylib; + off += cmd.dylib.name; + const padding = cmd.cmdsize - @sizeOf(macho.dylib_command); + log.debug("writing LC_LOAD_DYLIB padding of size {} at 0x{x}\n", .{ padding, off }); + try self.addPadding(padding, off); + log.debug("writing LC_LOAD_DYLIB path to libSystem at 0x{x}\n", .{off}); + try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); } - const off = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items), off); }, + .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, } + if (self.cmd_table_dirty) try self.writeCmdHeaders(); + + { + // Update symbol table. + const nlocals = @intCast(u32, self.local_symbols.items.len); + const nglobals = @intCast(u32, self.global_symbols.items.len); + const nundefs = @intCast(u32, self.undef_symbols.items.len); + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + symtab.nsyms = nlocals + nglobals + nundefs; + } + if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; @@ -669,32 +715,34 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 { pub fn deinit(self: *MachO) void { self.offset_table.deinit(self.base.allocator); self.string_table.deinit(self.base.allocator); - self.symbol_table.deinit(self.base.allocator); + self.undef_symbols.deinit(self.base.allocator); + self.global_symbols.deinit(self.base.allocator); + self.local_symbols.deinit(self.base.allocator); self.sections.deinit(self.base.allocator); self.load_commands.deinit(self.base.allocator); } pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { - if (decl.link.macho.symbol_table_index) |_| return; + if (decl.link.macho.local_sym_index != 0) return; - try self.symbol_table.ensureCapacity(self.base.allocator, self.symbol_table.items.len + 1); + try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1); try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); - log.debug("allocating symbol index {} for {}\n", .{ self.symbol_table.items.len, decl.name }); - decl.link.macho.symbol_table_index = @intCast(u32, self.symbol_table.items.len); - _ = self.symbol_table.addOneAssumeCapacity(); + log.debug("allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); + decl.link.macho.local_sym_index = @intCast(u32, self.local_symbols.items.len); + _ = self.local_symbols.addOneAssumeCapacity(); decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len); _ = self.offset_table.addOneAssumeCapacity(); - self.symbol_table.items[decl.link.macho.symbol_table_index.?] = .{ + self.local_symbols.items[decl.link.macho.local_sym_index] = .{ .n_strx = 0, .n_type = 0, .n_sect = 0, .n_desc = 0, .n_value = 0, }; - self.offset_table.items[decl.link.macho.offset_table_index.?] = 0; + self.offset_table.items[decl.link.macho.offset_table_index] = 0; } pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { @@ -716,16 +764,14 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { return; }, }; - log.debug("generated code {}\n", .{code}); const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); - const symbol = &self.symbol_table.items[decl.link.macho.symbol_table_index.?]; + const symbol = &self.local_symbols.items[decl.link.macho.local_sym_index]; const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment); log.debug("allocated text block for {} at 0x{x}\n", .{ decl_name, addr }); - log.debug("updated text section {}\n", .{self.sections.items[self.text_section_index.?]}); symbol.* = .{ .n_strx = name_str_index, @@ -734,18 +780,20 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .n_desc = 0, .n_value = addr, }; + self.offset_table.items[decl.link.macho.offset_table_index] = addr; - // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. - const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; - try self.updateDeclExports(module, decl, decl_exports); - try self.writeSymbol(decl.link.macho.symbol_table_index.?); + try self.writeSymbol(decl.link.macho.local_sym_index); + try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); const text_section = self.sections.items[self.text_section_index.?]; const section_offset = symbol.n_value - text_section.addr; const file_offset = text_section.offset + section_offset; - log.debug("file_offset 0x{x}\n", .{file_offset}); try self.base.file.?.pwriteAll(code, file_offset); + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; + try self.updateDeclExports(module, decl, decl_exports); } pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} @@ -759,34 +807,89 @@ pub fn updateDeclExports( const tracy = trace(@src()); defer tracy.end(); - if (decl.link.macho.symbol_table_index == null) return; - - const decl_sym = &self.symbol_table.items[decl.link.macho.symbol_table_index.?]; - // TODO implement - if (exports.len == 0) return; - - const exp = exports[0]; - self.entry_addr = decl_sym.n_value; - decl_sym.n_type |= macho.N_EXT; - exp.link.sym_index = 0; + try self.global_symbols.ensureCapacity(self.base.allocator, self.global_symbols.items.len + exports.len); + if (decl.link.macho.local_sym_index == 0) return; + const decl_sym = &self.local_symbols.items[decl.link.macho.local_sym_index]; + + for (exports) |exp| { + if (exp.options.section) |section_name| { + if (!mem.eql(u8, section_name, "__text")) { + try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); + module.failed_exports.putAssumeCapacityNoClobber( + exp, + try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + ); + continue; + } + } + const n_desc = switch (exp.options.linkage) { + .Internal => macho.REFERENCE_FLAG_PRIVATE_DEFINED, + .Strong => blk: { + if (mem.eql(u8, exp.options.name, "_start")) { + self.entry_addr = decl_sym.n_value; + } + break :blk macho.REFERENCE_FLAG_DEFINED; + }, + .Weak => macho.N_WEAK_REF, + .LinkOnce => { + try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); + module.failed_exports.putAssumeCapacityNoClobber( + exp, + try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), + ); + continue; + }, + }; + const n_type = decl_sym.n_type | macho.N_EXT; + if (exp.link.sym_index) |i| { + const sym = &self.global_symbols.items[i]; + sym.* = .{ + .n_strx = try self.updateString(sym.n_strx, exp.options.name), + .n_type = n_type, + .n_sect = @intCast(u8, self.text_section_index.?) + 1, + .n_desc = n_desc, + .n_value = decl_sym.n_value, + }; + } else { + const name_str_index = try self.makeString(exp.options.name); + _ = self.global_symbols.addOneAssumeCapacity(); + const i = self.global_symbols.items.len - 1; + self.global_symbols.items[i] = .{ + .n_strx = name_str_index, + .n_type = n_type, + .n_sect = @intCast(u8, self.text_section_index.?) + 1, + .n_desc = n_desc, + .n_value = decl_sym.n_value, + }; + + exp.link.sym_index = @intCast(u32, i); + } + } } pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { - return self.symbol_table.items[decl.link.macho.symbol_table_index.?].n_value; + assert(decl.link.macho.local_sym_index != 0); + return self.local_symbols.items[decl.link.macho.local_sym_index].n_value; } pub fn populateMissingMetadata(self: *MachO) !void { - if (self.segment_cmd_index == null) { - self.segment_cmd_index = @intCast(u16, self.load_commands.items.len); + switch (self.base.options.output_mode) { + .Exe => {}, + .Obj => return error.TODOImplementWritingObjFiles, + .Lib => return error.TODOImplementWritingLibFiles, + } + + if (self.pagezero_segment_cmd_index == null) { + self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.base.allocator, .{ .Segment = .{ .cmd = macho.LC_SEGMENT_64, .cmdsize = @sizeOf(macho.segment_command_64), - .segname = makeStaticString(""), + .segname = makeStaticString("__PAGEZERO"), .vmaddr = 0, - .vmsize = 0, + .vmsize = 0x100000000, // size always set to 4GB .fileoff = 0, .filesize = 0, .maxprot = 0, @@ -797,28 +900,34 @@ pub fn populateMissingMetadata(self: *MachO) !void { }); self.cmd_table_dirty = true; } - if (self.symtab_cmd_index == null) { - self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); + if (self.text_segment_cmd_index == null) { + self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + const prot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE; try self.load_commands.append(self.base.allocator, .{ - .Symtab = .{ - .cmd = macho.LC_SYMTAB, - .cmdsize = @sizeOf(macho.symtab_command), - .symoff = 0, - .nsyms = 0, - .stroff = 0, - .strsize = 0, + .Segment = .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__TEXT"), + .vmaddr = 0x100000000, // always starts at 4GB + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = prot, + .initprot = prot, + .nsects = 0, + .flags = 0, }, }); self.cmd_table_dirty = true; } if (self.text_section_index == null) { self.text_section_index = @intCast(u16, self.sections.items.len); - const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment; - segment.cmdsize += @sizeOf(macho.section_64); - segment.nsects += 1; + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + text_segment.cmdsize += @sizeOf(macho.section_64); + text_segment.nsects += 1; - const file_size = self.base.options.program_code_size_hint; - const off = @intCast(u32, self.findFreeSpace(file_size, 1)); + const file_size = mem.alignForwardGeneric(u64, self.base.options.program_code_size_hint, 0x1000); + const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); // TODO maybe findFreeSpace should return u32 directly? const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); @@ -826,10 +935,10 @@ pub fn populateMissingMetadata(self: *MachO) !void { try self.sections.append(self.base.allocator, .{ .sectname = makeStaticString("__text"), .segname = makeStaticString("__TEXT"), - .addr = 0, + .addr = text_segment.vmaddr + off, .size = file_size, .offset = off, - .@"align" = 0x1000, + .@"align" = 12, // 2^12 = 4096 .reloff = 0, .nreloc = 0, .flags = flags, @@ -838,43 +947,256 @@ pub fn populateMissingMetadata(self: *MachO) !void { .reserved3 = 0, }); - segment.vmsize += file_size; - segment.filesize += file_size; - segment.fileoff = off; + text_segment.vmsize = file_size + off; // We add off here since __TEXT segment includes everything prior to __text section. + text_segment.filesize = file_size + off; + } + if (self.data_segment_cmd_index == null) { + self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE; + try self.load_commands.append(self.base.allocator, .{ + .Segment = .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__DATA"), + .vmaddr = text_segment.vmaddr + text_segment.vmsize, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = prot, + .initprot = prot, + .nsects = 0, + .flags = 0, + }, + }); + self.cmd_table_dirty = true; + } + if (self.got_section_index == null) { + self.got_section_index = @intCast(u16, self.sections.items.len); + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + data_segment.cmdsize += @sizeOf(macho.section_64); + data_segment.nsects += 1; + + const file_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); + + log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + + try self.sections.append(self.base.allocator, .{ + .sectname = makeStaticString("__got"), + .segname = makeStaticString("__DATA"), + .addr = data_segment.vmaddr, + .size = file_size, + .offset = off, + .@"align" = 3, // 2^3 = 8 + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); - log.debug("initial text section {}\n", .{self.sections.items[self.text_section_index.?]}); + const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); + data_segment.vmsize = segment_size; + data_segment.filesize = segment_size; + data_segment.fileoff = off; + } + if (self.linkedit_segment_cmd_index == null) { + self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const prot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; + try self.load_commands.append(self.base.allocator, .{ + .Segment = .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__LINKEDIT"), + .vmaddr = data_segment.vmaddr + data_segment.vmsize, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = prot, + .initprot = prot, + .nsects = 0, + .flags = 0, + }, + }); + self.cmd_table_dirty = true; + } + if (self.dyld_info_cmd_index == null) { + self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .DyldInfo = .{ + .cmd = macho.LC_DYLD_INFO_ONLY, + .cmdsize = @sizeOf(macho.dyld_info_command), + .rebase_off = 0, + .rebase_size = 0, + .bind_off = 0, + .bind_size = 0, + .weak_bind_off = 0, + .weak_bind_size = 0, + .lazy_bind_off = 0, + .lazy_bind_size = 0, + .export_off = 0, + .export_size = 0, + }, + }); + self.cmd_table_dirty = true; + } + if (self.symtab_cmd_index == null) { + self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .Symtab = .{ + .cmd = macho.LC_SYMTAB, + .cmdsize = @sizeOf(macho.symtab_command), + .symoff = 0, + .nsyms = 0, + .stroff = 0, + .strsize = 0, + }, + }); + self.cmd_table_dirty = true; + } + if (self.dysymtab_cmd_index == null) { + self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .Dysymtab = .{ + .cmd = macho.LC_DYSYMTAB, + .cmdsize = @sizeOf(macho.dysymtab_command), + .ilocalsym = 0, + .nlocalsym = 0, + .iextdefsym = 0, + .nextdefsym = 0, + .iundefsym = 0, + .nundefsym = 0, + .tocoff = 0, + .ntoc = 0, + .modtaboff = 0, + .nmodtab = 0, + .extrefsymoff = 0, + .nextrefsyms = 0, + .indirectsymoff = 0, + .nindirectsyms = 0, + .extreloff = 0, + .nextrel = 0, + .locreloff = 0, + .nlocrel = 0, + }, + }); + self.cmd_table_dirty = true; + } + if (self.dylinker_cmd_index == null) { + self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len); + const cmdsize = mem.alignForwardGeneric(u64, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH), @sizeOf(u64)); + try self.load_commands.append(self.base.allocator, .{ + .Dylinker = .{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = @intCast(u32, cmdsize), + .name = @sizeOf(macho.dylinker_command), + }, + }); + self.cmd_table_dirty = true; + } + if (self.libsystem_cmd_index == null) { + self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); + const cmdsize = mem.alignForwardGeneric(u64, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), @sizeOf(u64)); + // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. + // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0. + const min_version = 0x10000; + const dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files + .current_version = min_version, + .compatibility_version = min_version, + }; + try self.load_commands.append(self.base.allocator, .{ + .Dylib = .{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = @intCast(u32, cmdsize), + .dylib = dylib, + }, + }); + self.cmd_table_dirty = true; + } + if (self.main_cmd_index == null) { + self.main_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .EntryPoint = .{ + .cmd = macho.LC_MAIN, + .cmdsize = @sizeOf(macho.entry_point_command), + .entryoff = 0x0, + .stacksize = 0, + }, + }); + self.cmd_table_dirty = true; + } + { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + if (dyld_info.export_off == 0) { + const nsyms = self.base.options.symbol_count_hint; + const file_size = @sizeOf(u64) * nsyms; + const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); + log.debug("found export trie free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + dyld_info.export_off = off; + dyld_info.export_size = @intCast(u32, file_size); + + const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); + linkedit.vmsize += segment_size; + linkedit.fileoff = off; + } } { + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; if (symtab.symoff == 0) { - const p_align = @sizeOf(macho.nlist_64); const nsyms = self.base.options.symbol_count_hint; - const file_size = p_align * nsyms; - const off = @intCast(u32, self.findFreeSpace(file_size, p_align)); + const file_size = @sizeOf(macho.nlist_64) * nsyms; + const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); symtab.symoff = off; symtab.nsyms = @intCast(u32, nsyms); + + const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); + linkedit.vmsize += segment_size; } if (symtab.stroff == 0) { try self.string_table.append(self.base.allocator, 0); const file_size = @intCast(u32, self.string_table.items.len); - const off = @intCast(u32, self.findFreeSpace(file_size, 1)); + const off = @intCast(u32, self.findFreeSpace(file_size, 0x1000)); log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); symtab.stroff = off; symtab.strsize = file_size; + + const segment_size = mem.alignForwardGeneric(u64, file_size, 0x1000); + linkedit.vmsize += segment_size; } } + if (self.dyld_stub_binder_index == null) { + self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); + const name = try self.makeString("dyld_stub_binder"); + try self.undef_symbols.append(self.base.allocator, .{ + .n_strx = name, + .n_type = macho.N_UNDF | macho.N_EXT, + .n_sect = 0, + .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER, + .n_value = 0, + }); + } } fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment; const text_section = &self.sections.items[self.text_section_index.?]; const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; var block_placement: ?*TextBlock = null; const addr = blk: { if (self.last_text_block) |last| { - const last_symbol = self.symbol_table.items[last.symbol_table_index.?]; + const last_symbol = self.local_symbols.items[last.local_sym_index]; + // TODO pad out with NOPs and reenable + // const ideal_capacity = last.size * alloc_num / alloc_den; + // const ideal_capacity_end_addr = last_symbol.n_value + ideal_capacity; + // const new_start_addr = mem.alignForwardGeneric(u64, ideal_capacity_end_addr, alignment); const end_addr = last_symbol.n_value + last.size; const new_start_addr = mem.alignForwardGeneric(u64, end_addr, alignment); block_placement = last; @@ -883,22 +1205,15 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, break :blk text_section.addr; } }; - log.debug("computed symbol address 0x{x}\n", .{addr}); const expand_text_section = block_placement == null or block_placement.?.next == null; if (expand_text_section) { const text_capacity = self.allocatedSize(text_section.offset); const needed_size = (addr + new_block_size) - text_section.addr; - log.debug("text capacity 0x{x}, needed size 0x{x}\n", .{ text_capacity, needed_size }); assert(needed_size <= text_capacity); // TODO handle growth self.last_text_block = text_block; - text_section.size = needed_size; - segment.vmsize = needed_size; - segment.filesize = needed_size; - if (alignment < text_section.@"align") { - text_section.@"align" = @intCast(u32, alignment); - } + text_section.size = needed_size; // TODO temp until we pad out with NOPs } text_block.size = new_block_size; @@ -936,22 +1251,27 @@ fn makeString(self: *MachO, bytes: []const u8) !u32 { return @intCast(u32, result); } -fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int { - const size = @intCast(Int, min_size); - if (size % alignment == 0) return size; - - const div = size / alignment; - return (div + 1) * alignment; +fn getString(self: *MachO, str_off: u32) []const u8 { + assert(str_off < self.string_table.items.len); + return mem.spanZ(@ptrCast([*:0]const u8, self.string_table.items.ptr + str_off)); } -fn commandSize(min_size: anytype) u32 { - return alignSize(u32, min_size, @sizeOf(u64)); +fn updateString(self: *MachO, old_str_off: u32, new_name: []const u8) !u32 { + const existing_name = self.getString(old_str_off); + if (mem.eql(u8, existing_name, new_name)) { + return old_str_off; + } + return self.makeString(new_name); } +/// TODO This should not heap allocate, instead it should utilize a fixed size, statically allocated +/// global const array. You could even use pwritev to write the same buffer multiple times with only +/// 1 syscall if you needed to, for example, write 8192 bytes using a buffer of only 4096 bytes. +/// This size parameter should probably be a usize not u64. fn addPadding(self: *MachO, size: u64, file_offset: u64) !void { if (size == 0) return; - const buf = try self.base.allocator.alloc(u8, size); + const buf = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buf); mem.set(u8, buf[0..], 0); @@ -961,11 +1281,8 @@ fn addPadding(self: *MachO, size: u64, file_offset: u64) !void { fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { const hdr_size: u64 = @sizeOf(macho.mach_header_64); - if (start < hdr_size) - return hdr_size; - + if (start < hdr_size) return hdr_size; const end = start + satMul(size, alloc_num) / alloc_den; - { const off = @sizeOf(macho.mach_header_64); var tight_size: u64 = 0; @@ -978,7 +1295,6 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { return test_end; } } - for (self.sections.items) |section| { const increased_size = satMul(section.size, alloc_num) / alloc_den; const test_end = section.offset + increased_size; @@ -986,7 +1302,15 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { return test_end; } } - + if (self.dyld_info_cmd_index) |dyld_info_index| { + const dyld_info = self.load_commands.items[dyld_info_index].DyldInfo; + const tight_size = dyld_info.export_size; + const increased_size = satMul(tight_size, alloc_num) / alloc_den; + const test_end = dyld_info.export_off + increased_size; + if (end > dyld_info.export_off and start < test_end) { + return test_end; + } + } if (self.symtab_cmd_index) |symtab_index| { const symtab = self.load_commands.items[symtab_index].Symtab; { @@ -1005,7 +1329,6 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { } } } - return null; } @@ -1021,6 +1344,10 @@ fn allocatedSize(self: *MachO, start: u64) u64 { if (section.offset <= start) continue; if (section.offset < min_pos) min_pos = section.offset; } + if (self.dyld_info_cmd_index) |dyld_info_index| { + const dyld_info = self.load_commands.items[dyld_info_index].DyldInfo; + if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = dyld_info.export_off; + } if (self.symtab_cmd_index) |symtab_index| { const symtab = self.load_commands.items[symtab_index].Symtab; if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff; @@ -1042,12 +1369,133 @@ fn writeSymbol(self: *MachO, index: usize) !void { defer tracy.end(); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const sym = [1]macho.nlist_64{self.symbol_table.items[index]}; + const sym = [1]macho.nlist_64{self.local_symbols.items[index]}; const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off }); try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); } +fn writeOffsetTableEntry(self: *MachO, index: usize) !void { + const sect = &self.sections.items[self.got_section_index.?]; + const endian = self.base.options.target.cpu.arch.endian(); + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeInt(u64, &buf, self.offset_table.items[index], endian); + const off = sect.offset + @sizeOf(u64) * index; + log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); + try self.base.file.?.pwriteAll(&buf, off); +} + +fn writeAllGlobalSymbols(self: *MachO) !void { + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const off = symtab.symoff + self.local_symbols.items.len * @sizeOf(macho.nlist_64); + const file_size = self.global_symbols.items.len * @sizeOf(macho.nlist_64); + log.debug("writing global symbols from 0x{x} to 0x{x}\n", .{ off, file_size + off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), off); +} + +fn writeAllUndefSymbols(self: *MachO) !void { + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const nlocals = self.local_symbols.items.len; + const nglobals = self.global_symbols.items.len; + const off = symtab.symoff + (nlocals + nglobals) * @sizeOf(macho.nlist_64); + const file_size = self.undef_symbols.items.len * @sizeOf(macho.nlist_64); + log.debug("writing undef symbols from 0x{x} to 0x{x}\n", .{ off, file_size + off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), off); +} + +fn writeExportTrie(self: *MachO) !void { + assert(self.entry_addr != null); + + // TODO implement mechanism for generating a prefix tree of the exported symbols + // single branch export trie + var buf = [_]u8{0} ** 24; + buf[0] = 0; // root node + buf[1] = 1; // 1 branch from root + mem.copy(u8, buf[2..], "_start"); + buf[8] = 0; + buf[9] = 9 + 1; + + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const addr = self.entry_addr.? - text_segment.vmaddr; + const written = try std.debug.leb.writeULEB128Mem(buf[12..], addr); + buf[10] = @intCast(u8, written) + 1; + buf[11] = 0; + + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; + try self.base.file.?.pwriteAll(buf[0..], dyld_info.export_off); +} + +fn writeStringTable(self: *MachO) !void { + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const allocated_size = self.allocatedSize(symtab.stroff); + const needed_size = self.string_table.items.len; + + if (needed_size > allocated_size) { + symtab.strsize = 0; + symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); + } + symtab.strsize = @intCast(u32, needed_size); + + log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + + try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); + + // TODO rework how we preallocate space for the entire __LINKEDIT segment instead of + // doing dynamic updates like this. + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + linkedit.filesize = symtab.stroff + symtab.strsize - linkedit.fileoff; +} + +fn writeCmdHeaders(self: *MachO) !void { + assert(self.cmd_table_dirty); + + // Write all load command headers first. + // Since command sizes are up-to-date and accurate, we will correctly + // leave space for any section headers that any of the segment load + // commands might consist of. + var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); + for (self.load_commands.items) |cmd| { + try cmd.write(&self.base.file.?, last_cmd_offset); + last_cmd_offset += cmd.cmdsize(); + } + { + // write __text section header + const off = if (self.text_segment_cmd_index) |text_segment_index| blk: { + var i: usize = 0; + var cmdsize: usize = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64); + while (i < text_segment_index) : (i += 1) { + cmdsize += self.load_commands.items[i].cmdsize(); + } + break :blk cmdsize; + } else { + // If we've landed in here, we are building a MachO object file, so we have + // only one, noname segment to append this section header to. + return error.TODOImplementWritingObjFiles; + }; + const idx = self.text_section_index.?; + log.debug("writing text section {} at 0x{x}\n", .{ self.sections.items[idx .. idx + 1], off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[idx .. idx + 1]), off); + } + { + // write __got section header + const off = if (self.data_segment_cmd_index) |data_segment_index| blk: { + var i: usize = 0; + var cmdsize: usize = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64); + while (i < data_segment_index) : (i += 1) { + cmdsize += self.load_commands.items[i].cmdsize(); + } + break :blk cmdsize; + } else { + // If we've landed in here, we are building a MachO object file, so we have + // only one, noname segment to append this section header to. + return error.TODOImplementWritingObjFiles; + }; + const idx = self.got_section_index.?; + log.debug("writing got section {} at 0x{x}\n", .{ self.sections.items[idx .. idx + 1], off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items[idx .. idx + 1]), off); + } +} + /// Writes Mach-O file header. /// Should be invoked last as it needs up-to-date values of ncmds and sizeof_cmds bookkeeping /// variables. diff --git a/src/main.zig b/src/main.zig @@ -268,16 +268,19 @@ const usage_build_generic = \\ -T[script], --script [script] Use a custom linker script \\ --version-script [path] Provide a version .map file \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) - \\ --each-lib-rpath Add rpath for each used dynamic library \\ --version [ver] Dynamic library semver \\ -rdynamic Add all symbols to the dynamic symbol table \\ -rpath [path] Add directory to the runtime library search path + \\ -feach-lib-rpath Ensure adding rpath for each used dynamic library + \\ -fno-each-lib-rpath Prevent adding rpath for each used dynamic library \\ --eh-frame-hdr Enable C++ exception handling by passing --eh-frame-hdr to linker + \\ --emit-relocs Enable output of relocation sections for post build tools \\ -dynamic Force output to be dynamically linked \\ -static Force output to be statically linked \\ -Bsymbolic Bind global references locally \\ --subsystem [subsystem] (windows) /SUBSYSTEM:<subsystem> to the linker\n" \\ --stack [size] Override default stack size + \\ --image-base [addr] Set base address for executable image \\ -framework [name] (darwin) link against framework \\ -F[dir] (darwin) add search path for frameworks \\ @@ -434,11 +437,13 @@ fn buildOutputType( var linker_z_defs = false; var test_evented_io = false; var stack_size_override: ?u64 = null; + var image_base_override: ?u64 = null; var use_llvm: ?bool = null; var use_lld: ?bool = null; var use_clang: ?bool = null; var link_eh_frame_hdr = false; - var each_lib_rpath = false; + var link_emit_relocs = false; + var each_lib_rpath: ?bool = null; var libc_paths_file: ?[]const u8 = null; var machine_code_model: std.builtin.CodeModel = .default; var runtime_args_start: ?usize = null; @@ -520,7 +525,7 @@ fn buildOutputType( //} const args = all_args[2..]; var i: usize = 0; - while (i < args.len) : (i += 1) { + args_loop: while (i < args.len) : (i += 1) { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { @@ -528,7 +533,10 @@ fn buildOutputType( return cleanExit(); } else if (mem.eql(u8, arg, "--")) { if (arg_mode == .run) { - runtime_args_start = i + 1; + // The index refers to all_args so skip `zig` `run` + // and `--` + runtime_args_start = i + 3; + break :args_loop; } else { fatal("unexpected end-of-parameter mark: --", .{}); } @@ -626,9 +634,11 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--stack")) { if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); i += 1; - stack_size_override = std.fmt.parseInt(u64, args[i], 10) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); - }; + stack_size_override = parseAnyBaseInt(args[i]); + } else if (mem.eql(u8, arg, "--image-base")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + image_base_override = parseAnyBaseInt(args[i]); } else if (mem.eql(u8, arg, "--name")) { if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); i += 1; @@ -733,8 +743,10 @@ fn buildOutputType( if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); i += 1; override_lib_dir = args[i]; - } else if (mem.eql(u8, arg, "--each-lib-rpath")) { + } else if (mem.eql(u8, arg, "-feach-lib-rpath")) { each_lib_rpath = true; + } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) { + each_lib_rpath = false; } else if (mem.eql(u8, arg, "--enable-cache")) { enable_cache = true; } else if (mem.eql(u8, arg, "--test-cmd-bin")) { @@ -838,6 +850,8 @@ fn buildOutputType( function_sections = true; } else if (mem.eql(u8, arg, "--eh-frame-hdr")) { link_eh_frame_hdr = true; + } else if (mem.eql(u8, arg, "--emit-relocs")) { + link_emit_relocs = true; } else if (mem.eql(u8, arg, "-Bsymbolic")) { linker_bind_global_refs_locally = true; } else if (mem.eql(u8, arg, "--verbose-link")) { @@ -1143,9 +1157,13 @@ fn buildOutputType( if (i >= linker_args.items.len) { fatal("expected linker arg after '{}'", .{arg}); } - stack_size_override = std.fmt.parseInt(u64, linker_args.items[i], 10) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); - }; + stack_size_override = parseAnyBaseInt(linker_args.items[i]); + } else if (mem.eql(u8, arg, "--image-base")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + image_base_override = parseAnyBaseInt(linker_args.items[i]); } else { warn("unsupported linker arg: {}", .{arg}); } @@ -1206,6 +1224,10 @@ fn buildOutputType( fatal("translate-c expects exactly 1 source file (found {})", .{c_source_files.items.len}); } + if (root_src_file == null and arg_mode == .zig_test) { + fatal("one zig source file is required to run `zig test`", .{}); + } + const root_name = if (provided_name) |n| n else blk: { if (arg_mode == .zig_test) { break :blk "test"; @@ -1446,6 +1468,11 @@ fn buildOutputType( cleanup_root_dir = dir; root_pkg_memory.root_src_directory = .{ .path = p, .handle = dir }; root_pkg_memory.root_src_path = try fs.path.relative(arena, p, src_path); + } else if (fs.path.dirname(src_path)) |p| { + const dir = try fs.cwd().openDir(p, .{}); + cleanup_root_dir = dir; + root_pkg_memory.root_src_directory = .{ .path = p, .handle = dir }; + root_pkg_memory.root_src_path = fs.path.basename(src_path); } else { root_pkg_memory.root_src_directory = .{ .path = null, .handle = fs.cwd() }; root_pkg_memory.root_src_path = src_path; @@ -1580,7 +1607,9 @@ fn buildOutputType( .linker_z_nodelete = linker_z_nodelete, .linker_z_defs = linker_z_defs, .link_eh_frame_hdr = link_eh_frame_hdr, + .link_emit_relocs = link_emit_relocs, .stack_size_override = stack_size_override, + .image_base_override = image_base_override, .strip = strip, .single_threaded = single_threaded, .function_sections = function_sections, @@ -2525,7 +2554,7 @@ fn fmtPathFile( const source_code = source_file.readToEndAllocOptions( fmt.gpa, max_src_size, - stat.size, + std.math.cast(usize, stat.size) catch return error.FileTooBig, @alignOf(u8), null, ) catch |err| switch (err) { @@ -3037,3 +3066,18 @@ pub fn cleanExit() void { process.exit(0); } } + +fn parseAnyBaseInt(prefixed_bytes: []const u8) u64 { + const base: u8 = if (mem.startsWith(u8, prefixed_bytes, "0x")) + 16 + else if (mem.startsWith(u8, prefixed_bytes, "0o")) + 8 + else if (mem.startsWith(u8, prefixed_bytes, "0b")) + 2 + else + @as(u8, 10); + const bytes = if (base == 10) prefixed_bytes else prefixed_bytes[2..]; + return std.fmt.parseInt(u64, bytes, base) catch |err| { + fatal("unable to parse '{}': {}", .{ prefixed_bytes, @errorName(err) }); + }; +} diff --git a/src/mingw.zig b/src/mingw.zig @@ -32,6 +32,8 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { var args = std.ArrayList([]const u8).init(arena); try add_cc_args(comp, arena, &args); try args.appendSlice(&[_][]const u8{ + "-D_SYSCRT=1", + "-DCRTDLL=1", "-U__CRTDLL__", "-D__MSVCRT__", // Uncomment these 3 things for crtu @@ -53,6 +55,8 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { var args = std.ArrayList([]const u8).init(arena); try add_cc_args(comp, arena, &args); try args.appendSlice(&[_][]const u8{ + "-D_SYSCRT=1", + "-DCRTDLL=1", "-U__CRTDLL__", "-D__MSVCRT__", }); @@ -437,11 +441,8 @@ fn findDef(comp: *Compilation, allocator: *Allocator, lib_name: []const u8) ![]u const lib_path = switch (target.cpu.arch) { .i386 => "lib32", .x86_64 => "lib64", - .arm, .armeb => switch (target.cpu.arch.ptrBitWidth()) { - 32 => "libarm32", - 64 => "libarm64", - else => unreachable, - }, + .arm, .armeb, .thumb, .thumbeb, .aarch64_32 => "libarm32", + .aarch64, .aarch64_be => "libarm64", else => unreachable, }; diff --git a/src/stage1.zig b/src/stage1.zig @@ -39,7 +39,11 @@ pub export fn main(argc: c_int, argv: [*]const [*:0]const u8) c_int { for (args) |*arg, i| { arg.* = mem.spanZ(argv[i]); } - stage2.mainArgs(gpa, arena, args) catch |err| fatal("{}", .{@errorName(err)}); + if (std.builtin.mode == .Debug) { + stage2.mainArgs(gpa, arena, args) catch unreachable; + } else { + stage2.mainArgs(gpa, arena, args) catch |err| fatal("{}", .{@errorName(err)}); + } return 0; } diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp @@ -18,11 +18,6 @@ #include "target.hpp" #include "tokenizer.hpp" -#ifndef NDEBUG -#define DBG_MACRO_NO_WARNING -#include <dbg.h> -#endif - struct AstNode; struct ZigFn; struct Scope; @@ -1456,6 +1451,7 @@ struct ZigTypeEnum { ContainerLayout layout; ResolveStatus resolve_status; + bool has_explicit_tag_type; bool non_exhaustive; bool resolve_loop_flag; }; @@ -1825,6 +1821,7 @@ enum BuiltinFnId { BuiltinFnIdWasmMemorySize, BuiltinFnIdWasmMemoryGrow, BuiltinFnIdSrc, + BuiltinFnIdReduce, }; struct BuiltinFnEntry { @@ -2440,6 +2437,15 @@ enum AtomicOrder { AtomicOrderSeqCst, }; +// synchronized with code in define_builtin_compile_vars +enum ReduceOp { + ReduceOp_and, + ReduceOp_or, + ReduceOp_xor, + ReduceOp_min, + ReduceOp_max, +}; + // synchronized with the code in define_builtin_compile_vars enum AtomicRmwOp { AtomicRmwOp_xchg, @@ -2549,6 +2555,7 @@ enum IrInstSrcId { IrInstSrcIdEmbedFile, IrInstSrcIdCmpxchg, IrInstSrcIdFence, + IrInstSrcIdReduce, IrInstSrcIdTruncate, IrInstSrcIdIntCast, IrInstSrcIdFloatCast, @@ -2671,6 +2678,7 @@ enum IrInstGenId { IrInstGenIdErrName, IrInstGenIdCmpxchg, IrInstGenIdFence, + IrInstGenIdReduce, IrInstGenIdTruncate, IrInstGenIdShuffleVector, IrInstGenIdSplat, @@ -3520,6 +3528,20 @@ struct IrInstGenFence { AtomicOrder order; }; +struct IrInstSrcReduce { + IrInstSrc base; + + IrInstSrc *op; + IrInstSrc *value; +}; + +struct IrInstGenReduce { + IrInstGen base; + + ReduceOp op; + IrInstGen *value; +}; + struct IrInstSrcTruncate { IrInstSrc base; diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp @@ -1802,10 +1802,18 @@ Error type_allowed_in_extern(CodeGen *g, ZigType *type_entry, bool *result) { } return type_allowed_in_extern(g, child_type, result); } - case ZigTypeIdEnum: - *result = type_entry->data.enumeration.layout == ContainerLayoutExtern || - type_entry->data.enumeration.layout == ContainerLayoutPacked; - return ErrorNone; + case ZigTypeIdEnum: { + if ((err = type_resolve(g, type_entry, ResolveStatusZeroBitsKnown))) + return err; + ZigType *tag_int_type = type_entry->data.enumeration.tag_int_type; + if (type_entry->data.enumeration.has_explicit_tag_type) { + return type_allowed_in_extern(g, tag_int_type, result); + } else { + *result = type_entry->data.enumeration.layout == ContainerLayoutExtern || + type_entry->data.enumeration.layout == ContainerLayoutPacked; + return ErrorNone; + } + } case ZigTypeIdUnion: *result = type_entry->data.unionation.layout == ContainerLayoutExtern || type_entry->data.unionation.layout == ContainerLayoutPacked; @@ -2639,9 +2647,11 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { if (decl_node->type == NodeTypeContainerDecl) { if (decl_node->data.container_decl.init_arg_expr != nullptr) { wanted_tag_int_type = analyze_type_expr(g, scope, decl_node->data.container_decl.init_arg_expr); + enum_type->data.enumeration.has_explicit_tag_type = true; } } else { wanted_tag_int_type = enum_type->data.enumeration.tag_int_type; + enum_type->data.enumeration.has_explicit_tag_type = true; } if (wanted_tag_int_type != nullptr) { @@ -3120,12 +3130,9 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { bool create_enum_type = is_auto_enum || (!is_explicit_enum && want_safety); bool *covered_enum_fields; bool *is_zero_bits = heap::c_allocator.allocate<bool>(field_count); - ZigLLVMDIEnumerator **di_enumerators; if (create_enum_type) { occupied_tag_values.init(field_count); - di_enumerators = heap::c_allocator.allocate<ZigLLVMDIEnumerator*>(field_count); - ZigType *tag_int_type; if (enum_type_node != nullptr) { tag_int_type = analyze_type_expr(g, scope, enum_type_node); @@ -3269,7 +3276,6 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { } if (create_enum_type) { - di_enumerators[i] = ZigLLVMCreateDebugEnumerator(g->dbuilder, buf_ptr(union_field->name), i); union_field->enum_field = &tag_type->data.enumeration.fields[i]; union_field->enum_field->name = union_field->name; union_field->enum_field->decl_index = i; @@ -3336,6 +3342,7 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { gen_field_index += 1; } } + heap::c_allocator.deallocate(is_zero_bits, field_count); bool src_have_tag = is_auto_enum || is_explicit_enum; @@ -3403,6 +3410,7 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { union_type->data.unionation.resolve_status = ResolveStatusInvalid; } } + heap::c_allocator.deallocate(covered_enum_fields, tag_type->data.enumeration.src_field_count); } if (union_type->data.unionation.resolve_status == ResolveStatusInvalid) { diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp @@ -2584,36 +2584,6 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir return nullptr; } -enum class ScalarizePredicate { - // Returns true iff all the elements in the vector are 1. - // Equivalent to folding all the bits with `and`. - All, - // Returns true iff there's at least one element in the vector that is 1. - // Equivalent to folding all the bits with `or`. - Any, -}; - -// Collapses a <N x i1> vector into a single i1 according to the given predicate -static LLVMValueRef scalarize_cmp_result(CodeGen *g, LLVMValueRef val, ScalarizePredicate predicate) { - assert(LLVMGetTypeKind(LLVMTypeOf(val)) == LLVMVectorTypeKind); - LLVMTypeRef scalar_type = LLVMIntType(LLVMGetVectorSize(LLVMTypeOf(val))); - LLVMValueRef casted = LLVMBuildBitCast(g->builder, val, scalar_type, ""); - - switch (predicate) { - case ScalarizePredicate::Any: { - LLVMValueRef all_zeros = LLVMConstNull(scalar_type); - return LLVMBuildICmp(g->builder, LLVMIntNE, casted, all_zeros, ""); - } - case ScalarizePredicate::All: { - LLVMValueRef all_ones = LLVMConstAllOnes(scalar_type); - return LLVMBuildICmp(g->builder, LLVMIntEQ, casted, all_ones, ""); - } - } - - zig_unreachable(); -} - - static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *operand_type, LLVMValueRef val1, LLVMValueRef val2) { @@ -2638,7 +2608,7 @@ static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *operand_type, LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail"); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); + ok_bit = ZigLLVMBuildAndReduce(g->builder, ok_bit); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2669,7 +2639,7 @@ static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *operand_type, LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail"); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); + ok_bit = ZigLLVMBuildAndReduce(g->builder, ok_bit); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2746,7 +2716,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast } if (operand_type->id == ZigTypeIdVector) { - is_zero_bit = scalarize_cmp_result(g, is_zero_bit, ScalarizePredicate::Any); + is_zero_bit = ZigLLVMBuildOrReduce(g->builder, is_zero_bit); } LLVMBasicBlockRef div_zero_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivZeroFail"); @@ -2771,7 +2741,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMValueRef den_is_neg_1 = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, neg_1_value, ""); LLVMValueRef overflow_fail_bit = LLVMBuildAnd(g->builder, num_is_int_min, den_is_neg_1, ""); if (operand_type->id == ZigTypeIdVector) { - overflow_fail_bit = scalarize_cmp_result(g, overflow_fail_bit, ScalarizePredicate::Any); + overflow_fail_bit = ZigLLVMBuildOrReduce(g->builder, overflow_fail_bit); } LLVMBuildCondBr(g->builder, overflow_fail_bit, overflow_fail_block, overflow_ok_block); @@ -2796,7 +2766,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail"); LLVMValueRef ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, floored, result, ""); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); + ok_bit = ZigLLVMBuildAndReduce(g->builder, ok_bit); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2813,7 +2783,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBasicBlockRef end_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivTruncEnd"); LLVMValueRef ltz = LLVMBuildFCmp(g->builder, LLVMRealOLT, val1, zero, ""); if (operand_type->id == ZigTypeIdVector) { - ltz = scalarize_cmp_result(g, ltz, ScalarizePredicate::Any); + ltz = ZigLLVMBuildOrReduce(g->builder, ltz); } LLVMBuildCondBr(g->builder, ltz, ltz_block, gez_block); @@ -2865,7 +2835,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail"); LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, ""); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); + ok_bit = ZigLLVMBuildAndReduce(g->builder, ok_bit); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2929,7 +2899,7 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast } if (operand_type->id == ZigTypeIdVector) { - is_zero_bit = scalarize_cmp_result(g, is_zero_bit, ScalarizePredicate::Any); + is_zero_bit = ZigLLVMBuildOrReduce(g->builder, is_zero_bit); } LLVMBasicBlockRef rem_zero_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemZeroOk"); @@ -2986,7 +2956,7 @@ static void gen_shift_rhs_check(CodeGen *g, ZigType *lhs_type, ZigType *rhs_type LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "CheckOk"); LLVMValueRef less_than_bit = LLVMBuildICmp(g->builder, LLVMIntULT, value, bit_count_value, ""); if (rhs_type->id == ZigTypeIdVector) { - less_than_bit = scalarize_cmp_result(g, less_than_bit, ScalarizePredicate::Any); + less_than_bit = ZigLLVMBuildOrReduce(g->builder, less_than_bit); } LLVMBuildCondBr(g->builder, less_than_bit, ok_block, fail_block); @@ -4415,6 +4385,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn } } LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false); + heap::c_allocator.deallocate(field_types, field_count); LLVMTypeRef ptr_frame_with_args_type = LLVMPointerType(frame_with_args_type, 0); casted_frame = LLVMBuildBitCast(g->builder, frame_result_loc, ptr_frame_with_args_type, ""); @@ -4429,6 +4400,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true), gen_param_values.at(arg_i)); } + gen_param_types.deinit(); if (instruction->modifier == CallModifierAsync) { gen_resume(g, fn_val, frame_result_loc, ResumeIdCall); @@ -4506,6 +4478,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn LLVMValueRef result_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, ""); return LLVMBuildLoad(g->builder, result_ptr, ""); } + } else { + gen_param_types.deinit(); } if (instruction->new_stack == nullptr || instruction->is_async_call_builtin) { @@ -4823,12 +4797,15 @@ static LLVMValueRef ir_render_asm_gen(CodeGen *g, IrExecutableGen *executable, I ret_type = get_llvm_type(g, instruction->base.value->type); } LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false); + heap::c_allocator.deallocate(param_types, input_and_output_count); bool is_volatile = instruction->has_side_effects || (asm_expr->output_list.length == 0); LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(&llvm_template), buf_len(&llvm_template), buf_ptr(&constraint_buf), buf_len(&constraint_buf), is_volatile, false, LLVMInlineAsmDialectATT); - return LLVMBuildCall(g->builder, asm_fn, param_values, (unsigned)input_and_output_count, ""); + LLVMValueRef built_call = LLVMBuildCall(g->builder, asm_fn, param_values, (unsigned)input_and_output_count, ""); + heap::c_allocator.deallocate(param_values, input_and_output_count); + return built_call; } static LLVMValueRef gen_non_null_bit(CodeGen *g, ZigType *maybe_type, LLVMValueRef maybe_handle) { @@ -5081,6 +5058,8 @@ static LLVMValueRef ir_render_phi(CodeGen *g, IrExecutableGen *executable, IrIns incoming_blocks[i] = instruction->incoming_blocks[i]->llvm_exit_block; } LLVMAddIncoming(phi, incoming_values, incoming_blocks, (unsigned)instruction->incoming_count); + heap::c_allocator.deallocate(incoming_values, instruction->incoming_count); + heap::c_allocator.deallocate(incoming_blocks, instruction->incoming_count); return phi; } @@ -5471,6 +5450,50 @@ static LLVMValueRef ir_render_cmpxchg(CodeGen *g, IrExecutableGen *executable, I return result_loc; } +static LLVMValueRef ir_render_reduce(CodeGen *g, IrExecutableGen *executable, IrInstGenReduce *instruction) { + LLVMValueRef value = ir_llvm_value(g, instruction->value); + + ZigType *value_type = instruction->value->value->type; + assert(value_type->id == ZigTypeIdVector); + ZigType *scalar_type = value_type->data.vector.elem_type; + + LLVMValueRef result_val; + switch (instruction->op) { + case ReduceOp_and: + assert(scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdBool); + result_val = ZigLLVMBuildAndReduce(g->builder, value); + break; + case ReduceOp_or: + assert(scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdBool); + result_val = ZigLLVMBuildOrReduce(g->builder, value); + break; + case ReduceOp_xor: + assert(scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdBool); + result_val = ZigLLVMBuildXorReduce(g->builder, value); + break; + case ReduceOp_min: { + if (scalar_type->id == ZigTypeIdInt) { + const bool is_signed = scalar_type->data.integral.is_signed; + result_val = ZigLLVMBuildIntMinReduce(g->builder, value, is_signed); + } else if (scalar_type->id == ZigTypeIdFloat) { + result_val = ZigLLVMBuildFPMinReduce(g->builder, value); + } else zig_unreachable(); + } break; + case ReduceOp_max: { + if (scalar_type->id == ZigTypeIdInt) { + const bool is_signed = scalar_type->data.integral.is_signed; + result_val = ZigLLVMBuildIntMaxReduce(g->builder, value, is_signed); + } else if (scalar_type->id == ZigTypeIdFloat) { + result_val = ZigLLVMBuildFPMaxReduce(g->builder, value); + } else zig_unreachable(); + } break; + default: + zig_unreachable(); + } + + return result_val; +} + static LLVMValueRef ir_render_fence(CodeGen *g, IrExecutableGen *executable, IrInstGenFence *instruction) { LLVMAtomicOrdering atomic_order = to_LLVMAtomicOrdering(instruction->order); LLVMBuildFence(g->builder, atomic_order, false, ""); @@ -6675,6 +6698,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutableGen *executabl return ir_render_cmpxchg(g, executable, (IrInstGenCmpxchg *)instruction); case IrInstGenIdFence: return ir_render_fence(g, executable, (IrInstGenFence *)instruction); + case IrInstGenIdReduce: + return ir_render_reduce(g, executable, (IrInstGenReduce *)instruction); case IrInstGenIdTruncate: return ir_render_truncate(g, executable, (IrInstGenTruncate *)instruction); case IrInstGenIdBoolNot: @@ -7457,10 +7482,14 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n } } if (make_unnamed_struct) { - return LLVMConstStruct(fields, type_entry->data.structure.gen_field_count, + LLVMValueRef unnamed_struct = LLVMConstStruct(fields, type_entry->data.structure.gen_field_count, type_entry->data.structure.layout == ContainerLayoutPacked); + heap::c_allocator.deallocate(fields, type_entry->data.structure.gen_field_count); + return unnamed_struct; } else { - return LLVMConstNamedStruct(get_llvm_type(g, type_entry), fields, type_entry->data.structure.gen_field_count); + LLVMValueRef named_struct = LLVMConstNamedStruct(get_llvm_type(g, type_entry), fields, type_entry->data.structure.gen_field_count); + heap::c_allocator.deallocate(fields, type_entry->data.structure.gen_field_count); + return named_struct; } } case ZigTypeIdArray: @@ -7485,9 +7514,13 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n values[len] = gen_const_val(g, type_entry->data.array.sentinel, ""); } if (make_unnamed_struct) { - return LLVMConstStruct(values, full_len, true); + LLVMValueRef unnamed_struct = LLVMConstStruct(values, full_len, true); + heap::c_allocator.deallocate(values, full_len); + return unnamed_struct; } else { - return LLVMConstArray(element_type_ref, values, (unsigned)full_len); + LLVMValueRef array = LLVMConstArray(element_type_ref, values, (unsigned)full_len); + heap::c_allocator.deallocate(values, full_len); + return array; } } case ConstArraySpecialBuf: { @@ -7509,7 +7542,9 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n ZigValue *elem_value = &const_val->data.x_array.data.s_none.elements[i]; values[i] = gen_const_val(g, elem_value, ""); } - return LLVMConstVector(values, len); + LLVMValueRef vector = LLVMConstVector(values, len); + heap::c_allocator.deallocate(values, len); + return vector; } case ConstArraySpecialBuf: { Buf *buf = const_val->data.x_array.data.s_buf; @@ -7518,7 +7553,9 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n for (uint64_t i = 0; i < len; i += 1) { values[i] = LLVMConstInt(g->builtin_types.entry_u8->llvm_type, buf_ptr(buf)[i], false); } - return LLVMConstVector(values, len); + LLVMValueRef vector = LLVMConstVector(values, len); + heap::c_allocator.deallocate(values, len); + return vector; } } zig_unreachable(); @@ -7740,6 +7777,7 @@ static void generate_error_name_table(CodeGen *g) { } LLVMValueRef err_name_table_init = LLVMConstArray(get_llvm_type(g, str_type), values, (unsigned)g->errors_by_index.length); + heap::c_allocator.deallocate(values, g->errors_by_index.length); g->err_name_table = LLVMAddGlobal(g->module, LLVMTypeOf(err_name_table_init), get_mangled_name(g, buf_ptr(buf_create_from_str("__zig_err_name_table")))); @@ -8631,6 +8669,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdWasmMemorySize, "wasmMemorySize", 1); create_builtin_fn(g, BuiltinFnIdWasmMemoryGrow, "wasmMemoryGrow", 2); create_builtin_fn(g, BuiltinFnIdSrc, "src", 0); + create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2); } static const char *bool_to_str(bool b) { @@ -8816,7 +8855,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { buf_append_str(contents, "/// Deprecated: use `std.Target.current.cpu.arch.endian()`\n"); buf_append_str(contents, "pub const endian = Target.current.cpu.arch.endian();\n"); buf_appendf(contents, "pub const output_mode = OutputMode.Obj;\n"); - buf_appendf(contents, "pub const link_mode = LinkMode.Static;\n"); + buf_appendf(contents, "pub const link_mode = LinkMode.%s;\n", ZIG_LINK_MODE); buf_appendf(contents, "pub const is_test = false;\n"); buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded)); buf_appendf(contents, "pub const abi = Abi.%s;\n", cur_abi); diff --git a/src/stage1/config.h.in b/src/stage1/config.h.in @@ -21,5 +21,6 @@ #define ZIG_CLANG_LIBRARIES "@CLANG_LIBRARIES@" #define ZIG_LLVM_CONFIG_EXE "@LLVM_CONFIG_EXE@" #define ZIG_DIA_GUIDS_LIB "@ZIG_DIA_GUIDS_LIB_ESCAPED@" +#define ZIG_LINK_MODE "@ZIG_LINK_MODE@" #endif diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp @@ -403,6 +403,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcCmpxchg *>(inst)); case IrInstSrcIdFence: return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcFence *>(inst)); + case IrInstSrcIdReduce: + return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcReduce *>(inst)); case IrInstSrcIdTruncate: return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcTruncate *>(inst)); case IrInstSrcIdIntCast: @@ -637,6 +639,8 @@ void destroy_instruction_gen(IrInstGen *inst) { return heap::c_allocator.destroy(reinterpret_cast<IrInstGenCmpxchg *>(inst)); case IrInstGenIdFence: return heap::c_allocator.destroy(reinterpret_cast<IrInstGenFence *>(inst)); + case IrInstGenIdReduce: + return heap::c_allocator.destroy(reinterpret_cast<IrInstGenReduce *>(inst)); case IrInstGenIdTruncate: return heap::c_allocator.destroy(reinterpret_cast<IrInstGenTruncate *>(inst)); case IrInstGenIdShuffleVector: @@ -1312,6 +1316,10 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcFence *) { return IrInstSrcIdFence; } +static constexpr IrInstSrcId ir_inst_id(IrInstSrcReduce *) { + return IrInstSrcIdReduce; +} + static constexpr IrInstSrcId ir_inst_id(IrInstSrcTruncate *) { return IrInstSrcIdTruncate; } @@ -1776,6 +1784,10 @@ static constexpr IrInstGenId ir_inst_id(IrInstGenFence *) { return IrInstGenIdFence; } +static constexpr IrInstGenId ir_inst_id(IrInstGenReduce *) { + return IrInstGenIdReduce; +} + static constexpr IrInstGenId ir_inst_id(IrInstGenTruncate *) { return IrInstGenIdTruncate; } @@ -3503,6 +3515,29 @@ static IrInstGen *ir_build_fence_gen(IrAnalyze *ira, IrInst *source_instr, Atomi return &instruction->base; } +static IrInstSrc *ir_build_reduce(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *op, IrInstSrc *value) { + IrInstSrcReduce *instruction = ir_build_instruction<IrInstSrcReduce>(irb, scope, source_node); + instruction->op = op; + instruction->value = value; + + ir_ref_instruction(op, irb->current_basic_block); + ir_ref_instruction(value, irb->current_basic_block); + + return &instruction->base; +} + +static IrInstGen *ir_build_reduce_gen(IrAnalyze *ira, IrInst *source_instruction, ReduceOp op, IrInstGen *value, ZigType *result_type) { + IrInstGenReduce *instruction = ir_build_inst_gen<IrInstGenReduce>(&ira->new_irb, + source_instruction->scope, source_instruction->source_node); + instruction->base.value->type = result_type; + instruction->op = op; + instruction->value = value; + + ir_ref_inst_gen(value); + + return &instruction->base; +} + static IrInstSrc *ir_build_truncate(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *dest_type, IrInstSrc *target) { @@ -6581,6 +6616,21 @@ static IrInstSrc *ir_gen_builtin_fn_call(IrBuilderSrc *irb, Scope *scope, AstNod IrInstSrc *fence = ir_build_fence(irb, scope, node, arg0_value); return ir_lval_wrap(irb, scope, fence, lval, result_loc); } + case BuiltinFnIdReduce: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstSrc *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_inst_src) + return arg0_value; + + AstNode *arg1_node = node->data.fn_call_expr.params.at(1); + IrInstSrc *arg1_value = ir_gen_node(irb, arg1_node, scope); + if (arg1_value == irb->codegen->invalid_inst_src) + return arg1_value; + + IrInstSrc *reduce = ir_build_reduce(irb, scope, node, arg0_value, arg1_value); + return ir_lval_wrap(irb, scope, reduce, lval, result_loc); + } case BuiltinFnIdDivExact: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -9607,6 +9657,7 @@ static IrInstSrc *ir_gen_continue(IrBuilderSrc *irb, Scope *continue_scope, AstN ScopeRuntime *scope_runtime = runtime_scopes.at(i); ir_mark_gen(ir_build_check_runtime_scope(irb, continue_scope, node, scope_runtime->is_comptime, is_comptime)); } + runtime_scopes.deinit(); IrBasicBlockSrc *dest_block = loop_scope->continue_block; if (!ir_gen_defers_for_block(irb, continue_scope, dest_block->scope, nullptr, nullptr)) @@ -15933,6 +15984,24 @@ static bool ir_resolve_comptime(IrAnalyze *ira, IrInstGen *value, bool *out) { return ir_resolve_bool(ira, value, out); } +static bool ir_resolve_reduce_op(IrAnalyze *ira, IrInstGen *value, ReduceOp *out) { + if (type_is_invalid(value->value->type)) + return false; + + ZigType *reduce_op_type = get_builtin_type(ira->codegen, "ReduceOp"); + + IrInstGen *casted_value = ir_implicit_cast(ira, value, reduce_op_type); + if (type_is_invalid(casted_value->value->type)) + return false; + + ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad); + if (!const_val) + return false; + + *out = (ReduceOp)bigint_as_u32(&const_val->data.x_enum_tag); + return true; +} + static bool ir_resolve_atomic_order(IrAnalyze *ira, IrInstGen *value, AtomicOrder *out) { if (type_is_invalid(value->value->type)) return false; @@ -21527,6 +21596,7 @@ static IrInstGen *ir_analyze_instruction_phi(IrAnalyze *ira, IrInstSrcPhi *phi_i predecessor->instruction_list.append(instrs_to_move.pop()); } predecessor->instruction_list.append(branch_instruction); + instrs_to_move.deinit(); } } @@ -21577,7 +21647,10 @@ static IrInstGen *ir_analyze_instruction_phi(IrAnalyze *ira, IrInstSrcPhi *phi_i } if (new_incoming_blocks.length == 1) { - return new_incoming_values.at(0); + IrInstGen *incoming_value = new_incoming_values.at(0); + new_incoming_blocks.deinit(); + new_incoming_values.deinit(); + return incoming_value; } ZigType *resolved_type = nullptr; @@ -24140,6 +24213,7 @@ static IrInstGen *ir_analyze_container_init_fields(IrAnalyze *ira, IrInst *sourc first_non_const_instruction = result_loc; } } + heap::c_allocator.deallocate(field_assign_nodes, actual_field_count); if (any_missing) return ira->codegen->invalid_inst_gen; @@ -24155,6 +24229,7 @@ static IrInstGen *ir_analyze_container_init_fields(IrAnalyze *ira, IrInst *sourc } } + const_ptrs.deinit(); IrInstGen *result = ir_get_deref(ira, source_instr, result_loc, nullptr); if (is_comptime && !instr_is_comptime(result)) { @@ -25028,7 +25103,7 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, IrInst *source_instr, fields[2]->special = ConstValSpecialStatic; fields[2]->type = ira->codegen->builtin_types.entry_bool; fields[2]->data.x_bool = attrs_type->data.pointer.is_volatile; - // alignment: u32 + // alignment: comptime_int ensure_field_index(result->type, "alignment", 3); fields[3]->type = ira->codegen->builtin_types.entry_num_lit_int; if (attrs_type->data.pointer.explicit_alignment != 0) { @@ -25432,11 +25507,17 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy union_field_val->special = ConstValSpecialStatic; union_field_val->type = type_info_union_field_type; - ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 2); + ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 3); + // field_type: type inner_fields[1]->special = ConstValSpecialStatic; inner_fields[1]->type = ira->codegen->builtin_types.entry_type; inner_fields[1]->data.x_type = union_field->type_entry; + // alignment: comptime_int + inner_fields[2]->special = ConstValSpecialStatic; + inner_fields[2]->type = ira->codegen->builtin_types.entry_num_lit_int; + bigint_init_unsigned(&inner_fields[2]->data.x_bigint, union_field->align); + ZigValue *name = create_const_str_lit(ira->codegen, union_field->name)->data.x_ptr.data.ref.pointee; init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(union_field->name), true); @@ -25503,7 +25584,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy struct_field_val->special = ConstValSpecialStatic; struct_field_val->type = type_info_struct_field_type; - ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 4); + ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 5); inner_fields[1]->special = ConstValSpecialStatic; inner_fields[1]->type = ira->codegen->builtin_types.entry_type; @@ -25519,10 +25600,16 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy } set_optional_payload(inner_fields[2], struct_field->init_val); + // is_comptime: bool inner_fields[3]->special = ConstValSpecialStatic; inner_fields[3]->type = ira->codegen->builtin_types.entry_bool; inner_fields[3]->data.x_bool = struct_field->is_comptime; + // alignment: comptime_int + inner_fields[4]->special = ConstValSpecialStatic; + inner_fields[4]->type = ira->codegen->builtin_types.entry_num_lit_int; + bigint_init_unsigned(&inner_fields[4]->data.x_bigint, struct_field->align); + ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee; init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(struct_field->name), true); @@ -25553,7 +25640,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy result->special = ConstValSpecialStatic; result->type = ir_type_info_get_type(ira, "Fn", nullptr); - ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 5); + ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 6); result->data.x_struct.fields = fields; // calling_convention: TypeInfo.CallingConvention @@ -25561,38 +25648,42 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy fields[0]->special = ConstValSpecialStatic; fields[0]->type = get_builtin_type(ira->codegen, "CallingConvention"); bigint_init_unsigned(&fields[0]->data.x_enum_tag, type_entry->data.fn.fn_type_id.cc); + // alignment: u29 + ensure_field_index(result->type, "alignment", 1); + fields[1]->special = ConstValSpecialStatic; + fields[1]->type = ira->codegen->builtin_types.entry_num_lit_int; + bigint_init_unsigned(&fields[1]->data.x_bigint, type_entry->data.fn.fn_type_id.alignment); // is_generic: bool - ensure_field_index(result->type, "is_generic", 1); + ensure_field_index(result->type, "is_generic", 2); bool is_generic = type_entry->data.fn.is_generic; - fields[1]->special = ConstValSpecialStatic; - fields[1]->type = ira->codegen->builtin_types.entry_bool; - fields[1]->data.x_bool = is_generic; - // is_varargs: bool - ensure_field_index(result->type, "is_var_args", 2); - bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args; fields[2]->special = ConstValSpecialStatic; fields[2]->type = ira->codegen->builtin_types.entry_bool; - fields[2]->data.x_bool = type_entry->data.fn.fn_type_id.is_var_args; - // return_type: ?type - ensure_field_index(result->type, "return_type", 3); + fields[2]->data.x_bool = is_generic; + // is_varargs: bool + ensure_field_index(result->type, "is_var_args", 3); + bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args; fields[3]->special = ConstValSpecialStatic; - fields[3]->type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type); + fields[3]->type = ira->codegen->builtin_types.entry_bool; + fields[3]->data.x_bool = is_varargs; + // return_type: ?type + ensure_field_index(result->type, "return_type", 4); + fields[4]->special = ConstValSpecialStatic; + fields[4]->type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type); if (type_entry->data.fn.fn_type_id.return_type == nullptr) - fields[3]->data.x_optional = nullptr; + fields[4]->data.x_optional = nullptr; else { ZigValue *return_type = ira->codegen->pass1_arena->create<ZigValue>(); return_type->special = ConstValSpecialStatic; return_type->type = ira->codegen->builtin_types.entry_type; return_type->data.x_type = type_entry->data.fn.fn_type_id.return_type; - fields[3]->data.x_optional = return_type; + fields[4]->data.x_optional = return_type; } // args: []TypeInfo.FnArg ZigType *type_info_fn_arg_type = ir_type_info_get_type(ira, "FnArg", nullptr); if ((err = type_resolve(ira->codegen, type_info_fn_arg_type, ResolveStatusSizeKnown))) { zig_unreachable(); } - size_t fn_arg_count = type_entry->data.fn.fn_type_id.param_count - - (is_varargs && type_entry->data.fn.fn_type_id.cc != CallingConventionC); + size_t fn_arg_count = type_entry->data.fn.fn_type_id.param_count; ZigValue *fn_arg_array = ira->codegen->pass1_arena->create<ZigValue>(); fn_arg_array->special = ConstValSpecialStatic; @@ -25600,7 +25691,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy fn_arg_array->data.x_array.special = ConstArraySpecialNone; fn_arg_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(fn_arg_count); - init_const_slice(ira->codegen, fields[4], fn_arg_array, 0, fn_arg_count, false); + init_const_slice(ira->codegen, fields[5], fn_arg_array, 0, fn_arg_count, false); for (size_t fn_arg_index = 0; fn_arg_index < fn_arg_count; fn_arg_index++) { FnTypeParamInfo *fn_param_info = &type_entry->data.fn.fn_type_id.param_info[fn_arg_index]; @@ -25869,8 +25960,9 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI buf_sprintf("sentinels are only allowed on slices and unknown-length pointers")); return ira->codegen->invalid_inst_gen->value->type; } - BigInt *bi = get_const_field_lit_int(ira, source_instr->source_node, payload, "alignment", 3); - if (bi == nullptr) + + BigInt *alignment = get_const_field_lit_int(ira, source_instr->source_node, payload, "alignment", 3); + if (alignment == nullptr) return ira->codegen->invalid_inst_gen->value->type; bool is_const; @@ -25897,7 +25989,7 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI is_const, is_volatile, ptr_len, - bigint_as_u32(bi), + bigint_as_u32(alignment), 0, // bit_offset_in_host 0, // host_int_bytes is_allowzero, @@ -26134,6 +26226,10 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI } if ((err = get_const_field_bool(ira, source_instr->source_node, field_value, "is_comptime", 3, &field->is_comptime))) return ira->codegen->invalid_inst_gen->value->type; + BigInt *alignment = get_const_field_lit_int(ira, source_instr->source_node, field_value, "alignment", 4); + if (alignment == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + field->align = bigint_as_u32(alignment); } return entry; @@ -26151,6 +26247,13 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI ContainerLayout layout = (ContainerLayout)bigint_as_u32(&layout_value->data.x_enum_tag); ZigType *tag_type = get_const_field_meta_type(ira, source_instr->source_node, payload, "tag_type", 1); + if (type_is_invalid(tag_type)) + return ira->codegen->invalid_inst_gen->value->type; + if (tag_type->id != ZigTypeIdInt) { + ir_add_error(ira, source_instr, buf_sprintf( + "TypeInfo.Enum.tag_type must be an integer type, not '%s'", buf_ptr(&tag_type->name))); + return ira->codegen->invalid_inst_gen->value->type; + } ZigValue *fields_value = get_const_field(ira, source_instr->source_node, payload, "fields", 2); if (fields_value == nullptr) @@ -26296,14 +26399,113 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI return ira->codegen->invalid_inst_gen->value->type; field->type_val = type_value; field->type_entry = type_value->data.x_type; + BigInt *alignment = get_const_field_lit_int(ira, source_instr->source_node, field_value, "alignment", 2); + if (alignment == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + field->align = bigint_as_u32(alignment); } return entry; } case ZigTypeIdFn: - case ZigTypeIdBoundFn: - ir_add_error(ira, source_instr, buf_sprintf( - "@Type not available for 'TypeInfo.%s'", type_id_name(tagTypeId))); - return ira->codegen->invalid_inst_gen->value->type; + case ZigTypeIdBoundFn: { + assert(payload->special == ConstValSpecialStatic); + assert(payload->type == ir_type_info_get_type(ira, "Fn", nullptr)); + + ZigValue *cc_value = get_const_field(ira, source_instr->source_node, payload, "calling_convention", 0); + if (cc_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + assert(cc_value->special == ConstValSpecialStatic); + assert(cc_value->type == get_builtin_type(ira->codegen, "CallingConvention")); + CallingConvention cc = (CallingConvention)bigint_as_u32(&cc_value->data.x_enum_tag); + + BigInt *alignment = get_const_field_lit_int(ira, source_instr->source_node, payload, "alignment", 1); + if (alignment == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + + Error err; + bool is_generic; + if ((err = get_const_field_bool(ira, source_instr->source_node, payload, "is_generic", 2, &is_generic))) + return ira->codegen->invalid_inst_gen->value->type; + if (is_generic) { + ir_add_error(ira, source_instr, buf_sprintf("TypeInfo.Fn.is_generic must be false for @Type")); + return ira->codegen->invalid_inst_gen->value->type; + } + + bool is_var_args; + if ((err = get_const_field_bool(ira, source_instr->source_node, payload, "is_var_args", 3, &is_var_args))) + return ira->codegen->invalid_inst_gen->value->type; + if (is_var_args && cc != CallingConventionC) { + ir_add_error(ira, source_instr, buf_sprintf("varargs functions must have C calling convention")); + return ira->codegen->invalid_inst_gen->value->type; + } + + ZigType *return_type = get_const_field_meta_type_optional(ira, source_instr->source_node, payload, "return_type", 4); + if (return_type == nullptr) { + ir_add_error(ira, source_instr, buf_sprintf("TypeInfo.Fn.return_type must be non-null for @Type")); + return ira->codegen->invalid_inst_gen->value->type; + } + + ZigValue *args_value = get_const_field(ira, source_instr->source_node, payload, "args", 5); + if (args_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + assert(args_value->special == ConstValSpecialStatic); + assert(is_slice(args_value->type)); + ZigValue *args_ptr = args_value->data.x_struct.fields[slice_ptr_index]; + ZigValue *args_len_value = args_value->data.x_struct.fields[slice_len_index]; + size_t args_len = bigint_as_usize(&args_len_value->data.x_bigint); + + FnTypeId fn_type_id = {}; + fn_type_id.return_type = return_type; + fn_type_id.param_info = heap::c_allocator.allocate<FnTypeParamInfo>(args_len); + fn_type_id.param_count = args_len; + fn_type_id.next_param_index = args_len; + fn_type_id.is_var_args = is_var_args; + fn_type_id.cc = cc; + fn_type_id.alignment = bigint_as_u32(alignment); + + assert(args_ptr->data.x_ptr.special == ConstPtrSpecialBaseArray); + assert(args_ptr->data.x_ptr.data.base_array.elem_index == 0); + ZigValue *args_arr = args_ptr->data.x_ptr.data.base_array.array_val; + assert(args_arr->special == ConstValSpecialStatic); + assert(args_arr->data.x_array.special == ConstArraySpecialNone); + for (size_t i = 0; i < args_len; i++) { + ZigValue *arg_value = &args_arr->data.x_array.data.s_none.elements[i]; + assert(arg_value->type == ir_type_info_get_type(ira, "FnArg", nullptr)); + FnTypeParamInfo *info = &fn_type_id.param_info[i]; + Error err; + bool is_generic; + if ((err = get_const_field_bool(ira, source_instr->source_node, arg_value, "is_generic", 0, &is_generic))) + return ira->codegen->invalid_inst_gen->value->type; + if (is_generic) { + ir_add_error(ira, source_instr, buf_sprintf("TypeInfo.FnArg.is_generic must be false for @Type")); + return ira->codegen->invalid_inst_gen->value->type; + } + if ((err = get_const_field_bool(ira, source_instr->source_node, arg_value, "is_noalias", 1, &info->is_noalias))) + return ira->codegen->invalid_inst_gen->value->type; + ZigType *type = get_const_field_meta_type_optional( + ira, source_instr->source_node, arg_value, "arg_type", 2); + if (type == nullptr) { + ir_add_error(ira, source_instr, buf_sprintf("TypeInfo.FnArg.arg_type must be non-null for @Type")); + return ira->codegen->invalid_inst_gen->value->type; + } + info->type = type; + } + + ZigType *entry = get_fn_type(ira->codegen, &fn_type_id); + + switch (tagTypeId) { + case ZigTypeIdFn: + return entry; + case ZigTypeIdBoundFn: { + ZigType *bound_fn_entry = new_type_table_entry(ZigTypeIdBoundFn); + bound_fn_entry->name = *buf_sprintf("(bound %s)", buf_ptr(&entry->name)); + bound_fn_entry->data.bound_fn.fn_type = entry; + return bound_fn_entry; + } + default: + zig_unreachable(); + } + } } zig_unreachable(); } @@ -26676,6 +26878,161 @@ static IrInstGen *ir_analyze_instruction_cmpxchg(IrAnalyze *ira, IrInstSrcCmpxch success_order, failure_order, instruction->is_weak, result_loc); } +static ErrorMsg *ir_eval_reduce(IrAnalyze *ira, IrInst *source_instr, ReduceOp op, ZigValue *value, ZigValue *out_value) { + assert(value->type->id == ZigTypeIdVector); + ZigType *scalar_type = value->type->data.vector.elem_type; + const size_t len = value->type->data.vector.len; + assert(len > 0); + + out_value->type = scalar_type; + out_value->special = ConstValSpecialStatic; + + if (scalar_type->id == ZigTypeIdBool) { + ZigValue *first_elem_val = &value->data.x_array.data.s_none.elements[0]; + + bool result = first_elem_val->data.x_bool; + for (size_t i = 1; i < len; i++) { + ZigValue *elem_val = &value->data.x_array.data.s_none.elements[i]; + + switch (op) { + case ReduceOp_and: + result = result && elem_val->data.x_bool; + if (!result) break; // Short circuit + break; + case ReduceOp_or: + result = result || elem_val->data.x_bool; + if (result) break; // Short circuit + break; + case ReduceOp_xor: + result = result != elem_val->data.x_bool; + break; + default: + zig_unreachable(); + } + } + + out_value->data.x_bool = result; + return nullptr; + } + + if (op != ReduceOp_min && op != ReduceOp_max) { + ZigValue *first_elem_val = &value->data.x_array.data.s_none.elements[0]; + + copy_const_val(ira->codegen, out_value, first_elem_val); + + for (size_t i = 1; i < len; i++) { + ZigValue *elem_val = &value->data.x_array.data.s_none.elements[i]; + + IrBinOp bin_op; + switch (op) { + case ReduceOp_and: bin_op = IrBinOpBinAnd; break; + case ReduceOp_or: bin_op = IrBinOpBinOr; break; + case ReduceOp_xor: bin_op = IrBinOpBinXor; break; + default: zig_unreachable(); + } + + ErrorMsg *msg = ir_eval_math_op_scalar(ira, source_instr, scalar_type, + out_value, bin_op, elem_val, out_value); + if (msg != nullptr) + return msg; + } + + return nullptr; + } + + ZigValue *candidate_elem_val = &value->data.x_array.data.s_none.elements[0]; + + ZigValue *dummy_cmp_value = ira->codegen->pass1_arena->create<ZigValue>(); + for (size_t i = 1; i < len; i++) { + ZigValue *elem_val = &value->data.x_array.data.s_none.elements[i]; + + IrBinOp bin_op; + switch (op) { + case ReduceOp_min: bin_op = IrBinOpCmpLessThan; break; + case ReduceOp_max: bin_op = IrBinOpCmpGreaterThan; break; + default: zig_unreachable(); + } + + ErrorMsg *msg = ir_eval_bin_op_cmp_scalar(ira, source_instr, + elem_val, bin_op, candidate_elem_val, dummy_cmp_value); + if (msg != nullptr) + return msg; + + if (dummy_cmp_value->data.x_bool) + candidate_elem_val = elem_val; + } + + ira->codegen->pass1_arena->destroy(dummy_cmp_value); + copy_const_val(ira->codegen, out_value, candidate_elem_val); + + return nullptr; +} + +static IrInstGen *ir_analyze_instruction_reduce(IrAnalyze *ira, IrInstSrcReduce *instruction) { + IrInstGen *op_inst = instruction->op->child; + if (type_is_invalid(op_inst->value->type)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *value_inst = instruction->value->child; + if (type_is_invalid(value_inst->value->type)) + return ira->codegen->invalid_inst_gen; + + ZigType *value_type = value_inst->value->type; + if (value_type->id != ZigTypeIdVector) { + ir_add_error(ira, &value_inst->base, + buf_sprintf("expected vector type, found '%s'", + buf_ptr(&value_type->name))); + return ira->codegen->invalid_inst_gen; + } + + ReduceOp op; + if (!ir_resolve_reduce_op(ira, op_inst, &op)) + return ira->codegen->invalid_inst_gen; + + ZigType *elem_type = value_type->data.vector.elem_type; + switch (elem_type->id) { + case ZigTypeIdInt: + break; + case ZigTypeIdBool: + if (op > ReduceOp_xor) { + ir_add_error(ira, &op_inst->base, + buf_sprintf("invalid operation for '%s' type", + buf_ptr(&elem_type->name))); + return ira->codegen->invalid_inst_gen; + } break; + case ZigTypeIdFloat: + if (op < ReduceOp_min) { + ir_add_error(ira, &op_inst->base, + buf_sprintf("invalid operation for '%s' type", + buf_ptr(&elem_type->name))); + return ira->codegen->invalid_inst_gen; + } break; + default: + // Vectors cannot have child types other than those listed above + zig_unreachable(); + } + + // special case zero bit types + switch (type_has_one_possible_value(ira->codegen, elem_type)) { + case OnePossibleValueInvalid: + return ira->codegen->invalid_inst_gen; + case OnePossibleValueYes: + return ir_const_move(ira, &instruction->base.base, + get_the_one_possible_value(ira->codegen, elem_type)); + case OnePossibleValueNo: + break; + } + + if (instr_is_comptime(value_inst)) { + IrInstGen *result = ir_const(ira, &instruction->base.base, elem_type); + if (ir_eval_reduce(ira, &instruction->base.base, op, value_inst->value, result->value)) + return ira->codegen->invalid_inst_gen; + return result; + } + + return ir_build_reduce_gen(ira, &instruction->base.base, op, value_inst, elem_type); +} + static IrInstGen *ir_analyze_instruction_fence(IrAnalyze *ira, IrInstSrcFence *instruction) { IrInstGen *order_inst = instruction->order->child; if (type_is_invalid(order_inst->value->type)) @@ -29835,6 +30192,7 @@ static IrInstGen *ir_analyze_bit_cast(IrAnalyze *ira, IrInst* source_instr, IrIn buf_write_value_bytes(ira->codegen, buf, val); if ((err = buf_read_value_bytes(ira, ira->codegen, source_instr->source_node, buf, result->value))) return ira->codegen->invalid_inst_gen; + heap::c_allocator.deallocate(buf, src_size_bytes); return result; } @@ -30872,6 +31230,8 @@ static IrInstGen *ir_analyze_instruction_bit_reverse(IrAnalyze *ira, IrInstSrcBi ira->codegen->is_big_endian, int_type->data.integral.is_signed); + heap::c_allocator.deallocate(comptime_buf, buf_size); + heap::c_allocator.deallocate(result_buf, buf_size); return result; } @@ -31424,6 +31784,8 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc return ir_analyze_instruction_cmpxchg(ira, (IrInstSrcCmpxchg *)instruction); case IrInstSrcIdFence: return ir_analyze_instruction_fence(ira, (IrInstSrcFence *)instruction); + case IrInstSrcIdReduce: + return ir_analyze_instruction_reduce(ira, (IrInstSrcReduce *)instruction); case IrInstSrcIdTruncate: return ir_analyze_instruction_truncate(ira, (IrInstSrcTruncate *)instruction); case IrInstSrcIdIntCast: @@ -31811,6 +32173,7 @@ bool ir_inst_gen_has_side_effects(IrInstGen *instruction) { case IrInstGenIdNegation: case IrInstGenIdNegationWrapping: case IrInstGenIdWasmMemorySize: + case IrInstGenIdReduce: return false; case IrInstGenIdAsm: @@ -31980,6 +32343,7 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { case IrInstSrcIdSpillEnd: case IrInstSrcIdWasmMemorySize: case IrInstSrcIdSrc: + case IrInstSrcIdReduce: return false; case IrInstSrcIdAsm: diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp @@ -200,6 +200,8 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { return "SrcCmpxchg"; case IrInstSrcIdFence: return "SrcFence"; + case IrInstSrcIdReduce: + return "SrcReduce"; case IrInstSrcIdTruncate: return "SrcTruncate"; case IrInstSrcIdIntCast: @@ -436,6 +438,8 @@ const char* ir_inst_gen_type_str(IrInstGenId id) { return "GenCmpxchg"; case IrInstGenIdFence: return "GenFence"; + case IrInstGenIdReduce: + return "GenReduce"; case IrInstGenIdTruncate: return "GenTruncate"; case IrInstGenIdBoolNot: @@ -1584,6 +1588,14 @@ static void ir_print_fence(IrPrintSrc *irp, IrInstSrcFence *instruction) { fprintf(irp->f, ")"); } +static void ir_print_reduce(IrPrintSrc *irp, IrInstSrcReduce *instruction) { + fprintf(irp->f, "@reduce("); + ir_print_other_inst_src(irp, instruction->op); + fprintf(irp->f, ", "); + ir_print_other_inst_src(irp, instruction->value); + fprintf(irp->f, ")"); +} + static const char *atomic_order_str(AtomicOrder order) { switch (order) { case AtomicOrderUnordered: return "Unordered"; @@ -1600,6 +1612,23 @@ static void ir_print_fence(IrPrintGen *irp, IrInstGenFence *instruction) { fprintf(irp->f, "fence %s", atomic_order_str(instruction->order)); } +static const char *reduce_op_str(ReduceOp op) { + switch (op) { + case ReduceOp_and: return "And"; + case ReduceOp_or: return "Or"; + case ReduceOp_xor: return "Xor"; + case ReduceOp_min: return "Min"; + case ReduceOp_max: return "Max"; + } + zig_unreachable(); +} + +static void ir_print_reduce(IrPrintGen *irp, IrInstGenReduce *instruction) { + fprintf(irp->f, "@reduce(.%s, ", reduce_op_str(instruction->op)); + ir_print_other_inst_gen(irp, instruction->value); + fprintf(irp->f, ")"); +} + static void ir_print_truncate(IrPrintSrc *irp, IrInstSrcTruncate *instruction) { fprintf(irp->f, "@truncate("); ir_print_other_inst_src(irp, instruction->dest_type); @@ -2749,6 +2778,9 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai case IrInstSrcIdFence: ir_print_fence(irp, (IrInstSrcFence *)instruction); break; + case IrInstSrcIdReduce: + ir_print_reduce(irp, (IrInstSrcReduce *)instruction); + break; case IrInstSrcIdTruncate: ir_print_truncate(irp, (IrInstSrcTruncate *)instruction); break; @@ -3097,6 +3129,9 @@ static void ir_print_inst_gen(IrPrintGen *irp, IrInstGen *instruction, bool trai case IrInstGenIdFence: ir_print_fence(irp, (IrInstGenFence *)instruction); break; + case IrInstGenIdReduce: + ir_print_reduce(irp, (IrInstGenReduce *)instruction); + break; case IrInstGenIdTruncate: ir_print_truncate(irp, (IrInstGenTruncate *)instruction); break; diff --git a/src/stage1/os.cpp b/src/stage1/os.cpp @@ -94,105 +94,6 @@ static clock_serv_t macos_monotonic_clock; extern char **environ; #endif -#if defined(ZIG_OS_POSIX) -static void populate_termination(Termination *term, int status) { - if (WIFEXITED(status)) { - term->how = TerminationIdClean; - term->code = WEXITSTATUS(status); - } else if (WIFSIGNALED(status)) { - term->how = TerminationIdSignaled; - term->code = WTERMSIG(status); - } else if (WIFSTOPPED(status)) { - term->how = TerminationIdStopped; - term->code = WSTOPSIG(status); - } else { - term->how = TerminationIdUnknown; - term->code = status; - } -} - -static void os_spawn_process_posix(ZigList<const char *> &args, Termination *term) { - const char **argv = heap::c_allocator.allocate<const char *>(args.length + 1); - for (size_t i = 0; i < args.length; i += 1) { - argv[i] = args.at(i); - } - argv[args.length] = nullptr; - - pid_t pid; - int rc = posix_spawnp(&pid, args.at(0), nullptr, nullptr, const_cast<char *const*>(argv), environ); - if (rc != 0) { - zig_panic("unable to spawn %s: %s", args.at(0), strerror(rc)); - } - - int status; - waitpid(pid, &status, 0); - populate_termination(term, status); -} -#endif - -#if defined(ZIG_OS_WINDOWS) - -static void os_windows_create_command_line(Buf *command_line, ZigList<const char *> &args) { - buf_resize(command_line, 0); - const char *prefix = "\""; - for (size_t arg_i = 0; arg_i < args.length; arg_i += 1) { - const char *arg = args.at(arg_i); - buf_append_str(command_line, prefix); - prefix = " \""; - size_t arg_len = strlen(arg); - for (size_t c_i = 0; c_i < arg_len; c_i += 1) { - if (arg[c_i] == '\"') { - zig_panic("TODO"); - } - buf_append_char(command_line, arg[c_i]); - } - buf_append_char(command_line, '\"'); - } -} - -static void os_spawn_process_windows(ZigList<const char *> &args, Termination *term) { - Buf command_line = BUF_INIT; - os_windows_create_command_line(&command_line, args); - - PROCESS_INFORMATION piProcInfo = {0}; - STARTUPINFOW siStartInfo = {0}; - siStartInfo.cb = sizeof(STARTUPINFOW); - - Slice<uint8_t> exe_slice = str(args.at(0)); - auto exe_utf16_slice = Slice<WCHAR>::alloc(exe_slice.len + 1); - exe_utf16_slice.ptr[utf8_to_utf16le(exe_utf16_slice.ptr, exe_slice)] = 0; - - auto command_line_utf16 = Slice<WCHAR>::alloc(buf_len(&command_line) + 1); - command_line_utf16.ptr[utf8_to_utf16le(command_line_utf16.ptr, buf_to_slice(&command_line))] = 0; - - BOOL success = CreateProcessW(exe_utf16_slice.ptr, command_line_utf16.ptr, nullptr, nullptr, TRUE, CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, - &siStartInfo, &piProcInfo); - - if (!success) { - zig_panic("CreateProcess failed. exe: %s command_line: %s", args.at(0), buf_ptr(&command_line)); - } - - WaitForSingleObject(piProcInfo.hProcess, INFINITE); - - DWORD exit_code; - if (!GetExitCodeProcess(piProcInfo.hProcess, &exit_code)) { - zig_panic("GetExitCodeProcess failed"); - } - term->how = TerminationIdClean; - term->code = exit_code; -} -#endif - -void os_spawn_process(ZigList<const char *> &args, Termination *term) { -#if defined(ZIG_OS_WINDOWS) - os_spawn_process_windows(args, term); -#elif defined(ZIG_OS_POSIX) - os_spawn_process_posix(args, term); -#else -#error "missing os_spawn_process implementation" -#endif -} - void os_path_dirname(Buf *full_path, Buf *out_dirname) { return os_path_split(full_path, out_dirname, nullptr); } @@ -280,71 +181,27 @@ void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path) { buf_append_buf(out_full_path, basename); } -Error os_path_real(Buf *rel_path, Buf *out_abs_path) { -#if defined(ZIG_OS_WINDOWS) - PathSpace rel_path_space = slice_to_prefixed_file_w(buf_to_slice(rel_path)); - PathSpace out_abs_path_space; - - if (_wfullpath(&out_abs_path_space.data.items[0], &rel_path_space.data.items[0], PATH_MAX_WIDE) == nullptr) { - zig_panic("_wfullpath failed"); - } - utf16le_ptr_to_utf8(out_abs_path, &out_abs_path_space.data.items[0]); - return ErrorNone; -#elif defined(ZIG_OS_POSIX) - buf_resize(out_abs_path, PATH_MAX + 1); - char *result = realpath(buf_ptr(rel_path), buf_ptr(out_abs_path)); - if (!result) { - int err = errno; - if (err == EACCES) { - return ErrorAccess; - } else if (err == ENOENT) { - return ErrorFileNotFound; - } else if (err == ENOMEM) { - return ErrorNoMem; - } else { - return ErrorFileSystem; - } - } - buf_resize(out_abs_path, strlen(buf_ptr(out_abs_path))); - return ErrorNone; -#else -#error "missing os_path_real implementation" -#endif -} - -#if defined(ZIG_OS_WINDOWS) -// Ported from std/os/path.zig -static bool isAbsoluteWindows(Slice<uint8_t> path) { - if (path.ptr[0] == '/') - return true; - - if (path.ptr[0] == '\\') { - return true; - } - if (path.len < 3) { - return false; - } - if (path.ptr[1] == ':') { - if (path.ptr[2] == '/') - return true; - if (path.ptr[2] == '\\') - return true; - } - return false; -} -#endif - -bool os_path_is_absolute(Buf *path) { -#if defined(ZIG_OS_WINDOWS) - return isAbsoluteWindows(buf_to_slice(path)); -#elif defined(ZIG_OS_POSIX) - return buf_ptr(path)[0] == '/'; -#else -#error "missing os_path_is_absolute implementation" -#endif -} -#if defined(ZIG_OS_WINDOWS) +#if defined(ZIG_OS_WINDOWS) +// Ported from std/os/path.zig +static bool isAbsoluteWindows(Slice<uint8_t> path) { + if (path.ptr[0] == '/') + return true; + + if (path.ptr[0] == '\\') { + return true; + } + if (path.len < 3) { + return false; + } + if (path.ptr[1] == ':') { + if (path.ptr[2] == '/') + return true; + if (path.ptr[2] == '\\') + return true; + } + return false; +} enum WindowsPathKind { WindowsPathKindNone, @@ -687,7 +544,7 @@ static Buf os_path_resolve_posix(Buf **paths_ptr, size_t paths_len) { size_t max_size = 0; for (size_t i = 0; i < paths_len; i += 1) { Buf *p = paths_ptr[i]; - if (os_path_is_absolute(p)) { + if (buf_ptr(p)[0] == '/') { first_index = i; have_abs = true; max_size = 0; @@ -748,6 +605,7 @@ static Buf os_path_resolve_posix(Buf **paths_ptr, size_t paths_len) { Buf return_value = BUF_INIT; buf_init_from_mem(&return_value, (char *)result_ptr, result_index); + heap::c_allocator.deallocate(result_ptr, result_len); return return_value; } #endif @@ -786,256 +644,6 @@ Error os_fetch_file(FILE *f, Buf *out_buf) { zig_unreachable(); } -Error os_file_exists(Buf *full_path, bool *result) { -#if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); - *result = GetFileAttributesW(&path_space.data.items[0]) != INVALID_FILE_ATTRIBUTES; - return ErrorNone; -#else - *result = access(buf_ptr(full_path), F_OK) != -1; - return ErrorNone; -#endif -} - -#if defined(ZIG_OS_POSIX) -static Error os_exec_process_posix(ZigList<const char *> &args, - Termination *term, Buf *out_stderr, Buf *out_stdout) -{ - int stdin_pipe[2]; - int stdout_pipe[2]; - int stderr_pipe[2]; - int err_pipe[2]; - - int err; - if ((err = pipe(stdin_pipe))) - zig_panic("pipe failed"); - if ((err = pipe(stdout_pipe))) - zig_panic("pipe failed"); - if ((err = pipe(stderr_pipe))) - zig_panic("pipe failed"); - if ((err = pipe(err_pipe))) - zig_panic("pipe failed"); - - pid_t pid = fork(); - if (pid == -1) - zig_panic("fork failed: %s", strerror(errno)); - if (pid == 0) { - // child - if (dup2(stdin_pipe[0], STDIN_FILENO) == -1) - zig_panic("dup2 failed"); - - if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1) - zig_panic("dup2 failed"); - - if (dup2(stderr_pipe[1], STDERR_FILENO) == -1) - zig_panic("dup2 failed"); - - const char **argv = heap::c_allocator.allocate<const char *>(args.length + 1); - argv[args.length] = nullptr; - for (size_t i = 0; i < args.length; i += 1) { - argv[i] = args.at(i); - } - execvp(argv[0], const_cast<char * const *>(argv)); - Error report_err = ErrorUnexpected; - if (errno == ENOENT) { - report_err = ErrorFileNotFound; - } - if (write(err_pipe[1], &report_err, sizeof(Error)) == -1) { - zig_panic("write failed"); - } - exit(1); - } else { - // parent - close(stdin_pipe[0]); - close(stdin_pipe[1]); - close(stdout_pipe[1]); - close(stderr_pipe[1]); - - int status; - waitpid(pid, &status, 0); - populate_termination(term, status); - - FILE *stdout_f = fdopen(stdout_pipe[0], "rb"); - FILE *stderr_f = fdopen(stderr_pipe[0], "rb"); - Error err1 = os_fetch_file(stdout_f, out_stdout); - Error err2 = os_fetch_file(stderr_f, out_stderr); - - fclose(stdout_f); - fclose(stderr_f); - - if (err1) return err1; - if (err2) return err2; - - Error child_err = ErrorNone; - if (write(err_pipe[1], &child_err, sizeof(Error)) == -1) { - zig_panic("write failed"); - } - close(err_pipe[1]); - if (read(err_pipe[0], &child_err, sizeof(Error)) == -1) { - zig_panic("write failed"); - } - close(err_pipe[0]); - return child_err; - } -} -#endif - -#if defined(ZIG_OS_WINDOWS) - -//static void win32_panic(const char *str) { -// DWORD err = GetLastError(); -// LPSTR messageBuffer = nullptr; -// FormatMessageA( -// FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, -// NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); -// zig_panic(str, messageBuffer); -// LocalFree(messageBuffer); -//} - -static Error os_exec_process_windows(ZigList<const char *> &args, - Termination *term, Buf *out_stderr, Buf *out_stdout) -{ - Buf command_line = BUF_INIT; - os_windows_create_command_line(&command_line, args); - - HANDLE g_hChildStd_IN_Rd = NULL; - HANDLE g_hChildStd_IN_Wr = NULL; - HANDLE g_hChildStd_OUT_Rd = NULL; - HANDLE g_hChildStd_OUT_Wr = NULL; - HANDLE g_hChildStd_ERR_Rd = NULL; - HANDLE g_hChildStd_ERR_Wr = NULL; - - SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) { - zig_panic("StdoutRd CreatePipe"); - } - - if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { - zig_panic("Stdout SetHandleInformation"); - } - - if (!CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr, 0)) { - zig_panic("stderr CreatePipe"); - } - - if (!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0)) { - zig_panic("stderr SetHandleInformation"); - } - - if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) { - zig_panic("Stdin CreatePipe"); - } - - if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) { - zig_panic("Stdin SetHandleInformation"); - } - - - PROCESS_INFORMATION piProcInfo = {0}; - STARTUPINFO siStartInfo = {0}; - siStartInfo.cb = sizeof(STARTUPINFO); - siStartInfo.hStdError = g_hChildStd_ERR_Wr; - siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; - siStartInfo.hStdInput = g_hChildStd_IN_Rd; - siStartInfo.dwFlags |= STARTF_USESTDHANDLES; - - const char *exe = args.at(0); - BOOL success = CreateProcess(exe, buf_ptr(&command_line), nullptr, nullptr, TRUE, 0, nullptr, nullptr, - &siStartInfo, &piProcInfo); - - if (!success) { - if (GetLastError() == ERROR_FILE_NOT_FOUND) { - CloseHandle(piProcInfo.hProcess); - CloseHandle(piProcInfo.hThread); - return ErrorFileNotFound; - } - zig_panic("CreateProcess failed. exe: %s command_line: %s", exe, buf_ptr(&command_line)); - } - - if (!CloseHandle(g_hChildStd_IN_Wr)) { - zig_panic("stdinwr closehandle"); - } - - CloseHandle(g_hChildStd_IN_Rd); - CloseHandle(g_hChildStd_ERR_Wr); - CloseHandle(g_hChildStd_OUT_Wr); - - static const size_t BUF_SIZE = 4 * 1024; - { - DWORD dwRead; - char chBuf[BUF_SIZE]; - - buf_resize(out_stdout, 0); - for (;;) { - success = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUF_SIZE, &dwRead, NULL); - if (!success || dwRead == 0) break; - - buf_append_mem(out_stdout, chBuf, dwRead); - } - CloseHandle(g_hChildStd_OUT_Rd); - } - { - DWORD dwRead; - char chBuf[BUF_SIZE]; - - buf_resize(out_stderr, 0); - for (;;) { - success = ReadFile( g_hChildStd_ERR_Rd, chBuf, BUF_SIZE, &dwRead, NULL); - if (!success || dwRead == 0) break; - - buf_append_mem(out_stderr, chBuf, dwRead); - } - CloseHandle(g_hChildStd_ERR_Rd); - } - - WaitForSingleObject(piProcInfo.hProcess, INFINITE); - - DWORD exit_code; - if (!GetExitCodeProcess(piProcInfo.hProcess, &exit_code)) { - zig_panic("GetExitCodeProcess failed"); - } - term->how = TerminationIdClean; - term->code = exit_code; - - CloseHandle(piProcInfo.hProcess); - CloseHandle(piProcInfo.hThread); - - return ErrorNone; -} -#endif - -Error os_execv(const char *exe, const char **argv) { -#if defined(ZIG_OS_WINDOWS) - return ErrorUnsupportedOperatingSystem; -#else - execv(exe, (char *const *)argv); - switch (errno) { - case ENOMEM: - return ErrorSystemResources; - case EIO: - return ErrorFileSystem; - default: - return ErrorUnexpected; - } -#endif -} - -Error os_exec_process(ZigList<const char *> &args, - Termination *term, Buf *out_stderr, Buf *out_stdout) -{ -#if defined(ZIG_OS_WINDOWS) - return os_exec_process_windows(args, term, out_stderr, out_stdout); -#elif defined(ZIG_OS_POSIX) - return os_exec_process_posix(args, term, out_stderr, out_stdout); -#else -#error "missing os_exec_process implementation" -#endif -} - Error os_write_file(Buf *full_path, Buf *contents) { #if defined(ZIG_OS_WINDOWS) PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); @@ -1074,35 +682,6 @@ static Error copy_open_files(FILE *src_f, FILE *dest_f) { } } -Error os_dump_file(Buf *src_path, FILE *dest_file) { - Error err; - -#if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); - FILE *src_f = _wfopen(&path_space.data.items[0], L"rb"); -#else - FILE *src_f = fopen(buf_ptr(src_path), "rb"); -#endif - if (!src_f) { - int err = errno; - if (err == ENOENT) { - return ErrorFileNotFound; - } else if (err == EACCES || err == EPERM) { - return ErrorAccess; - } else { - return ErrorFileSystem; - } - } - copy_open_files(src_f, dest_file); - if ((err = copy_open_files(src_f, dest_file))) { - fclose(src_f); - return err; - } - - fclose(src_f); - return ErrorNone; -} - #if defined(ZIG_OS_WINDOWS) static void windows_filetime_to_os_timestamp(FILETIME *ft, OsTimeStamp *mtime) { mtime->sec = (((ULONGLONG) ft->dwHighDateTime) << 32) + ft->dwLowDateTime; @@ -1116,88 +695,6 @@ static FILETIME windows_os_timestamp_to_filetime(OsTimeStamp mtime) { } #endif -static Error set_file_times(OsFile file, OsTimeStamp ts) { -#if defined(ZIG_OS_WINDOWS) - FILETIME ft = windows_os_timestamp_to_filetime(ts); - if (SetFileTime(file, nullptr, &ft, &ft) == 0) { - return ErrorUnexpected; - } - return ErrorNone; -#else - struct timespec times[2] = { - { (time_t)ts.sec, (long)ts.nsec }, - { (time_t)ts.sec, (long)ts.nsec }, - }; - if (futimens(file, times) == -1) { - switch (errno) { - case EBADF: - zig_panic("futimens EBADF"); - default: - return ErrorUnexpected; - } - } - return ErrorNone; -#endif -} - -Error os_update_file(Buf *src_path, Buf *dst_path) { - Error err; - - OsFile src_file; - OsFileAttr src_attr; - if ((err = os_file_open_r(src_path, &src_file, &src_attr))) { - return err; - } - - OsFile dst_file; - OsFileAttr dst_attr; - if ((err = os_file_open_w(dst_path, &dst_file, &dst_attr, src_attr.mode))) { - os_file_close(&src_file); - return err; - } - - if (src_attr.size == dst_attr.size && - src_attr.mode == dst_attr.mode && - src_attr.mtime.sec == dst_attr.mtime.sec && - src_attr.mtime.nsec == dst_attr.mtime.nsec) - { - os_file_close(&src_file); - os_file_close(&dst_file); - return ErrorNone; - } -#if defined(ZIG_OS_WINDOWS) - if (SetEndOfFile(dst_file) == 0) { - return ErrorUnexpected; - } -#else - if (ftruncate(dst_file, 0) == -1) { - return ErrorUnexpected; - } -#endif -#if defined(ZIG_OS_WINDOWS) - FILE *src_libc_file = _fdopen(_open_osfhandle((intptr_t)src_file, _O_RDONLY), "rb"); - FILE *dst_libc_file = _fdopen(_open_osfhandle((intptr_t)dst_file, 0), "wb"); -#else - FILE *src_libc_file = fdopen(src_file, "rb"); - FILE *dst_libc_file = fdopen(dst_file, "wb"); -#endif - assert(src_libc_file); - assert(dst_libc_file); - - if ((err = copy_open_files(src_libc_file, dst_libc_file))) { - fclose(src_libc_file); - fclose(dst_libc_file); - return err; - } - if (fflush(dst_libc_file) == -1) { - return ErrorUnexpected; - } - err = set_file_times(dst_file, src_attr.mtime); - fclose(src_libc_file); - fclose(dst_libc_file); - return err; -} - Error os_copy_file(Buf *src_path, Buf *dest_path) { #if defined(ZIG_OS_WINDOWS) PathSpace src_path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); @@ -1358,14 +855,6 @@ bool os_stderr_tty(void) { #endif } -Error os_delete_file(Buf *path) { - if (remove(buf_ptr(path))) { - return ErrorFileSystem; - } else { - return ErrorNone; - } -} - Error os_rename(Buf *src_path, Buf *dest_path) { if (buf_eql_buf(src_path, dest_path)) { return ErrorNone; @@ -1384,30 +873,6 @@ Error os_rename(Buf *src_path, Buf *dest_path) { return ErrorNone; } -OsTimeStamp os_timestamp_calendar(void) { - OsTimeStamp result; -#if defined(ZIG_OS_WINDOWS) - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - windows_filetime_to_os_timestamp(&ft, &result); -#elif defined(__MACH__) - mach_timespec_t mts; - - kern_return_t err = clock_get_time(macos_calendar_clock, &mts); - assert(!err); - - result.sec = mts.tv_sec; - result.nsec = mts.tv_nsec; -#else - struct timespec tms; - clock_gettime(CLOCK_REALTIME, &tms); - - result.sec = tms.tv_sec; - result.nsec = tms.tv_nsec; -#endif - return result; -} - OsTimeStamp os_timestamp_monotonic(void) { OsTimeStamp result; #if defined(ZIG_OS_WINDOWS) @@ -1501,49 +966,8 @@ Error os_make_dir(Buf *path) { #endif } -static void init_rand() { -#if defined(ZIG_OS_WINDOWS) - char bytes[sizeof(unsigned)]; - unsigned seed; - RtlGenRandom(bytes, sizeof(unsigned)); - memcpy(&seed, bytes, sizeof(unsigned)); - srand(seed); -#elif defined(ZIG_OS_LINUX) - unsigned char *ptr_random = (unsigned char*)getauxval(AT_RANDOM); - unsigned seed; - memcpy(&seed, ptr_random, sizeof(seed)); - srand(seed); -#elif defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) - unsigned seed; - size_t len = sizeof(seed); - int mib[2] = { CTL_KERN, KERN_ARND }; - if (sysctl(mib, 2, &seed, &len, NULL, 0) != 0) { - zig_panic("unable to query random data from sysctl"); - } - srand(seed); -#else - int fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC); - if (fd == -1) { - zig_panic("unable to open /dev/urandom"); - } - char bytes[sizeof(unsigned)]; - ssize_t amt_read; - while ((amt_read = read(fd, bytes, sizeof(unsigned))) == -1) { - if (errno == EINTR) continue; - zig_panic("unable to read /dev/urandom"); - } - if (amt_read != sizeof(unsigned)) { - zig_panic("unable to read enough bytes from /dev/urandom"); - } - close(fd); - unsigned seed; - memcpy(&seed, bytes, sizeof(unsigned)); - srand(seed); -#endif -} int os_init(void) { - init_rand(); #if defined(ZIG_OS_WINDOWS) _setmode(fileno(stdout), _O_BINARY); _setmode(fileno(stderr), _O_BINARY); @@ -1580,71 +1004,6 @@ int os_init(void) { return 0; } -Error os_self_exe_path(Buf *out_path) { -#if defined(ZIG_OS_WINDOWS) - PathSpace path_space; - DWORD copied_amt = GetModuleFileNameW(nullptr, &path_space.data.items[0], PATH_MAX_WIDE); - if (copied_amt <= 0) { - return ErrorFileNotFound; - } - utf16le_ptr_to_utf8(out_path, &path_space.data.items[0]); - return ErrorNone; - -#elif defined(ZIG_OS_DARWIN) - // How long is the executable's path? - uint32_t u32_len = 0; - int ret1 = _NSGetExecutablePath(nullptr, &u32_len); - assert(ret1 != 0); - - Buf *tmp = buf_alloc_fixed(u32_len); - - // Fill the executable path. - int ret2 = _NSGetExecutablePath(buf_ptr(tmp), &u32_len); - assert(ret2 == 0); - - // According to libuv project, PATH_MAX*2 works around a libc bug where - // the resolved path is sometimes bigger than PATH_MAX. - buf_resize(out_path, PATH_MAX*2); - char *real_path = realpath(buf_ptr(tmp), buf_ptr(out_path)); - if (!real_path) { - buf_init_from_buf(out_path, tmp); - return ErrorNone; - } - - // Resize out_path for the correct length. - buf_resize(out_path, strlen(buf_ptr(out_path))); - - return ErrorNone; -#elif defined(ZIG_OS_LINUX) - buf_resize(out_path, PATH_MAX); - ssize_t amt = readlink("/proc/self/exe", buf_ptr(out_path), buf_len(out_path)); - if (amt == -1) { - return ErrorUnexpected; - } - buf_resize(out_path, amt); - return ErrorNone; -#elif defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_DRAGONFLY) - buf_resize(out_path, PATH_MAX); - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - size_t cb = PATH_MAX; - if (sysctl(mib, 4, buf_ptr(out_path), &cb, nullptr, 0) != 0) { - return ErrorUnexpected; - } - buf_resize(out_path, cb - 1); - return ErrorNone; -#elif defined(ZIG_OS_NETBSD) - buf_resize(out_path, PATH_MAX); - int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; - size_t cb = PATH_MAX; - if (sysctl(mib, 4, buf_ptr(out_path), &cb, nullptr, 0) != 0) { - return ErrorUnexpected; - } - buf_resize(out_path, cb - 1); - return ErrorNone; -#endif - return ErrorFileNotFound; -} - #define VT_RED "\x1b[31;1m" #define VT_GREEN "\x1b[32;1m" #define VT_CYAN "\x1b[36;1m" @@ -1954,392 +1313,3 @@ PathSpace slice_to_prefixed_file_w(Slice<uint8_t> path) { return path_space; } #endif - -// Ported from std.os.getAppDataDir -Error os_get_app_data_dir(Buf *out_path, const char *appname) { -#if defined(ZIG_OS_WINDOWS) - WCHAR *dir_path_ptr; - switch (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, nullptr, &dir_path_ptr)) { - case S_OK: - // defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr)); - utf16le_ptr_to_utf8(out_path, dir_path_ptr); - CoTaskMemFree(dir_path_ptr); - buf_appendf(out_path, "\\%s", appname); - return ErrorNone; - case E_OUTOFMEMORY: - return ErrorNoMem; - default: - return ErrorUnexpected; - } - zig_unreachable(); -#elif defined(ZIG_OS_DARWIN) - const char *home_dir = getenv("HOME"); - if (home_dir == nullptr) { - // TODO use /etc/passwd - return ErrorFileNotFound; - } - buf_resize(out_path, 0); - buf_appendf(out_path, "%s/Library/Application Support/%s", home_dir, appname); - return ErrorNone; -#elif defined(ZIG_OS_POSIX) - const char *cache_dir = getenv("XDG_CACHE_HOME"); - if (cache_dir == nullptr) { - cache_dir = getenv("HOME"); - if (cache_dir == nullptr) { - // TODO use /etc/passwd - return ErrorFileNotFound; - } - if (cache_dir[0] == 0) { - return ErrorFileNotFound; - } - buf_init_from_str(out_path, cache_dir); - if (buf_ptr(out_path)[buf_len(out_path) - 1] != '/') { - buf_append_char(out_path, '/'); - } - buf_appendf(out_path, ".cache/%s", appname); - } else { - if (cache_dir[0] == 0) { - return ErrorFileNotFound; - } - buf_init_from_str(out_path, cache_dir); - if (buf_ptr(out_path)[buf_len(out_path) - 1] != '/') { - buf_append_char(out_path, '/'); - } - buf_appendf(out_path, "%s", appname); - } - return ErrorNone; -#endif -} - -#if defined(ZIG_OS_LINUX) || defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) -static int self_exe_shared_libs_callback(struct dl_phdr_info *info, size_t size, void *data) { - ZigList<Buf *> *libs = reinterpret_cast< ZigList<Buf *> *>(data); - if (info->dlpi_name[0] == '/') { - libs->append(buf_create_from_str(info->dlpi_name)); - } - return 0; -} -#endif - -Error os_self_exe_shared_libs(ZigList<Buf *> &paths) { -#if defined(ZIG_OS_LINUX) || defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) - paths.resize(0); - dl_iterate_phdr(self_exe_shared_libs_callback, &paths); - return ErrorNone; -#elif defined(ZIG_OS_DARWIN) - paths.resize(0); - uint32_t img_count = _dyld_image_count(); - for (uint32_t i = 0; i != img_count; i += 1) { - const char *name = _dyld_get_image_name(i); - paths.append(buf_create_from_str(name)); - } - return ErrorNone; -#elif defined(ZIG_OS_WINDOWS) - // zig is built statically on windows, so we can return an empty list - paths.resize(0); - return ErrorNone; -#else -#error unimplemented -#endif -} - -Error os_file_open_rw(Buf *full_path, OsFile *out_file, OsFileAttr *attr, bool need_write, uint32_t mode) { -#if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); - HANDLE result = CreateFileW(&path_space.data.items[0], - need_write ? (GENERIC_READ|GENERIC_WRITE) : GENERIC_READ, - need_write ? 0 : FILE_SHARE_READ, - nullptr, - need_write ? OPEN_ALWAYS : OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, nullptr); - - if (result == INVALID_HANDLE_VALUE) { - DWORD err = GetLastError(); - switch (err) { - case ERROR_SHARING_VIOLATION: - return ErrorSharingViolation; - case ERROR_ALREADY_EXISTS: - return ErrorPathAlreadyExists; - case ERROR_FILE_EXISTS: - return ErrorPathAlreadyExists; - case ERROR_FILE_NOT_FOUND: - return ErrorFileNotFound; - case ERROR_PATH_NOT_FOUND: - return ErrorFileNotFound; - case ERROR_ACCESS_DENIED: - return ErrorAccess; - case ERROR_PIPE_BUSY: - return ErrorPipeBusy; - default: - return ErrorUnexpected; - } - } - *out_file = result; - - if (attr != nullptr) { - BY_HANDLE_FILE_INFORMATION file_info; - if (!GetFileInformationByHandle(result, &file_info)) { - CloseHandle(result); - return ErrorUnexpected; - } - windows_filetime_to_os_timestamp(&file_info.ftLastWriteTime, &attr->mtime); - attr->inode = (((uint64_t)file_info.nFileIndexHigh) << 32) | file_info.nFileIndexLow; - attr->mode = 0; - attr->size = (((uint64_t)file_info.nFileSizeHigh) << 32) | file_info.nFileSizeLow; - } - - return ErrorNone; -#else - for (;;) { - int fd = open(buf_ptr(full_path), - need_write ? (O_RDWR|O_CLOEXEC|O_CREAT) : (O_RDONLY|O_CLOEXEC), mode); - if (fd == -1) { - switch (errno) { - case EINTR: - continue; - case EINVAL: - zig_unreachable(); - case EFAULT: - zig_unreachable(); - case EACCES: - case EPERM: - return ErrorAccess; - case EISDIR: - return ErrorIsDir; - case ENOENT: - return ErrorFileNotFound; - default: - return ErrorFileSystem; - } - } - struct stat statbuf; - if (fstat(fd, &statbuf) == -1) { - close(fd); - return ErrorFileSystem; - } - if (S_ISDIR(statbuf.st_mode)) { - close(fd); - return ErrorIsDir; - } - *out_file = fd; - - if (attr != nullptr) { - attr->inode = statbuf.st_ino; -#if defined(ZIG_OS_DARWIN) - attr->mtime.sec = statbuf.st_mtimespec.tv_sec; - attr->mtime.nsec = statbuf.st_mtimespec.tv_nsec; -#else - attr->mtime.sec = statbuf.st_mtim.tv_sec; - attr->mtime.nsec = statbuf.st_mtim.tv_nsec; -#endif - attr->mode = statbuf.st_mode; - attr->size = statbuf.st_size; - } - return ErrorNone; - } -#endif -} - -Error os_file_open_r(Buf *full_path, OsFile *out_file, OsFileAttr *attr) { - return os_file_open_rw(full_path, out_file, attr, false, 0); -} - -Error os_file_open_w(Buf *full_path, OsFile *out_file, OsFileAttr *attr, uint32_t mode) { - return os_file_open_rw(full_path, out_file, attr, true, mode); -} - -Error os_file_open_lock_rw(Buf *full_path, OsFile *out_file) { -#if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); - for (;;) { - HANDLE result = CreateFileW(&path_space.data.items[0], GENERIC_READ | GENERIC_WRITE, - 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (result == INVALID_HANDLE_VALUE) { - DWORD err = GetLastError(); - switch (err) { - case ERROR_SHARING_VIOLATION: - // TODO wait for the lock instead of sleeping - Sleep(10); - continue; - case ERROR_ALREADY_EXISTS: - return ErrorPathAlreadyExists; - case ERROR_FILE_EXISTS: - return ErrorPathAlreadyExists; - case ERROR_FILE_NOT_FOUND: - return ErrorFileNotFound; - case ERROR_PATH_NOT_FOUND: - return ErrorFileNotFound; - case ERROR_ACCESS_DENIED: - return ErrorAccess; - case ERROR_PIPE_BUSY: - return ErrorPipeBusy; - default: - return ErrorUnexpected; - } - } - *out_file = result; - return ErrorNone; - } -#else - int fd; - for (;;) { - fd = open(buf_ptr(full_path), O_RDWR|O_CLOEXEC|O_CREAT, 0666); - if (fd == -1) { - switch (errno) { - case EINTR: - continue; - case EINVAL: - zig_unreachable(); - case EFAULT: - zig_unreachable(); - case EACCES: - case EPERM: - return ErrorAccess; - case EISDIR: - return ErrorIsDir; - case ENOENT: - return ErrorFileNotFound; - case ENOTDIR: - return ErrorNotDir; - default: - return ErrorFileSystem; - } - } - break; - } - for (;;) { - struct flock lock; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - if (fcntl(fd, F_SETLKW, &lock) == -1) { - switch (errno) { - case EINTR: - continue; - case EBADF: - zig_unreachable(); - case EFAULT: - zig_unreachable(); - case EINVAL: - zig_unreachable(); - default: - close(fd); - return ErrorFileSystem; - } - } - break; - } - *out_file = fd; - return ErrorNone; -#endif -} - -Error os_file_read(OsFile file, void *ptr, size_t *len) { -#if defined(ZIG_OS_WINDOWS) - DWORD amt_read; - if (ReadFile(file, ptr, *len, &amt_read, nullptr) == 0) - return ErrorUnexpected; - *len = amt_read; - return ErrorNone; -#else - for (;;) { - ssize_t rc = read(file, ptr, *len); - if (rc == -1) { - switch (errno) { - case EINTR: - continue; - case EBADF: - zig_unreachable(); - case EFAULT: - zig_unreachable(); - case EISDIR: - return ErrorIsDir; - default: - return ErrorFileSystem; - } - } - *len = rc; - return ErrorNone; - } -#endif -} - -Error os_file_read_all(OsFile file, Buf *contents) { - Error err; - size_t index = 0; - for (;;) { - size_t amt = buf_len(contents) - index; - - if (amt < 4096) { - buf_resize(contents, buf_len(contents) + (4096 - amt)); - amt = buf_len(contents) - index; - } - - if ((err = os_file_read(file, buf_ptr(contents) + index, &amt))) - return err; - - if (amt == 0) { - buf_resize(contents, index); - return ErrorNone; - } - - index += amt; - } -} - -Error os_file_overwrite(OsFile file, Buf *contents) { -#if defined(ZIG_OS_WINDOWS) - if (SetFilePointer(file, 0, nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER) - return ErrorFileSystem; - if (!SetEndOfFile(file)) - return ErrorFileSystem; - DWORD bytes_written; - if (!WriteFile(file, buf_ptr(contents), buf_len(contents), &bytes_written, nullptr)) - return ErrorFileSystem; - return ErrorNone; -#else - if (lseek(file, 0, SEEK_SET) == -1) - return ErrorUnexpectedSeekFailure; - if (ftruncate(file, 0) == -1) - return ErrorUnexpectedFileTruncationFailure; - for (;;) { - if (write(file, buf_ptr(contents), buf_len(contents)) == -1) { - switch (errno) { - case EINTR: - continue; - case EINVAL: - zig_unreachable(); - case EBADF: - zig_unreachable(); - case EFAULT: - zig_unreachable(); - case EDQUOT: - return ErrorDiskQuota; - case ENOSPC: - return ErrorDiskSpace; - case EFBIG: - return ErrorFileTooBig; - case EIO: - return ErrorFileSystem; - case EPERM: - return ErrorAccess; - default: - return ErrorUnexpectedWriteFailure; - } - } - return ErrorNone; - } -#endif -} - -void os_file_close(OsFile *file) { -#if defined(ZIG_OS_WINDOWS) - CloseHandle(*file); - *file = NULL; -#else - close(*file); - *file = -1; -#endif -} diff --git a/src/stage1/os.hpp b/src/stage1/os.hpp @@ -70,66 +70,25 @@ enum TermColor { TermColorReset, }; -enum TerminationId { - TerminationIdClean, - TerminationIdSignaled, - TerminationIdStopped, - TerminationIdUnknown, -}; - -struct Termination { - TerminationId how; - int code; -}; - -#if defined(ZIG_OS_WINDOWS) -#define OsFile void * -#else -#define OsFile int -#endif - struct OsTimeStamp { int64_t sec; int64_t nsec; }; -struct OsFileAttr { - OsTimeStamp mtime; - uint64_t size; - uint64_t inode; - uint32_t mode; -}; - int os_init(void); -void os_spawn_process(ZigList<const char *> &args, Termination *term); -Error os_exec_process(ZigList<const char *> &args, - Termination *term, Buf *out_stderr, Buf *out_stdout); -Error os_execv(const char *exe, const char **argv); - void os_path_dirname(Buf *full_path, Buf *out_dirname); void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename); void os_path_extname(Buf *full_path, Buf *out_basename, Buf *out_extname); void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path); -Error os_path_real(Buf *rel_path, Buf *out_abs_path); Buf os_path_resolve(Buf **paths_ptr, size_t paths_len); bool os_path_is_absolute(Buf *path); Error ATTRIBUTE_MUST_USE os_make_path(Buf *path); Error ATTRIBUTE_MUST_USE os_make_dir(Buf *path); -Error ATTRIBUTE_MUST_USE os_file_open_r(Buf *full_path, OsFile *out_file, OsFileAttr *attr); -Error ATTRIBUTE_MUST_USE os_file_open_w(Buf *full_path, OsFile *out_file, OsFileAttr *attr, uint32_t mode); -Error ATTRIBUTE_MUST_USE os_file_open_lock_rw(Buf *full_path, OsFile *out_file); -Error ATTRIBUTE_MUST_USE os_file_read(OsFile file, void *ptr, size_t *len); -Error ATTRIBUTE_MUST_USE os_file_read_all(OsFile file, Buf *contents); -Error ATTRIBUTE_MUST_USE os_file_overwrite(OsFile file, Buf *contents); -void os_file_close(OsFile *file); - Error ATTRIBUTE_MUST_USE os_write_file(Buf *full_path, Buf *contents); Error ATTRIBUTE_MUST_USE os_copy_file(Buf *src_path, Buf *dest_path); -Error ATTRIBUTE_MUST_USE os_update_file(Buf *src_path, Buf *dest_path); -Error ATTRIBUTE_MUST_USE os_dump_file(Buf *src_path, FILE *dest_file); Error ATTRIBUTE_MUST_USE os_fetch_file(FILE *file, Buf *out_contents); Error ATTRIBUTE_MUST_USE os_fetch_file_path(Buf *full_path, Buf *out_contents); @@ -139,22 +98,11 @@ Error ATTRIBUTE_MUST_USE os_get_cwd(Buf *out_cwd); bool os_stderr_tty(void); void os_stderr_set_color(TermColor color); -Error os_delete_file(Buf *path); - -Error ATTRIBUTE_MUST_USE os_file_exists(Buf *full_path, bool *result); - Error os_rename(Buf *src_path, Buf *dest_path); OsTimeStamp os_timestamp_monotonic(void); -OsTimeStamp os_timestamp_calendar(void); bool os_is_sep(uint8_t c); -Error ATTRIBUTE_MUST_USE os_self_exe_path(Buf *out_path); - -Error ATTRIBUTE_MUST_USE os_get_app_data_dir(Buf *out_path, const char *appname); - -Error ATTRIBUTE_MUST_USE os_self_exe_shared_libs(ZigList<Buf *> &paths); - const size_t PATH_MAX_WIDE = 32767; struct PathSpace { diff --git a/src/stage1/zig0.cpp b/src/stage1/zig0.cpp @@ -430,6 +430,16 @@ int main(int argc, char **argv) { return print_error_usage(arg0); } + if (override_lib_dir == nullptr) { + fprintf(stderr, "missing --override-lib-dir\n"); + return print_error_usage(arg0); + } + + if (emit_bin_path == nullptr) { + fprintf(stderr, "missing -femit-bin=\n"); + return print_error_usage(arg0); + } + ZigStage1 *stage1 = zig_stage1_create(optimize_mode, nullptr, 0, in_file, strlen(in_file), diff --git a/src/target.zig b/src/target.zig @@ -130,7 +130,6 @@ pub fn osRequiresLibC(target: std.Target) bool { pub fn libcNeedsLibUnwind(target: std.Target) bool { return switch (target.os.tag) { - .windows, .macosx, .ios, .watchos, @@ -138,6 +137,7 @@ pub fn libcNeedsLibUnwind(target: std.Target) bool { .freestanding, => false, + .windows => target.abi != .msvc, else => true, }; } diff --git a/src/test.zig b/src/test.zig @@ -56,6 +56,12 @@ pub const TestContext = struct { }, }; + pub const File = struct { + /// Contents of the importable file. Doesn't yet support incremental updates. + src: [:0]const u8, + path: []const u8, + }; + pub const TestType = enum { Zig, ZIR, @@ -78,6 +84,8 @@ pub const TestContext = struct { extension: TestType, cbe: bool = false, + files: std.ArrayList(File), + /// Adds a subcase in which the module is updated with `src`, and the /// resulting ZIR is validated against `result`. pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { @@ -156,6 +164,7 @@ pub const TestContext = struct { .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, .extension = T, + .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } @@ -182,6 +191,7 @@ pub const TestContext = struct { .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, .extension = T, + .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } @@ -204,6 +214,7 @@ pub const TestContext = struct { .output_mode = .Obj, .extension = T, .cbe = true, + .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } @@ -505,6 +516,10 @@ pub const TestContext = struct { }); defer comp.destroy(); + for (case.files.items) |file| { + try tmp.dir.writeFile(file.path, file.src); + } + for (case.updates.items) |update, update_index| { var update_node = root_node.start("update", 3); update_node.activate(); diff --git a/src/translate_c.zig b/src/translate_c.zig @@ -5527,58 +5527,41 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { const ParseError = Error || error{ParseError}; fn parseCExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { - const node = try parseCPrefixOpExpr(c, m, scope); - switch (m.next().?) { - .QuestionMark => { - // must come immediately after expr - _ = try appendToken(c, .RParen, ")"); - const if_node = try transCreateNodeIf(c); - if_node.condition = node; - if_node.body = try parseCPrimaryExpr(c, m, scope); - if (m.next().? != .Colon) { - try m.fail(c, "unable to translate C expr: expected ':'", .{}); - return error.ParseError; - } - if_node.@"else" = try transCreateNodeElse(c); - if_node.@"else".?.body = try parseCPrimaryExpr(c, m, scope); - return &if_node.base; - }, - .Comma => { - _ = try appendToken(c, .Semicolon, ";"); - var block_scope = try Scope.Block.init(c, scope, true); - defer block_scope.deinit(); - - var last = node; - while (true) { - // suppress result - const lhs = try transCreateNodeIdentifier(c, "_"); - const op_token = try appendToken(c, .Equal, "="); - const op_node = try c.arena.create(ast.Node.SimpleInfixOp); - op_node.* = .{ - .base = .{ .tag = .Assign }, - .op_token = op_token, - .lhs = lhs, - .rhs = last, - }; - try block_scope.statements.append(&op_node.base); + // TODO parseCAssignExpr here + const node = try parseCCondExpr(c, m, scope); + if (m.next().? != .Comma) { + m.i -= 1; + return node; + } + _ = try appendToken(c, .Semicolon, ";"); + var block_scope = try Scope.Block.init(c, scope, true); + defer block_scope.deinit(); - last = try parseCPrefixOpExpr(c, m, scope); - _ = try appendToken(c, .Semicolon, ";"); - if (m.next().? != .Comma) { - m.i -= 1; - break; - } - } + var last = node; + while (true) { + // suppress result + const lhs = try transCreateNodeIdentifier(c, "_"); + const op_token = try appendToken(c, .Equal, "="); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = .Assign }, + .op_token = op_token, + .lhs = lhs, + .rhs = last, + }; + try block_scope.statements.append(&op_node.base); - const break_node = try transCreateNodeBreak(c, block_scope.label, last); - try block_scope.statements.append(&break_node.base); - return try block_scope.complete(c); - }, - else => { + last = try parseCCondExpr(c, m, scope); + _ = try appendToken(c, .Semicolon, ";"); + if (m.next().? != .Comma) { m.i -= 1; - return node; - }, + break; + } } + + const break_node = try transCreateNodeBreak(c, block_scope.label, last); + try block_scope.statements.append(&break_node.base); + return try block_scope.complete(c); } fn parseCNumLit(c: *Context, m: *MacroCtx) ParseError!*ast.Node { @@ -5805,7 +5788,7 @@ fn zigifyEscapeSequences(ctx: *Context, m: *MacroCtx) ![]const u8 { return bytes[0..i]; } -fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { +fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { const tok = m.next().?; const slice = m.slice(); switch (tok) { @@ -5952,6 +5935,30 @@ fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.N } } +fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCPrimaryExprInner(c, m, scope); + // In C the preprocessor would handle concatting strings while expanding macros. + // This should do approximately the same by concatting any strings and identifiers + // after a primary expression. + while (true) { + var op_token: ast.TokenIndex = undefined; + var op_id: ast.Node.Tag = undefined; + switch (m.peek().?) { + .StringLiteral, .Identifier => {}, + else => break, + } + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = .ArrayCat }, + .op_token = try appendToken(c, .PlusPlus, "++"), + .lhs = node, + .rhs = try parseCPrimaryExprInner(c, m, scope), + }; + node = &op_node.base; + } + return node; +} + fn nodeIsInfixOp(tag: ast.Node.Tag) bool { return switch (tag) { .Add, @@ -6053,31 +6060,268 @@ fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node { return &group_node.base; } -fn parseCSuffixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { - var node = try parseCPrimaryExpr(c, m, scope); +fn macroGroup(c: *Context, node: *ast.Node) !*ast.Node { + if (!nodeIsInfixOp(node.tag)) return node; + + const group_node = try c.arena.create(ast.Node.GroupedExpression); + group_node.* = .{ + .lparen = try appendToken(c, .LParen, "("), + .expr = node, + .rparen = try appendToken(c, .RParen, ")"), + }; + return &group_node.base; +} + +fn parseCCondExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + const node = try parseCOrExpr(c, m, scope); + if (m.peek().? != .QuestionMark) { + return node; + } + _ = m.next(); + + // must come immediately after expr + _ = try appendToken(c, .RParen, ")"); + const if_node = try transCreateNodeIf(c); + if_node.condition = node; + if_node.body = try parseCOrExpr(c, m, scope); + if (m.next().? != .Colon) { + try m.fail(c, "unable to translate C expr: expected ':'", .{}); + return error.ParseError; + } + if_node.@"else" = try transCreateNodeElse(c); + if_node.@"else".?.body = try parseCCondExpr(c, m, scope); + return &if_node.base; +} + +fn parseCOrExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCAndExpr(c, m, scope); + while (m.next().? == .PipePipe) { + const lhs_node = try macroIntToBool(c, node); + const op_token = try appendToken(c, .Keyword_or, "or"); + const rhs_node = try parseCAndExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = .BoolOr }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroIntToBool(c, rhs_node), + }; + node = &op_node.base; + } + m.i -= 1; + return node; +} + +fn parseCAndExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCBitOrExpr(c, m, scope); + while (m.next().? == .AmpersandAmpersand) { + const lhs_node = try macroIntToBool(c, node); + const op_token = try appendToken(c, .Keyword_and, "and"); + const rhs_node = try parseCBitOrExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = .BoolAnd }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroIntToBool(c, rhs_node), + }; + node = &op_node.base; + } + m.i -= 1; + return node; +} + +fn parseCBitOrExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCBitXorExpr(c, m, scope); + while (m.next().? == .Pipe) { + const lhs_node = try macroBoolToInt(c, node); + const op_token = try appendToken(c, .Pipe, "|"); + const rhs_node = try parseCBitXorExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = .BitOr }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } + m.i -= 1; + return node; +} + +fn parseCBitXorExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCBitAndExpr(c, m, scope); + while (m.next().? == .Caret) { + const lhs_node = try macroBoolToInt(c, node); + const op_token = try appendToken(c, .Caret, "^"); + const rhs_node = try parseCBitAndExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = .BitXor }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } + m.i -= 1; + return node; +} + +fn parseCBitAndExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCEqExpr(c, m, scope); + while (m.next().? == .Ampersand) { + const lhs_node = try macroBoolToInt(c, node); + const op_token = try appendToken(c, .Ampersand, "&"); + const rhs_node = try parseCEqExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = .BitAnd }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } + m.i -= 1; + return node; +} + +fn parseCEqExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCRelExpr(c, m, scope); while (true) { var op_token: ast.TokenIndex = undefined; var op_id: ast.Node.Tag = undefined; - var bool_op = false; - switch (m.next().?) { - .Period => { - if (m.next().? != .Identifier) { - try m.fail(c, "unable to translate C expr: expected identifier", .{}); - return error.ParseError; - } + switch (m.peek().?) { + .BangEqual => { + op_token = try appendToken(c, .BangEqual, "!="); + op_id = .BangEqual; + }, + .EqualEqual => { + op_token = try appendToken(c, .EqualEqual, "=="); + op_id = .EqualEqual; + }, + else => return node, + } + _ = m.next(); + const lhs_node = try macroBoolToInt(c, node); + const rhs_node = try parseCRelExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = op_id }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } +} - node = try transCreateNodeFieldAccess(c, node, m.slice()); - continue; +fn parseCRelExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCShiftExpr(c, m, scope); + while (true) { + var op_token: ast.TokenIndex = undefined; + var op_id: ast.Node.Tag = undefined; + switch (m.peek().?) { + .AngleBracketRight => { + op_token = try appendToken(c, .AngleBracketRight, ">"); + op_id = .GreaterThan; }, - .Arrow => { - if (m.next().? != .Identifier) { - try m.fail(c, "unable to translate C expr: expected identifier", .{}); - return error.ParseError; - } - const deref = try transCreateNodePtrDeref(c, node); - node = try transCreateNodeFieldAccess(c, deref, m.slice()); - continue; + .AngleBracketRightEqual => { + op_token = try appendToken(c, .AngleBracketRightEqual, ">="); + op_id = .GreaterOrEqual; + }, + .AngleBracketLeft => { + op_token = try appendToken(c, .AngleBracketLeft, "<"); + op_id = .LessThan; }, + .AngleBracketLeftEqual => { + op_token = try appendToken(c, .AngleBracketLeftEqual, "<="); + op_id = .LessOrEqual; + }, + else => return node, + } + _ = m.next(); + const lhs_node = try macroBoolToInt(c, node); + const rhs_node = try parseCShiftExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = op_id }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } +} + +fn parseCShiftExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCAddSubExpr(c, m, scope); + while (true) { + var op_token: ast.TokenIndex = undefined; + var op_id: ast.Node.Tag = undefined; + switch (m.peek().?) { + .AngleBracketAngleBracketLeft => { + op_token = try appendToken(c, .AngleBracketAngleBracketLeft, "<<"); + op_id = .BitShiftLeft; + }, + .AngleBracketAngleBracketRight => { + op_token = try appendToken(c, .AngleBracketAngleBracketRight, ">>"); + op_id = .BitShiftRight; + }, + else => return node, + } + _ = m.next(); + const lhs_node = try macroBoolToInt(c, node); + const rhs_node = try parseCAddSubExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = op_id }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } +} + +fn parseCAddSubExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCMulExpr(c, m, scope); + while (true) { + var op_token: ast.TokenIndex = undefined; + var op_id: ast.Node.Tag = undefined; + switch (m.peek().?) { + .Plus => { + op_token = try appendToken(c, .Plus, "+"); + op_id = .Add; + }, + .Minus => { + op_token = try appendToken(c, .Minus, "-"); + op_id = .Sub; + }, + else => return node, + } + _ = m.next(); + const lhs_node = try macroBoolToInt(c, node); + const rhs_node = try parseCMulExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = op_id }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } +} + +fn parseCMulExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCUnaryExpr(c, m, scope); + while (true) { + var op_token: ast.TokenIndex = undefined; + var op_id: ast.Node.Tag = undefined; + switch (m.next().?) { .Asterisk => { if (m.peek().? == .RParen) { // type *) @@ -6105,59 +6349,57 @@ fn parseCSuffixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast. op_id = .BitShiftLeft; } }, - .AngleBracketAngleBracketLeft => { - op_token = try appendToken(c, .AngleBracketAngleBracketLeft, "<<"); - op_id = .BitShiftLeft; - }, - .AngleBracketAngleBracketRight => { - op_token = try appendToken(c, .AngleBracketAngleBracketRight, ">>"); - op_id = .BitShiftRight; - }, - .Pipe => { - op_token = try appendToken(c, .Pipe, "|"); - op_id = .BitOr; - }, - .Ampersand => { - op_token = try appendToken(c, .Ampersand, "&"); - op_id = .BitAnd; - }, - .Plus => { - op_token = try appendToken(c, .Plus, "+"); - op_id = .Add; - }, - .Minus => { - op_token = try appendToken(c, .Minus, "-"); - op_id = .Sub; - }, - .AmpersandAmpersand => { - op_token = try appendToken(c, .Keyword_and, "and"); - op_id = .BoolAnd; - bool_op = true; - }, - .PipePipe => { - op_token = try appendToken(c, .Keyword_or, "or"); - op_id = .BoolOr; - bool_op = true; + .Slash => { + op_id = .Div; + op_token = try appendToken(c, .Slash, "/"); }, - .AngleBracketRight => { - op_token = try appendToken(c, .AngleBracketRight, ">"); - op_id = .GreaterThan; + .Percent => { + op_id = .Mod; + op_token = try appendToken(c, .Percent, "%"); }, - .AngleBracketRightEqual => { - op_token = try appendToken(c, .AngleBracketRightEqual, ">="); - op_id = .GreaterOrEqual; + else => { + m.i -= 1; + return node; }, - .AngleBracketLeft => { - op_token = try appendToken(c, .AngleBracketLeft, "<"); - op_id = .LessThan; + } + const lhs_node = try macroBoolToInt(c, node); + const rhs_node = try parseCUnaryExpr(c, m, scope); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); + op_node.* = .{ + .base = .{ .tag = op_id }, + .op_token = op_token, + .lhs = lhs_node, + .rhs = try macroBoolToInt(c, rhs_node), + }; + node = &op_node.base; + } +} + +fn parseCPostfixExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { + var node = try parseCPrimaryExpr(c, m, scope); + while (true) { + switch (m.next().?) { + .Period => { + if (m.next().? != .Identifier) { + try m.fail(c, "unable to translate C expr: expected identifier", .{}); + return error.ParseError; + } + + node = try transCreateNodeFieldAccess(c, node, m.slice()); + continue; }, - .AngleBracketLeftEqual => { - op_token = try appendToken(c, .AngleBracketLeftEqual, "<="); - op_id = .LessOrEqual; + .Arrow => { + if (m.next().? != .Identifier) { + try m.fail(c, "unable to translate C expr: expected identifier", .{}); + return error.ParseError; + } + const deref = try transCreateNodePtrDeref(c, node); + node = try transCreateNodeFieldAccess(c, deref, m.slice()); + continue; }, .LBracket => { const arr_node = try transCreateNodeArrayAccess(c, node); - arr_node.index_expr = try parseCPrefixOpExpr(c, m, scope); + arr_node.index_expr = try parseCExpr(c, m, scope); arr_node.rtoken = try appendToken(c, .RBracket, "]"); node = &arr_node.base; if (m.next().? != .RBracket) { @@ -6171,7 +6413,7 @@ fn parseCSuffixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast. var call_params = std.ArrayList(*ast.Node).init(c.gpa); defer call_params.deinit(); while (true) { - const arg = try parseCPrefixOpExpr(c, m, scope); + const arg = try parseCCondExpr(c, m, scope); try call_params.append(arg); switch (m.next().?) { .Comma => _ = try appendToken(c, .Comma, ","), @@ -6204,7 +6446,7 @@ fn parseCSuffixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast. defer init_vals.deinit(); while (true) { - const val = try parseCPrefixOpExpr(c, m, scope); + const val = try parseCCondExpr(c, m, scope); try init_vals.append(val); switch (m.next().?) { .Comma => _ = try appendToken(c, .Comma, ","), @@ -6239,90 +6481,57 @@ fn parseCSuffixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast. node = &zero_init_call.base; continue; }, - .BangEqual => { - op_token = try appendToken(c, .BangEqual, "!="); - op_id = .BangEqual; - }, - .EqualEqual => { - op_token = try appendToken(c, .EqualEqual, "=="); - op_id = .EqualEqual; - }, - .Slash => { - op_id = .Div; - op_token = try appendToken(c, .Slash, "/"); - }, - .Percent => { - op_id = .Mod; - op_token = try appendToken(c, .Percent, "%"); - }, - .StringLiteral => { - op_id = .ArrayCat; - op_token = try appendToken(c, .PlusPlus, "++"); - - m.i -= 1; - }, - .Identifier => { - op_id = .ArrayCat; - op_token = try appendToken(c, .PlusPlus, "++"); - - m.i -= 1; + .PlusPlus, .MinusMinus => { + try m.fail(c, "TODO postfix inc/dec expr", .{}); + return error.ParseError; }, else => { m.i -= 1; return node; }, } - const cast_fn = if (bool_op) macroIntToBool else macroBoolToInt; - const lhs_node = try cast_fn(c, node); - const rhs_node = try parseCPrefixOpExpr(c, m, scope); - const op_node = try c.arena.create(ast.Node.SimpleInfixOp); - op_node.* = .{ - .base = .{ .tag = op_id }, - .op_token = op_token, - .lhs = lhs_node, - .rhs = try cast_fn(c, rhs_node), - }; - node = &op_node.base; } } -fn parseCPrefixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { +fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { switch (m.next().?) { .Bang => { const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!"); - node.rhs = try parseCPrefixOpExpr(c, m, scope); + node.rhs = try macroIntToBool(c, try parseCUnaryExpr(c, m, scope)); return &node.base; }, .Minus => { const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-"); - node.rhs = try parseCPrefixOpExpr(c, m, scope); + node.rhs = try macroBoolToInt(c, try parseCUnaryExpr(c, m, scope)); return &node.base; }, - .Plus => return try parseCPrefixOpExpr(c, m, scope), + .Plus => return try parseCUnaryExpr(c, m, scope), .Tilde => { const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~"); - node.rhs = try parseCPrefixOpExpr(c, m, scope); + node.rhs = try macroBoolToInt(c, try parseCUnaryExpr(c, m, scope)); return &node.base; }, .Asterisk => { - const node = try parseCPrefixOpExpr(c, m, scope); + const node = try macroGroup(c, try parseCUnaryExpr(c, m, scope)); return try transCreateNodePtrDeref(c, node); }, .Ampersand => { const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&"); - node.rhs = try parseCPrefixOpExpr(c, m, scope); + node.rhs = try macroGroup(c, try parseCUnaryExpr(c, m, scope)); return &node.base; }, .Keyword_sizeof => { const inner = if (m.peek().? == .LParen) blk: { _ = m.next(); - const inner = try parseCExpr(c, m, scope); + // C grammar says this should be 'type-name' but we have to + // use parseCMulExpr to correctly handle pointer types. + const inner = try parseCMulExpr(c, m, scope); if (m.next().? != .RParen) { try m.fail(c, "unable to translate C expr: expected ')'", .{}); return error.ParseError; } break :blk inner; - } else try parseCPrefixOpExpr(c, m, scope); + } else try parseCUnaryExpr(c, m, scope); //(@import("std").meta.sizeof(dest, x)) const import_fn_call = try c.createBuiltinCall("@import", 1); @@ -6344,7 +6553,9 @@ fn parseCPrefixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast. try m.fail(c, "unable to translate C expr: expected '('", .{}); return error.ParseError; } - const inner = try parseCExpr(c, m, scope); + // C grammar says this should be 'type-name' but we have to + // use parseCMulExpr to correctly handle pointer types. + const inner = try parseCMulExpr(c, m, scope); if (m.next().? != .RParen) { try m.fail(c, "unable to translate C expr: expected ')'", .{}); return error.ParseError; @@ -6355,9 +6566,13 @@ fn parseCPrefixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast. builtin_call.rparen_token = try appendToken(c, .RParen, ")"); return &builtin_call.base; }, + .PlusPlus, .MinusMinus => { + try m.fail(c, "TODO unary inc/dec expr", .{}); + return error.ParseError; + }, else => { m.i -= 1; - return try parseCSuffixOpExpr(c, m, scope); + return try parseCPostfixExpr(c, m, scope); }, } } diff --git a/src/type.zig b/src/type.zig @@ -89,6 +89,8 @@ pub const Type = extern union { .anyerror_void_error_union, .error_union => return .ErrorUnion, .anyframe_T, .@"anyframe" => return .AnyFrame, + + .empty_struct => return .Struct, } } @@ -439,6 +441,7 @@ pub const Type = extern union { }, .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet), .error_set_single => return self.copyPayloadShallow(allocator, Payload.ErrorSetSingle), + .empty_struct => return self.copyPayloadShallow(allocator, Payload.EmptyStruct), } } @@ -505,6 +508,8 @@ pub const Type = extern union { .@"null" => return out_stream.writeAll("@Type(.Null)"), .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"), + // TODO this should print the structs name + .empty_struct => return out_stream.writeAll("struct {}"), .@"anyframe" => return out_stream.writeAll("anyframe"), .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"), .const_slice_u8 => return out_stream.writeAll("[]const u8"), @@ -788,6 +793,7 @@ pub const Type = extern union { .@"null", .@"undefined", .enum_literal, + .empty_struct, => false, }; } @@ -910,6 +916,7 @@ pub const Type = extern union { .@"null", .@"undefined", .enum_literal, + .empty_struct, => unreachable, }; } @@ -932,6 +939,7 @@ pub const Type = extern union { .@"undefined" => unreachable, .enum_literal => unreachable, .single_const_pointer_to_comptime_int => unreachable, + .empty_struct => unreachable, .u8, .i8, @@ -1107,6 +1115,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .single_const_pointer, @@ -1181,6 +1190,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .const_slice, @@ -1252,6 +1262,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .single_const_pointer, @@ -1332,6 +1343,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .pointer => { @@ -1407,6 +1419,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .pointer => { @@ -1524,6 +1537,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, .array => self.cast(Payload.Array).?.elem_type, @@ -1651,6 +1665,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, .array => self.cast(Payload.Array).?.len, @@ -1716,6 +1731,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, .single_const_pointer, @@ -1798,6 +1814,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .int_signed, @@ -1872,6 +1889,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .int_unsigned, @@ -1936,6 +1954,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, .int_unsigned => .{ .signed = false, .bits = self.cast(Payload.IntUnsigned).?.bits }, @@ -2018,6 +2037,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, .usize, @@ -2129,6 +2149,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, }; } @@ -2206,6 +2227,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, } } @@ -2282,6 +2304,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, } } @@ -2358,6 +2381,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, }; } @@ -2431,6 +2455,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, }; } @@ -2504,6 +2529,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => unreachable, }; } @@ -2577,6 +2603,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => false, }; } @@ -2636,6 +2663,7 @@ pub const Type = extern union { .error_set_single, => return null, + .empty_struct => return Value.initTag(.empty_struct_value), .void => return Value.initTag(.void_value), .noreturn => return Value.initTag(.unreachable_value), .@"null" => return Value.initTag(.null_value), @@ -2743,6 +2771,7 @@ pub const Type = extern union { .anyerror_void_error_union, .error_set, .error_set_single, + .empty_struct, => return false, .c_const_pointer, @@ -2760,6 +2789,80 @@ pub const Type = extern union { (self.isSinglePointer() and self.elemType().zigTypeTag() == .Array); } + /// Asserts that the type is a container. (note: ErrorSet is not a container). + pub fn getContainerScope(self: Type) *Module.Scope.Container { + return switch (self.tag()) { + .f16, + .f32, + .f64, + .f128, + .c_longdouble, + .comptime_int, + .comptime_float, + .u8, + .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .bool, + .type, + .anyerror, + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .function, + .single_const_pointer_to_comptime_int, + .const_slice_u8, + .c_void, + .void, + .noreturn, + .@"null", + .@"undefined", + .int_unsigned, + .int_signed, + .array, + .array_sentinel, + .array_u8, + .array_u8_sentinel_0, + .single_const_pointer, + .single_mut_pointer, + .many_const_pointer, + .many_mut_pointer, + .const_slice, + .mut_slice, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, + .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, + .c_const_pointer, + .c_mut_pointer, + .pointer, + => unreachable, + + .empty_struct => self.cast(Type.Payload.EmptyStruct).?.scope, + }; + } + /// This enum does not directly correspond to `std.builtin.TypeId` because /// it has extra enum tags in it, as a way of using less memory. For example, /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types @@ -2835,6 +2938,7 @@ pub const Type = extern union { anyframe_T, error_set, error_set_single, + empty_struct, pub const last_no_payload_tag = Tag.const_slice_u8; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -2942,6 +3046,14 @@ pub const Type = extern union { /// memory is owned by `Module` name: []const u8, }; + + /// Mostly used for namespace like structs with zero fields. + /// Most commonly used for files. + pub const EmptyStruct = struct { + base: Payload = .{ .tag = .empty_struct }, + + scope: *Module.Scope.Container, + }; }; }; diff --git a/src/value.zig b/src/value.zig @@ -68,6 +68,7 @@ pub const Value = extern union { one, void_value, unreachable_value, + empty_struct_value, empty_array, null_value, bool_true, @@ -182,6 +183,7 @@ pub const Value = extern union { .null_value, .bool_true, .bool_false, + .empty_struct_value, => unreachable, .ty => { @@ -312,6 +314,8 @@ pub const Value = extern union { .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"), .anyframe_type => return out_stream.writeAll("anyframe"), + // TODO this should print `NAME{}` + .empty_struct_value => return out_stream.writeAll("struct {}{}"), .null_value => return out_stream.writeAll("null"), .undef => return out_stream.writeAll("undefined"), .zero => return out_stream.writeAll("0"), @@ -475,6 +479,7 @@ pub const Value = extern union { .float_128, .enum_literal, .@"error", + .empty_struct_value, => unreachable, }; } @@ -543,6 +548,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .undef => unreachable, @@ -626,6 +632,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .undef => unreachable, @@ -709,6 +716,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .undef => unreachable, @@ -820,6 +828,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .zero, @@ -833,7 +842,7 @@ pub const Value = extern union { .int_u64 => { const x = self.cast(Payload.Int_u64).?.int; if (x == 0) return 0; - return std.math.log2(x) + 1; + return @intCast(usize, std.math.log2(x) + 1); }, .int_i64 => { @panic("TODO implement i64 intBitCountTwosComp"); @@ -907,6 +916,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .zero, @@ -1078,6 +1088,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .zero, @@ -1152,6 +1163,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .zero, @@ -1300,6 +1312,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .ref_val => self.cast(Payload.RefVal).?.val, @@ -1383,6 +1396,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => unreachable, .empty_array => unreachable, // out of bounds array index @@ -1483,6 +1497,7 @@ pub const Value = extern union { .enum_literal, .error_set, .@"error", + .empty_struct_value, => false, .undef => unreachable, diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp @@ -1126,6 +1126,34 @@ LLVMValueRef ZigLLVMBuildAtomicRMW(LLVMBuilderRef B, enum ZigLLVM_AtomicRMWBinOp singleThread ? SyncScope::SingleThread : SyncScope::System)); } +LLVMValueRef ZigLLVMBuildAndReduce(LLVMBuilderRef B, LLVMValueRef Val) { + return wrap(unwrap(B)->CreateAndReduce(unwrap(Val))); +} + +LLVMValueRef ZigLLVMBuildOrReduce(LLVMBuilderRef B, LLVMValueRef Val) { + return wrap(unwrap(B)->CreateOrReduce(unwrap(Val))); +} + +LLVMValueRef ZigLLVMBuildXorReduce(LLVMBuilderRef B, LLVMValueRef Val) { + return wrap(unwrap(B)->CreateXorReduce(unwrap(Val))); +} + +LLVMValueRef ZigLLVMBuildIntMaxReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed) { + return wrap(unwrap(B)->CreateIntMaxReduce(unwrap(Val), is_signed)); +} + +LLVMValueRef ZigLLVMBuildIntMinReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed) { + return wrap(unwrap(B)->CreateIntMinReduce(unwrap(Val), is_signed)); +} + +LLVMValueRef ZigLLVMBuildFPMaxReduce(LLVMBuilderRef B, LLVMValueRef Val) { + return wrap(unwrap(B)->CreateFPMaxReduce(unwrap(Val))); +} + +LLVMValueRef ZigLLVMBuildFPMinReduce(LLVMBuilderRef B, LLVMValueRef Val) { + return wrap(unwrap(B)->CreateFPMinReduce(unwrap(Val))); +} + static_assert((Triple::ArchType)ZigLLVM_UnknownArch == Triple::UnknownArch, ""); static_assert((Triple::ArchType)ZigLLVM_arm == Triple::arm, ""); static_assert((Triple::ArchType)ZigLLVM_armeb == Triple::armeb, ""); diff --git a/src/zig_llvm.h b/src/zig_llvm.h @@ -455,6 +455,14 @@ LLVMValueRef ZigLLVMBuildAtomicRMW(LLVMBuilderRef B, enum ZigLLVM_AtomicRMWBinOp LLVMValueRef PTR, LLVMValueRef Val, LLVMAtomicOrdering ordering, LLVMBool singleThread); +LLVMValueRef ZigLLVMBuildAndReduce(LLVMBuilderRef B, LLVMValueRef Val); +LLVMValueRef ZigLLVMBuildOrReduce(LLVMBuilderRef B, LLVMValueRef Val); +LLVMValueRef ZigLLVMBuildXorReduce(LLVMBuilderRef B, LLVMValueRef Val); +LLVMValueRef ZigLLVMBuildIntMaxReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed); +LLVMValueRef ZigLLVMBuildIntMinReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed); +LLVMValueRef ZigLLVMBuildFPMaxReduce(LLVMBuilderRef B, LLVMValueRef Val); +LLVMValueRef ZigLLVMBuildFPMinReduce(LLVMBuilderRef B, LLVMValueRef Val); + #define ZigLLVM_DIFlags_Zero 0U #define ZigLLVM_DIFlags_Private 1U #define ZigLLVM_DIFlags_Protected 2U diff --git a/src/zir.zig b/src/zir.zig @@ -161,6 +161,8 @@ pub const Inst = struct { @"fn", /// Returns a function type. fntype, + /// @import(operand) + import, /// Integer literal. int, /// Convert an integer value to another integer type, asserting that the destination type @@ -315,6 +317,7 @@ pub const Inst = struct { .ensure_err_payload_void, .anyframe_type, .bitnot, + .import, => UnOp, .add, @@ -489,6 +492,7 @@ pub const Inst = struct { .error_set, .slice, .slice_start, + .import, => false, .@"break", diff --git a/src/zir_sema.zig b/src/zir_sema.zig @@ -134,6 +134,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?), .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?), .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?), + .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?), } } @@ -1047,6 +1048,19 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr .val = Value.initPayload(&ref_payload.base), }); }, + .Struct => { + const container_scope = child_type.getContainerScope(); + if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| { + // TODO if !decl.is_pub and inDifferentFiles() "{} is private" + return mod.analyzeDeclRef(scope, fieldptr.base.src, decl); + } + + if (&container_scope.file_scope.base == mod.root_scope) { + return mod.fail(scope, fieldptr.base.src, "root source file has no member called '{}'", .{field_name}); + } else { + return mod.fail(scope, fieldptr.base.src, "container '{}' has no member called '{}'", .{ child_type, field_name }); + } + }, else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{child_type}), } }, @@ -1190,6 +1204,24 @@ fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); } +fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try resolveConstString(mod, scope, inst.positionals.operand); + + const file_scope = mod.analyzeImport(scope, inst.base.src, operand) catch |err| switch (err) { + // error.ImportOutsidePkgPath => { + // return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand}); + // }, + error.FileNotFound => { + return mod.fail(scope, inst.base.src, "unable to find '{}'", .{operand}); + }, + else => { + // TODO user friendly error to string + return mod.fail(scope, inst.base.src, "unable to open '{}': {}", .{ operand, @errorName(err) }); + }, + }; + return mod.constType(scope, inst.base.src, file_scope.root_container.ty); +} + fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{}); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig @@ -2,20 +2,175 @@ const tests = @import("tests.zig"); const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { - cases.add("slice sentinel mismatch", + cases.add("@Type for exhaustive enum with undefined tag type", + \\const TypeInfo = @import("builtin").TypeInfo; + \\const Tag = @Type(.{ + \\ .Enum = .{ + \\ .layout = .Auto, + \\ .tag_type = undefined, + \\ .fields = &[_]TypeInfo.EnumField{}, + \\ .decls = &[_]TypeInfo.Declaration{}, + \\ .is_exhaustive = false, + \\ }, + \\}); \\export fn entry() void { - \\ const x = @import("std").meta.Vector(3, f32){ 25, 75, 5, 0 }; + \\ _ = @intToEnum(Tag, 0); \\} , &[_][]const u8{ - "tmp.zig:2:62: error: index 3 outside vector of size 3", + "tmp.zig:2:20: error: use of undefined value here causes undefined behavior", }); - cases.add("slice sentinel mismatch", + cases.add("extern struct with non-extern-compatible integer tag type", + \\pub const E = enum(u31) { A, B, C }; + \\pub const S = extern struct { + \\ e: E, + \\}; \\export fn entry() void { - \\ const y: [:1]const u8 = &[_:2]u8{ 1, 2 }; + \\ const s: S = undefined; \\} , &[_][]const u8{ - "tmp.zig:2:37: error: expected type '[:1]const u8', found '*const [2:2]u8'", + "tmp.zig:3:5: error: extern structs cannot contain fields of type 'E'", + }); + + cases.add("@Type for exhaustive enum with non-integer tag type", + \\const TypeInfo = @import("builtin").TypeInfo; + \\const Tag = @Type(.{ + \\ .Enum = .{ + \\ .layout = .Auto, + \\ .tag_type = bool, + \\ .fields = &[_]TypeInfo.EnumField{}, + \\ .decls = &[_]TypeInfo.Declaration{}, + \\ .is_exhaustive = false, + \\ }, + \\}); + \\export fn entry() void { + \\ _ = @intToEnum(Tag, 0); + \\} + , &[_][]const u8{ + "tmp.zig:2:20: error: TypeInfo.Enum.tag_type must be an integer type, not 'bool'", + }); + + cases.add("extern struct with extern-compatible but inferred integer tag type", + \\pub const E = enum { + \\@"0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10",@"11",@"12", + \\@"13",@"14",@"15",@"16",@"17",@"18",@"19",@"20",@"21",@"22",@"23", + \\@"24",@"25",@"26",@"27",@"28",@"29",@"30",@"31",@"32",@"33",@"34", + \\@"35",@"36",@"37",@"38",@"39",@"40",@"41",@"42",@"43",@"44",@"45", + \\@"46",@"47",@"48",@"49",@"50",@"51",@"52",@"53",@"54",@"55",@"56", + \\@"57",@"58",@"59",@"60",@"61",@"62",@"63",@"64",@"65",@"66",@"67", + \\@"68",@"69",@"70",@"71",@"72",@"73",@"74",@"75",@"76",@"77",@"78", + \\@"79",@"80",@"81",@"82",@"83",@"84",@"85",@"86",@"87",@"88",@"89", + \\@"90",@"91",@"92",@"93",@"94",@"95",@"96",@"97",@"98",@"99",@"100", + \\@"101",@"102",@"103",@"104",@"105",@"106",@"107",@"108",@"109", + \\@"110",@"111",@"112",@"113",@"114",@"115",@"116",@"117",@"118", + \\@"119",@"120",@"121",@"122",@"123",@"124",@"125",@"126",@"127", + \\@"128",@"129",@"130",@"131",@"132",@"133",@"134",@"135",@"136", + \\@"137",@"138",@"139",@"140",@"141",@"142",@"143",@"144",@"145", + \\@"146",@"147",@"148",@"149",@"150",@"151",@"152",@"153",@"154", + \\@"155",@"156",@"157",@"158",@"159",@"160",@"161",@"162",@"163", + \\@"164",@"165",@"166",@"167",@"168",@"169",@"170",@"171",@"172", + \\@"173",@"174",@"175",@"176",@"177",@"178",@"179",@"180",@"181", + \\@"182",@"183",@"184",@"185",@"186",@"187",@"188",@"189",@"190", + \\@"191",@"192",@"193",@"194",@"195",@"196",@"197",@"198",@"199", + \\@"200",@"201",@"202",@"203",@"204",@"205",@"206",@"207",@"208", + \\@"209",@"210",@"211",@"212",@"213",@"214",@"215",@"216",@"217", + \\@"218",@"219",@"220",@"221",@"222",@"223",@"224",@"225",@"226", + \\@"227",@"228",@"229",@"230",@"231",@"232",@"233",@"234",@"235", + \\@"236",@"237",@"238",@"239",@"240",@"241",@"242",@"243",@"244", + \\@"245",@"246",@"247",@"248",@"249",@"250",@"251",@"252",@"253", + \\@"254",@"255" + \\}; + \\pub const S = extern struct { + \\ e: E, + \\}; + \\export fn entry() void { + \\ if (@TagType(E) != u8) @compileError("did not infer u8 tag type"); + \\ const s: S = undefined; + \\} + , &[_][]const u8{ + "tmp.zig:31:5: error: extern structs cannot contain fields of type 'E'", + }); + + cases.add("@Type for tagged union with extra enum field", + \\const TypeInfo = @import("builtin").TypeInfo; + \\const Tag = @Type(.{ + \\ .Enum = .{ + \\ .layout = .Auto, + \\ .tag_type = u2, + \\ .fields = &[_]TypeInfo.EnumField{ + \\ .{ .name = "signed", .value = 0 }, + \\ .{ .name = "unsigned", .value = 1 }, + \\ .{ .name = "arst", .value = 2 }, + \\ }, + \\ .decls = &[_]TypeInfo.Declaration{}, + \\ .is_exhaustive = true, + \\ }, + \\}); + \\const Tagged = @Type(.{ + \\ .Union = .{ + \\ .layout = .Auto, + \\ .tag_type = Tag, + \\ .fields = &[_]TypeInfo.UnionField{ + \\ .{ .name = "signed", .field_type = i32, .alignment = @alignOf(i32) }, + \\ .{ .name = "unsigned", .field_type = u32, .alignment = @alignOf(u32) }, + \\ }, + \\ .decls = &[_]TypeInfo.Declaration{}, + \\ }, + \\}); + \\export fn entry() void { + \\ var tagged = Tagged{ .signed = -1 }; + \\ tagged = .{ .unsigned = 1 }; + \\} + , &[_][]const u8{ + "tmp.zig:15:23: error: enum field missing: 'arst'", + "tmp.zig:27:24: note: referenced here", + }); + cases.add("@Type(.Fn) with is_generic = true", + \\const Foo = @Type(.{ + \\ .Fn = .{ + \\ .calling_convention = .Unspecified, + \\ .alignment = 0, + \\ .is_generic = true, + \\ .is_var_args = false, + \\ .return_type = u0, + \\ .args = &[_]@import("builtin").TypeInfo.FnArg{}, + \\ }, + \\}); + \\comptime { _ = Foo; } + , &[_][]const u8{ + "tmp.zig:1:20: error: TypeInfo.Fn.is_generic must be false for @Type", + }); + + cases.add("@Type(.Fn) with is_var_args = true and non-C callconv", + \\const Foo = @Type(.{ + \\ .Fn = .{ + \\ .calling_convention = .Unspecified, + \\ .alignment = 0, + \\ .is_generic = false, + \\ .is_var_args = true, + \\ .return_type = u0, + \\ .args = &[_]@import("builtin").TypeInfo.FnArg{}, + \\ }, + \\}); + \\comptime { _ = Foo; } + , &[_][]const u8{ + "tmp.zig:1:20: error: varargs functions must have C calling convention", + }); + + cases.add("@Type(.Fn) with return_type = null", + \\const Foo = @Type(.{ + \\ .Fn = .{ + \\ .calling_convention = .Unspecified, + \\ .alignment = 0, + \\ .is_generic = false, + \\ .is_var_args = false, + \\ .return_type = null, + \\ .args = &[_]@import("builtin").TypeInfo.FnArg{}, + \\ }, + \\}); + \\comptime { _ = Foo; } + , &[_][]const u8{ + "tmp.zig:1:20: error: TypeInfo.Fn.return_type must be non-null for @Type", }); cases.add("@Type for union with opaque field", @@ -25,7 +180,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ .layout = .Auto, \\ .tag_type = null, \\ .fields = &[_]TypeInfo.UnionField{ - \\ .{ .name = "foo", .field_type = @Type(.Opaque) }, + \\ .{ .name = "foo", .field_type = @Type(.Opaque), .alignment = 1 }, \\ }, \\ .decls = &[_]TypeInfo.Declaration{}, \\ }, @@ -38,6 +193,22 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:13:17: note: referenced here", }); + cases.add("slice sentinel mismatch", + \\export fn entry() void { + \\ const x = @import("std").meta.Vector(3, f32){ 25, 75, 5, 0 }; + \\} + , &[_][]const u8{ + "tmp.zig:2:62: error: index 3 outside vector of size 3", + }); + + cases.add("slice sentinel mismatch", + \\export fn entry() void { + \\ const y: [:1]const u8 = &[_:2]u8{ 1, 2 }; + \\} + , &[_][]const u8{ + "tmp.zig:2:37: error: expected type '[:1]const u8', found '*const [2:2]u8'", + }); + cases.add("@Type for union with zero fields", \\const TypeInfo = @import("builtin").TypeInfo; \\const Untagged = @Type(.{ @@ -94,9 +265,9 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ .layout = .Auto, \\ .tag_type = Tag, \\ .fields = &[_]TypeInfo.UnionField{ - \\ .{ .name = "signed", .field_type = i32 }, - \\ .{ .name = "unsigned", .field_type = u32 }, - \\ .{ .name = "arst", .field_type = f32 }, + \\ .{ .name = "signed", .field_type = i32, .alignment = @alignOf(i32) }, + \\ .{ .name = "unsigned", .field_type = u32, .alignment = @alignOf(u32) }, + \\ .{ .name = "arst", .field_type = f32, .alignment = @alignOf(f32) }, \\ }, \\ .decls = &[_]TypeInfo.Declaration{}, \\ }, @@ -111,42 +282,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:27:24: note: referenced here", }); - cases.add("@Type for tagged union with extra enum field", - \\const TypeInfo = @import("builtin").TypeInfo; - \\const Tag = @Type(.{ - \\ .Enum = .{ - \\ .layout = .Auto, - \\ .tag_type = u2, - \\ .fields = &[_]TypeInfo.EnumField{ - \\ .{ .name = "signed", .value = 0 }, - \\ .{ .name = "unsigned", .value = 1 }, - \\ .{ .name = "arst", .field_type = 2 }, - \\ }, - \\ .decls = &[_]TypeInfo.Declaration{}, - \\ .is_exhaustive = true, - \\ }, - \\}); - \\const Tagged = @Type(.{ - \\ .Union = .{ - \\ .layout = .Auto, - \\ .tag_type = Tag, - \\ .fields = &[_]TypeInfo.UnionField{ - \\ .{ .name = "signed", .field_type = i32 }, - \\ .{ .name = "unsigned", .field_type = u32 }, - \\ }, - \\ .decls = &[_]TypeInfo.Declaration{}, - \\ }, - \\}); - \\export fn entry() void { - \\ var tagged = Tagged{ .signed = -1 }; - \\ tagged = .{ .unsigned = 1 }; - \\} - , &[_][]const u8{ - "tmp.zig:9:32: error: no member named 'field_type' in struct 'std.builtin.EnumField'", - "tmp.zig:18:21: note: referenced here", - "tmp.zig:27:18: note: referenced here", - }); - cases.add("@Type with undefined", \\comptime { \\ _ = @Type(.{ .Array = .{ .len = 0, .child = u8, .sentinel = undefined } }); @@ -7556,7 +7691,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { }); cases.add( // fixed bug #2032 - "compile diagnostic string for top level decl type", + "compile diagnostic string for top level decl type", \\export fn entry() void { \\ var foo: u32 = @This(){}; \\} diff --git a/test/stage1/behavior/bugs/1467.zig b/test/stage1/behavior/bugs/1467.zig @@ -0,0 +1,7 @@ +pub const E = enum(u32) { A, B, C }; +pub const S = extern struct { + e: E, +}; +test "bug 1467" { + const s: S = undefined; +} diff --git a/test/stage1/behavior/type.zig b/test/stage1/behavior/type.zig @@ -320,8 +320,8 @@ test "Type.Union" { .layout = .Auto, .tag_type = null, .fields = &[_]TypeInfo.UnionField{ - .{ .name = "int", .field_type = i32 }, - .{ .name = "float", .field_type = f32 }, + .{ .name = "int", .field_type = i32, .alignment = @alignOf(f32) }, + .{ .name = "float", .field_type = f32, .alignment = @alignOf(f32) }, }, .decls = &[_]TypeInfo.Declaration{}, }, @@ -336,8 +336,8 @@ test "Type.Union" { .layout = .Packed, .tag_type = null, .fields = &[_]TypeInfo.UnionField{ - .{ .name = "signed", .field_type = i32 }, - .{ .name = "unsigned", .field_type = u32 }, + .{ .name = "signed", .field_type = i32, .alignment = @alignOf(i32) }, + .{ .name = "unsigned", .field_type = u32, .alignment = @alignOf(u32) }, }, .decls = &[_]TypeInfo.Declaration{}, }, @@ -363,8 +363,8 @@ test "Type.Union" { .layout = .Auto, .tag_type = Tag, .fields = &[_]TypeInfo.UnionField{ - .{ .name = "signed", .field_type = i32 }, - .{ .name = "unsigned", .field_type = u32 }, + .{ .name = "signed", .field_type = i32, .alignment = @alignOf(i32) }, + .{ .name = "unsigned", .field_type = u32, .alignment = @alignOf(u32) }, }, .decls = &[_]TypeInfo.Declaration{}, }, @@ -392,7 +392,7 @@ test "Type.Union from Type.Enum" { .layout = .Auto, .tag_type = Tag, .fields = &[_]TypeInfo.UnionField{ - .{ .name = "working_as_expected", .field_type = u32 }, + .{ .name = "working_as_expected", .field_type = u32, .alignment = @alignOf(u32) }, }, .decls = &[_]TypeInfo.Declaration{}, }, @@ -408,7 +408,7 @@ test "Type.Union from regular enum" { .layout = .Auto, .tag_type = E, .fields = &[_]TypeInfo.UnionField{ - .{ .name = "working_as_expected", .field_type = u32 }, + .{ .name = "working_as_expected", .field_type = u32, .alignment = @alignOf(u32) }, }, .decls = &[_]TypeInfo.Declaration{}, }, @@ -416,3 +416,30 @@ test "Type.Union from regular enum" { _ = T; _ = @typeInfo(T).Union; } + +test "Type.Fn" { + // wasm doesn't support align attributes on functions + if (builtin.arch == .wasm32 or builtin.arch == .wasm64) return error.SkipZigTest; + + const foo = struct { + fn func(a: usize, b: bool) align(4) callconv(.C) usize { + return 0; + } + }.func; + const Foo = @Type(@typeInfo(@TypeOf(foo))); + const foo_2: Foo = foo; +} + +test "Type.BoundFn" { + // wasm doesn't support align attributes on functions + if (builtin.arch == .wasm32 or builtin.arch == .wasm64) return error.SkipZigTest; + + const TestStruct = packed struct { + pub fn foo(self: *const @This()) align(4) callconv(.Unspecified) void {} + }; + const test_instance: TestStruct = undefined; + testing.expect(std.meta.eql( + @typeName(@TypeOf(test_instance.foo)), + @typeName(@Type(@typeInfo(@TypeOf(test_instance.foo)))), + )); +} diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig @@ -211,7 +211,9 @@ fn testUnion() void { expect(notag_union_info.Union.tag_type == null); expect(notag_union_info.Union.layout == .Auto); expect(notag_union_info.Union.fields.len == 2); + expect(notag_union_info.Union.fields[0].alignment == @alignOf(void)); expect(notag_union_info.Union.fields[1].field_type == u32); + expect(notag_union_info.Union.fields[1].alignment == @alignOf(u32)); const TestExternUnion = extern union { foo: *c_void, @@ -229,13 +231,18 @@ test "type info: struct info" { } fn testStruct() void { + const unpacked_struct_info = @typeInfo(TestUnpackedStruct); + expect(unpacked_struct_info.Struct.fields[0].alignment == @alignOf(u32)); + const struct_info = @typeInfo(TestStruct); expect(struct_info == .Struct); expect(struct_info.Struct.layout == .Packed); expect(struct_info.Struct.fields.len == 4); + expect(struct_info.Struct.fields[0].alignment == 2 * @alignOf(usize)); expect(struct_info.Struct.fields[2].field_type == *TestStruct); expect(struct_info.Struct.fields[2].default_value == null); expect(struct_info.Struct.fields[3].default_value.? == 4); + expect(struct_info.Struct.fields[3].alignment == 1); expect(struct_info.Struct.decls.len == 2); expect(struct_info.Struct.decls[0].is_pub); expect(!struct_info.Struct.decls[0].data.Fn.is_extern); @@ -244,8 +251,12 @@ fn testStruct() void { expect(struct_info.Struct.decls[0].data.Fn.fn_type == fn (*const TestStruct) void); } +const TestUnpackedStruct = struct { + fieldA: u32 = 4, +}; + const TestStruct = packed struct { - fieldA: usize, + fieldA: usize align(2 * @alignOf(usize)), fieldB: void, fieldC: *Self, fieldD: u32 = 4, @@ -255,6 +266,8 @@ const TestStruct = packed struct { }; test "type info: function type info" { + // wasm doesn't support align attributes on functions + if (builtin.arch == .wasm32 or builtin.arch == .wasm64) return error.SkipZigTest; testFunction(); comptime testFunction(); } @@ -262,11 +275,14 @@ test "type info: function type info" { fn testFunction() void { const fn_info = @typeInfo(@TypeOf(foo)); expect(fn_info == .Fn); + expect(fn_info.Fn.alignment == 0); expect(fn_info.Fn.calling_convention == .C); expect(!fn_info.Fn.is_generic); expect(fn_info.Fn.args.len == 2); expect(fn_info.Fn.is_var_args); expect(fn_info.Fn.return_type.? == usize); + const fn_aligned_info = @typeInfo(@TypeOf(fooAligned)); + expect(fn_aligned_info.Fn.alignment == 4); const test_instance: TestStruct = undefined; const bound_fn_info = @typeInfo(@TypeOf(test_instance.foo)); @@ -274,7 +290,8 @@ fn testFunction() void { expect(bound_fn_info.BoundFn.args[0].arg_type.? == *const TestStruct); } -extern fn foo(a: usize, b: bool, ...) usize; +extern fn foo(a: usize, b: bool, ...) callconv(.C) usize; +extern fn fooAligned(a: usize, b: bool, ...) align(4) callconv(.C) usize; test "typeInfo with comptime parameter in struct fn def" { const S = struct { diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig @@ -484,3 +484,43 @@ test "vector shift operators" { S.doTheTest(); comptime S.doTheTest(); } + +test "vector reduce operation" { + const S = struct { + fn doTheTestReduce(comptime op: builtin.ReduceOp, x: anytype, expected: anytype) void { + const N = @typeInfo(@TypeOf(x)).Array.len; + const TX = @typeInfo(@TypeOf(x)).Array.child; + + var r = @reduce(op, @as(Vector(N, TX), x)); + expectEqual(expected, r); + } + fn doTheTest() void { + doTheTestReduce(.And, [4]bool{ true, false, true, true }, @as(bool, false)); + doTheTestReduce(.Or, [4]bool{ false, true, false, false }, @as(bool, true)); + doTheTestReduce(.Xor, [4]bool{ true, true, true, false }, @as(bool, true)); + + doTheTestReduce(.And, [4]u1{ 1, 0, 1, 1 }, @as(u1, 0)); + doTheTestReduce(.Or, [4]u1{ 0, 1, 0, 0 }, @as(u1, 1)); + doTheTestReduce(.Xor, [4]u1{ 1, 1, 1, 0 }, @as(u1, 1)); + + doTheTestReduce(.And, [4]u32{ 0xffffffff, 0xffff5555, 0xaaaaffff, 0x10101010 }, @as(u32, 0x1010)); + doTheTestReduce(.Or, [4]u32{ 0xffff0000, 0xff00, 0xf0, 0xf }, ~@as(u32, 0)); + doTheTestReduce(.Xor, [4]u32{ 0x00000000, 0x33333333, 0x88888888, 0x44444444 }, ~@as(u32, 0)); + + doTheTestReduce(.Min, [4]i32{ 1234567, -386, 0, 3 }, @as(i32, -386)); + doTheTestReduce(.Max, [4]i32{ 1234567, -386, 0, 3 }, @as(i32, 1234567)); + + doTheTestReduce(.Min, [4]u32{ 99, 9999, 9, 99999 }, @as(u32, 9)); + doTheTestReduce(.Max, [4]u32{ 99, 9999, 9, 99999 }, @as(u32, 99999)); + + doTheTestReduce(.Min, [4]f32{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f32, -100.0)); + doTheTestReduce(.Max, [4]f32{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f32, 10.0e9)); + + doTheTestReduce(.Min, [4]f64{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f64, -100.0)); + doTheTestReduce(.Max, [4]f64{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f64, 10.0e9)); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/stage2/arm.zig b/test/stage2/arm.zig @@ -0,0 +1,116 @@ +const std = @import("std"); +const TestContext = @import("../../src/test.zig").TestContext; + +const linux_arm = std.zig.CrossTarget{ + .cpu_arch = .arm, + .os_tag = .linux, +}; + +pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exe("hello world", linux_arm); + // Regular old hello world + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{r7}" (4), + \\ [arg1] "{r0}" (1), + \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{r2}" (14) + \\ : "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{r7}" (1), + \\ [arg1] "{r0}" (0) + \\ : "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + } + + { + var case = ctx.exe("parameters and return values", linux_arm); + // Testing simple parameters and return values + // + // TODO: The parameters to the asm statement in print() had to + // be in a specific order because otherwise the write to r0 + // would overwrite the len parameter which resides in r0 + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(id(14)); + \\ exit(); + \\} + \\ + \\fn id(x: u32) u32 { + \\ return x; + \\} + \\ + \\fn print(len: u32) void { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{r7}" (4), + \\ [arg3] "{r2}" (len), + \\ [arg1] "{r0}" (1), + \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")) + \\ : "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{r7}" (1), + \\ [arg1] "{r0}" (0) + \\ : "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + } + + { + var case = ctx.exe("non-leaf functions", linux_arm); + // Testing non-leaf functions + case.addCompareOutput( + \\export fn _start() noreturn { + \\ foo(); + \\ exit(); + \\} + \\ + \\fn foo() void { + \\ bar(); + \\} + \\ + \\fn bar() void {} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{r7}" (1), + \\ [arg1] "{r0}" (0) + \\ : "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + } +} diff --git a/test/stage2/test.zig b/test/stage2/test.zig @@ -21,11 +21,6 @@ const linux_riscv64 = std.zig.CrossTarget{ .os_tag = .linux, }; -const linux_arm = std.zig.CrossTarget{ - .cpu_arch = .arm, - .os_tag = .linux, -}; - const wasi = std.zig.CrossTarget{ .cpu_arch = .wasm32, .os_tag = .wasi, @@ -35,6 +30,7 @@ pub fn addCases(ctx: *TestContext) !void { try @import("zir.zig").addCases(ctx); try @import("cbe.zig").addCases(ctx); try @import("spu-ii.zig").addCases(ctx); + try @import("arm.zig").addCases(ctx); { var case = ctx.exe("hello world with updates", linux_x64); @@ -76,7 +72,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "Hello, World!\n", ); // Now change the message only @@ -108,7 +104,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n", ); // Now we print it twice. @@ -151,10 +147,13 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("hello world", macosx_x64); case.addError("", &[_][]const u8{":1:1: error: no entry point found"}); - } - { - var case = ctx.exe("hello world", linux_riscv64); + // Incorrect return type + case.addError( + \\export fn _start() noreturn { + \\} + , &[_][]const u8{":2:1: error: expected noreturn, found void"}); + // Regular old hello world case.addCompareOutput( \\export fn _start() noreturn { @@ -164,23 +163,23 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ \\fn print() void { - \\ asm volatile ("ecall" + \\ asm volatile ("syscall" \\ : - \\ : [number] "{a7}" (64), - \\ [arg1] "{a0}" (1), - \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")), - \\ [arg3] "{a2}" ("Hello, World!\n".len) - \\ : "rcx", "r11", "memory" + \\ : [number] "{rax}" (0x2000004), + \\ [arg1] "{rdi}" (1), + \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{rdx}" (14) + \\ : "memory" \\ ); \\ return; \\} \\ \\fn exit() noreturn { - \\ asm volatile ("ecall" + \\ asm volatile ("syscall" \\ : - \\ : [number] "{a7}" (94), - \\ [arg1] "{a0}" (0) - \\ : "rcx", "r11", "memory" + \\ : [number] "{rax}" (0x2000001), + \\ [arg1] "{rdi}" (0) + \\ : "memory" \\ ); \\ unreachable; \\} @@ -190,36 +189,37 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exe("hello world", linux_arm); + var case = ctx.exe("hello world", linux_riscv64); // Regular old hello world case.addCompareOutput( \\export fn _start() noreturn { \\ print(); + \\ \\ exit(); \\} \\ \\fn print() void { - \\ asm volatile ("svc #0" + \\ asm volatile ("ecall" \\ : - \\ : [number] "{r7}" (4), - \\ [arg1] "{r0}" (1), - \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")), - \\ [arg3] "{r2}" (14) - \\ : "memory" + \\ : [number] "{a7}" (64), + \\ [arg1] "{a0}" (1), + \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{a2}" ("Hello, World!\n".len) + \\ : "rcx", "r11", "memory" \\ ); \\ return; \\} \\ \\fn exit() noreturn { - \\ asm volatile ("svc #0" + \\ asm volatile ("ecall" \\ : - \\ : [number] "{r7}" (1), - \\ [arg1] "{r0}" (0) - \\ : "memory" + \\ : [number] "{a7}" (94), + \\ [arg1] "{a0}" (0) + \\ : "rcx", "r11", "memory" \\ ); \\ unreachable; \\} - , + , "Hello, World!\n", ); } @@ -244,7 +244,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "Hello, World!\n", ); } @@ -271,7 +271,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); } @@ -298,7 +298,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); } @@ -329,7 +329,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -362,7 +362,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -398,7 +398,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -435,7 +435,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -465,7 +465,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -499,7 +499,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -523,7 +523,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -562,7 +562,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "hello\nhello\nhello\nhello\n", ); @@ -599,7 +599,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -641,7 +641,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -693,7 +693,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -755,7 +755,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -788,7 +788,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -820,7 +820,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -845,7 +845,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -871,7 +871,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "", ); @@ -904,12 +904,50 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , + , "hello\nhello\nhello\nhello\nhello\n", ); } { + var case = ctx.exe("basic import", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ @import("print.zig").print(); + \\ exit(); + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (@as(usize, 0)) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + try case.files.append(.{ + .src = + \\pub fn print() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (@as(usize, 1)), + \\ [arg1] "{rdi}" (@as(usize, 1)), + \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{rdx}" (@as(usize, 14)) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + , + .path = "print.zig", + }); + } + + { var case = ctx.exe("wasm function calls", wasi); case.addCompareOutput( @@ -923,7 +961,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ bar(); \\} \\fn bar() void {} - , + , "42\n", ); @@ -941,7 +979,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ bar(); \\} \\fn bar() void {} - , + , "42\n", ); @@ -957,10 +995,10 @@ pub fn addCases(ctx: *TestContext) !void { \\ bar(); \\} \\fn bar() void {} - , - // This is what you get when you take the bits of the IEE-754 - // representation of 42.0 and reinterpret them as an unsigned - // integer. Guess that's a bug in wasmtime. + , + // This is what you get when you take the bits of the IEE-754 + // representation of 42.0 and reinterpret them as an unsigned + // integer. Guess that's a bug in wasmtime. "1109917696\n", ); } diff --git a/test/translate_c.zig b/test/translate_c.zig @@ -3,6 +3,22 @@ const std = @import("std"); const CrossTarget = std.zig.CrossTarget; pub fn addCases(cases: *tests.TranslateCContext) void { + cases.add("macro expressions respect C operator precedence", + \\#define FOO *((foo) + 2) + \\#define VALUE (1 + 2 * 3 + 4 * 5 + 6 << 7 | 8 == 9) + \\#define _AL_READ3BYTES(p) ((*(unsigned char *)(p)) \ + \\ | (*((unsigned char *)(p) + 1) << 8) \ + \\ | (*((unsigned char *)(p) + 2) << 16)) + , &[_][]const u8{ + \\pub const FOO = (foo + 2).*; + , + \\pub const VALUE = ((((1 + (2 * 3)) + (4 * 5)) + 6) << 7) | @boolToInt(8 == 9); + , + \\pub inline fn _AL_READ3BYTES(p: anytype) @TypeOf(((@import("std").meta.cast([*c]u8, p)).* | (((@import("std").meta.cast([*c]u8, p)) + 1).* << 8)) | (((@import("std").meta.cast([*c]u8, p)) + 2).* << 16)) { + \\ return ((@import("std").meta.cast([*c]u8, p)).* | (((@import("std").meta.cast([*c]u8, p)) + 1).* << 8)) | (((@import("std").meta.cast([*c]u8, p)) + 2).* << 16); + \\} + }); + cases.add("extern variable in block scope", \\float bar; \\int foo() { @@ -2978,7 +2994,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("string concatenation in macros: three strings", \\#define FOO "a" "b" "c" , &[_][]const u8{ - \\pub const FOO = "a" ++ ("b" ++ "c"); + \\pub const FOO = "a" ++ "b" ++ "c"; }); cases.add("multibyte character literals",