ldc-1.40.0-src/0000755000000000000000000000000014727563353011640 5ustar rootrootldc-1.40.0-src/CHANGELOG.md0000644000000000000000000027264414727557031013464 0ustar rootroot# LDC master #### Big news #### Platform support #### Bug fixes # LDC 1.40.0 (2024-12-15) #### Big news - Frontend, druntime and Phobos are at version [2.110.0](https://dlang.org/changelog/2.110.0.html). (#4707, #4737, #4749, #4768, #4784, #4792, #4798) - Support for [LLVM 19](https://releases.llvm.org/19.1.0/docs/ReleaseNotes.html). The prebuilt packages use v19.1.3 (incl. macOS arm64). (#4712, #4735, #4763, #4772) - Objective-C: The compiler now properly supports Objective-C classes and protocols, as well as swift stub classes (via the `@swift` UDA). (#4777) - Android: NDK for prebuilt package bumped from r26d to r27c. (#4711, #4772) - ldc2.conf: `%%ldcconfigpath%%` placeholder added - specifies the directory where current configuration file is located. (#4717) - Add support for building against a system copy of zlib through `-DPHOBOS_SYSTEM_ZLIB=ON`. (#4742) - Emscripten: The compiler now mimicks a musl Linux platform wrt. extra predefined versions (`linux`, `Posix`, `CRuntime_Musl`, `CppRuntime_LLVM`). (#4750) #### Platform support - Supports LLVM 15 - 19. #### Bug fixes - Fix potentially corrupt IR layouts for bit fields. (#4646, #4708) - Fix potentially corrupt IR layouts for explicitly under-aligned aggregates, a regression introduced in LDC v1.31. (#4734, #4736) - ELF: Emit (most) instantiated symbols in COMDATs for proper link-time culling. (#3589, #4748) - Support scalar right-hand-sides when bit-shifting vectors. (#3606, #4781) - Fix LLVM data layout for the SPIR-V target used in D-Compute on LLVM 19+. (#4772) # LDC 1.39.0 (2024-07-04) #### Big news - Frontend, druntime and Phobos are at version [2.109.1](https://dlang.org/changelog/2.109.0.html). (#4660, #4692) - LLVM for prebuilt packages bumped to v18.1.6 (except for macOS arm64). (#4678) - Added CI testing of Alpine Linux with musl libc (including some bug fixes). Removed the libunwind dependency when linking with musl libc. (#4650, #4691) #### Platform support - Supports LLVM 15 - 18. Support for LLVM 11 - 14 was dropped. The CLI options `-passmanager` and `-opaque-pointers` were removed. # LDC 1.38.0 (2024-05-11) #### Big news - Frontend, druntime and Phobos are at version [2.108.1](https://dlang.org/changelog/2.108.0.html). (#4591, #4615, #4619, #4622, #4623, #4640) - Support for [LLVM 18](https://releases.llvm.org/18.1.0/docs/ReleaseNotes.html). The prebuilt packages use v18.1.5 (except for macOS arm64). (#4599, #4605, #4607, #4604, #4628, #4642) - Android: Switch to native ELF TLS, supported since API level 29 (Android v10), dropping our former custom TLS emulation (requiring a modified LLVM and a legacy ld.bfd linker). The prebuilt packages themselves require Android v10+ (armv7a) / v11+ (aarch64) too, and are built with NDK r26d. Shared druntime and Phobos libraries are now available (`-link-defaultlib-shared`), as on regular Linux. (#4618) - Please don't use the official macOS arm64 package (incl. the universal package on arm64) to cross-compile to Android. That package still uses our previous LLVM v17.0.6, which still includes the custom TLS emulation, but druntime expects native TLS now on Android. Resort to the x86_64 package in that case. #### Platform support - Supports LLVM 11 - 18. #### Bug fixes - Android: Support the lld linker. (#3918) # LDC 1.37.0 (2024-03-03) #### Big news - Frontend, druntime and Phobos are at version [2.107.1](https://dlang.org/changelog/2.107.0.html). (#4563, #4577, #4587) #### Bug fixes - Fix if-statement elision on constant true/false condition. (#4556, #4559) # LDC 1.36.0 (2024-01-06) #### Big news - Frontend, druntime and Phobos are at version [2.106.1](https://dlang.org/changelog/2.106.0.html). (#4522, #4539, #4551) - Support for [LLVM 17](https://releases.llvm.org/17.0.1/docs/ReleaseNotes.html). The prebuilt packages use v17.0.6. (#4533, #4540) - New command-line options `-fno-{exceptions,moduleinfo,rtti}` to selectively enable some `-betterC` effects. (#4522) - New command-line option `-fprofile-sample-use` for using sample-based profile data for optimization. Functionality and usage is identical to Clang's option with same name. (#4531) - New `ldc-profgen` tool for sample-based PGO, a copy of LLVM's [llvm-profgen](https://llvm.org/docs/CommandGuide/llvm-profgen.html). (#4536) #### Platform support - Supports LLVM 11.0 - 17.0. # LDC 1.35.0 (2023-10-15) #### Big news - Frontend, druntime and Phobos are at version [2.105.2+](https://dlang.org/changelog/2.105.0.html). (#4476, #4498, #4513) - The Windows installer now supports non-admin installs *without* an explicit `/CURRENTUSER` switch. (#4495) #### Platform support - Initial compiler support for LoongArch64. druntime support is pending. (#4500) #### Bug fixes - ImportC: - Fix `static` linkage. (#4484, #4487) - Make gcc builtins available. (#4483) - Apple: Support weird `asm("_" "")` mangling stuff. (#4485, #4486) - AArch64: Fix an ABI-related ICE. (#4489, #4490) - Fix GC2Stack optimization regression introduced in v1.24. (#4510, #4511) - Fix druntime ABI divergence when compiling with sanitizers support. (#4508, #4509) - Windows: Fix an instance of missed backslash-escaping in `-ftime-trace` JSON. (#4506, #4507) # LDC 1.34.0 (2023-08-26) #### Big news - Frontend, druntime and Phobos are at version [2.104.2](https://dlang.org/changelog/2.104.0.html). (#4440) - Support for [LLVM 16](https://releases.llvm.org/16.0.0/docs/ReleaseNotes.html). The prebuilt packages use v16.0.6. (#4411, #4423) - We have come across miscompiles with LLVM 16's newly-enabled-by-default function specializations (on Win64 and macOS). To be on the safe side, LDC disables them by default for all targets via `-func-specialization-size-threshold=1000000000` in `etc/ldc2.conf` (and separately for LTO on Posix platforms). To enable the function specializations, explicitly override it with e.g. `-func-specialization-size-threshold=100` (the LLVM 16 default) and, for LTO on Posix, a similar LTO plugin option in the linker cmdline (see linker cmdline with `-v`). #### Platform support - Supports LLVM 11.0 - 16.0. Support for LLVM 9 and 10 was dropped. - 64-bit RISC-V: Now defaults to `-mattr=+m,+a,+f,+d,+c` ('rv64gc' ABI) for non-bare-metal targets, i.e., if the target triple includes a valid operating system. (#4390) #### Bug fixes - Fix function pointers/delegates on Harvard architectures (e.g., AVR). (#4432, #4465) # LDC 1.33.0 (2023-07-23) #### Big news - Frontend, druntime and Phobos are at version [2.103.1](https://dlang.org/changelog/2.103.0.html), incl. new command-line option `-verror-supplements`. (#4345) - The `--plugin` commandline option now also accepts semantic analysis plugins. Semantic analysis plugins are recognized by exporting the symbol: `extern(C) void runSemanticAnalysis(Module m)`. The plugin's `runSemanticAnalysis` function is called for each module, after all other semantic analysis steps (also after DCompute SemA), just before object codegen. (#4430) - New tool `ldc-build-plugin` that helps compiling user plugins. It downloads the correct LDC source version (if it's not already available), and calls LDC with the correct commandline flags to build a plugin. (#4430) - New commandline option `-femit-local-var-lifetime` that enables variable lifetime (scope) annotation to LLVM IR codegen. Lifetime annotation enables stack memory reuse for local variables with non-overlapping scope. (#4395) - C files are now automatically preprocessed using the external C compiler (configurable via `-gcc` or the `CC` environment variable, and `-Xcc` for extra flags). Extra preprocessor flags (e.g., include dirs and manual defines) can be added via new command-line option `-P`. (#4417) - Windows: If `clang-cl.exe` is on `PATH`, it is preferred over Microsoft's `cl.exe` by default (e.g., to avoid printing the C source file name to stderr during preprocessing). - Less pedantic checks for conflicting C(++) function declarations when compiling multiple modules to a single object file ('Error: Function type does not match previously declared function with the same mangled name'). The error now only appears if an object file actually references multiple conflicting functions. (#4420) - New command-line option `--fcf-protection`, which enables Intel's Control-Flow Enforcement Technology (CET). (#4437) #### Platform support - Supports LLVM 9.0 - 15.0. #### Bug fixes - Handle potential lambda mangle collisions across separately compiled object files (and the linker then silently picking an arbitrary implementation). Lambdas (and their nested global variables) are now internal to each referencing object file (`static` linkage in C). (#4415) # LDC 1.32.2 (2023-05-12) #### Big news - New command-line option `--fwarn-stack-size=` with LLVM 13+. (#4378) - New command-line option `--fsplit-stack` for incremental stack allocations, see https://llvm.org/docs/SegmentedStacks.html. (#4379) - New UDA `ldc.attributes.noSplitStack` disables it on a per-function basis. (#4382) - New command-line option `--indent` for the `timetrace2txt` tool. (#4391) #### Bug fixes - Fix potentially huge compile slowdowns with `-g` and LLVM 15+. (#4354, #4393) - Treat *all* LLVM warnings as regular warnings (e.g., errors with `-w`). Requires LLVM 13+. (#4384) # LDC 1.32.1 (2023-04-17) #### Big news - The prebuilt Linux packages are now generated on a Ubuntu 20.04 box, so the min required `glibc` version has been raised from 2.26 to 2.31. (#4367) #### Bug fixes - Fix empty `ldc.gccbuiltins_*` modules with LLVM 15+. (#4347, #4350) - Fix v1.31 regression wrt. potentially wrong constant pointer offsets. (#4362, #4365) - Windows: Fix v1.32 regression wrt. leaking `Throwable.info` backtraces. (#4369) - Fix C assert calls for newlib targets. (#4351) # LDC 1.32.0 (2023-03-12) #### Big news - Frontend, druntime and Phobos are at version [2.102.2](https://dlang.org/changelog/2.102.0.html). (#4323, #4341) - LLVM for prebuilt packages bumped to v15.0.7. (#4311) - Linker-level dead code elimination is enabled by default for Apple, wasm and *all* ELF targets too now. (#4320) - Vector comparisons (==, !=, <, <=, >, >=) now yield a vector mask. Identity comparisons (`is`, `!is`) still yield a scalar `bool`. (3a59ee81) - New `timetrace2txt` tool for easier inspection of `--ftime-trace` output. (#4335) - `--ftime-trace` now also traces CTFE execution: the start expression of CTFE and function calls during CTFE. (#4339) #### Platform support - Supports LLVM 9.0 - 15.0. - Now supports `-mabi` for RISC-V targets. (#4322) #### Bug fixes - GC closures including variables with alignment > 16 bytes are now properly aligned. (ef8ba481) - Fix regression with LLVM 13+: some errors in inline assembly don't stop compilation. (#4293, #4331) # LDC 1.31.0 (2022-02-11) #### Big news - Frontend, druntime and Phobos are at version [2.101.2](https://dlang.org/changelog/2.101.0.html). (#4141, #4279) - Bit fields support. (#4015) - macOS on Apple M1: linking with `-g` is working again without unaligned pointer warnings/errors. This fixes file:line debug information in exception backtraces (requiring `atos`, a macOS development tool installed with Xcode), without the need to set MACOSX_DEPLOYMENT_TARGET=11 and using a modified LLVM. (#4291) - *Preliminary* support for LLVM 15, incl. adding support for the 'new' pass manager (`-passmanager`) and opaque IR pointers (`-opaque-pointers`). (way too many PRs to list!) - New command-line option `-fno-delete-null-pointer-checks`, mimicking the same option of GCC and Clang. (#4297) - New UDA `ldc.attributes.callingConvention("...")`, which overrides the default calling convention. For expert use only! (#4299) - New command-line option `-fno-discard-value-names` to keep value names in LLVM IR. (#4012) - dcompute: Support for OpenCL image I/O. (#3835) #### Platform support - Initial ABI support for 64-bit RISC-V. (#4007) #### Bug fixes - dcompute: Fix v1.29 regression when trying to use intrinsics. (#4266, #4267) - Fix 64-bit symbol offsets. (#4264, #4283) - Add missing 32-bit LTO versions of druntime & Phobos to Linux multilib package. (#4234, #4235) - Fix compiler crash. (#4130, #4135) #### Internals - The former druntime and dmd-testsuite git submodules are now part of the LDC repo directly, leaving Phobos as single remaining submodule. We are now using a subset of the DMD repo (which includes druntime since v2.101), rewritten via `git filter-repo` and exposed as `dmd-rewrite-*` branches/tags in the LDC repo, to merge newer frontend+druntime+tests from upstream DMD. The `tests/d2/dmd-testsuite` dir was moved to `tests/dmd`. (#4274, #4276) # LDC 1.30.0 (2022-07-20) #### Big news - Frontend, druntime and Phobos are at version [2.100.1](https://dlang.org/changelog/2.100.0.html). (#3970, #4008, #4009) - LLVM for prebuilt packages bumped to v14.0.3. (#3952, #3979) - All LLVM targets are enabled now (=> more targets for cross-compilation). - For the Mac package, the minimum supported macOS version has been raised to v10.12. - The minimum D version for bootstrapping has been raised to v2.079 (for GDC: v9.4), in line with DMD. (#3956) - The minimum LLVM version has been raised to v9.0. (#3960) - New LeakSanitizer support via `-fsanitize=leak` (not (yet?) supported on Windows). (#4005) - New prebuilt *universal* macOS package, runnable on both x86_64 and arm64, and enabling x86_64/arm64 macOS/iOS cross-compilation targets out of the box (`-mtriple={x86_64,arm64}-apple-{macos,ios12.0}`). The x86_64 package doesn't bundle any arm64 libs anymore; the arm64 package newly bundles iOS libs (arm64). (#3958) - Avoid an external x86_64-only dub, use the bundled universal dub executable instead. #### Platform support - Supports LLVM 9.0 - 14.0. #### Bug fixes - Enable output of variable names in ASan and MSan error reporting. (#4004) - Report unexpected type repaints as fatal ICEs instead of crashing. (#3990, #3991) #### Internals - Main CI was moved from Azure Pipelines to GitHub Actions. Any fork on GitHub can trivially reuse the fully automated prebuilt packages generation & upload to a GitHub release. (#3978) # LDC 1.29.0 (2022-04-08) #### Big news - Frontend, druntime and Phobos are at version [2.099.1](https://dlang.org/changelog/2.099.0.html). (#3917, #3893, #3937, #3953) - Support for LLVM 13 and 14. The prebuilt packages use v13.0.1. (#3842, #3951) - On Linux, LDC doesn't default to the `ld.gold` linker anymore. The combination of LLVM 13+ and older gold linkers can apparently cause problems. We recommend using LLD, e.g., via `-linker=lld` or by setting your default `/usr/bin/ld` symlink; it's significantly faster too. - `-linkonce-templates` is less aggressive by default now and IMHO production-ready. (#3924) - When linking manually (not via LDC) against *shared* druntime, it is now required to link the bundled `lib/ldc_rt.dso.o[bj]` object file into each binary. It replaces the previously Windows-specific `dso_windows.obj`. (#3850) - Breaking `extern(D)` ABI change for all targets: formal parameters of non-variadic functions aren't reversed anymore, in line with the spec. For 32-bit x86, the *first* parameter is accordingly now potentially passed in EAX, not the last one. So non-variadic `extern(D)` functions with multiple explicit parameters will break if expecting parameters in specific registers or stack slots, e.g., naked DMD-style inline assembly. (#3873, ldc-developers/phobos@3d725fce8f0acb78bf6cb984a8462e81e8e1b715) #### Platform support - Supports LLVM 6.0 - 14.0. - Basic compiler support for Newlib targets, i.e., triples like `arm-none-newlibeabi`. (#3946) #### Bug fixes - Linux: Make LTO work with LLD. (#3786, #3850) - Windows: Fix most undefined symbols with `-dllimport=all` without `-linkonce-templates`. (#3916, #3923, #3926, #3927, #3928, #3931, #3932) - Capture NRVO variable by ref for stack closures. (#3883, #3902) - `-ftime-trace`: JSON-escape filenames. (#3947, #3948) - RISC-V: Use 128-bit quadruple `real`. (#3892) # LDC 1.28.1 (2022-01-13) #### Big news - Frontend, druntime and Phobos are at version [2.098.1+](https://dlang.org/changelog/2.098.0.html). (#3886, #3896) - New `@hidden` UDA (as counterpart of `export`). (#3855) - Support 'raw mangles' via leading `\1` in `pragma(mangle)` strings, e.g., to access magic linker symbols on Mac. (#3854) - New `@noSanitize` UDA to selectively disable sanitizer instrumentation of functions. (#3889) - WebAssembly: Larger default stack size (1 MB) and protection against stack overflow overwriting global memory. (#3882) #### Bug fixes - Linux x86/x64: Fix TLS range with static druntime and bfd/lld linkers. (#3849, https://github.com/ldc-developers/druntime/commit/ec3c0aafbf4b6f3345e276e21a26ffee077470cf) - Support `rdtscp` in DMD-style inline assembly. (#3895) # LDC 1.28.0 (2021-10-20) #### Big news - Frontend, druntime and Phobos are at version [2.098.0+](https://dlang.org/changelog/2.098.0.html). (#3821, #3839, #3844, #3852) - Windows: `-dllimport=defaultLibsOnly` (e.g., implied by `-link-defaultlib-shared -fvisibility=hidden`) doesn't require `-linkonce-templates` anymore. (#3816) - dcompute: Add support for OpenCL image I/O. (#3835) #### Platform support - Supports LLVM 6.0 - 12.0. #### Bug fixes - Fix dynamic casts across binary boundaries (DLLs etc.). (dlang/druntime#3543) - Windows: Fix potentially wrongly caught exceptions due to non-unique `TypeInfo_Class` names. (#3520) - Don't silently ignore invalid external tool specifications. (#3841) - LLVM v11.1: Add missing PGO `ldc-profdata` tool. # LDC 1.27.1 (2021-08-14) #### Big news - Frontend, druntime and Phobos are at version [2.097.2](https://dlang.org/changelog/2.097.0.html). (#3811) - Revamped and improved `-ftime-trace` implementation for compiler profiling/tracing, now excluding LLVM-internal traces, adding frontend memory tracing, source file location infos etc. (#3797) - An official prebuilt package for Linux AArch64 is available again after migrating from Shippable to Travis. (#3733) #### Bug fixes - ICE for 64-bit targets with 32-bit pointer size. (#3802, #3808) - Implement `core.atomic.pause()` for some architectures. (#3806, #3807) # LDC 1.27.0 (2021-07-31) #### Big news - Frontend, druntime and Phobos are at version [2.097.1+](https://dlang.org/changelog/2.097.0.html). (#3741, #3770, #3771, #3790, #3794, #3796, #3799) **(new)** - LLVM for prebuilt packages bumped to **v12.0.1**, and Linux base image to Ubuntu 18.04. Unfortunately, the dynamic-compile (JIT) functionality is lost this way - it needs some [more work](https://github.com/ldc-developers/ldc/pull/3184) to adapt to a newer LLVM API. (#3701, #3789) - Prebuilt packages now bundle [reggae](https://github.com/atilaneves/reggae), a meta build tool to generate [ninja](https://github.com/ninja-build/ninja/releases)/make build files for dub projects (and more). Building large projects with many dependencies can be significantly sped-up via parallelization and dependency tracking for incremental builds. (#3739) Basic [usage](https://github.com/atilaneves/reggae#d-projects-and-dub-integration), in a dub project dir (containing a `dub.{sdl,json}` file): ``` reggae -b ninja|make --dc=ldc2 # only needed the first time or when adding source files ninja|make [-j] ``` - Greatly improved **DLL support on Windows**, making it almost as easy as on Posix: - `-fvisibility=public` now also affects Windows, exporting all defined symbols as on Posix, without explicit `export` visibility. Compiling a DLL with `-shared` now defaults to `-fvisibility=public` for consistency with Posix. (#3703) - This paved the way for druntime and Phobos DLLs, now bundled with prebuilt Windows packages and linkable via `-link-defaultlib-shared` (default with `-shared`, consistent with Posix targets). Previous hacks to partially accomodate for multiple, statically linked druntimes and Phobos in a single process (GC proxy etc.) aren't required any longer. With `-link-defaultlib-shared`, LDC now defaults to `-mscrtlib=msvcrt`, linking against the shared MSVC runtime. (ldc-developers/druntime#197, #3704, ldc-developers/druntime#198) - Limitation: TLS variables cannot be accessed directly across DLL boundaries. This can be worked around with an accessor function, e.g., ldc-developers/druntime@5d3e21a35d. - Non-TLS `extern(D)` global variables *not* defined in a root module are `dllimport`ed (with `-fvisibility=public`, or - restricted to druntime/Phobos symbols - with `-link-defaultlib-shared`). Compiling all modules of a DLL at once thus avoids linker warnings about 'importing locally defined symbol'. When linking a DLL against a static library, the static library may likely need to be compiled with `-fvisibility=public` to make its globals importable from the DLL. There's a new `-dllimport` option for explicit control. (#3763) - Caveat: symbols aren't uniqued across the whole process, so can be defined in multiple DLLs/executables, each with their own address, so you cannot rely on TypeInfos, instantiated symbols and functions to have the same address for the whole process. - When linking manually (not via LDC), binaries linked against druntime DLL need to include new `lib\dso_windows.obj`. - To restore the previous behavior of `-shared`, add `-fvisibility=hidden -link-defaultlib-shared=false`. - Windows: ANSI color codes can now be enforced for redirected stderr via `-enable-color`. (#3744) - Prebuilt Linux and Mac packages now use the [mimalloc](https://github.com/microsoft/mimalloc) allocator, significantly increasing compiler performance in some cases. (#3758, #3759) - The prebuilt macOS x64 package now bundles *shared* druntime/Phobos libs for iOS too. (#3764) - Possibly more performant *shared* Phobos library by compiling to a single object file with implicit cross-module inlining. (#3757) - New `-cov-increment` option for more performant coverage count execution. (#3724) - `-fsanitize=memory`: Bundle according LLVM compiler-rt library and add new `-fsanitize-memory-track-origins` option. (#3751) - New LDC-specific language addition: `__traits(initSymbol, )` with semantics equivalent to `TypeInfo.initializer()`, but circumventing the `TypeInfo` indirection and thus e.g. also usable for `-betterC` code. (#3774, ldc-developers/druntime#201) #### Platform support - Supports LLVM 6.0 - 12.0. #### Bug fixes - Fix debuginfo source file paths, e.g., including directories in exception stack traces. (#3687) - Fix potentially corrupt context pointers for nested functions with `-linkonce-templates`. (#3690, #3766) - Predefine version `CppRuntime_Gcc` for musl targets. (#3769) - RVO: In-place construct `.__ctor()`. (#3778, #3779) - `-linkonce-templates`: Make sure special struct TypeInfo members are semantically analyzed before emitting the `TypeInfo`. (#3783) # LDC 1.26.0 (2021-04-28) #### Big news - Frontend, druntime and Phobos are at version [2.096.1+](https://dlang.org/changelog/2.096.0.html), incl. new `ldmd2` command-line option `-gdwarf=` (use `-dwarf-version` for `ldc2`). (#3678, #3706) #### Platform support - Supports LLVM 6.0 - 12.0. #### Bug fixes - v1.25 regression: TypeInfo for interface gives invalid string for name. (#3693) - Make enums show up correctly as members of a struct when debugging. (#3688, #3694) - Some new GCC builtins are available in `ldc.gccbuiltins_*`, by not rejecting LLVM `i1` anymore (mapping to D `bool` instead). Thanks Bruce! (#3682) - dcompute: Don't reject CUDA versions 7.x - 8.0.0. (#3683) - Don't enforce the frame pointer for functions with GCC-style inline asm. (#3685) - `-i`: Exclude `ldc.*` modules by default. (#3679) - Fix some cases of insufficient alignment for arguments and parameters. (#3692, #3698) - Fix a few issues with LLVM 12. (#3697, #3708) # LDC 1.25.0 (2021-02-21) #### Big news - Frontend, druntime and Phobos are at version [2.095.1](https://dlang.org/changelog/2.095.0.html), incl. new command-line option `-makedeps`. (#3620, #3658, #3668) - Support for **LLVM 12** and LLVM 11.1. (#3663, ldc-developers/druntime#195) - LLVM for prebuilt packages bumped to v11.0.1. (#3639) - New prebuilt package for **native macOS/arm64** ('Apple silicon'). (#3666) - LDC invocations can now be nicely profiled via `--ftime-trace`. (#3624) - Struct TypeInfos are emitted into *referencing* object files only, and special TypeInfo member functions into the owning object file only. (#3491) - Windows: - New CI-automated [Windows installer](https://github.com/ldc-developers/ldc/releases/download/v1.25.0/ldc2-1.25.0-windows-multilib.exe) corresponding to the multilib package. (#3601) - Bundled MinGW-based libs bumped to MinGW-w64 v8.0.0. (#3605) - Bundled libcurl upgraded to v7.74.0. (#3638) - Breaking ABI changes: - `extern(D)`: Pass non-PODs by ref to temporary. (#3612) - Win64: Pass/return delegates like slices - in (up to) 2 GP registers. (#3609) - Win64 `extern(D)`: Pass/return Homogeneous Vector Aggregates in SIMD registers. (#3610) - `-linkonce-templates` comes with a new experimental template emission scheme and is now suited for projects consisting of multiple object files too. It's similar to C++, emitting templated symbols into *each* referencing compilation unit with optimizer-discardable `linkonce_odr` linkage. The consequences are manifold - each object file is self-sufficient wrt. templated symbols, naturally working around any template-culling bugs and also meaning increased opportunity for inlining and less need for LTO. The probably biggest advantage is that the optimizer can discard unused `linkonce_odr` symbols early instead of optimizing and forwarding to the assembler. So this is especially useful to **decrease compilation times with `-O`** and can at least in some scenarios greatly outweigh the (potentially very much) higher number of symbols defined by the glue layer - on my box, building optimized dub (all-at-once) is 28% faster with `-linkonce-templates`, and building the optimized Phobos unittests (per module) 56% faster. Libraries compiled with `-linkonce-templates` can generally *not* be linked against dependent code compiled without `-linkonce-templates`; the other way around works. (#3600) - Emit function/delegate literals as `linkonce_odr`, as they are emitted into each referencing compilation unit too. (#3650) - Exploit ABI specifics with `-preview=in`. (#3578) - Musl: Switch to cherry-picked libunwind-based backtrace alternative. (#3641, ldc-developers/druntime#192) #### Platform support - Supports LLVM 6.0 - 12.0. #### Bug fixes - Fix LTO with `-link-internally`. The prebuilt Windows packages don't bundle an external `lld-link.exe` LLD linker anymore. (#2657, #3604) - Add source location information for `TypeInfo` diagnostics with `-betterC`. (#3631, #3632) - Keep init symbols of built-in `TypeInfo` classes mutable just like any other TypeInfo, so that e.g. `synchronized()` can be used on the implicit monitor. (#3599) - Windows: Fix colliding EH TypeDescriptors for exceptions with the same `TypeInfo_Class` name. (#3501, #3614) - Predefine version `FreeStanding` when targeting bare-metal. (#3607, #3608) - druntime: Define `rt.aaA.AA` as naked pointer, no struct wrapper. (#3613) - Misc. fixes and improvements for the CMake scripts, incl. new defaults for `LDC_INSTALL_{LTOPLUGIN,LLVM_RUNTIME_LIBS}`. (#3647, #3655, #3654, #3673) - `-cleanup-obj`: Put object files into unique temporary directory by default. (#3643, #3660) - druntime: Add missing `core.atomic.atomicFetch{Add,Sub}`. (#3646, ldc-developers/druntime#193) - Fix regression wrt. non-deleted temporary `-run` executable. (#3636) #### Internals - Ignore `-enable-cross-module-inlining` if inlining is generally disabled. (#3664) - Travis CI ported to GitHub Actions (excl. Linux/AArch64). (#3661, #3662) # LDC 1.24.0 (2020-10-24) #### Big news - Frontend, druntime and Phobos are at version [2.094.1+](https://dlang.org/changelog/2.094.0.html), incl. new command-line options `-cov=ctfe`, `-vtemplates=list-instances` and `-HC=` . (#3560, #3582, #3588, #3593) - Support for **LLVM 11**. The prebuilt packages use v11.0.0; x86 packages newly include the LLVM backend for AMD GPUs. (#3546, #3586) - Experimental support for **macOS on 64-bit ARM**, thanks Guillaume! All druntime/Phobos unit tests pass. The macOS package includes prebuilt druntime/Phobos; adapt the SDK path in `etc/ldc2.conf` and then use `-mtriple=arm64-apple-macos` to cross-compile. (dlang/druntime#3226, #3583) #### Platform support - Supports LLVM 6.0 - 11.0. #### Bug fixes - Fix potentially wrong context pointers when calling delegate literals. (#3553, #3554) - Fix alignment issue when casting vector rvalue to static array. (c8889a9219) - Make sure lambdas in `pragma(inline, true)` functions are emitted into each referencing compilation unit. (#3570) - Fix `-Xcc=-Wl,...` by dropping support for comma-separated list of `cc` options. (c61b1357ed) - Fix ThreadSanitizer support by not detaching main thread upon program termination. (#3572) - Traverse full chain of nested aggregates when resolving a nested variable. (#3556, #3558) #### Internals - CI: Linux AArch64 is now also tested by a Travis job, because Shippable has sadly become unreliable. (#3469) - Building LDC with an LDC host compiler might be somewhat faster now (requires `-DLDC_LINK_MANUALLY=OFF` in the CMake command-line on non-Windows hosts). (#3575) # LDC 1.23.0 (2020-08-19) #### Big news - Frontend, druntime and Phobos are at version [2.093.1+](https://dlang.org/changelog/2.093.0.html), incl. new command-line option `-vtemplates`. (#3476, #3538, #3541) - Min required LLVM version raised to v6.0, dropping support for v3.9-5.0. (#3493) - LLVM for prebuilt packages bumped to v10.0.1. (#3513) - The prebuilt Mac package now also includes prebuilt druntime/Phobos for the iOS/x86_64 simulator, making cross-compilation work out of the box with `-mtriple=x86_64-apple-ios12.0`. (#3478) - Windows: New `-gdwarf` CLI option to emit DWARF debuginfos for MSVC targets, e.g., for debugging with gdb/lldb. (#3533) - New `-platformlib` CLI option to override the default linked-with platform libraries, e.g., when targeting bare-metal. (#3374, #3475) #### Platform support - Supports LLVM 6.0 - 10.0. #### Bug fixes - Fix regression since v1.22: shared druntime potentially overriding libstdc++ symbols and breaking exceptions in C++ libraries. (#3530, #3537) - Fix naked DMD-style asm emission for non-Mac x86 Darwin targets (e.g., iOS simulators). (#3478) - `-betterC`: Don't use unsupported EH for handling clean-ups. (#3479, #3482) - dcompute: Fix wrong address space loads and stores. Thx Rob! (#3428) - Fix ICE wrt. missing IR declarations for some forward-declared functions. (#3496, #3503) - Fix ICE wrt. inline IR and empty parameter types tuple. (#3509) - Fix PGO issues. (#3375, #3511, #3512, #3524) - Improve support for LLVM's ThreadSanitizer. (#3522) - Fix linker cmdline length limitation via response files. (#3535, #3536) #### Internals - Compiler performance wrt. string literals emission has been improved. Thx @looked-at-me! (#3490, #3492) - Link libstdc++ statically for `libldc-jit.so` of prebuilt Linux packages, to increase portability. (#3473, #3474) - Set up Visual D when using the Visual Studio CMake generator, making LDC compiler development on Windows a smooth out-of-the-box experience. (#3494) # LDC 1.22.0 (2020-06-16) #### Big news - Frontend, druntime and Phobos are at version [2.092.1+](https://dlang.org/changelog/2.092.0.html). (#3413, #3416, #3429, #3434, #3452, #3467) - **AArch64**: All known ABI issues have been fixed. C(++) interop should now be on par with x86_64, and variadics usable with `core.{vararg,stdc.stdarg}`. (#3421) - Windows hosts: DMD's Visual C++ toolchain detection has been adopted. As that's orders of magnitude faster than the previous method involving the MS batch file, auto-detection has been enabled by default, so if you have a non-ancient Visual C++ installation, it will now be used automatically for linking. The environment setup has been reduced to the bare minimum (`LIB` and `PATH`). (#3415) - **FreeBSD** x64: CI with CirrusCI is now fully green and includes automated prebuilt package generation. The package depends on the `llvm` ports package and should currently work on FreeBSD 11-13. (#3453, #3464) - Link-time overridable `@weak` functions are now emulated for Windows targets and work properly for ELF platforms. For ELF, LDC doesn't emit any COMDATs anymore. (#3424) - New `ldc.gccbuiltins_{amdgcn,nvvm}` for AMD GCN and NVIDIA PTX targets. (#3411) - druntime: Significant speed-up for `core.math.ldexp`. (#3440, #3446) #### Platform support - Supports LLVM 3.9 - 10.0. #### Bug fixes - Cross-module inlining (incl. `pragma(inline, true)`): Enable emission into multiple object files. This may have a significant impact on performance (incl. druntime/Phobos) when not using LTO. (#3126, #3442) - Android: Fix TLS initialization regression (introduced in v1.21) and potential alignment issues. Unfortunately, the `ld.bfd` linker is required for our custom TLS emulation scheme, unless you're willing to use a custom linker script. So `-linker=bfd` is the new default for Android targets. (#3462) - Casting (static and dynamic) arrays to vectors now loads the data instead of splatting the first element. (#3418, #3419) - Fix return statements potentially accessing memory from destructed temporaries. (#3426) - Add proper support for `-checkaction=halt`. (#3430, #3431) - druntime: Include `core.stdcpp.*` modules. (#3103, #3158) - GCC-style asm: Add support for indirect input operands (`"m"`). (#3438) - FreeBSD: Fix backtraces for optimized code by switching to external `libexecinfo`. (#3108, #3453) - FreeBSD: Fix C math related issues (incl. CTFE math issues) by bringing `core.stdc.{math,tgmath}` up to speed. (dlang/druntime#3119) - Fix ICE for captured parameters not passed on the LLVM level. (#3441) - Convenience fixes for RISC-V and other exotic targets. (#3457, #3460) #### Internals - When printing compile-time reals to hex strings (mangling, .di headers), LDC now uses LLVM instead of the host C runtime, for proper and consistent results. (#3410) - One limitation for exotic hosts wrt. C `long double` precision has been lifted. (#3414) - For AVR targets, the compiler now predefines `AVR` and emits all TLS globals as regular `__gshared` ones. (#3420) - WebAssembly: New memory grow/size intrinsics. (ldc-developers/druntime#187) - New `-fno-plt` option to avoid PLT external calls. (#3443) - iOS/arm64 CI, running the debug druntime & Phobos unittests on an iPhone 6S. Thx Jacob for this tedious work! (#3379, #3450) # LDC 1.21.0 (2020-04-23) #### Big news - Frontend, druntime and Phobos are at version [2.091.1+](https://dlang.org/changelog/2.091.1.html), incl. new CLI switches `-verror-style` and `-HC`, `-HCd`, `-HCf`. (#3333, #3399) - **iOS** (incl. watchOS and tvOS) support has landed in druntime and Phobos (thanks Jacob!). All unittests are green on iOS/arm64. The prebuilt macOS package includes prebuilt druntime & Phobos libraries for iOS/arm64, for first `-mtriple=arm64-apple-ios12.0` cross-compilation experiments. (#3373) - LLVM for prebuilt packages upgraded to v10.0.0. Android NDK version bumped to r21. (#3307, #3387, #3398) - Initial support for **GCC/GDC-style inline assembly** syntax, besides DMD-style inline asm and LDC-specific `__asm`, enabling to write inline asm that is portable across GDC/LDC and corresponds to the GCC syntax in C. See ldc-developers/druntime#171 for examples wrt. how to transition from `__asm` to similar GCC-style asm. (#3304) - Inline assembly diagnostics have been extended by the D source location. (#3339) - **Android**: - Revamped druntime initialization, fixing related issues for i686/x86_64 targets, enabling the usage of the `ld.gold` linker (bfd isn't required anymore) as well as getting rid of the D `main()` requirement. (#3350, #3357, ldc-developers/druntime#178) - Reduced size for shared libraries by compiling druntime and Phobos with hidden visibility. (#3377) #### Platform support - Supports LLVM 3.9 - 10.0. #### Bug fixes - Fixed tail calls in thunks, affecting **AArch64** (the debug libraries now work) and possibly other architectures. (#3329, #3332) - Windows: Do not emit any column infos for CodeView by default (like clang) & add `-gcolumn-info`. (#3102, #3388) - Windows: Do not leak MSVC-environment-setup into `-run` child processes. A new `LDC_VSDIR_FORCE` environment variable can be used to enforce MSVC toolchain setup. (#3340, #3341) - Windows: Fix memory leak when throwing exceptions in threads. (#3369, ldc-developers/druntime#181) - Try to use `memcmp` for (in)equality of non-mutable static arrays and mutable slices. (#3400, #3401) - `ldc.gccbuiltins_*`: Lift 256-bit vector limit, adding 174 AVX512 builtins for x86; 512-bit vector aliases have been added to `core.simd`. (#3405, #3406) #### Internals - `core.bitop.{bts,btr,btc}` are now CTFE-able. (ldc-developers/druntime#182) - Do not fallback to host for critical section size of unknown targets. (#3389) - Linux: Possibility to avoid passing `-fuse-ld` to `cc` via `-linker=`. (#3382) - WebAssembly: Switch from legacy linked-list ModuleInfo registry to `__minfo` section. (#3348) - Windows: Bundled libcurl upgraded to v7.69.1, incl. the option to link it statically. (#3378) - Windows: Switch to wide `wmain` C entry point in druntime. (#3351) - druntime unittests are now compiled with `-checkaction=context`. #### Known issues - When building LDC, old LDC 0.17.*/ltsmaster host compilers miscompile LDC ≥ 1.21, leading to potential segfaults of the built LDC. Ltsmaster can still be used to bootstrap a first compiler and then let that compiler compile itself. (#3354) # LDC 1.20.1 (2020-03-07) #### Bug fixes - Non-Windows: Revert to strong `ModuleInfo.importedModules` references for correct module constructors execution order. (#3346, #3347) # LDC 1.20.0 (2020-02-14) #### Big news - Frontend, druntime and Phobos are at version [2.090.1+](https://dlang.org/changelog/2.090.1.html). (#3262, #3296, #3306, #3317, #3326) - Codegen preparations for: - iOS/tvOS/watchOS on AArch64. Thanks Jacob! (#3288) - WASI (WebAssembly System Interface) (#3295) - The config file for multilib builds has been restructured by adding a separate section for the multilib target. This avoids `--no-warn-search-mismatch` for the linker and enables support for LLD. (#3276) - Support for embedding `pragma({lib,linkerDirective}, ...)` in Mach-O object files. (#3259) E.g., `pragma(linkerDirective, "-framework", "CoreFoundation");` makes Apple's linker pull in that framework when pulling in the compiled object file. ELF object files newly embed `pragma(lib, ...)` library names in a special `.deplibs` section, but that only works with LLD 9+ for now. - The `ldc-build-runtime` tool has been slightly revised; `--dFlags` now extends the base D flags instead of overriding them. (1200601d44280d5f948a577b444ffa2dd4f9e433) - `ModuleInfo.importedModules` are now emitted as weak references (except on Windows, for LLD compatibility), following DMD. (#3262) - Windows: Bundled MinGW-based libs now support wide `wmain` and `wWinMain` C entry points. (#3311) #### Platform support - Supports LLVM 3.9 - 10.0. #### Bug fixes - Potential stack overflows on Linux in GC worker threads. (#3127, dlang/druntime#2904) - Support 2 leading dashes (not just 1) in command-line pre-parsing, thus fixing config file section lookup when using `--mtriple` and not ignoring `--conf` and `--lowmem` any longer. (#3268, #3275) - Support for data directives in DMD-style inline asm. (#3299, #3301) - Cherry-picked fixes for soft-float targets. (#3292, dlang/phobos#7362, dlang/phobos#7366, dlang/phobos#7377) - ICE during debuginfo generation for function literals inside enum declarations. (#3272, #3274) #### Internals - Misc. tweaks for `dmd-testsuite`: (#3287, #3306) - Significantly accelerated by skipping uninteresting permutations. - Switch from Makefile to `run.d`, incl. moving LDC-specific exceptions from Makefile to individual test files and support for extended `DISABLED` directives. - Addition of (recommendable!) Cirrus CI service (incl. FreeBSD) and removal of Semaphore CI. (#3298) - Some improvements for `gdmd` host compilers, incl. CI tests. (#3286) # LDC 1.19.0 (2019-12-20) #### Big news - Frontend, druntime and Phobos are at version [2.089.1+](https://dlang.org/changelog/2.089.1.html). (#3192, #3210, #3215, #3232, #3242, #3255, #3261) - LLVM for prebuilt packages upgraded to v9.0.1; our fork has moved to [ldc-developers/llvm-project](https://github.com/ldc-developers/llvm-project). The x86[_64] packages newly include the experimental **AVR** backend. (#3244) - **Android**: A prebuilt AArch64 package has been added. It also includes prebuilt druntime/Phobos libraries for x86_64; the armv7a package includes the i686 libraries. So all 4 Android targets are covered with prebuilt druntime/Phobos. (#3244) - Breaking `extern(D)` ABI change for Posix x86[_64]: non-POD arguments are now passed by ref under the hood, just like they already were for `extern(C++)`. Some superfluous implicit blits have been optimized away as well, for all targets. (#3204) - Posix: Defaults to `cc` now for linking, not `gcc` (or `clang` for FreeBSD 10+) - if the `CC` environment variable isn't set. Override with `-gcc=`. (#3202) - Codegen elision of dead branches for `if` statements with constant condition (not depending on enabled LLVM optimizations). (#3134) - druntime: New `llvm_sideeffect` intrinsic, new `@cold` function UDA and extended CAS functionality in `core.atomic` (incl. support for weak CAS and separate failure ordering). (https://github.com/ldc-developers/druntime/pull/166, https://github.com/ldc-developers/druntime/pull/167, #3220) - Windows: Bundled MinGW-based libs have been upgraded to use the .def files from MinGW-w64 v7.0.0. They now also contain a default `DllMain` entry point as well as `_[v]snprintf`. ([libs](https://github.com/ldc-developers/mingw-w64-libs/releases/tag/v7.0.0-rc.1), #3142) #### Platform support - Supports LLVM 3.9 - 9.0. #### Bug fixes - Misc. CMake issues with some LLVM 9 configurations. (#3079, #3198) - Equality/identity comparisons of vectors with length ≥ 32. (#3208, #3209) - `ldc.gccbuiltins_*` druntime modules now available to non-installed compiler too. (#3194, #3201) - Potential ICE when applying `@assumeUsed` on global union. (#3221, #3222) - `Context from outer function, but no outer function?` regression introduced in v1.11 (inability to access outer context from `extern(C++)` methods). (#3234, #3235) - Lvalue expressions with nested temporaries to be destructed yielding a wrong lvalue. (#3233) - druntime: Cherry-picked fix wrt. GC potentially collecting objects still referenced in other threads' TLS area. (dlang/druntime#2558) # LDC 1.18.0 (2019-10-16) #### Big news - Frontend, druntime and Phobos are at version [2.088.1](https://dlang.org/changelog/2.088.1.html). (#3143, #3161, #3176, #3190) - Support for **LLVM 9.0**. The prebuilt packages have been upgraded to [LLVM 9.0.0](http://releases.llvm.org/9.0.0/docs/ReleaseNotes.html). (#3166) - Preliminary **Android** CI, incl. experimental prebuilt armv7a package generation (API level 21, i.e., Android 5+). (#3164) - Bundled dub upgraded to v1.17.0+ with improved LDC support, incl. cross-compilation (e.g., `--arch=x86_64-pc-windows-msvc`). (https://github.com/dlang/dub/pull/1755, [Wiki](https://wiki.dlang.org/Cross-compiling_with_LDC)) - Init symbols of zero-initialized structs are no longer emitted. (#3131) - druntime: DMD-compatible `{load,store}Unaligned` and `prefetch` added to `core.simd`. (https://github.com/ldc-developers/druntime/pull/163) - JIT improvements, incl. multi-threaded compilation. (#2758, #3154, #3174) #### Platform support - Supports LLVM 3.9 - 9.0. #### Bug fixes - Don't error out when initializing a `void` vector. (#3130, #3139) - druntime: Fix exception chaining for latest MSVC runtime v14.23, shipping with Visual Studio 2019 v16.3. (https://github.com/ldc-developers/druntime/pull/164) - Keep lvalue-ness when casting associative array to another AA. (#3162, #3179) # LDC 1.17.0 (2019-08-25) #### Big news - Frontend, druntime and Phobos are at version [2.087.1+](https://dlang.org/changelog/2.087.1.html). (#3093, #3124) - The upstream fix wrt. [local templates can now receive local symbols](https://issues.dlang.org/show_bug.cgi?id=5710) hasn't been ported yet. (#3125) - LLVM for prebuilt packages upgraded to v8.0.1. (#3113) - Breaking change: Init symbols, TypeInfos and vtables of non-`export`ed aggregates are now hidden with `-fvisibility=hidden`. (#3129) - LLVM 8+: New intrinsics `llvm_*_sat` (saturation arithmetic) and `llvm_{min,max}imum`. Thanks Stefanos! (https://github.com/ldc-developers/druntime/pull/161, https://github.com/ldc-developers/druntime/pull/162) #### Platform support - Supports LLVM 3.9 - 8.0. #### Bug fixes - Fix for v1.16.0 regression when returning `void` expressions. (#3094, #3095) - `-lowmem` (and on Windows, `--DRT-*` options) in response files (e.g., used by dub) aren't ignored anymore. (#3086) - Windows: LDC and LDMD now internally use UTF-8 strings only, incl. command-line options and environment variables. The LDC install dir, source file names etc. can now contain non-ASCII chars. For proper console output, especially to stderr, you'll need Windows 10 v1809+ and may need to set a Unicode console font (e.g., Consolas). (#611, #3086) - Android: Linker errors when building LDC/LDMD should be fixed. (#3128) - Support for recent `gdmd` as D host compiler. Thanks Moritz! (#3087) - Do not require gold plugin when linking with LLD. (#3105) - Enable linker stripping on FreeBSD (with non-`bfd` linkers). (#3106) - Some JIT bind fixes. (#3099, #3100) #### Known issues - If you encounter segfaults in GC worker threads with shared druntime on Linux that are fixed by disabling new parallel GC marking (e.g., via `--DRT-gcopt=parallel:0` in executable cmdline), please let us know about it: #3127 # LDC 1.16.0 (2019-06-20) #### Big news - Frontend, druntime and Phobos are at version [2.086.1](https://dlang.org/changelog/2.086.1.html), incl. a DIP1008 fix. (#3062, #3076, #3091) - Non-Windows x86: Faster `real` versions of `std.math.{tan,expi}`. (#2855) - dcompute: New `__traits(getTargetInfo, "dcomputeTargets")`. (#3090) #### Platform support - Supports LLVM 3.9 - 8.0 (incl. 7.1). #### Bug fixes - Make `pragma(LDC_no_typeinfo)` actually elide TypeInfo emission for structs, classes and interfaces. (#3068) - Windows: Fix DLL entry point in MinGW-based libs. (https://github.com/ldc-developers/mingw-w64-libs/commit/8d930c129daa798379b3d563617847f8e895f43e) - WebAssembly: Use `--export-dynamic` when linking with LLD 8+. (#3023, #3072) - Fix corrupt `this` in functions nested in in/out contracts. (45460a1) - Fix identity comparisons of integral vectors. (a44c78f) - Improved handling of unsupported vector ops. (a44c78f) - uClibc: Fix C assert calls. (#3078, #3082) - Improved error message on global variable collision. (#3080, #3081) # LDC 1.15.0 (2019-04-06) #### Big news - Frontend, druntime and Phobos are at version **2.085.1**, incl. new command-line options `-preview`, `-revert`, `-checkaction=context`, `-verrors-context` and `-extern-std`. (#3003, #3039, #3053) - The Objective-C improvements from DMD 2.085 are not implemented. (#3007) - Support for **LLVM 8.0**. The prebuilt packages have been upgraded to LLVM 8.0.0 and include the Khronos SPIRV-LLVM-Translator, so that dcompute can now emit **OpenCL** too. (#3005) - Compiler memory requirements can now be reduced via the new `-lowmem` switch, which enables the garbage collector for the front-end and sacrifices compile times for less required memory. In some cases, the overall max process memory can be reduced by more than 60%; see https://github.com/ldc-developers/ldc/pull/2916#issuecomment-443433594 for some numbers. (#2916) - Note for package maintainers: this feature requires a recent D host compiler (most notably, it doesn't work with ltsmaster), ideally LDC 1.15 itself due to important GC memory overhead improvements in 2.085 druntime. - Support for generic `@llvmAttr("name")` parameter UDAs, incl. new `@restrict` with C-like semantics. (#3043) - macOS: 32-bit support was dropped in the sense of not being CI-tested anymore and the prebuilt macOS package now containing x86_64 libraries only. `MACOSX_DEPLOYMENT_TARGET` for the prebuilt package has been raised from 10.8 to 10.9. - Prebuilt packages don't depend on libtinfo and libedit anymore. (#1827, #3019) - x86: SSSE3 isn't required for the prebuilt packages and generated optimized binaries anymore. (#3045) #### Platform support - Supports LLVM 3.9 - 8.0. #### Bug fixes - Implicit cross-module-inlining of functions annotated with `pragma(inline, true)` without explicit `-enable-cross-module-inlining` has been restored. (#2552, #3014) - Propagate well-known length of newly allocated dynamic arrays for better optimizability. (#3041, #3042) - JIT: Support implicit `__chkstk` calls for Windows targets, e.g., for large stack allocations. (#3051) #### Internals - Addition of **Azure Pipelines** as CI service. It is the new main CI service and responsible for creating all prebuilt x86(_64) packages. AppVeyor has been dropped completely and CircleCI rededicated. (#2998) # LDC 1.14.0 (2019-02-17) #### Big news - Frontend, druntime and Phobos are at version **2.084.1**, incl. new command-line options `-mixin`, `-{enable,disable}-switch-errors` and `-checkaction`. (#2946, #2977, #2999) - Options `-release`, `-d-debug` and `-unittest` don't override preceding, more specific options (`-{enable,disable}-{asserts,invariants,preconditions,postconditions,contracts}`) anymore. - Linking WebAssembly doesn't require `-link-internally` (integrated LLD) anymore; an external linker (default: `wasm-ld`, override with `-linker`) can be used as well. (#2951) - Prebuilt Windows packages include LTO-able 32-bit druntime/Phobos too (previously: Win64 only). - AddressSanitizer support for fibers (requires [rebuilding the runtime libraries](https://wiki.dlang.org/Building_LDC_runtime_libraries) with CMake option `RT_SUPPORT_SANITIZERS=ON`). (#2975, https://github.com/ldc-developers/druntime/pull/152) - Support `pragma(LDC_extern_weak)` for function declarations - if the function isn't available when linking, its address is null. (#2984) #### Platform support - Supports LLVM 3.9 - 7.0. #### Bug fixes - Fix C++ mangling regression for functions with multiple `real` parameters introduced with v1.13, preventing to build DMD. (#2954, https://github.com/dlang/dmd/pull/9129) - Fix context of some nested aggregates. (#2960, #2969) - Support templated LLVM intrinsics with vector arguments. (#2962, #2971) - Avoid crashes with `-allinst` (fix emission of only speculatively nested functions). (#2932, #2940) - Fix XRay support for LLVM 7+. (#2965) - AArch64: Fix DMD-style profile measurements. (#2950) - Be less picky about placement of pragmas (allow intermediate `extern(C)` etc.). (#2599) - MSVC: Fix `real` C++ mangling to match Visual C++ `long double`. (#2974) - Fix bad ICE noticed when building protobuf-d. (#2990, #2992) - Fix ICE when directly indexing vector return value. (#2988, #2991) - Fix identity comparisons of complex numbers. (#2918, #2993) - MIPS32 fix for `core.stdc.stdarg`. (#2989, https://github.com/ldc-developers/druntime/pull/153) - Fix `core.atomic.cas()` for 64-bit floating-point values. (#3000, #3001) #### Known issues - Buggy older `ld.bfd` linker versions may wrongly strip out required symbols, e.g., ModuleInfos (so that e.g. no module ctors/dtors are run). LDC defaults to `ld.gold` on Linux. # LDC 1.13.0 (2018-12-16) #### Big news - Frontend, druntime and Phobos are at version **2.083.1**. (#2878, #2893, #2920, #2933) - The **Windows packages are now fully self-sufficient**, i.e., a Visual Studio/C++ Build Tools installation isn't required anymore, as we now ship with MinGW-w64-based libraries, similar to DMD. Check out the included [README.txt](https://github.com/ldc-developers/ldc/blob/master/packaging/README.txt) for all relevant details. (https://github.com/dlang/installer/pull/346, https://github.com/ldc-developers/ldc/pull/2886, [Wiki: Cross-compiling with LDC](https://wiki.dlang.org/Cross-compiling_with_LDC)) - Debug info improvements: * For GDB: printing global and imported symbols, non-member and member function calls. (#2826) * For Visual Studio and mago: names, by-value params, nested variables. (#2895, #2908, #2909, #2912) * Associative arrays now showing up properly (at least with mago), not as opaque `void*` anymore. (#2869) * `-gc` now translates D names to C++ ones, e.g., to use the regular Visual Studio debugger (bypassing mago) and as preparation for VS Code debugging with Microsoft's C/C++ plug-in ([screenshots](https://github.com/ldc-developers/ldc/pull/2869#issuecomment-427862154)). Thanks to Oleksandr for this contribution and the AA fix! (#2869) - New command-line option `-fvisibility=hidden` to hide functions/globals not marked as `export` (for non-Windows targets), primarily to reduce the size of shared libraries. Thanks to Andrey for stepping up! (#2894, #2923) - Dropped support for LLVM 3.7 and 3.8. (#2872) - LLVM for prebuilt packages upgraded to [v7.0.1](https://github.com/ldc-developers/llvm/releases/tag/ldc-v7.0.1). - Linux: now defaulting to `ld.gold` linker in general, not just with `-flto=thin`, as buggy older `ld.bfd` versions may wrongly strip out required symbols (change with `-linker`). (#2870) - Improved support for Android/x86[_64], musl libc and FreeBSD/AArch64. (#2917, https://github.com/ldc-developers/druntime/pull/146) - LDC-specific druntime: `ldc.simd.inlineIR` moved/renamed to `ldc.llvmasm.__ir` (with deprecated legacy alias). (#2931) - New CMake option `COMPILE_D_MODULES_SEPARATELY` builds D files in the DDMD frontend separately to reduce the time required to build LDC with many CPU cores and/or for iterative development. (#2914) #### Platform support - Supports LLVM 3.9 - 7.0. - Alpine linux/x64: built against Musl libc to support Docker images based on the Alpine distro, requires the `llvm5-libs`, `musl-dev`, `binutils-gold` and `gcc` packages to build and link D apps and the `tzdata` and `curl-dev` packages for certain stdlib modules. #### Bug fixes - 32-bit Android/ARM regression introduced in v1.12. (#2892) - Non-Windows x86_64 ABI fixes wrt. what's passed in registers, relevant for C[++] interop. (#2864) - Alignment of `scope` allocated class instances. (#2919) # LDC 1.12.0 (2018-10-13) #### Big news - Frontend, druntime and Phobos are at version **2.082.1**. (#2818, #2837, #2858, #2873) - Significant performance improvements for some transcendental `std.math` functions in single and double precision, at least for x86. (https://github.com/dlang/phobos/pull/6272#issuecomment-373967109) - Support for **LLVM 7**, which is used for the prebuilt packages. Due to an LLVM 7.0.0 [regression](https://bugs.llvm.org/show_bug.cgi?id=38289), the prebuilt x86[_64] LDC binaries require a **CPU with SSSE3**, and so will your optimized binaries (unless compiling with `-mattr=-ssse3`). (#2850) - **JIT compilation**: new `ldc.dynamic_compile.bind` function with interface similar to C++ `std::bind`, allowing to generate efficient specialized versions of functions (much like [Easy::jit](https://github.com/jmmartinez/easy-just-in-time) for C++). (#2726) - LTO now working for Win64 too; the prebuilt package includes the required external LLD linker and the optional LTO default libs. Enable as usual with `-flto= [-defaultlib=druntime-ldc-lto,phobos2-ldc-lto]`. (#2774) - Config file: new `lib-dirs` array for directories to be searched for libraries, incl. LLVM compiler-rt libraries. (#2790) #### Platform support - Supports LLVM 3.7 - 7.0. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. - Alpine linux/x64: built against Musl libc to support Docker images based on the Alpine distro, requires the `llvm5-libs`, `musl-dev`, and `gcc` packages to build and link D apps and the `tzdata` and `libcurl` packages for certain stdlib modules. - Android/ARM: This release slightly changes the way emulated TLS is interfaced, but is missing a patch for 32-bit ARM. [See the wiki for instructions on patching that file manually before cross-compiling the runtime libraries for 32-bit Android/ARM](https://wiki.dlang.org/Build_D_for_Android). #### Bug fixes - Fix IR-based PGO on Windows (requires our LLVM fork). (#2539) - Fix C++ class construction with D `new` on Posix. (#2801) - Android: No more text relocations in Phobos zlib, required for API level 23+. (#2822, #2835) - Declare extern const/immutable globals as IR constants. (#2849, #2852) - Fix issue when emitting both object and textual assembly files at once (`-output-o -output-s`). (#2847) - Support address of struct member as key/value in AA literal. (#2859, #2860) - Fix ICE when computing addresses relative to functions/labels. (#2865, #2867) # LDC 1.11.0 (2018-08-18) #### Big news - Frontend, druntime and Phobos are at version **2.081.2**. (#2752, #2772, #2776, #2791, #2815) - Add some support for classes without TypeInfos, for `-betterC` and/or a minimal (d)runtime. (#2765) - LLVM for prebuilt packages upgraded to v6.0.1. The x86_64 packages feature some more LLVM targets for cross-compilation (experiments): MIPS, MSP430, RISC-V and WebAssembly. (#2760) - Rudimentary support for compiling & linking directly to **WebAssembly**. See the [dedicated Wiki page](https://wiki.dlang.org/Generating_WebAssembly_with_LDC) for how to get started. (#2766, #2779, #2785) - **AArch64** (64-bit ARM) now mostly working on Linux/glibc and Android. Current `ltsmaster`/0.17.6 is able to bootstrap v1.11, which can also bootstrap itself; most tests pass. (Preliminary) [CI](https://app.shippable.com/github/ldc-developers/ldc/dashboard) has been set up. (#2802, #2817, #2813) - LDC on Windows now uses 80-bit **compile-time** `real`s. This allows for seamless cross-compilation to other x86(_64) targets, e.g., without `real.min` underflowing to 0 and `real.max` overflowing to infinity. (#2752) - New `@naked` UDA in `ldc.attributes` & enhanced functionality for `@llvmAttr("")`. (#2773) #### Platform support - Supports LLVM 3.7 - 6.0. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. #### Bug fixes - `extern(C++)` on Posix: Pass non-PODs indirectly by value. (#2728) - `extern(C++)` on Windows/MSVC: Methods return *all* structs via hidden sret pointer. (#2720, #1935) - Make GC2Stack IR optimization pass work as intended. (#2750) - Work around inline assembly regression with LLVM 6 on Win32. The prebuilt Win32 package is now using LLVM 6.0.1 too. (#2629, #2770) - Fix overzealous check for multiple `main()` functions. (#2778) - Fix corrupt prefix in integrated LLD's console output. (#2781) - No context ptr for nested non-`extern(D)` functions. (#2808, #2809) # LDC 1.10.0 (2018-06-19) #### Big news - Frontend, druntime and Phobos are at version **2.080.1**. (#2665, #2719, #2737) - No support for Objective-C class/static methods yet. (#2670) - Breaking Win64 `extern(D)` ABI change: Pass vectors directly in registers, analogous to the MS vector calling convention. (#2714) - Config file: For cross-compilation, support additional sections named as regex for specific target triples, e.g., `"86(_64)?-.*-linux": { … };`; see the comment in `etc/ldc2.conf`. (#2718) #### Platform support - Supports LLVM 3.7 - 6.0. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. #### Bug fixes - CMake and druntime fixes for DragonFlyBSD, thanks Diederik! (#2690, #2691, #2692, https://github.com/ldc-developers/druntime/pull/138, https://github.com/ldc-developers/druntime/pull/139, https://github.com/ldc-developers/phobos/pull/64) - DMD-style inline asm label naming issue in overloaded functions. (#2667, #2694) - Linux: misc. exception stack trace fixes & extensions, incl. default DWARF v4 debuginfo emission with LLVM 6. (#2677) - Predefine version `D_HardFloat` instead of `D_SoftFloat` for `-float-abi=softfp`. (#2678) - Bash completion installed to the wrong place with custom `CMAKE_INSTALL_PREFIX`. (#2679, #2179, #2693) - Default to `ld.gold` linker for ThinLTO on Linux. (#2696) - Fix compilation issues on 64-bit macOS with DMD host compiler ≥ 2.079. (#2703, #2704) - druntime: Fix `core.stdc.stdint.(u)int64_t` on 64-bit macOS etc. (#2700) - Define `D_AVX` and `D_AVX2` if the target supports them. (#2711) - Fix sporadic front-end segfaults. (#2713) - Win64: Fix `extern(C++)` ABI wrt. passing small non-POD structs by value. (#2706) - Misc. druntime/Phobos fixes and upstream cherry-picks for ARM, AArch64, MIPS etc. - Fix potential LDC crashes when returning static array results from inline IR. (#2729) - Win64: Fix terminate handler for VC runtime DLL version 14.14.x.y. (#2739) # LDC 1.9.0 (2018-04-30) #### Big news - Frontend, druntime and Phobos are at version **2.079.1**, incl. new switches `-i[=]` (include imports in compilation) and `-Xi`. (#2587) - Support a **minimal (d)runtime**. (#2641) - Win32 breaking ABI change: add extra underscore for mangled names of D symbols. (#2598) - *No* breaking ABI change for 64-bit macOS wrt. C++ mangling of D `(u)long`. It's still mangled as C++ `(unsigned) long` in order not to break `size_t` and `ptrdiff_t` interop, whereas DMD 2.079 mangles it as `(unsigned) long long` (which, in combination with missing `core.stdc.config.cpp_(u)long`, makes it impossible to represent a C++ size_t/ptrdiff_t with DMD 2.079 on 64-bit macOS). - Support for **LLVM 6**. It's used for the prebuilt packages, except for the 32-bit Windows package (due to #2629). (#2608) - Integrated LLD (enable with `-link-internally`) now also able to **(cross-)link ELF and Mach-O binaries**, in addition to the existing Windows COFF support. (#2203) - Prebuilt Linux and macOS packages now ship with **LTO default libs** (druntime & Phobos). Keep on using `-flto=` to restrict LTO to your code, or opt for `-flto= -defaultlib=phobos2-ldc-lto,druntime-ldc-lto` to include the default libs. (#2640) - When linking against shared default libs, LDC now sets a default rpath (absolute path to the LDC lib dir(s); configurable in the `etc/ldc2.conf` file). (#2659) - New convenience mixin for fuzzing: `ldc.libfuzzer.DefineTestOneInput`. (#2510) #### Platform support - Supports LLVM 3.7 - 6.0. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. #### Bug fixes - DMD-style inline asm: - Fix semantics of `extended ptr` for MSVC targets. (#2653) - Add missing EIP register. (#2654) - macOS: Fix install_name and symlinks of shared fat druntime/Phobos libs. (#2659, #2615) - Make `-static` override `-link-defaultlib-shared`. (#2646) - Make interface thunks forward variadic args. (#2613) - Fix `va_arg()` for PowerPC. (https://github.com/ldc-developers/druntime/pull/121) - MSVC: Support exporting naked functions. (#2648) - Only emit interface vtables in the declaring module. (#2647) - Call `_Unwind_Resume()` directly. (#2642) # LDC 1.8.0 (2018-03-04) #### Big news - Frontend, druntime and Phobos are at version **2.078.3**, incl. new switches `-dip1008` and `-transition=` as well as `pragma(crt_{con,de}structor)`. (#2486) - New switch `-link-defaultlib-shared` to link against shared druntime/Phobos. It defaults to true for shared libraries (`-shared`), so it's primarily useful for executables. (#2443) - Support for plugins via `-plugin=...` (see [this example](https://github.com/ldc-developers/ldc/tree/master/tests/plugins/addFuncEntryCall)). The mechanism is identical to Clang's LLVM-IR pass plugins and thus supports those as well, e.g., the [AFLfuzz LLVM-mode plugin](https://github.com/mirrorer/afl/blob/master/llvm_mode/afl-llvm-pass.so.cc), [Easy::Jit](https://github.com/jmmartinez/easy-just-in-time). (#2554) - Support for LLVM IR-based Profile-Guided Optimization via `-fprofile-{generate,use}` (not working on Windows yet). (#2474) - Basic support for [LLVM XRay instrumentation](https://llvm.org/docs/XRay.html) via `-fxray-{instrument,instruction-threshold}`. (#2465) - DMD-style function trace profiling via `-profile` (LDMD) / `-fdmd-trace-functions` (LDC). (#2477) - New UDA `ldc.attributes.assumeUsed` to prevent a symbol from being optimized away. (#2457) - The PGO helper library `ldc-profile-rt` was replaced by LLVM's vanilla profiling library. Our subset of [LLVM compiler-rt](https://compiler-rt.llvm.org/) libraries is now also shipped on Windows (excl. fuzzer). (#2527, #2544) - Cherry-picked upstream Musl C runtime support for Docker images based on Alpine and added a native Alpine/x64 compiler, which requires the `llvm5`, `musl-dev`, and `gcc` packages to run and link D apps and the `tzdata` and `libcurl` packages for certain stdlib modules. #### Platform support - Supports LLVM 3.7 - 5.0. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. #### Bug fixes - Strict left-to-right evaluation/load order of function arguments. (#2450, #2502) - Inline asm silently ignores opcodes db, ds, di, dl, df, dd, de. (#2548) - Missed optimization for `scope` allocated classes. (#2515, #2516) - Don't eliminate frame pointer by default at `-O0`. (#2480, #2483) - LLVM complaining about invalid IR pointer arithmetics. (#2537) - `llvm_expect()` doesn't work with CTFE. (#2458, #2506) - `.{so,dylib}` file command line arguments should be forwarded to linker. (#2445, #2485) - macOS: Set shared stdlib install_name to `@rpath/`. (#2442, #2581) - `array ~= element` issue if rhs affects the lhs length. (#2588, #2589) - EH segfaults when checking D class catch handlers against thrown C++ exception. (#2590) # LDC 1.7.0 (2018-01-06) #### Big news - Frontend, druntime and Phobos are at version **2.077.1**. (#2401, #2430) - **C++ exceptions** can now be caught in D code, for Linux and MSVC targets (and possibly more). A logical step after consolidating LDC's exception handling for non-MSVC targets with DMD's DWARF implementation. (#2405) - Automated building of release and [CI packages](https://github.com/ldc-developers/ldc/releases/tag/CI). (#2438) #### Platform support - Supports LLVM 3.7 - 5.0. (binary packages on GitHub are built with LLVM 5.0.1) - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. #### Bug fixes - ICE on chained ref-returning opIndex op-assign. (#2415) - Windows: `export` visibility ignored for globals. (#2437) - Print error message when trying to use shared libraries with static runtime. (#2454) - ldc-1.7.0-beta1 regression: ICE with implicit cast. (#2471) - CMake: use llvm-config to determine LLVM's cmake directory, if possible. (#2482) # LDC 1.6.0 (2017-11-26) #### Big news - Frontend, druntime and Phobos are at version **2.076.1** (#2362), including `-betterC` semantics (#2365). - Experimental support for **dynamic codegen at runtime** (JIT-style) to tune performance-critical parts for the used CPU and/or treat special runtime variables as constants. See UDAs `@dynamicCompile`, `@dynamicCompileConst` in `ldc.attributes`; compile with command-line option `-enable-dynamic-compile` and use the `ldc.dynamic_compile` module to generate the code at runtime before invoking it. Congratulations to Ivan Butygin for implementing this non-trivial feature! (#2293) - Many `std.math` functions are now CTFE-able. (#2259) #### Platform support - Supports LLVM 3.7 - 5.0. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. #### Bug fixes - Can't link against wsock32 and ws2_32 on Windows. (#468) - PGO incompatible with MSVC EH. (#1943) - Regression: ModuleInfos not emitted as COMDATs. (#2409) - Incorrect C assert function signature for Android. (#2417) - Overzealous error check when attempting to evaluate object as constant. (#2422) # LDC 1.5.0 (2017-10-29) #### Big news - Frontend, druntime and Phobos are at version **2.075.1**. (#2252) - New command-line option `-fp-contract` to control fused floating-point math, as well as about 25 new hidden options influencing codegen, see `-help-hidden` (`-enable-unsafe-fp-math`, `-debugger-tune` etc.). (#2148) - New command-line option `-linker`. Use `-linker=lld-link` to use an external LLD executable for MSVC targets (with experimental LTO support) or `-linker=` for other targets. (#2386) #### Breaking changes - Win32: the mangled names of D symbols now start with `_D`, not with `__D`, compatible with DMD. (#2353) #### Platform support - Supports LLVM 3.7 - 5.0. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017. #### Changes to the prebuilt packages - LLVM upgraded to [5.0.0](https://github.com/ldc-developers/llvm/releases/tag/ldc-v5.0.0-2). #### Bug fixes - Cyclic dependencies with `-cov`. (#2177) - ICE when capturing `this` in constructors. (#1728) - Objective-C bugs. (#2387, #2388) - LLVM/LLD 5.0: `-link-internally` broken. ([LLD patch](https://github.com/ldc-developers/llvm/releases/tag/ldc-v5.0.0-2)) - LLVM 5.0: need to build LDC with CMake option `-DLDC_WITH_LLD=OFF` to avoid conflicting command-line options. (#2148) - LLVM 5.0 & non-Windows targets: names of members in static libraries generated by LDC's internal archiver contain path information. (#2349) - ~~Workaround for Xcode 9 ranlib bug: don't use internal (LLVM) archiver by default for OSX targets. (#2350)~~ Xcode 9.0.1 fixes that bug, please upgrade. - Captured lazy parameters may be garbage. (#2302, #2330) - Packed struct layout regression (#2346) and `T.alignof` not respecting explicit type alignment via `align(N)`. (#2347) - OSX and Win32: mangling issue for druntime's `rt_options`. (#1970, #2354) - MinGW Win64: ABI regression wrt. functions returning x87 reals. (#2358) - Potential file permission problem when copying over LLVM libraries during LDC build. (#2337) - PPC64: Forward reference error with 1.3 release. (#2200) #### Known issues - LLVM 5.0: potentially failing LLVM assertion when emitting debuginfos and using inlining at the same time. (#2361) # LDC 1.4.0 (2017-09-11) #### Big news - Frontend, druntime and Phobos are at version **2.074.1**. (#2076) - **ldc-build-runtime**: a small D tool that makes it easy to compile the LDC runtime and standard library yourself, for example, to enable LTO-ability/sanitizers or cross-compiling executables and shared libraries for other platforms, like Android/ARM. ([Wiki page](https://wiki.dlang.org/Building_LDC_runtime_libraries)) - @joakim-noah's Android fixes have finally been fully incorporated, enabling every host to (cross-)compile to Android. (https://github.com/ldc-developers/llvm/commit/8655f3208cce28bb7f903cadf5f58a3911392bdc) [Instructions on using this ldc release to cross-compile D apps for Android are on the wiki](https://wiki.dlang.org/Build_D_for_Android), including how to try out the native Android/arm package, ie a D compiler that you can run _on_ your Android smartphone or tablet. - Improved support for [AddressSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer). LDC will automatically link with the AddressSanitizer runtime library when `-fsanitize=address` is passed (when LDC can find the AddressSanitizer library). - [libFuzzer](https://llvm.org/docs/LibFuzzer.html) sanitizer support using `-fsanitize=fuzzer` (same as Clang). This flag implies `-fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp` and automatically links-in the runtime libFuzzer library if LDC can locate the runtime library. (With LLVM 4.0, there is a dependency on sanitizer runtime, so manually link the ASan library or use `-fsanitize=fuzzer,address`.) - New `-fsanitize-blacklist=` command-line option to exclude functions from sanitizer instrumentation (identical to Clang). The file must adhere to the textual [Sanitizer Special Case List format](https://clang.llvm.org/docs/SanitizerSpecialCaseList.html). - New `-fsanitize-coverage=...` command-line option with the same [functionality as Clang](https://clang.llvm.org/docs/SanitizerCoverage.html). - The config file sections now feature an additional `post-switches` list for switches to be appended to the command line (the existing `switches` list is prepended). E.g., this now allows the user to override the directory containing the runtime libraries via `-L-L/my/runtime/libs` in the command line. (#2281) #### Breaking changes - The `-sanitize` command-line option has been renamed to `-fsanitize*`, for clang conformance. - The semantics of an empty `-conf=` command-line option have been changed from 'use default config file' to 'use no config file'. - The binary representations of the init values for `float/double/real` have been unified to a special quiet NaN, with both most significant mantissa bits set, on all hosts and for all targets. (#2207) #### Platform support - Supports LLVM 3.7 - 5.0. Support for 3.5 and 3.6 has been dropped. - Windows: Supports Visual Studio/C++ Build Tools 2015 and 2017, incl. the latest Visual Studio 2017 Update 15.3. #### Changes to the prebuilt packages - Consistent usage of a [minimally tailored](https://github.com/ldc-developers/llvm/releases/tag/ldc-v4.0.1) LLVM 4.0.1. - Newly enabled LLVM target `NVPTX` in order to target [CUDA via DCompute](http://forum.dlang.org/thread/smrnykcwpllukwtlfzxg@forum.dlang.org). - Linux x86_64: - Shipping with the LLVM **LTO plugin** for the `gold` linker. On Ubuntu 14.04 and later, `-flto=full|thin -Xcc=-fuse-ld=gold` should work out of the box. - Build environment upgraded from Ubuntu 12.04 and gcc 4.9 to Ubuntu 14.04 and gcc 6.3. - Windows/MSVC: Build environment upgraded from Visual Studio 2015 Update 3 to Visual Studio 2017 15.3.3 (WinSDK 10.0.15063). #### Bug fixes - Misc. debuginfo issues, incl. adaptations to internal LLVM 5.0 changes: (#2315) - `ref` parameters and closure parameters declared with wrong address and hence potentially showing garbage. - Win64: parameters > 64 bit passed by value showing garbage. - Win64: debuginfos for closure and nested variables now finally available starting with LLVM 5.0. - LLVM error `Global variable initializer type does not match global variable type!` for `T.init` with explicit initializers for dominated members in nested unions. (#2108) - Inconsistent handling of lvalue slicees wrt. visible side-effects of slice lower/upper bound expressions. (#1433) - Misc. dcompute issues. (#2195, #2215) - Potential LDC crashes due to dangling pointers after replacing IR globals (required in some cases if the type contains unions) almost fully mitigated. (#1829) - Multiple arrayop function emissions. (#2216) - Potentially incorrect memory layout for unnaturally aligned aggregates. (#2235) - Wrong `-m32/64` in command-line for external ARM assembler used via `-no-integrated-as`. #### Internals - Misc. CI improvements: - Addition of high-performant SemaphoreCI (incl. enabled LLVM/LDC assertions). - CircleCI upgraded to 2.0, testing with latest gcc. - Compile all D files for (non-unittest) druntime/Phobos at once. May be disabled via CMake option `COMPILE_ALL_D_FILES_AT_ONCE=OFF`. (#2231) #### Known issues - ThinLTO may not work well with the `ld.bfd` linker, use `ld.gold` instead (`-Xcc=-fuse-ld=gold`). - When building with LLVM 5.0, you may need `-DLDC_WITH_LLD=OFF` in the CMake command line. Otherwise, if the LLD headers are available and LDC is built with LLD integration, the produced LDC binary will refuse to work due to conflicting command-line options. # LDC 1.3.0 (2017-07-07) #### Big news - Frontend, druntime and Phobos are at version **2.073.2**. - A first experimental version of DCompute for **OpenCL/CUDA** targets has landed. See [announcement](http://forum.dlang.org/thread/zcfqujlgnultnqfksbjh@forum.dlang.org). - LLVM 3.9+: Experimental integration of **LLD**, the LLVM cross-linker, for MSVC targets. Check out these hassle-free [instructions](https://github.com/ldc-developers/ldc/pull/2142#issuecomment-304472412) to make LDC emit Windows executables and DLLs on any host! (#2142) - libconfig was replaced by an ad-hoc parser (in D), getting rid of the build and runtime dependency and shrinking the license file by roughly 50%. Thanks again, Remi! (#2016) - LDC now ships with static and shared runtime libs on supported platforms. (#1960) - LLVM 3.9+: Static libraries are now generated by LDC (LLVM) by default, not by system `ar` or `lib.exe`. This means that LDC can cross-compile and -archive static libs for all supported targets. Command-line option `-ar` allows specifying an external archiver to be used. (#2030) - New command-line options `-dip1000`, `-mv` and `-mscrtlib` (#2041). - Ships with dlang tools rdmd, ddemangle and dustmite. #### New features - LLVM 4.0+: Output LLVM optimization records via `-fsave-optimization-record`. (#2089) - New `-Xcc` command-line option for C compiler flags when linking via gcc/clang. Thanks Adrian! (#2104) - New function UDA `@ldc.attributes.llvmFastMathFlag("contract")` that specifically enables floating point operation fusing (fused multiply-add), previously only achievable with `@fastmath`. (#2060) #### Platform support - Supports LLVM 3.5 - 4.0. - Additional LLVM targets have been enabled for the prebuilt x86_64 packages: ARM, AArch64 and PowerPC. - Windows: Supports Visual Studio/Build Tools 2015 and **2017**. (#2065) - NetBSD: The 2.074 druntime patches have been cherry-picked. #### Bug fixes - LTO flags leaking into standard libraries when building LDC with LTO. (#2077) - Debug info fixes for class types - thanks Elie! (#2130) - OSX: Incomplete backtrace. (#2097) - Phobos on ARM: alignment and 64-bit `real` issues. (#2024) - Windows: EH-related crashes when linking against shared MS runtimes. (#2080) - ICE when initializing vector with `TVector.init`. (#2101) - Weird object file type autodetection. (#2105) - Typesafe variadics emitted as LLVM variadics. (#2121) - Superfluous masking of `bool` values. (#2131) - Output for `-mcpu=help` or `-mattr=help` printed multiple times. (#2073) - LDMD refuses some duplicate command-line options. (#2110) - Change format of predefined versions output for DMD compatibility. (#1962) - Fix potential segfault when formatting error msg (#2160) - Fix ICE when using `-main -cov` (#2164) - Make inlining threshold customizable via (existing) `-inline-threshold`, fix performance decrease with `-boundscheck=off` (#2161, #2180) - Switch Android onto the sectionELF style of module registry (#2172) - Check fiber migration on Android/ARM too (https://github.com/ldc-developers/druntime/pull/97) - Android moduleinfo section druntime fix (https://github.com/ldc-developers/druntime/pull/98) #### Building LDC - Building LDC requires a preinstalled D compiler. #### Internals - LDC now features D unittests itself. Just add some to LDC's D modules and they'll be compiled and executed by CI. (#2016) # LDC 1.2.0 (2017-04-21) #### Big news - Frontend, druntime and Phobos are at version **2.072.2**. #### New features - New function attribute `@ldc.attributes.allocSize` (#1610), see https://wiki.dlang.org/LDC-specific_language_changes#.40.28ldc.attributes.allocSize.29 #### Platform support - Supports LLVM 3.5 - 4.0. - Exception backtrace robustness has been significantly improved. - Emission of `reals` with differing precision to the host platform's `real` has been fixed.
(The compiler still uses the host platform's D real type to represent compile-time floating-point values, so parsing of literals and CTFE is restricted to the host real precision. For instance, LDC on AArch64 with its quad-precision reals would now make for a universal cross-compiler. On the other hand, cross-compiling from ARM to x86 with 80 bit reals still does not work – for example, `real.max` would silently overflow at compile-time and be emitted as 80-bit infinity.) #### Bug fixes - Compilation error with DMD 2.074 host compiler. - LLVM error when accessing `typeid(null)` (#2062). - Some LLVM intrinsics not available for LLVM ≥ 4.0 (#2037). - Spurious crashes on OS X user program shutdown when linking against static druntime lib. - Lexing floating-point literals may fail on PowerPC (#2046). - LDC crashes when trying to repaint static arrays (#2033). - No stack trace on Linux (#2004) and Windows (#1976, https://github.com/ldc-developers/druntime/pull/85). - Generated documentation file is immediately deleted when compiling at the same time. - LDMD doesn't append default file extension if `-of` option doesn't contain any (#2001, #2002). #### Building LDC - Building LDC requires a preinstalled D compiler. # LDC 1.1.1 (2017-02-23) #### Bug fixes - Linux: Always build C parts of standard library as PIC (#2009). This makes the binary packages usable on Ubuntu 16.10 (where executables are linked as position-independent code by default, in contrast to the older system used for preparing the packages). # LDC 1.1.0 (2017-01-26) #### Big news - Frontend, druntime and Phobos are at version **2.071.2**. - **[Link-Time Optimization (LTO)](https://johanengelen.github.io/ldc/2016/11/10/Link-Time-Optimization-LDC.html)** with `-flto={thin|full}` (LLVM ≥ 3.9). LTO requires linker support and is therefore currently only supported on Linux (`ld.gold` with LLVM plugin) and OS X. For more details, please refer to LLVM's and Clang's documentation, for example [Clang's ThinLTO documentation](http://clang.llvm.org/docs/ThinLTO.html). (#1840) - **Experimental cross-module inlining** (#1577, enable with `-enable-cross-module-inlining`) - **[Profile-guided optimization (PGO)](https://johanengelen.github.io/ldc/2016/07/15/Profile-Guided-Optimization-with-LDC.html)** (#1219) - Windows: enable C-style DLL exports/imports via `export` (functions only) (#1856) - Experimental IR-to-obj caching with `-cache=` (#1572, #1753, #1812, #1893) - Accept bitcode files on commandline (#1539) - `@ldc.attributes.fastmath` for [aggressive math optimization](https://johanengelen.github.io/ldc/2016/10/11/Math-performance-LDC.html) (#1472, #1438) - Binary distribution now bundles DUB (v1.2.0) (#1573) - **Breaking changes to command-line semantics** (see http://forum.dlang.org/post/ubobkfmsspbsmjunosna@forum.dlang.org). #### New features - New traits `__traits(targetCPU)` and `__traits(targetHasFeature, )` (#1434) - Drastic reduction of large symbol name lengths with optional `-hash-threshold` (#1445) - `@ldc.attributes.optStrategy(...)` for per-function optimization setting (#1637) - Extend intrinsic `llvm_memory_fence` for single-thread fences (#1837) - Add function instrumentation and profiling options via `-finstrument-functions` (#1845) - Add line-tables-only debuginfo via `-gline-tables-only` (#1861) - Implement DMD-compatible `-betterC` (#1872) #### Platform support - Supports LLVM 3.5 - 3.9 and current 4.0 release candidate. - ABI fixes, mainly for PowerPC targets. For bootstrapping, make sure to use source branch `ltsmaster` or the latest 0.17.x release, as all existing LDC releases ≥ 1.0 for PowerPC are unusable. (#1905) - Added ARM assembly code for Phobos `std.bigint`. (https://github.com/ldc-developers/phobos/pull/31) - Added some definitions for OpenBSD. (https://github.com/ldc-developers/druntime/commit/1ef83229673f5ae23f6a2a97f8e6b039647fbf87) - Updates for Solaris (https://github.com/ldc-developers/druntime/pull/71, https://github.com/ldc-developers/druntime/pull/72, https://github.com/ldc-developers/druntime/pull/73, https://github.com/ldc-developers/druntime/pull/74, https://github.com/ldc-developers/druntime/pull/75, https://github.com/ldc-developers/druntime/pull/79) - Linux: changed default to fully relocatable, position independent code (PIC). Change back to non-relocatable with `-relocation-model=static`. (#1664) #### Bug fixes - Potential crash when generating debuginfos for nested variables AND optimizing (#1933, #1963, #1984) - Alignment and size of critical sections, causing crashes on ARM (#1955, #1956) - `-finstrument-functions` using wrong return address (#1961) - Response files expanded too late, preventing cross-compilation on Windows when using dub (#1941, #1942) - Non-Windows x86_64 ABI bug wrt. returning static arrays (#1925, #1938) - Some array literals wrongly promoted to constants (#1924, #1927) - Misc. DUB regressions introduced by beta 3 (#1819) - Don't output static libs (with relative target filename) in `-od` objects directory (for LDC, but continue to do so for LDMD, for DMD compatibility). - LDMD: avoid object file collisions (due to multiple D source files with identical name in different dirs) when creating a static lib and remove the object files on success, mimicking DMD. - Create output directories recursively. - Potential ICE when building vibe.d projects (#1741) - ICE when calling an abstract function. (#1822) - ICE for invalid `__asm` constraints. (#802) - Wrong code for LLVM inline assembly returning a tuple (`__asmtuple`). (#1823) - Potential ICE wrt. captured variables. (#1864) - ARM: ICE when using LTO. (#1860) - Union layout and initialization, fixing the compilation of DMD (#1846, fixing most cases of #1829) - Allow custom file extension for .ll/.bc./.s output files. (#1843) - Windows: produced binaries with debuginfos are now large-address-aware too. (#442, #1876) - Fix debuginfos for parameters. (#1816) - Allow alignment of global variables < pointer size. (#1825) - Promote more immutable array literals to LLVM constants. (#506, #1821, #1838) - ICE when incrementing a complex variable. (#1806) - `llvm.va_start` not matched with `llvm.va_end` (#1744) - ldmd2 ignores -od option for libraries. (#1724) - ICE: toConstElem(CastExp) doesn't support NewExp as cast source. (#1723) - Mark runtime intrinsic shims as pragma(inline, true). (#1715) - pragma(inline, false) is incompatible with store/loadUnaligned. (#1711) - ICE: function not fully analyzed; previous unreported errors compiling std.variant.VariantN!(16LU, int, string).VariantN.__xopEquals? (#1698) - Segfault at at ldc/ldc-1.1.0/driver/main.cpp:1351. (#1696) - Make sure MSVC Build Tools are automatically detected by LDC. (#1690) - Update Windows README.txt. (#1689) - [ldc2-1.1.0-beta2] Missing symbol with inlining enabled. (#1678) - [REG ldc-1.1.0-beta2] ICE with templated classes. (#1677) - FreeBSD: Fix shared library build, working Hello World. (#1673) - Strange compile time error. (#1638) - LDC+DUB on Windows: folder separator is ignored. (#1621) - Fix evaluation order issues. (#1620, #1623) - Ubuntu 16.10 linker failures due to PIE by default (relocation R_X86_64_32S … can not be used). (#1618) - ICE on returning struct with zero-length static array. (#1611) - Debug info generation fixes for LLVM >= 3.8. (#1598) - ICE after return in the middle of a function on Win64/MSVC. (#1582) - Enums with referenced struct members result in floating point error. (#1581) - `pragma(inline, {true|false})` is no longer ignored (#1577) - Static array initialization with single element misdetected as direct construction via sret. (#1548) - ICE on static typeid. (#1540) - super doesn't work. (#1450) - Sub-expression evaluation order fixes. (#1327) - Add suffix to LTO linker plugin name to disambiguate with LLVM installation. (#1898) #### Building LDC - LDC now requires a preinstalled D compiler. (Versions `0.17.*` and the `ltsmaster` branch can be used to 'bootstrap' a build when only a C++ compiler is available.) - On Unix-like systems we now use gcc for linking. (#1594) #### Internals - optimizer: Skip adding verifier function pass if `-disable-verify` is given. (#1591) - DValue refactoring. (#1562) - Several improvements to generated IR. (#1528, #1630) - The vtable's of inherited interfaces are now put between the class's _monitor field and the user data fields. (https://issues.dlang.org/show_bug.cgi?id=15644) # LDC 1.0.0 (2016-06-03) #### Big news - Frontend, druntime and Phobos are at version **2.070.2**. #### Platform support - Support for LLVM 3.5 - 3.8 and preliminary support for LLVM 3.9. - Objective-C Support. (#1419) - ARM platform is now fully supported. (#1283, #489) - Better support for Android. (#1447) - Preliminary support for AArch64. #### Bug fixes - Outdated Copyright notice in LICENSE file. (#1322) - libconfig.so.8 not found (ubuntu 14.04) (#1460) - Wrong template filter on atomicOp. (#1454) - Runtime error on synchronized(typeid(SomeInterface)) { }. (#1377) - TypeInfo is stored read-only, but mutable from D. (#1337) - Inline assembly regression with local variable references. (#1292) - Compile error on Linux/PPC and Linux/PPC64 due to missing import in Phobos. #### Building LDC - LDC now requires a preinstalled D compiler. - Building on OS X requires ld64-264 or above (shipping with Xcode 7.3). This avoid spurious crashes during exception handling. XCode 7.3.1 should be used to avoid linker errors. (#1444, #1512) #### Internals - Linking against LLVM shared library is now supported. # LDC 0.17.6 (2018-08-24) #### News - Added support for **LLVM 6.0 and 7.0**. (https://github.com/ldc-developers/ldc/pull/2600, https://github.com/ldc-developers/ldc/pull/2825) - Backported **AArch64** fixes from master; most tests passing on Linux/glibc and Android. (https://github.com/ldc-developers/ldc/pull/2575, https://github.com/ldc-developers/ldc/pull/2811, https://github.com/ldc-developers/phobos/pull/49, https://github.com/ldc-developers/phobos/pull/50, https://github.com/ldc-developers/phobos/pull/51, https://github.com/ldc-developers/phobos/pull/52, https://github.com/ldc-developers/phobos/pull/53, https://github.com/ldc-developers/phobos/pull/54, https://github.com/ldc-developers/phobos/pull/55, https://github.com/ldc-developers/phobos/pull/56) - Fix generation of debug info. (https://github.com/ldc-developers/ldc/pull/2594) - Added support for bootstrapping on **DragonFly BSD**. (https://github.com/ldc-developers/ldc/pull/2580, https://github.com/ldc-developers/ldc/pull/2593, https://github.com/ldc-developers/ldc/pull/2689, https://github.com/ldc-developers/druntime/pull/110, https://github.com/ldc-developers/phobos/pull/45) - Fixed missing definition in `std.datetime` on Solaris. (https://github.com/ldc-developers/phobos/pull/46) - Fixed `std.datetime` unittest failure. (https://github.com/ldc-developers/phobos/pull/59) - Fixed tests for PowerPC. (https://github.com/ldc-developers/ldc/pull/2634, https://github.com/ldc-developers/ldc/pull/2635) - Improvements for **MIPS**. - Make `core.stdc.stdarg.va_*` functions `nothrow` to enable compiling the **2.082** frontend. (https://github.com/ldc-developers/ldc/pull/2821) - CI updates. # LDC 0.17.5 (2017-09-12) #### News - Added LLVM 5.0 support. - druntime: fixes for Android and addition of `core.math.yl2x[p1]()` for x86(_64) targets. - dmd-testsuite: backported `runnable/cppa.d` fix for GCC > 5. - CI updates. # LDC 0.17.4 (2017-03-23) #### News - Added LLVM 4.0 support. # LDC 0.17.3 (2017-02-01) #### Big news - Full stdlib and dmd testsuite passes on Android/ARM. #### Bug fixes - Fixes for PPC64-LE, MIPS64 and ARM/AArch64 ABIs (#1905) # LDC 0.17.2 (2016-10-09) #### Platform support - Support for LLVM 3.5 - 3.9. #### Bug fixes - Fixed soft float and hard float issues on ARM. - Fixed ABI error on Linux/PPC and Linux/PPC64. - Fixed error in `core.stdc.stdarg` on Linux/PPC and Linux/PPC64. - Fixed issue with `__tls_get_addr` on Linux/PPC and Linux/PPC64. # LDC 0.17.1 (2016-03-22) #### Big news - ARM platform is now a first class target for LDC. It passes most test cases (except 2 failures) and can successfully compile the D version of the compiler. #### Platform support - ARM platform is now fully supported. (#1283, #489) - Preliminary support for AArch64. - Preliminary support for LLVM 3.9. #### Bug fixes - Inline assembly regression with local variable references. (#1292) - Compile error on Linux/PPC and Linux/PPC64 due to missing import in Phobos. #### Building LDC - Linking against LLVM shared library is now supported. # LDC 0.17.0 (2016-02-13) #### Big news: - Frontend, druntime and Phobos are at version **2.068.2**. - The **exception handling** runtime now **no** longer allocates **GC** memory (although it still uses C `malloc` if there are more than 8 concurrent exceptions or nested `finally` blocks per thread). _Note:_ Creating the `Throwable`s in user code (e.g. `new Exception("…")`) and the `Runtime.traceHandler` may GC-allocate still. (Thanks for this goes to our newest contributor, @Philpax). - The `@ldc.attributes.section("…")` attribute can now be used to explicitly specify the object file section a variable or function is emitted to. - The `@ldc.attributes.target("…")` attribute can now be used to explicitly specify CPU features or architecture for a function. - The `-static` option can be used to create fully static binaries on Linux (akin to the GCC option of the same name). - `core.atomic.atomicOp()` now exploits LLVM read-modify-write intrinsics instead of using a compare-and-swap loop. As side-effect, the atomic intrinsics in module `ldc.intrinsics` have been renamed: - `llvm_atomic_cmp_swap` => `llvm_atomic_cmp_xchg` - `llvm_atomic_swap` => `llvm_atomic_rmw_xchg` - `llvm_atomic_load_*` => `llvm_atomic_rmw_*` #### Platform support: - Improved ARM support. (#1280) - The compiler now supports NetBSD. (#1247) (Thanks for this goes to @nrTQgc.) - The float ABI can now be derived from the second field of the triple. E.g. the hardfloat ABI is used if triple `armv7a-hardfloat-linux-gnueabi` is given. (#1253) - Support for fibers on AArch64. - Support for LLVM 3.8 and preliminary support for LLVM 3.9 #### Bug fixes: - make install problem. (#1289) - When a class contains a union, other fields are not statically initialized. (#1286) - Compiling DCD with -singleobj causes segmentation fault. (#1275) - 0.17.0-beta2: Cannot build DCD. (#1266) - Invalid bitcast error. (#1211) - 0.16.0-beta1: Trivial program fails on FreeBSD. (#1119) - Can't build gtk-d 3.1.4. (#1112) - x86 ABI: Fix Solaris regression and work around MSVC byval alignment issue. (#1230) - Atomic RMW operations emit subpar x86 assembly. (#1195) - align() not respected for local variable declarations. (#1154) - Codegen optimizations are no longer disabled when `-g` is given. (75b3270a) - Debug information is now generated for `ref` and `out` parameters. (#1177) - `core.internal.convert` tests do not depend on `real` padding bytes any longer. (#788) #### Building LDC: - LDC now requires LLVM 3.5–3.8 and thus also a C++11-capable compiler to build. #### Internals: - The LDC-specific parts of the source code have received a big overhaul to make use of some C++11 features and to unify the style (the LLVM style as per `clang-format` is now used). - The groundwork for a code generation test suite working on the LLVM IR level has been laid, together with some first test cases for alignment issues. - LDC now emits more optional LLVM IR attributes for more optimization opportunities. (#1232) #### Known issues: - LDC does not zero the padding area of a real variable. This may lead to wrong results if the padding area is also considered. See #770. Does not apply to real members inside structs etc. - Phobos does not compile on MinGW platform. ldc-1.40.0-src/.editorconfig0000644000000000000000000000105014727557031014305 0ustar rootroot# EditorConfig file: http://editorconfig.org/ root = true # Default settings for all file types: # + save files in UTF-8 encoding # + remove whitespace at the end of lines [*] charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true # File type specific settings: [*.{asm,S,c,m,d,di,dd,ddoc,dt,json,sdl,R,bat,bash,fish,sh,zsh}] indent_style = space indent_size = 4 # Make and Windows module-definition files use tabs for indentation [*.{mak,Makefile,def}] indent_style = tab [*.{cpp,h,yml}] indent_style = space indent_size = 2 ldc-1.40.0-src/ir/0000755000000000000000000000000014727557031012246 5ustar rootrootldc-1.40.0-src/ir/irtypefunction.h0000644000000000000000000000225414727557031015504 0ustar rootroot//===-- ir/irtypefunction.h - IrType subclasses for callables ---*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Provides the IrType subclasses used to represent D function/delegate types. // //===----------------------------------------------------------------------===// #pragma once #include "ir/irtype.h" struct IrFuncTy; /// class IrTypeFunction : public IrType { public: /// static IrTypeFunction *get(Type *dt); /// IrTypeFunction *isFunction() override { return this; } /// IrFuncTy &getIrFuncTy() override { return irFty; } protected: /// IrTypeFunction(Type *dt, llvm::Type *lt, IrFuncTy irFty); /// IrFuncTy irFty; }; /// class IrTypeDelegate : public IrType { public: /// static IrTypeDelegate *get(Type *dt); /// IrTypeDelegate *isDelegate() override { return this; } /// IrFuncTy &getIrFuncTy() override { return irFty; } protected: /// IrTypeDelegate(Type *dt, LLType *lt, IrFuncTy irFty); /// IrFuncTy irFty; }; ldc-1.40.0-src/ir/irvar.cpp0000644000000000000000000002111214727557031014072 0ustar rootroot//===-- irvar.cpp ---------------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irvar.h" #include "dmd/declaration.h" #include "dmd/errors.h" #include "dmd/init.h" #include "gen/dynamiccompile.h" #include "gen/irstate.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" #include "gen/mangling.h" #include "gen/pragma.h" #include "gen/uda.h" #include "ir/irdsymbol.h" ////////////////////////////////////////////////////////////////////////////// LLValue *IrGlobal::getValue(bool define) { if (!value) { declare(); if (!define) define = defineOnDeclare(V, /*isFunction=*/false); } if (define) { if (V->storage_class & STCextern) { // external } else if (!gIR->funcGenStates.empty() && gIR->topfunc()->getLinkage() == LLGlobalValue::AvailableExternallyLinkage) { // don't define globals while codegen'ing available_externally functions } else { auto gvar = llvm::dyn_cast(value); const bool isDefined = !gvar // bitcast pointer to a helper global || gvar->hasInitializer(); if (!isDefined) this->define(); } } return value; } llvm::Type *IrGlobal::getType() { return llvm::dyn_cast(value)->getValueType(); } void IrGlobal::declare() { Logger::println("Declaring global: %s", V->toChars()); LOG_SCOPE IF_LOG { if (V->parent) { Logger::println("parent: %s (%s)", V->parent->toChars(), V->parent->kind()); } else { Logger::println("parent: null"); } } assert(!value); // If a const/immutable value has a proper initializer (not "= void"), // it cannot be assigned again in a static constructor. Thus, we can // emit it as read-only data. // We also do so for forward-declared (extern) globals, just like clang. const bool isLLConst = (V->isConst() || V->isImmutable()) && ((V->_init && !V->_init->isVoidInitializer()) || (V->storage_class & STCextern)); const auto irMangle = getIRMangledName(V); // Windows: for globals with `export` visibility, initialize the DLL storage // class with dllimport unless the variable is defined in a root module // (=> no extra indirection for other root modules, assuming *all* root // modules will be linked together to one or more binaries). // [Defining a global overrides its DLL storage class.] bool useDLLImport = false; if (global.params.targetTriple->isOSWindows()) { // dllimport isn't supported for thread-local globals (MSVC++ neither) if (!V->isThreadlocal()) { // implicitly include extern(D) globals with -dllimport useDLLImport = (V->isExport() || V->_linkage == LINK::d) && dllimportDataSymbol(V); } } // Since the type of a global must exactly match the type of its // initializer, we cannot know the type until after we have emitted the // latter (e.g. in case of unions, …). However, it is legal for the // initializer to refer to the address of the variable. Thus, we first // create a global with the generic type (note the assignment to // value!), and in case we also do an initializer with a different type // later, swap it out and replace any existing uses with bitcasts to the // previous type. LLGlobalVariable *gvar = declareGlobal(V->loc, gIR->module, DtoMemType(V->type), irMangle, isLLConst, V->isThreadlocal(), useDLLImport); value = gvar; if (V->llvmInternal == LLVMextern_weak) gvar->setLinkage(llvm::GlobalValue::ExternalWeakLinkage); // Set the alignment (it is important not to use type->alignsize because // VarDeclarations can have an align() attribute independent of the type // as well). gvar->setAlignment(llvm::MaybeAlign(DtoAlignment(V))); applyVarDeclUDAs(V, gvar); if (dynamicCompileConst) addDynamicCompiledVar(gIR, this); IF_LOG Logger::cout() << *gvar << '\n'; } void IrGlobal::define() { Logger::println("Defining global: %s", V->toChars()); LOG_SCOPE if (global.params.v.tls && V->isThreadlocal() && !(V->storage_class & STCtemp)) { message("%s: `%s` is thread local", V->loc.toChars(), V->toChars()); } LLConstant *initVal = DtoConstInitializer(V->loc, V->type, V->_init, V->isCsymbol()); // Set the initializer, swapping out the variable if the types do not // match. auto gvar = llvm::cast(value); gvar = gIR->setGlobalVarInitializer(gvar, initVal, V); value = gvar; // dllexport isn't supported for thread-local globals (MSVC++ neither); // don't let LLVM create a useless /EXPORT directive (yields the same linker // error anyway when trying to dllimport). if (gvar->hasDLLExportStorageClass() && V->isThreadlocal()) gvar->setDLLStorageClass(LLGlobalValue::DefaultStorageClass); // If this global is used from a naked function, we need to create an // artificial "use" for it, or it could be removed by the optimizer if // the only reference to it is in inline asm. // Also prevent linker-level dead-symbol-elimination from stripping // special `rt_*` druntime symbol overrides (e.g., from executables linked // against *shared* druntime; required at least for Apple's ld64 linker). const auto name = gvar->getName(); if (nakedUse || name == "rt_options" || name == "rt_envvars_enabled" || name == "rt_cmdline_enabled") { gIR->usedArray.push_back(gvar); } // Also set up the debug info. gIR->DBuilder.EmitGlobalVariable(gvar, V); IF_LOG Logger::cout() << *gvar << '\n'; } ////////////////////////////////////////////////////////////////////////////// IrVar *getIrVar(VarDeclaration *decl) { assert(isIrVarCreated(decl)); assert(decl->ir->irVar != NULL); return decl->ir->irVar; } llvm::Value *getIrValue(VarDeclaration *decl) { return getIrVar(decl)->value; } bool isIrVarCreated(VarDeclaration *decl) { int t = decl->ir->type(); bool isIrVar = t == IrDsymbol::GlobalType || t == IrDsymbol::LocalType || t == IrDsymbol::ParamterType || t == IrDsymbol::FieldType; assert(isIrVar || t == IrDsymbol::NotSet); return isIrVar; } ////////////////////////////////////////////////////////////////////////////// IrGlobal *getIrGlobal(VarDeclaration *decl, bool create) { if (!isIrGlobalCreated(decl) && create) { assert(decl->ir->irGlobal == NULL); decl->ir->irGlobal = new IrGlobal(decl); decl->ir->m_type = IrDsymbol::GlobalType; } assert(decl->ir->irGlobal != NULL); return decl->ir->irGlobal; } bool isIrGlobalCreated(VarDeclaration *decl) { int t = decl->ir->type(); assert(t == IrDsymbol::GlobalType || t == IrDsymbol::NotSet); return t == IrDsymbol::GlobalType; } ////////////////////////////////////////////////////////////////////////////// IrLocal *getIrLocal(VarDeclaration *decl, bool create) { if (!isIrLocalCreated(decl) && create) { assert(decl->ir->irLocal == NULL); decl->ir->irLocal = new IrLocal(decl); decl->ir->m_type = IrDsymbol::LocalType; } assert(decl->ir->irLocal != NULL); return decl->ir->irLocal; } bool isIrLocalCreated(VarDeclaration *decl) { int t = decl->ir->type(); assert(t == IrDsymbol::LocalType || t == IrDsymbol::ParamterType || t == IrDsymbol::NotSet); return t == IrDsymbol::LocalType || t == IrDsymbol::ParamterType; } ////////////////////////////////////////////////////////////////////////////// IrParameter *getIrParameter(VarDeclaration *decl, bool create) { if (!isIrParameterCreated(decl) && create) { assert(decl->ir->irParam == NULL); decl->ir->irParam = new IrParameter(decl); decl->ir->m_type = IrDsymbol::ParamterType; } return decl->ir->irParam; } bool isIrParameterCreated(VarDeclaration *decl) { int t = decl->ir->type(); assert(t == IrDsymbol::ParamterType || t == IrDsymbol::NotSet); return t == IrDsymbol::ParamterType; } ////////////////////////////////////////////////////////////////////////////// IrField *getIrField(VarDeclaration *decl, bool create) { if (!isIrFieldCreated(decl) && create) { assert(decl->ir->irField == NULL); decl->ir->irField = new IrField(decl); decl->ir->m_type = IrDsymbol::FieldType; } assert(decl->ir->irField != NULL); return decl->ir->irField; } bool isIrFieldCreated(VarDeclaration *decl) { int t = decl->ir->type(); assert(t == IrDsymbol::FieldType || t == IrDsymbol::NotSet); return t == IrDsymbol::FieldType; } ldc-1.40.0-src/ir/irtype.cpp0000644000000000000000000001315214727557031014270 0ustar rootroot//===-- irtype.cpp --------------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irtype.h" #include "dmd/expression.h" #include "dmd/mtype.h" #include "dmd/target.h" #include "gen/irstate.h" #include "gen/logger.h" #include "gen/llvmhelpers.h" #include "gen/tollvm.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/LLVMContext.h" // These functions use getGlobalContext() as they are invoked before gIR // is set. IrType::IrType(Type *dt, LLType *lt) : dtype(dt), type(lt) { assert(dt && "null D Type"); assert(lt && "null LLVM Type"); assert(!getIrType(dt) && "already has IrType"); } IrFuncTy &IrType::getIrFuncTy() { llvm_unreachable("cannot get IrFuncTy from non lazy/function/delegate"); } ////////////////////////////////////////////////////////////////////////////// IrTypeBasic::IrTypeBasic(Type *dt) : IrType(dt, basic2llvm(dt)) {} IrTypeBasic *IrTypeBasic::get(Type *dt) { auto t = new IrTypeBasic(dt); getIrType(dt) = t; return t; } LLType *IrTypeBasic::getComplexType(llvm::LLVMContext &ctx, LLType *type) { llvm::Type *types[] = {type, type}; return llvm::StructType::get(ctx, types, false); } llvm::Type *IrTypeBasic::basic2llvm(Type *t) { llvm::LLVMContext &ctx = getGlobalContext(); switch (t->ty) { case TY::Tvoid: case TY::Tnoreturn: return llvm::Type::getVoidTy(ctx); case TY::Tint8: case TY::Tuns8: case TY::Tchar: return llvm::Type::getInt8Ty(ctx); case TY::Tint16: case TY::Tuns16: case TY::Twchar: return llvm::Type::getInt16Ty(ctx); case TY::Tint32: case TY::Tuns32: case TY::Tdchar: return llvm::Type::getInt32Ty(ctx); case TY::Tint64: case TY::Tuns64: return llvm::Type::getInt64Ty(ctx); case TY::Tint128: case TY::Tuns128: return llvm::IntegerType::get(ctx, 128); case TY::Tfloat32: case TY::Timaginary32: return llvm::Type::getFloatTy(ctx); case TY::Tfloat64: case TY::Timaginary64: return llvm::Type::getDoubleTy(ctx); case TY::Tfloat80: case TY::Timaginary80: return target.realType; case TY::Tcomplex32: return getComplexType(ctx, llvm::Type::getFloatTy(ctx)); case TY::Tcomplex64: return getComplexType(ctx, llvm::Type::getDoubleTy(ctx)); case TY::Tcomplex80: return getComplexType(ctx, target.realType); case TY::Tbool: return llvm::Type::getInt1Ty(ctx); default: llvm_unreachable("Unknown basic type."); } } ////////////////////////////////////////////////////////////////////////////// IrTypePointer::IrTypePointer(Type *dt, LLType *lt) : IrType(dt, lt) {} IrTypePointer *IrTypePointer::get(Type *dt) { assert((dt->ty == TY::Tpointer || dt->ty == TY::Tnull) && "not pointer/null type"); auto &ctype = getIrType(dt); assert(!ctype); unsigned addressSpace = dt->ty == TY::Tpointer && dt->nextOf()->ty == TY::Tfunction ? gDataLayout->getProgramAddressSpace() : 0; auto t = new IrTypePointer(dt, getOpaquePtrType(addressSpace)); ctype = t; return t; } ////////////////////////////////////////////////////////////////////////////// IrTypeSArray::IrTypeSArray(Type *dt, LLType *lt) : IrType(dt, lt) {} IrTypeSArray *IrTypeSArray::get(Type *dt) { assert(dt->ty == TY::Tsarray && "not static array type"); auto &ctype = getIrType(dt); assert(!ctype); LLType *elemType = DtoMemType(dt->nextOf()); // We might have already built the type during DtoMemType e.g. as part of a // forward reference in a struct. if (!ctype) { TypeSArray *tsa = static_cast(dt); uint64_t dim = static_cast(tsa->dim->toUInteger()); ctype = new IrTypeSArray(dt, llvm::ArrayType::get(elemType, dim)); } return ctype->isSArray(); } ////////////////////////////////////////////////////////////////////////////// IrTypeArray::IrTypeArray(Type *dt, LLType *lt) : IrType(dt, lt) {} IrTypeArray *IrTypeArray::get(Type *dt) { assert(dt->ty == TY::Tarray && "not dynamic array type"); auto &ctype = getIrType(dt); assert(!ctype); llvm::Type *types[] = {DtoSize_t(), getOpaquePtrType()}; LLType *at = llvm::StructType::get(getGlobalContext(), types, false); ctype = new IrTypeArray(dt, at); return ctype->isArray(); } ////////////////////////////////////////////////////////////////////////////// IrTypeVector::IrTypeVector(Type *dt, llvm::Type *lt) : IrType(dt, lt) {} IrTypeVector *IrTypeVector::get(Type *dt) { TypeVector *tv = dt->isTypeVector(); assert(tv && "not vector type"); auto &ctype = getIrType(dt); assert(!ctype); TypeSArray *tsa = tv->basetype->isTypeSArray(); assert(tsa); LLType *elemType = DtoMemType(tsa->next); // Could have already built the type as part of a struct forward reference, // just as for pointers and arrays. if (!ctype) { LLType *lt = llvm::VectorType::get(elemType, tsa->dim->toUInteger(), /*Scalable=*/false); ctype = new IrTypeVector(dt, lt); } return ctype->isVector(); } ////////////////////////////////////////////////////////////////////////////// IrType *&getIrType(Type *t, bool create) { // See remark in DtoType(). assert( (t->ty != TY::Tstruct || t == static_cast(t)->sym->type) && "use sd->type for structs"); assert((t->ty != TY::Tclass || t == static_cast(t)->sym->type) && "use cd->type for classes"); t = stripModifiers(t); if (create) { DtoType(t); assert(t->ctype); } return t->ctype; } ldc-1.40.0-src/ir/irfunction.h0000644000000000000000000000700714727557031014603 0ustar rootroot//===-- ir/irfunction.h - Codegen state for D functions ---------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Represents the state of a D function/method/... on its way through the // codegen process. // //===----------------------------------------------------------------------===// #pragma once #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseMapInfo.h" #include "gen/llvm.h" #include "ir/irfuncty.h" #include class FuncDeclaration; class TypeFunction; class VarDeclaration; // represents a function struct IrFunction { // constructor explicit IrFunction(FuncDeclaration *fd); // annotations void setNeverInline(); void setAlwaysInline(); void setLLVMFunc(llvm::Function *function); /// Returns the associated LLVM function. /// Use getLLVMCallee() for the LLVM function to be used for calls. llvm::Function *getLLVMFunc() const; llvm::CallingConv::ID getCallingConv() const; llvm::FunctionType *getLLVMFuncType() const; llvm::StringRef getLLVMFuncName() const; bool hasLLVMPersonalityFn() const; void setLLVMPersonalityFn(llvm::Constant *personality); /// Returns the associated LLVM function to be used for calls (potentially /// some sort of wrapper, e.g., a JIT wrapper). llvm::Function *getLLVMCallee() const; bool isDynamicCompiled() const; FuncDeclaration *decl = nullptr; TypeFunction *type = nullptr; llvm::Value *sretArg = nullptr; // sret pointer arg llvm::Value *thisArg = nullptr; // class/struct 'this' arg llvm::Value *nestArg = nullptr; // nested function 'this' arg llvm::StructType *frameType = nullptr; // type of nested context unsigned frameTypeAlignment = 0; // its alignment // number of enclosing functions with variables accessed by nested functions // (-1 if neither this function nor any enclosing ones access variables from // enclosing functions) int depth = -1; bool nestedContextCreated = false; // holds whether nested context is created // TODO: Move to FuncGenState? llvm::Value *_arguments = nullptr; llvm::Value *_argptr = nullptr; llvm::DISubprogram *diSubprogram = nullptr; std::stack diLexicalBlocks; using VariableMap = llvm::DenseMap; // Debug info for all variables VariableMap variableMap; IrFuncTy irFty; /// Stores the FastMath options for this functions. /// These are set e.g. by math related UDA's from ldc.attributes. llvm::FastMathFlags FMF; /// target CPU was overriden by attribute bool targetCpuOverridden = false; /// target features was overriden by attributes bool targetFeaturesOverridden = false; /// This functions was marked for dynamic compilation bool dynamicCompile = false; /// This functions was marked emit-only for dynamic compilation bool dynamicCompileEmit = false; /// Dynamic compilation thunk, all attempts to call or take address of the /// original function will be redirected to it llvm::Function *rtCompileFunc = nullptr; private: llvm::Function *func = nullptr; }; IrFunction *getIrFunc(FuncDeclaration *decl, bool create = false); bool isIrFuncCreated(FuncDeclaration *decl); /// Returns the associated LLVM function to be used for calls (potentially /// some sort of wrapper, e.g., a JIT wrapper). llvm::Function *DtoCallee(FuncDeclaration *decl, bool create = true); ldc-1.40.0-src/ir/irtypestruct.h0000644000000000000000000000164214727557031015203 0ustar rootroot//===-- ir/irtypestruct.h - IrType for structs and unions -------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #pragma once #include "ir/irtypeaggr.h" class StructDeclaration; class TypeStruct; /// IrType for struct/union types. class IrTypeStruct : public IrTypeAggr { public: /// static IrTypeStruct *get(StructDeclaration *sd); /// IrTypeStruct *isStruct() override { return this; } /// static void resetDComputeTypes(); protected: /// explicit IrTypeStruct(StructDeclaration *sd); /// static std::vector dcomputeTypes; /// StructDeclaration this type represents. StructDeclaration *sd = nullptr; /// DMD TypeStruct of this type. TypeStruct *ts = nullptr; }; ldc-1.40.0-src/ir/irtypeaggr.cpp0000644000000000000000000002655514727557031015144 0ustar rootroot//===-- irtypeaggr.cpp ----------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irtypeaggr.h" #include "dmd/aggregate.h" #include "dmd/errors.h" #include "dmd/init.h" #include "gen/irstate.h" #include "gen/logger.h" #include "gen/llvmhelpers.h" #include "llvm/IR/DerivedTypes.h" using namespace dmd; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // FIXME A similar function is in ir/iraggr.cpp and RTTIBuilder::push(). static inline size_t add_zeros(std::vector &defaultTypes, size_t startOffset, size_t endOffset) { assert(startOffset <= endOffset); const size_t paddingSize = endOffset - startOffset; if (paddingSize) { llvm::ArrayType *pad = llvm::ArrayType::get( llvm::Type::getInt8Ty(gIR->context()), paddingSize); defaultTypes.push_back(pad); } return paddingSize ? 1 : 0; } AggrTypeBuilder::AggrTypeBuilder(unsigned offset) : m_offset(offset) { m_defaultTypes.reserve(32); } void AggrTypeBuilder::addType(llvm::Type *type, unsigned size) { const unsigned fieldAlignment = getABITypeAlign(type); assert(fieldAlignment); // If the field offset does not have natural alignment, mark the aggregate as // packed for IR. if ((m_offset & (fieldAlignment - 1)) != 0) { m_packed = true; } m_defaultTypes.push_back(type); m_offset += size; m_fieldIndex++; m_maxFieldIRAlignment = std::max(m_maxFieldIRAlignment, fieldAlignment); } void AggrTypeBuilder::addAggregate(AggregateDeclaration *ad) { addAggregate(ad, nullptr, Aliases::AddToVarGEPIndices); } void AggrTypeBuilder::addAggregate( AggregateDeclaration *ad, const AggrTypeBuilder::VarInitMap *explicitInits, AggrTypeBuilder::Aliases aliases) { const size_t n = ad->fields.length; if (n == 0) return; // Objective-C instance variables are laid out at runtime. // as such, we should not generate the aggregate body. if (auto klass = ad->isClassDeclaration()) { if (klass->classKind == ClassKind::objc) { this->addType(getOpaquePtrType(), getPointerSize()); return; } } // Unions may lead to overlapping fields, and we need to flatten them for LLVM // IR. We usually take the first field (in declaration order) of an // overlapping set, but a literal with an explicit initializer for a dominated // field might require us to select that field. struct Data { VarDeclaration *field; LLType *llType; uint64_t size; }; LLSmallVector actualFields; // list of pairs: alias => actual field (same offset, same LL type (not // checked for bit fields)) LLSmallVector, 16> aliasPairs; // Bit fields additionally complicate matters. E.g.: // struct S { // unsigned char a:7; // byte offset 0, bit offset 0, bit width 7 // _Bool b:1; // byte offset 0, bit offset 7, bit width 1 // _Bool c:1; // byte offset 1, bit offset 0, bit width 1 // unsigned d:22; // byte offset 1, bit offset 1, bit width 22 // _Bool e:1; // byte offset 3, bit offset 7, bit width 1 // }; // => group 1: byte offset 0, size 1 (`a`, with alias `b`) // group 2: byte offset 1, size 3 (`c`, with alias `d` and extra member `e` // (with greater byte offset)) // list of pairs: extra bit field member (greater byte offset) => first member // of bit field group LLSmallVector, 8> extraBitFieldMembers; // Iterate over all fields in declaration order, in 1 or 2 passes. for (int pass = explicitInits ? 0 : 1; pass < 2; ++pass) { for (size_t i = 0; i < ad->fields.length; ++i) { const auto field = ad->fields[i]; bool haveExplicitInit = explicitInits && explicitInits->find(field) != explicitInits->end(); uint64_t fieldSize = size(field->type); const bool isBitField = field->isBitFieldDeclaration() != nullptr; if (isBitField) { const auto group = BitFieldGroup::startingFrom( i, ad->fields.length, [ad](size_t i) { return ad->fields[i]; }); if (!haveExplicitInit && explicitInits) { haveExplicitInit = llvm::any_of( group.bitFields, [explicitInits](BitFieldDeclaration *bf) { return explicitInits->find(bf) != explicitInits->end(); }); } fieldSize = group.sizeInBytes; // final pass: create unconditional aliases/extra members for the other // bit fields if (pass == 1) { for (size_t j = 1; j < group.bitFields.size(); ++j) { auto bf = group.bitFields[j]; if (bf->offset == group.byteOffset) { aliasPairs.push_back({bf, field}); } else { extraBitFieldMembers.push_back( {bf, field->isBitFieldDeclaration()}); } } } // skip the other bit fields in this pass i += group.bitFields.size() - 1; } // 1st pass: only for fields with explicit initializer if (pass == 0 && !haveExplicitInit) continue; // final pass: only for fields without explicit initializer if (pass == 1 && haveExplicitInit) continue; // skip empty fields if (fieldSize == 0) continue; const uint64_t f_begin = field->offset; const uint64_t f_end = f_begin + fieldSize; // use an i8 array for bit field groups const auto llType = isBitField ? llvm::ArrayType::get(getI8Type(), fieldSize) : DtoMemType(field->type); // check for overlap with existing fields (on a byte level, not bits) bool overlaps = false; if (isBitField || field->overlapped()) { for (const auto &existing : actualFields) { const uint64_t e_begin = existing.field->offset; const uint64_t e_end = e_begin + existing.size; if (e_begin < f_end && e_end > f_begin) { overlaps = true; if (aliases == Aliases::AddToVarGEPIndices && e_begin == f_begin && existing.llType == llType) { aliasPairs.push_back(std::make_pair(field, existing.field)); } break; } } } if (!overlaps) actualFields.push_back({field, llType, fieldSize}); } } // Now we can build a list of LLVM types for the actual LL fields. // Make sure to zero out any padding and set the GEP indices for the directly // indexable variables. // first we sort the list by offset std::sort(actualFields.begin(), actualFields.end(), [](const Data &l, const Data &r) { return l.field->offset < r.field->offset; }); for (const auto &af : actualFields) { const auto vd = af.field; const auto llType = af.llType; if (vd->offset < m_offset) { error(vd->loc, "%s `%s` @ %u overlaps previous field @ %u. This is an ICE, please file an " "LDC issue.", vd->kind(), vd->toPrettyChars(), vd->offset, m_offset); fatal(); } // Add an explicit field for any padding so we can zero it, as per TDPL // §7.1.1. if (m_offset < vd->offset) { m_fieldIndex += add_zeros(m_defaultTypes, m_offset, vd->offset); m_offset = vd->offset; } // add default type m_defaultTypes.push_back(llType); if (!llType->isSized()) { error(vd->loc, "unexpected IR type forward declaration for aggregate member of " "type `%s`. This is an ICE, please file an LDC issue.", vd->type->toPrettyChars()); fatal(); } const unsigned fieldAlignment = getABITypeAlign(llType); const unsigned fieldSize = getTypeAllocSize(llType); assert(fieldSize <= af.size); // advance offset to right past this field if (!m_packed) { assert(fieldAlignment); m_packed = ((m_offset & (fieldAlignment - 1)) != 0); } m_offset += fieldSize; // set the field index m_varGEPIndices[vd] = m_fieldIndex; // let any aliases reuse this field/GEP index for (const auto &pair : aliasPairs) { if (pair.second == vd) m_varGEPIndices[pair.first] = m_fieldIndex; } // store extra bit field members of this group for (const auto &pair : extraBitFieldMembers) { if (pair.second == vd) m_extraBitFieldMembers.push_back(pair); } ++m_fieldIndex; m_maxFieldIRAlignment = std::max(m_maxFieldIRAlignment, fieldAlignment); } } void AggrTypeBuilder::alignCurrentOffset(unsigned alignment) { unsigned aligned = (m_offset + alignment - 1) & ~(alignment - 1); if (m_offset < aligned) { m_fieldIndex += add_zeros(m_defaultTypes, m_offset, aligned); m_offset = aligned; } } void AggrTypeBuilder::addTailPadding(unsigned aggregateSize) { assert(m_offset <= aggregateSize && "IR aggregate type is larger than the corresponding D type"); if (m_offset < aggregateSize) add_zeros(m_defaultTypes, m_offset, aggregateSize); // check if the aggregate size makes it packed in IR terms if (!m_packed && (aggregateSize & (m_maxFieldIRAlignment - 1))) m_packed = true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// IrTypeAggr::IrTypeAggr(AggregateDeclaration *ad) : IrType(ad->type, LLStructType::create(gIR->context(), ad->toPrettyChars())), aggr(ad) {} unsigned IrTypeAggr::getMemberLocation(VarDeclaration *var, bool& isFieldIdx) { // Note: The interface is a bit more general than what we actually return. // Specifically, the frontend offset information we use for overlapping // fields is always based at the object start. const auto &varGEPIndices = getVarGEPIndices(); auto it = varGEPIndices.find(var); if (it != varGEPIndices.end()) { isFieldIdx = true; return it->second; } else { isFieldIdx = false; return var->offset; } } ////////////////////////////////////////////////////////////////////////////// BitFieldGroup BitFieldGroup::startingFrom( size_t startFieldIndex, size_t numTotalFields, std::function getFieldFn) { BitFieldGroup group; for (size_t i = startFieldIndex; i < numTotalFields; ++i) { auto bf = getFieldFn(i)->isBitFieldDeclaration(); if (!bf) break; unsigned bitOffset = bf->bitOffset; if (i == startFieldIndex) { group.byteOffset = bf->offset; } else if (bf->offset >= group.byteOffset + group.sizeInBytes || bf->offset < group.byteOffset) { // unions // starts a new bit field group break; } else { // the byte offset might not match the group's bitOffset += (bf->offset - group.byteOffset) * 8; } const auto sizeInBytes = (bitOffset + bf->fieldWidth + 7) / 8; group.sizeInBytes = std::max(group.sizeInBytes, sizeInBytes); group.bitFields.push_back(bf); } return group; } unsigned BitFieldGroup::getBitOffset(BitFieldDeclaration *member) const { assert(member->offset >= byteOffset); return (member->offset - byteOffset) * 8 + member->bitOffset; } ldc-1.40.0-src/ir/irmodule.cpp0000644000000000000000000000257014727557031014576 0ustar rootroot//===-- irmodule.cpp ------------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irmodule.h" #include "dmd/module.h" #include "gen/irstate.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "gen/mangling.h" #include "gen/tollvm.h" #include "ir/irdsymbol.h" #include "ir/irfunction.h" IrModule::IrModule(Module *module) : M(module) {} llvm::GlobalVariable *IrModule::moduleInfoSymbol() { if (moduleInfoVar) { return moduleInfoVar; } const auto irMangle = getIRMangledModuleInfoSymbolName(M); const bool useDLLImport = !M->isRoot() && dllimportDataSymbol(M); moduleInfoVar = declareGlobal(Loc(), gIR->module, llvm::StructType::create(gIR->context()), irMangle, false, false, useDLLImport); return moduleInfoVar; } IrModule *getIrModule(Module *m) { if (!m) { m = gIR->func()->decl->getModule(); } assert(m && "null module"); if (m->ir->m_type == IrDsymbol::NotSet) { m->ir->irModule = new IrModule(m); m->ir->m_type = IrDsymbol::ModuleType; } assert(m->ir->m_type == IrDsymbol::ModuleType); return m->ir->irModule; } ldc-1.40.0-src/ir/irstruct.cpp0000644000000000000000000001337714727557031014644 0ustar rootroot//===-- irstruct.cpp ------------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "dmd/errors.h" #include "dmd/mangle.h" #include "dmd/mtype.h" #include "dmd/template.h" #include "gen/irstate.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" #include "gen/mangling.h" #include "gen/rttibuilder.h" #include "gen/runtime.h" #include "gen/structs.h" #include "gen/tollvm.h" #include "gen/typinf.h" #include "ir/iraggr.h" #include "ir/irtypeclass.h" using namespace dmd; namespace { LLStructType* getTypeInfoStructMemType() { Type *t = getStructTypeInfoType(); IrTypeClass *tc = getIrType(t, true)->isClass(); assert(tc && "invalid TypeInfo_Struct type"); return llvm::cast(tc->getMemoryLLType()); } } LLGlobalVariable* IrStruct::getTypeInfoSymbol(bool define) { if (!typeInfo) { OutBuffer mangledName; mangledName.writestring("TypeInfo_S"); mangleToBuffer(aggrdecl, mangledName); const auto length = mangledName.length(); mangledName.prependstring(("_D" + std::to_string(length)).c_str()); mangledName.writestring("6__initZ"); const auto irMangle = getIRMangledVarName(mangledName.peekChars(), LINK::d); // We need to keep the symbol mutable as the type is not declared as // immutable on the D side, and e.g. synchronized() can be used on the // implicit monitor. const bool isConstant = false; // Struct TypeInfos are emitted into each referencing CU. const bool useDLLImport = false; typeInfo = declareGlobal(aggrdecl->loc, gIR->module, getTypeInfoStructMemType(), irMangle, isConstant, false, useDLLImport); emitTypeInfoMetadata(typeInfo, aggrdecl->type); if (!define) define = defineOnDeclare(aggrdecl, /*isFunction=*/false); } if (define) { auto init = getTypeInfoInit(); if (!typeInfo->hasInitializer()) defineGlobal(typeInfo, init, aggrdecl); } return typeInfo; } LLConstant *IrStruct::getTypeInfoInit() { // The upstream implementation is in dmd/todt.d, // TypeInfoDtVisitor.visit(TypeInfoStructDeclaration). if (constTypeInfo) { return constTypeInfo; } auto sd = aggrdecl->isStructDeclaration(); IF_LOG Logger::println("Defining TypeInfo for struct: %s", sd->toChars()); LOG_SCOPE; // we need (dummy) TypeInfos for opaque structs too const bool isOpaque = !sd->members; // make sure xtoHash/xopEquals/xopCmp etc. are semantically analyzed if (!isOpaque && sd->semanticRun < PASS::semantic3done) { Logger::println( "Struct hasn't had semantic3 yet, calling semanticTypeInfoMembers()"); semanticTypeInfoMembers(sd); } TypeStruct *ts = sd->type->isTypeStruct(); // check declaration in object.d const auto structTypeInfoType = getStructTypeInfoType(); const auto structTypeInfoDecl = Type::typeinfostruct; // For x86_64 (except Win64) and AAPCS64 targets, class TypeInfo_Struct // contains 2 additional fields (m_arg1/m_arg2) which are used for the // TypeInfo-based core.stdc.stdarg.va_arg implementations in druntime. const auto &triple = *global.params.targetTriple; const auto arch = triple.getArch(); const bool withArgTypes = (arch == llvm::Triple::x86_64 && !triple.isOSWindows()) || (!triple.isOSDarwin() && // Apple uses a simpler scheme (arch == llvm::Triple::aarch64 || arch == llvm::Triple::aarch64_be)); const unsigned expectedFields = 11 + (withArgTypes ? 2 : 0); const unsigned actualFields = structTypeInfoDecl->fields.length - 1; // union of xdtor/xdtorti counts as 2 overlapping fields if (actualFields != expectedFields) { error(Loc(), "Unexpected number of `object.TypeInfo_Struct` fields; " "druntime version does not match compiler"); fatal(); } RTTIBuilder b(structTypeInfoType); // string mangledName if (isOpaque) { b.push_null_void_array(); } else { b.push_string(ts->deco); } // void[] m_init // The protocol is to write a null pointer for zero-initialized structs. // The length field is always needed for tsize(). if (isOpaque) { b.push_null_void_array(); } else { llvm::Constant *initPtr; if (ts->isZeroInit(Loc())) { initPtr = getNullPtr(); } else { initPtr = getInitSymbol(); } b.push_void_array(sd->size(Loc()), initPtr); } // function xtoHash b.push_funcptr(isOpaque ? nullptr : sd->xhash); // function xopEquals b.push_funcptr(isOpaque ? nullptr : sd->xeq); // function xopCmp b.push_funcptr(isOpaque ? nullptr : sd->xcmp); // function xtoString b.push_funcptr(isOpaque ? nullptr : search_toString(sd)); // StructFlags m_flags b.push_uint(!isOpaque && hasPointers(ts) ? 1 : 0); // function xdtor/xdtorti b.push_funcptr(isOpaque ? nullptr : sd->tidtor); // function xpostblit FuncDeclaration *xpostblit = isOpaque ? nullptr : sd->postblit; if (xpostblit && (xpostblit->storage_class & STCdisable)) { xpostblit = nullptr; } b.push_funcptr(xpostblit); // uint m_align b.push_uint(isOpaque ? 0 : DtoAlignment(ts)); if (withArgTypes) { // TypeInfo m_arg1 // TypeInfo m_arg2 for (unsigned i = 0; i < 2; i++) { if (auto t = isOpaque ? nullptr : sd->argType(i)) { t = merge(t); b.push_typeinfo(t); } else { b.push_null(getTypeInfoType()); } } } // immutable(void)* m_RTInfo if (!isOpaque && sd->getRTInfo) { b.push(toConstElem(sd->getRTInfo, gIR)); } else { b.push_size_as_vp(!isOpaque && hasPointers(ts) ? 1 : 0); } constTypeInfo = b.get_constant(getTypeInfoStructMemType()); return constTypeInfo; } ldc-1.40.0-src/ir/irtypefunction.cpp0000644000000000000000000000375314727557031016044 0ustar rootroot//===-- irtypefunction.cpp ------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irtypefunction.h" #include "dmd/mtype.h" #include "gen/functions.h" #include "gen/irstate.h" #include "gen/tollvm.h" #include "llvm/IR/DerivedTypes.h" using namespace dmd; IrTypeFunction::IrTypeFunction(Type *dt, llvm::Type *lt, IrFuncTy irFty_) : IrType(dt, lt), irFty(std::move(irFty_)) {} IrTypeFunction *IrTypeFunction::get(Type *dt) { TypeFunction *tf = dt->isTypeFunction(); assert(tf); auto &ctype = getIrType(tf); assert(!ctype); IrFuncTy irFty(tf); llvm::Type *lt = DtoFunctionType(tf, irFty, nullptr, nullptr); // Could have already built the type as part of a struct forward reference, // just as for pointers and arrays. if (!ctype) { ctype = new IrTypeFunction(dt, lt, irFty); } return ctype->isFunction(); } ////////////////////////////////////////////////////////////////////////////// IrTypeDelegate::IrTypeDelegate(Type *dt, llvm::Type *lt, IrFuncTy irFty_) : IrType(dt, lt), irFty(std::move(irFty_)) {} IrTypeDelegate *IrTypeDelegate::get(Type *t) { assert(t->ty == TY::Tdelegate); TypeFunction *tf = t->nextOf()->isTypeFunction(); assert(tf); auto &ctype = getIrType(t); assert(!ctype); IrFuncTy irFty(tf); llvm::Type *ltf = DtoFunctionType(tf, irFty, nullptr, pointerTo(Type::tvoid)); llvm::Type *fptr = ltf->getPointerTo(gDataLayout->getProgramAddressSpace()); llvm::Type *types[] = {getOpaquePtrType(), fptr}; LLStructType *lt = LLStructType::get(gIR->context(), types, false); // Could have already built the type as part of a struct forward reference, // just as for pointers and arrays. if (!ctype) { ctype = new IrTypeDelegate(t, lt, irFty); } return ctype->isDelegate(); } ldc-1.40.0-src/ir/irvar.h0000644000000000000000000000472714727557031013554 0ustar rootroot//===-- ir/irdsymbol.h - Codegen state for D vars/fields/params -*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Classes for representing the status of D variables on their way though the // codegen process. // //===----------------------------------------------------------------------===// #pragma once #include "llvm/IR/Type.h" #include "llvm/IR/DebugInfo.h" struct IrFuncTyArg; class VarDeclaration; struct IrVar { explicit IrVar(VarDeclaration *var) : V(var) {} IrVar(VarDeclaration *var, llvm::Value *value) : V(var), value(value) {} VarDeclaration *V = nullptr; llvm::Value *value = nullptr; bool dynamicCompileConst = false; }; // represents a global variable struct IrGlobal : IrVar { explicit IrGlobal(VarDeclaration *v) : IrVar(v) {} // This var is used by a naked function. bool nakedUse = false; llvm::Value *getValue(bool define = false); llvm::Type *getType(); private: void declare(); void define(); }; // represents a local variable variable struct IrLocal : IrVar { explicit IrLocal(VarDeclaration *v) : IrVar(v) {} IrLocal(VarDeclaration *v, llvm::Value *value) : IrVar(v, value) {} IrLocal(VarDeclaration *v, int nestedDepth, int nestedIndex) : IrVar(v), nestedDepth(nestedDepth), nestedIndex(nestedIndex) {} // Used for hybrid nested context creation. int nestedDepth = 0; int nestedIndex = -1; }; // represents a function parameter struct IrParameter : IrLocal { explicit IrParameter(VarDeclaration *v) : IrLocal(v) {} IrFuncTyArg *arg = nullptr; bool isVthis = false; // true, if it is the 'this' parameter }; // represents an aggregate field variable struct IrField : IrVar { explicit IrField(VarDeclaration *v) : IrVar(v){}; }; IrVar *getIrVar(VarDeclaration *decl); llvm::Value *getIrValue(VarDeclaration *decl); bool isIrVarCreated(VarDeclaration *decl); IrGlobal *getIrGlobal(VarDeclaration *decl, bool create = false); bool isIrGlobalCreated(VarDeclaration *decl); IrLocal *getIrLocal(VarDeclaration *decl, bool create = false); bool isIrLocalCreated(VarDeclaration *decl); IrParameter *getIrParameter(VarDeclaration *decl, bool create = false); bool isIrParameterCreated(VarDeclaration *decl); IrField *getIrField(VarDeclaration *decl, bool create = false); bool isIrFieldCreated(VarDeclaration *decl); ldc-1.40.0-src/ir/irfunction.cpp0000644000000000000000000000564014727557031015137 0ustar rootroot//===-- irfunction.cpp ----------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irfunction.h" #include "driver/cl_options.h" #include "gen/functions.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "gen/irstate.h" #include "gen/tollvm.h" #include "ir/irdsymbol.h" IrFunction::IrFunction(FuncDeclaration *fd) : irFty(nullptr /*set immediately below*/), FMF(opts::defaultFMF) { decl = fd; Type *t = fd->type->toBasetype(); assert(t->ty == TY::Tfunction); type = static_cast(t); irFty.type = type; } void IrFunction::setNeverInline() { assert(!func->hasFnAttribute(llvm::Attribute::AlwaysInline) && "function can't be never- and always-inline at the same time"); func->addFnAttr(llvm::Attribute::NoInline); } void IrFunction::setAlwaysInline() { assert(!func->hasFnAttribute(llvm::Attribute::NoInline) && "function can't be never- and always-inline at the same time"); func->addFnAttr(llvm::Attribute::AlwaysInline); } void IrFunction::setLLVMFunc(llvm::Function *function) { assert(function != nullptr); func = function; } llvm::Function *IrFunction::getLLVMFunc() const { return func; } llvm::CallingConv::ID IrFunction::getCallingConv() const { assert(func != nullptr); return func->getCallingConv(); } llvm::FunctionType *IrFunction::getLLVMFuncType() const { assert(func != nullptr); return func->getFunctionType(); } bool IrFunction::hasLLVMPersonalityFn() const { assert(func != nullptr); return func->hasPersonalityFn(); } void IrFunction::setLLVMPersonalityFn(llvm::Constant *personality) { assert(func != nullptr); func->setPersonalityFn(personality); } llvm::StringRef IrFunction::getLLVMFuncName() const { assert(func != nullptr); return func->getName(); } llvm::Function *IrFunction::getLLVMCallee() const { assert(func != nullptr); return rtCompileFunc != nullptr ? rtCompileFunc : func; } bool IrFunction::isDynamicCompiled() const { return dynamicCompile || dynamicCompileEmit; } IrFunction *getIrFunc(FuncDeclaration *decl, bool create) { if (!isIrFuncCreated(decl) && create) { assert(decl->ir->irFunc == NULL); decl->ir->irFunc = new IrFunction(decl); decl->ir->m_type = IrDsymbol::FuncType; } assert(decl->ir->irFunc != NULL); return decl->ir->irFunc; } bool isIrFuncCreated(FuncDeclaration *decl) { assert(decl); assert(decl->ir); IrDsymbol::Type t = decl->ir->type(); assert(t == IrDsymbol::FuncType || t == IrDsymbol::NotSet); return t == IrDsymbol::FuncType; } llvm::Function *DtoCallee(FuncDeclaration *decl, bool create) { assert(decl != nullptr); if (create) { DtoDeclareFunction(decl); } return getIrFunc(decl)->getLLVMCallee(); } ldc-1.40.0-src/ir/irforw.h0000644000000000000000000000224114727557031013726 0ustar rootroot//===-- ir/irforw.h - Forward declarations used in ir/ code ----*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Some common forward declarations for use in ir/ headers. // //===----------------------------------------------------------------------===// #pragma once // dmd forward declarations class Module; class Dsymbol; struct Declaration; class VarDeclaration; class FuncDeclaration; struct AggregateDeclaration; class StructDeclaration; class ClassDeclaration; struct InterfaceDeclaration; struct Expression; struct BaseClass; struct Array; struct Argument; class Type; class TypeStruct; class TypeClass; struct TypeEnum; struct TypeArray; class TypeFunction; // llvm forward declarations namespace llvm { class Value; class GlobalValue; class GlobalVariable; class Function; class Constant; class ConstantStruct; class ConstantArray; class DataLayout; class Type; class StructType; class ArrayType; class PointerType; class BasicBlock; class Instruction; } ldc-1.40.0-src/ir/irdsymbol.h0000644000000000000000000000444314727557031014430 0ustar rootroot//===-- ir/irdsymbol.h - Codegen state for D symbols ------------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Represents the status of a D symbol on its way though the codegen process. // //===----------------------------------------------------------------------===// #pragma once #include struct IrModule; struct IrFunction; class IrAggr; struct IrGlobal; struct IrLocal; struct IrParameter; struct IrField; struct IrVar; class Dsymbol; class AggregateDeclaration; class FuncDeclaration; class VarDeclaration; class Module; namespace llvm { class Value; } struct IrDsymbol { enum Type { NotSet, ModuleType, AggrType, FuncType, GlobalType, LocalType, ParamterType, FieldType }; enum State { Initial, Resolved, Declared, Defined }; static std::vector list; static void resetAll(); // overload all of these to make sure // the static list is up to date IrDsymbol(); IrDsymbol(const IrDsymbol &s); ~IrDsymbol(); void reset(); Type type() const { return m_type; } State state() const { return m_state; } bool isResolved() const { return m_state >= Resolved; } bool isDeclared() const { return m_state >= Declared; } bool isDefined() const { return m_state >= Defined; } void setResolved(); void setDeclared(); void setDefined(); private: friend IrModule *getIrModule(Module *m); friend IrAggr *getIrAggr(AggregateDeclaration *decl, bool create); friend IrFunction *getIrFunc(FuncDeclaration *decl, bool create); friend IrVar *getIrVar(VarDeclaration *decl); friend IrGlobal *getIrGlobal(VarDeclaration *decl, bool create); friend IrLocal *getIrLocal(VarDeclaration *decl, bool create); friend IrParameter *getIrParameter(VarDeclaration *decl, bool create); friend IrField *getIrField(VarDeclaration *decl, bool create); union { void *irData; IrModule *irModule; IrAggr *irAggr; IrFunction *irFunc; IrVar *irVar; IrGlobal *irGlobal; IrLocal *irLocal; IrParameter *irParam; IrField *irField; }; Type m_type = Type::NotSet; State m_state = State::Initial; }; ldc-1.40.0-src/ir/irtype.h0000644000000000000000000001145414727557031013740 0ustar rootroot//===-- ir/irtype.h - IrType base and primitive types -----------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // The types derived from IrType are used to attach LLVM type information and // other codegen metadata (e.g. for vtbl resolution) to frontend Types. // //===----------------------------------------------------------------------===// #pragma once #include "ir/irfuncty.h" ////////////////////////////////////////////////////////////////////////////// // forward declarations namespace llvm { class LLVMContext; class Type; } class Type; class IrTypeAggr; class IrTypeArray; class IrTypeBasic; class IrTypeClass; class IrTypeDelegate; class IrTypeFunction; class IrTypePointer; class IrTypeSArray; class IrTypeStruct; class IrTypeVector; ////////////////////////////////////////////////////////////////////////////// /// Code generation state/metadata for D types. The mapping from IrType to /// Type is injective but not surjective. /// /// Derived classes should be created using their static get() methods, which /// makes sure that uniqueness is preserved in the face of forward references. /// /// Note that the get() methods expect the IrType of the passed type/symbol not /// to be set yet. Another option would be to just return the existing IrType /// in such cases. This would bring the API more in line with the llvm::Type /// get() functions. Currently all clients use the DtoType() wrapper anyway /// instead of directly handling IrType instances, so keeping the assertions /// allows us to check for any uniqueness violations that might have slipped /// through. // TODO: Implement the described changes (now that the forward reference // handling logic seems to work correctly) and get rid of the "no-op" DtoType // calls in IrAggr, ... that only exist for their side effect. class IrType { public: virtual ~IrType() = default; /// virtual IrTypeAggr *isAggr() { return nullptr; } /// virtual IrTypeArray *isArray() { return nullptr; } /// virtual IrTypeBasic *isBasic() { return nullptr; } /// virtual IrTypeClass *isClass() { return nullptr; } /// virtual IrTypeDelegate *isDelegate() { return nullptr; } /// virtual IrTypeFunction *isFunction() { return nullptr; } /// virtual IrTypePointer *isPointer() { return nullptr; } /// virtual IrTypeSArray *isSArray() { return nullptr; } /// virtual IrTypeStruct *isStruct() { return nullptr; } /// virtual IrTypeVector *isVector() { return nullptr; } /// Type *getDType() { return dtype; } /// virtual llvm::Type *getLLType() { return type; } /// virtual IrFuncTy &getIrFuncTy(); protected: /// IrType(Type *dt, llvm::Type *lt); /// Type *dtype = nullptr; /// LLVM type. llvm::Type *type = nullptr; }; ////////////////////////////////////////////////////////////////////////////// /// IrType for basic D types. class IrTypeBasic : public IrType { public: /// static IrTypeBasic *get(Type *dt); /// IrTypeBasic *isBasic() override { return this; } protected: /// explicit IrTypeBasic(Type *dt); /// static llvm::Type *getComplexType(llvm::LLVMContext &ctx, llvm::Type *type); /// static llvm::Type *basic2llvm(Type *t); }; ////////////////////////////////////////////////////////////////////////////// /// IrType from pointers. class IrTypePointer : public IrType { public: /// static IrTypePointer *get(Type *dt); /// IrTypePointer *isPointer() override { return this; } protected: /// IrTypePointer(Type *dt, llvm::Type *lt); }; ////////////////////////////////////////////////////////////////////////////// /// IrType for static arrays class IrTypeSArray : public IrType { public: /// static IrTypeSArray *get(Type *dt); /// IrTypeSArray *isSArray() override { return this; } protected: /// IrTypeSArray(Type *dt, LLType *lt); }; ////////////////////////////////////////////////////////////////////////////// /// IrType for dynamic arrays class IrTypeArray : public IrType { public: /// static IrTypeArray *get(Type *dt); /// IrTypeArray *isArray() override { return this; } protected: /// IrTypeArray(Type *dt, llvm::Type *lt); }; ////////////////////////////////////////////////////////////////////////////// /// IrType for vectors class IrTypeVector : public IrType { public: /// static IrTypeVector *get(Type *dt); /// IrTypeVector *isVector() override { return this; } protected: /// explicit IrTypeVector(Type *dt, llvm::Type *lt); }; ////////////////////////////////////////////////////////////////////////////// /// Returns a reference to the IrType* associated with the specified D type. IrType *&getIrType(Type *t, bool create = false); ldc-1.40.0-src/ir/irtypeaggr.h0000644000000000000000000000712614727557031014602 0ustar rootroot//===-- ir/irtypeaggr.h - IrType subclasses for aggregates ------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #pragma once #include "ir/irtype.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/IR/DebugInfo.h" #include #include namespace llvm { class Constant; class StructType; } class AggregateDeclaration; class BitFieldDeclaration; class VarDeclaration; using VarGEPIndices = std::map; using ExtraBitFieldMembers = std::vector>; class AggrTypeBuilder { public: using VarInitMap = std::map; enum class Aliases { Skip, AddToVarGEPIndices }; explicit AggrTypeBuilder(unsigned offset = 0); void addType(llvm::Type *type, unsigned size); void addAggregate(AggregateDeclaration *ad); void addAggregate(AggregateDeclaration *ad, const VarInitMap *explicitInits, Aliases aliases); void alignCurrentOffset(unsigned alignment); void addTailPadding(unsigned aggregateSize); unsigned currentFieldIndex() const { return m_fieldIndex; } const std::vector &defaultTypes() const { return m_defaultTypes; } const VarGEPIndices &varGEPIndices() const { return m_varGEPIndices; } const ExtraBitFieldMembers &extraBitFieldMembers() const { return m_extraBitFieldMembers; } bool isPacked() const { return m_packed; } unsigned currentOffset() const { return m_offset; } protected: std::vector m_defaultTypes; VarGEPIndices m_varGEPIndices; // list of pairs: extra bit field member (greater byte offset) => first member // of bit field group ExtraBitFieldMembers m_extraBitFieldMembers; unsigned m_offset = 0; unsigned m_fieldIndex = 0; unsigned m_maxFieldIRAlignment = 1; bool m_packed = false; // in IR terms }; /// Base class of IrTypes for aggregate types. class IrTypeAggr : public IrType { public: /// IrTypeAggr *isAggr() override { return this; } /// Returns the index of the field in the LLVM struct type that corresponds /// to the given member variable, plus the offset to the actual field start /// due to overlapping (union) fields, if any. unsigned getMemberLocation(VarDeclaration *var, bool& isFieldIdx); protected: /// explicit IrTypeAggr(AggregateDeclaration *ad); /// AggregateDeclaration this type represents. AggregateDeclaration *aggr = nullptr; /// Stores the mapping from member variables to field indices in the actual /// LLVM type. If a member variable is not present, this means that it does /// not resolve to a "clean" GEP but extra offsetting due to overlapping /// members is needed (i.e., a union). /// /// We need to keep track of this separately, because there is no way to get /// the field index of a variable in the frontend, it only stores the byte /// offset. VarGEPIndices varGEPIndices; virtual const VarGEPIndices &getVarGEPIndices() { return varGEPIndices; } }; // A helper for aggregating consecutive bit fields to a group. struct BitFieldGroup { unsigned byteOffset = 0; // from aggregate start unsigned sizeInBytes = 0; // to cover the highest bit llvm::SmallVector bitFields; static BitFieldGroup startingFrom(size_t startFieldIndex, size_t numTotalFields, std::function getFieldFn); unsigned getBitOffset(BitFieldDeclaration *member) const; }; ldc-1.40.0-src/ir/irtypeclass.cpp0000644000000000000000000001165214727557031015321 0ustar rootroot//===-- irtypeclass.cpp ---------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irtypeclass.h" #include "dmd/aggregate.h" #include "dmd/declaration.h" #include "dmd/dsymbol.h" #include "dmd/errors.h" #include "dmd/mtype.h" #include "dmd/target.h" #include "dmd/template.h" #include "gen/functions.h" #include "gen/irstate.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" #include "gen/tollvm.h" #include "llvm/IR/DerivedTypes.h" IrTypeClass::IrTypeClass(ClassDeclaration *cd) : IrTypeAggr(cd), cd(cd), tc(static_cast(cd->type)) { vtbl_type = LLArrayType::get(getOpaquePtrType(), cd->vtbl.length); } void IrTypeClass::addClassData(AggrTypeBuilder &builder, ClassDeclaration *currCd) { // First, recursively add the fields for our base class and interfaces, if // any. if (currCd->baseClass) { addClassData(builder, currCd->baseClass); } if (currCd->vtblInterfaces && currCd->vtblInterfaces->length > 0) { // KLUDGE: The first pointer in the vtbl will be of type object.Interface; // extract that from the "well-known" object.TypeInfo_Class definition. // For C++ interfaces, this vtbl entry has to be omitted builder.alignCurrentOffset(target.ptrsize); for (auto b : *currCd->vtblInterfaces) { IF_LOG Logger::println("Adding interface vtbl for %s", b->sym->toPrettyChars()); // add to the interface map addInterfaceToMap(b->sym, builder.currentFieldIndex()); auto vtblTy = LLArrayType::get(getOpaquePtrType(), b->sym->vtbl.length); builder.addType(llvm::PointerType::get(vtblTy, 0), target.ptrsize); ++num_interface_vtbls; } } // Finally, the data members for this class. builder.addAggregate(currCd); } IrTypeClass *IrTypeClass::get(ClassDeclaration *cd) { const auto t = new IrTypeClass(cd); getIrType(cd->type) = t; return t; } llvm::Type *IrTypeClass::getLLType() { return getOpaquePtrType(); } // Lazily build the actual IR struct type when needed. // Note that it is this function that initializes most fields! llvm::Type *IrTypeClass::getMemoryLLType() { if (!isaStruct(type)->isOpaque()) return type; IF_LOG Logger::println("Building class type %s @ %s", cd->toPrettyChars(), cd->loc.toChars()); LOG_SCOPE; const unsigned instanceSize = cd->structsize; IF_LOG Logger::println("Instance size: %u", instanceSize); AggrTypeBuilder builder; // Objective-C just has an ISA pointer, so just // throw that in there. if (cd->classKind == ClassKind::objc) { builder.addType(getOpaquePtrType(), target.ptrsize); isaStruct(type)->setBody(builder.defaultTypes(), builder.isPacked()); return type; } // add vtbl builder.addType(llvm::PointerType::get(vtbl_type, 0), target.ptrsize); if (cd->isInterfaceDeclaration()) { // interfaces are just a vtable num_interface_vtbls = cd->vtblInterfaces ? cd->vtblInterfaces->length : 0; } else { // classes have monitor and fields if (!cd->isCPPclass() && !cd->isCPPinterface()) { // add monitor builder.addType(getOpaquePtrType(), target.ptrsize); } // add data members recursively addClassData(builder, cd); // add tail padding if (instanceSize) // can be 0 for opaque classes builder.addTailPadding(instanceSize); } // set struct body and copy GEP indices isaStruct(type)->setBody(builder.defaultTypes(), builder.isPacked()); varGEPIndices = builder.varGEPIndices(); if (!cd->isInterfaceDeclaration() && instanceSize && getTypeAllocSize(type) != instanceSize) { error(cd->loc, "ICE: class IR size does not match the frontend size"); fatal(); } IF_LOG Logger::cout() << "class type: " << *type << std::endl; return type; } size_t IrTypeClass::getInterfaceIndex(ClassDeclaration *inter) { getMemoryLLType(); // lazily resolve auto it = interfaceMap.find(inter); if (it == interfaceMap.end()) { return ~0UL; } return it->second; } unsigned IrTypeClass::getNumInterfaceVtbls() { getMemoryLLType(); // lazily resolve return num_interface_vtbls; } const VarGEPIndices &IrTypeClass::getVarGEPIndices() { getMemoryLLType(); // lazily resolve return varGEPIndices; } void IrTypeClass::addInterfaceToMap(ClassDeclaration *inter, size_t index) { // don't duplicate work or overwrite indices if (interfaceMap.find(inter) != interfaceMap.end()) { return; } // add this interface interfaceMap.insert(std::make_pair(inter, index)); // add the direct base interfaces recursively - they // are accessed through the same index if (inter->interfaces.length > 0) { BaseClass *b = inter->interfaces.ptr[0]; addInterfaceToMap(b->sym, index); } } ldc-1.40.0-src/ir/irfuncty.h0000644000000000000000000000730014727557031014262 0ustar rootroot//===-- ir/irfuncty.h - Function type codegen metadata ----------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Additional information attached to a function type during codegen. Handles // LLVM attributes attached to a function and its parameters, etc. // //===----------------------------------------------------------------------===// #pragma once #include "gen/attributes.h" #include "llvm/ADT/SmallVector.h" #include #if defined(_MSC_VER) #include "dmd/root/array.h" #endif class DValue; class Type; class TypeFunction; struct ABIRewrite; namespace llvm { class Type; class Value; class Instruction; class Function; class FunctionType; } /// Represents a function type argument (both explicit and implicit as well as /// return values). /// /// Instances of this only exist for arguments that are actually lowered to an /// LLVM parameter (e.g. not for empty structs). struct IrFuncTyArg { /** This is the original D type as the frontend knows it * May NOT be rewritten!!! */ Type *const type = nullptr; /// The index of the declaration in the FuncDeclaration::parameters array /// corresponding to this argument. size_t parametersIdx = -1; /// This is the final LLVM Type used for the parameter/return value type llvm::Type *ltype = nullptr; /** These are the final LLVM attributes used for the function. * Must be valid for the LLVM Type and byref setting */ llvm::AttrBuilder attrs; /** 'true' if the final LLVM argument is a LLVM reference type. * Must be true when the D Type is a value type, but the final * LLVM Type is a reference type! */ bool byref = false; /** Pointer to the ABIRewrite structure needed to rewrite LLVM ValueS * to match the final LLVM Type when passing arguments and getting * return values */ ABIRewrite *rewrite = nullptr; /// Helper to check if the 'inreg' attribute is set bool isInReg() const; /// Helper to check if the 'sret' attribute is set bool isSRet() const; /// Helper to check if the 'byval' attribute is set bool isByVal() const; /** @param t D type of argument/return value as known by the frontend * @param byref Initial value for the 'byref' field. If true the initial * LLVM Type will be of DtoType(type->pointerTo()), instead * of just DtoType(type) */ IrFuncTyArg(Type *t, bool byref); IrFuncTyArg(Type *t, bool byref, llvm::AttrBuilder); IrFuncTyArg(const IrFuncTyArg &) = delete; ~IrFuncTyArg(); }; // represents a function type struct IrFuncTy { // D type TypeFunction *type; // The final LLVM type llvm::FunctionType *funcType = nullptr; // return value IrFuncTyArg *ret = nullptr; // null if not applicable IrFuncTyArg *arg_sret = nullptr; IrFuncTyArg *arg_this = nullptr; IrFuncTyArg *arg_nest = nullptr; IrFuncTyArg *arg_objcSelector = nullptr; IrFuncTyArg *arg_arguments = nullptr; // normal explicit arguments // typedef llvm::SmallVector ArgList; using ArgList = std::vector; ArgList args; // reserved for ABI-specific data void *tag = nullptr; llvm::Value *putRet(DValue *dval); llvm::Value *getRetRVal(Type *dty, llvm::Value *val); llvm::Value *getRetLVal(Type *dty, llvm::Value *val); llvm::Value *putArg(const IrFuncTyArg &arg, DValue *dval, bool isLValueExp, bool isLastArgExp); llvm::Value *getParamLVal(Type *dty, size_t idx, llvm::Value *val); AttrSet getParamAttrs(bool passThisBeforeSret); IrFuncTy(TypeFunction *tf) : type(tf) {} }; ldc-1.40.0-src/ir/irtypestruct.cpp0000644000000000000000000000637514727557031015546 0ustar rootroot//===-- irtypestruct.cpp --------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irtypestruct.h" #include "dmd/aggregate.h" #include "dmd/declaration.h" #include "dmd/errors.h" #include "dmd/init.h" #include "dmd/mtype.h" #include "dmd/template.h" #include "gen/dcompute/target.h" #include "gen/dcompute/druntime.h" #include "gen/irstate.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" #include "gen/tollvm.h" #include "llvm/IR/DerivedTypes.h" ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// IrTypeStruct::IrTypeStruct(StructDeclaration *sd) : IrTypeAggr(sd), sd(sd), ts(static_cast(sd->type)) {} ////////////////////////////////////////////////////////////////////////////// std::vector IrTypeStruct::dcomputeTypes; /// Resets special DCompute structs so they get re-created /// with the proper address space when generating device code. void IrTypeStruct::resetDComputeTypes() { for (auto irTypeStruct : dcomputeTypes) { auto &ctype = getIrType(irTypeStruct->dtype); delete ctype; ctype = nullptr; } dcomputeTypes.clear(); } ////////////////////////////////////////////////////////////////////////////// IrTypeStruct *IrTypeStruct::get(StructDeclaration *sd) { IF_LOG Logger::println("Building struct type %s @ %s", sd->toPrettyChars(), sd->loc.toChars()); LOG_SCOPE; auto t = new IrTypeStruct(sd); getIrType(sd->type) = t; // if it's a forward declaration, all bets are off, stick with the opaque if (sd->sizeok != Sizeok::done) { // but rewrite the name for special OpenCL types if (isFromLDC_OpenCL(sd)) { const int prefixlen = 4; // == sizeof("ldc.") LLStructType *st = static_cast(t->type); st->setName(st->getName().substr(prefixlen)); } return t; } if(isFromLDC_DCompute(sd)) { dcomputeTypes.push_back(t); } // For ldc.dcomptetypes.Pointer!(uint n,T), // emit { T addrspace(gIR->dcomputetarget->mapping[n])* } llvm::Optional p; if (gIR->dcomputetarget && (p = toDcomputePointer(sd))) { // Translate the virtual dcompute address space into the real one for // the target int realAS = gIR->dcomputetarget->mapping[p->addrspace]; llvm::SmallVector body; body.push_back(DtoMemType(p->type)->getPointerTo(realAS)); isaStruct(t->type)->setBody(body, false); VarGEPIndices v; v[sd->fields[0]] = 0; t->varGEPIndices = v; } else { AggrTypeBuilder builder; builder.addAggregate(sd); builder.addTailPadding(sd->structsize); isaStruct(t->type)->setBody(builder.defaultTypes(), builder.isPacked()); t->varGEPIndices = builder.varGEPIndices(); if (getTypeAllocSize(t->type) != sd->structsize) { error(sd->loc, "ICE: struct IR size does not match the frontend size"); fatal(); } } IF_LOG Logger::cout() << "final struct type: " << *t->type << std::endl; return t; } ldc-1.40.0-src/ir/irmodule.h0000644000000000000000000000245714727557031014247 0ustar rootroot//===-- ir/irmodule.h - Codegen state for top-level D modules ---*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Represents the state of a D module on its way through code generation. // //===----------------------------------------------------------------------===// #pragma once #include class FuncDeclaration; class VarDeclaration; class Module; namespace llvm { class GlobalVariable; class Function; class DIModule; } struct IrModule { IrModule(Module *module); virtual ~IrModule() = default; Module *const M = nullptr; llvm::GlobalVariable *moduleInfoSymbol(); // static ctors/dtors/unittests using FuncDeclList = std::list; using GatesList = std::list; FuncDeclList ctors; FuncDeclList dtors; FuncDeclList sharedCtors; FuncDeclList standaloneSharedCtors; FuncDeclList sharedDtors; GatesList gates; GatesList sharedGates; FuncDeclList unitTests; llvm::Function *coverageCtor = nullptr; llvm::DIModule *diModule = nullptr; private: llvm::GlobalVariable *moduleInfoVar = nullptr; }; IrModule *getIrModule(Module *m); ldc-1.40.0-src/ir/iraggr.cpp0000644000000000000000000003510514727557031014231 0ustar rootroot//===-- iraggr.cpp --------------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/iraggr.h" #include "dmd/aggregate.h" #include "dmd/declaration.h" #include "dmd/errors.h" #include "dmd/expression.h" #include "dmd/identifier.h" #include "dmd/init.h" #include "dmd/mtype.h" #include "dmd/target.h" #include "gen/irstate.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" #include "gen/mangling.h" #include "gen/pragma.h" #include "gen/tollvm.h" #include "ir/irdsymbol.h" #include "ir/irtypeclass.h" #include "ir/irtypestruct.h" #include #include using namespace dmd; ////////////////////////////////////////////////////////////////////////////// llvm::StructType *IrAggr::getLLStructType() { if (llStructType) return llStructType; LLType *llType = DtoType(type); if (auto irClassType = getIrType(type)->isClass()) llType = irClassType->getMemoryLLType(); llStructType = llvm::dyn_cast(llType); return llStructType; } ////////////////////////////////////////////////////////////////////////////// bool IrAggr::suppressTypeInfo() const { return !global.params.useTypeInfo || !Type::dtypeinfo || aggrdecl->llvmInternal == LLVMno_typeinfo; } ////////////////////////////////////////////////////////////////////////////// bool IrAggr::useDLLImport() const { return dllimportDataSymbol(aggrdecl); } ////////////////////////////////////////////////////////////////////////////// LLGlobalVariable *IrAggr::getInitSymbol(bool define) { #if LDC_LLVM_VER >= 1800 #define startswith starts_with #endif if (!init) { const auto irMangle = getIRMangledInitSymbolName(aggrdecl); // Init symbols of built-in TypeInfo classes (in rt.util.typeinfo) are // special. auto cd = aggrdecl->isClassDeclaration(); const bool isBuiltinTypeInfo = cd && llvm::StringRef(cd->ident->toChars()).startswith("TypeInfo_"); // Only declare the symbol if it isn't yet, otherwise the init symbol of // built-in TypeInfos may clash with an existing base-typed forward // declaration when compiling the rt.util.typeinfo unittests. init = gIR->module.getGlobalVariable(irMangle); if (init) { assert(!init->hasInitializer() && "existing init symbol not expected to be defined"); assert((isBuiltinTypeInfo || init->getValueType() == getLLStructType()) && "type of existing init symbol declaration doesn't match"); } else { // Init symbols of built-in TypeInfos need to be kept mutable as the type // is not declared as immutable on the D side, and e.g. synchronized() can // be used on the implicit monitor. const bool isConstant = !isBuiltinTypeInfo; init = declareGlobal(aggrdecl->loc, gIR->module, getLLStructType(), irMangle, isConstant, false, useDLLImport()); } init->setAlignment(llvm::MaybeAlign(DtoAlignment(type))); if (!define) define = defineOnDeclare(aggrdecl, /*isFunction=*/false); } if (define) { auto initConstant = getDefaultInit(); if (!init->hasInitializer()) { init = gIR->setGlobalVarInitializer(init, initConstant, aggrdecl); } } return init; #if LDC_LLVM_VER >= 1800 #undef startswith #endif } ////////////////////////////////////////////////////////////////////////////// llvm::Constant *IrAggr::getDefaultInit() { if (constInit) { return constInit; } IF_LOG Logger::println("Building default initializer for %s", aggrdecl->toPrettyChars()); LOG_SCOPE; VarInitMap noExplicitInitializers; constInit = createInitializerConstant(noExplicitInitializers); return constInit; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // helper function that adds zero bytes to a vector of constants // FIXME A similar function is in ir/irtypeaggr.cpp static inline size_t add_zeros(llvm::SmallVectorImpl &constants, size_t startOffset, size_t endOffset) { assert(startOffset <= endOffset); const size_t paddingSize = endOffset - startOffset; if (paddingSize) { llvm::ArrayType *pad = llvm::ArrayType::get( llvm::Type::getInt8Ty(gIR->context()), paddingSize); constants.push_back(llvm::Constant::getNullValue(pad)); } return paddingSize ? 1 : 0; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// LLConstant *IrAggr::getDefaultInitializer(VarDeclaration *field) { // Issue 9057 workaround caused by issue 14666 fix, see DMD upstream // commit 069f570005. if (field->_init && field->semanticRun < PASS::semantic2done && field->_scope) { semantic2(field, field->_scope); } return DtoConstInitializer(field->_init ? field->_init->loc : field->loc, field->type, field->_init, field->isCsymbol()); } // return a constant array of type arrTypeD initialized with a constant value, // or that constant value static llvm::Constant *FillSArrayDims(Type *arrTypeD, llvm::Constant *init) { // Check whether we actually need to expand anything. // KLUDGE: We don't have the initializer type here, so we can only check // the size without doing an expensive recursive D <-> LLVM type comparison. // The better way to solve this would be to just fix the initializer // codegen in any place where a scalar initializer might still be generated. if (gDataLayout->getTypeAllocSize(init->getType()) >= size(arrTypeD)) { return init; } if (arrTypeD->ty == TY::Tsarray) { init = FillSArrayDims(arrTypeD->nextOf(), init); size_t dim = static_cast(arrTypeD)->dim->toUInteger(); llvm::ArrayType *arrty = llvm::ArrayType::get(init->getType(), dim); return llvm::ConstantArray::get(arrty, std::vector(dim, init)); } return init; } llvm::Constant * IrAggr::createInitializerConstant(const VarInitMap &explicitInitializers) { IF_LOG Logger::println("Creating initializer constant for %s", aggrdecl->toChars()); LOG_SCOPE; llvm::SmallVector constants; unsigned offset = 0; auto cd = aggrdecl->isClassDeclaration(); IrClass *irClass = cd ? static_cast(this) : nullptr; if (irClass) { // add vtbl constants.push_back(irClass->getVtblSymbol()); offset += target.ptrsize; // add monitor (except for C++ classes) if (!cd->isCPPclass()) { constants.push_back(getNullPtr()); offset += target.ptrsize; } } // Add the initializers for the member fields. unsigned dummy = 0; bool isPacked = false; addFieldInitializers(constants, explicitInitializers, aggrdecl, offset, dummy, isPacked); // tail padding? const size_t structsize = aggrdecl->size(Loc()); if (offset < structsize) { add_zeros(constants, offset, structsize); } else if (offset > structsize) { error(Loc(), "ICE: IR aggregate constant size exceeds the frontend size"); fatal(); } // get LL field types llvm::SmallVector types; types.reserve(constants.size()); for (auto c : constants) types.push_back(c->getType()); const auto llStructType = getLLStructType(); bool isCompatible = (types.size() == llStructType->getNumElements()); if (isCompatible) { for (size_t i = 0; i < types.size(); i++) { if (types[i] != llStructType->getElementType(i)) { isCompatible = false; break; } } } // build constant LLStructType *llType = llStructType; if (!isCompatible) { // Note: isPacked only reflects whether there are misaligned IR fields, // not checking whether the aggregate contains appropriate tail padding. // So the resulting constant might contain more (implicit) tail padding than // the actual type, which should be harmless. llType = LLStructType::get(gIR->context(), types, isPacked); assert(getTypeAllocSize(llType) >= structsize); } llvm::Constant *c = LLConstantStruct::get(llType, constants); IF_LOG Logger::cout() << "final initializer: " << *c << std::endl; return c; } void IrAggr::addFieldInitializers( llvm::SmallVectorImpl &constants, const VarInitMap &explicitInitializers, AggregateDeclaration *decl, unsigned &offset, unsigned &interfaceVtblIndex, bool &isPacked) { using llvm::APInt; if (ClassDeclaration *cd = decl->isClassDeclaration()) { if (cd->baseClass) { addFieldInitializers(constants, explicitInitializers, cd->baseClass, offset, interfaceVtblIndex, isPacked); } // has interface vtbls? if (cd->vtblInterfaces && cd->vtblInterfaces->length > 0) { // Align interface infos to pointer size. unsigned aligned = (offset + target.ptrsize - 1) & ~(target.ptrsize - 1); if (offset < aligned) { add_zeros(constants, offset, aligned); offset = aligned; } IrClass *irClass = static_cast(this); for (auto bc : *cd->vtblInterfaces) { constants.push_back( irClass->getInterfaceVtblSymbol(bc, interfaceVtblIndex)); offset += target.ptrsize; ++interfaceVtblIndex; } } } AggrTypeBuilder b(offset); b.addAggregate(decl, explicitInitializers.empty() ? nullptr : &explicitInitializers, AggrTypeBuilder::Aliases::Skip); offset = b.currentOffset(); if (!isPacked) isPacked = b.isPacked(); const size_t baseLLFieldIndex = constants.size(); const size_t numNewLLFields = b.defaultTypes().size(); constants.resize(constants.size() + numNewLLFields, nullptr); const auto getFieldInit = [&explicitInitializers](VarDeclaration *field) { const auto explicitIt = explicitInitializers.find(field); return explicitIt != explicitInitializers.end() ? explicitIt->second : getDefaultInitializer(field); }; // IR field index => APInt for bitfield group std::unordered_map bitFieldGroupConstants; const auto addToBitFieldGroup = [&](BitFieldDeclaration *bf, unsigned fieldIndex, unsigned bitOffset) { LLConstant *init = getFieldInit(bf); if (init->isNullValue()) { // no bits to set return; } if (bitFieldGroupConstants.find(fieldIndex) == bitFieldGroupConstants.end()) { const auto fieldType = llvm::cast(b.defaultTypes()[fieldIndex]); // an i8 array const auto bitFieldSize = fieldType->getNumElements(); bitFieldGroupConstants.emplace(fieldIndex, APInt(bitFieldSize * 8, 0)); } APInt &constant = bitFieldGroupConstants[fieldIndex]; const auto intSizeInBits = constant.getBitWidth(); const APInt oldVal = constant; const APInt bfVal = init->getUniqueInteger().zextOrTrunc(intSizeInBits); const APInt mask = APInt::getLowBitsSet(intSizeInBits, bf->fieldWidth) << bitOffset; assert(!oldVal.intersects(mask) && "has masked bits set already"); constant = oldVal | ((bfVal << bitOffset) & mask); }; // add explicit and non-overlapping implicit initializers const auto &gepIndices = b.varGEPIndices(); for (const auto &pair : gepIndices) { const auto field = pair.first; const auto fieldIndex = pair.second; if (auto bf = field->isBitFieldDeclaration()) { // multiple bit fields can map to a single IR field for the whole group addToBitFieldGroup(bf, fieldIndex, bf->bitOffset); } else { LLConstant *&constant = constants[baseLLFieldIndex + fieldIndex]; assert(!constant); constant = FillSArrayDims(field->type, getFieldInit(field)); } } // add extra bit field members (greater byte offset than the group's first // member) for (const auto &pair : b.extraBitFieldMembers()) { const auto bf = pair.first; const auto primary = pair.second; const auto fieldIndexIt = gepIndices.find(primary); assert(fieldIndexIt != gepIndices.end()); assert(bf->offset > primary->offset); addToBitFieldGroup(bf, fieldIndexIt->second, (bf->offset - primary->offset) * 8 + bf->bitOffset); } for (const auto &pair : bitFieldGroupConstants) { const unsigned fieldIndex = pair.first; const APInt intValue = pair.second; LLConstant *&constant = constants[baseLLFieldIndex + fieldIndex]; assert(!constant && "already have a constant for a bitfield group?"); // convert APInt to i8 array const auto i8Type = getI8Type(); const auto numBytes = intValue.getBitWidth() / 8; llvm::SmallVector bytes; for (unsigned i = 0; i < numBytes; ++i) { APInt byteVal = intValue.extractBits(8, i * 8); bytes.push_back(LLConstant::getIntegerValue(i8Type, byteVal)); } constant = llvm::ConstantArray::get(LLArrayType::get(i8Type, numBytes), bytes); } // TODO: sanity check that all explicit initializers have been dealt with? // (potential issue for bitfields in unions...) // zero out remaining fields (padding and/or zero-initialized bit fields) for (size_t i = 0; i < numNewLLFields; i++) { auto &init = constants[baseLLFieldIndex + i]; if (!init) init = llvm::Constant::getNullValue(b.defaultTypes()[i]); } } IrAggr *getIrAggr(AggregateDeclaration *decl, bool create) { if (!isIrAggrCreated(decl) && create) { assert(decl->ir->irAggr == nullptr); if (auto cd = decl->isClassDeclaration()) { decl->ir->irAggr = new IrClass(cd); } else { decl->ir->irAggr = new IrStruct(decl->isStructDeclaration()); } decl->ir->m_type = IrDsymbol::AggrType; } assert(decl->ir->irAggr != nullptr); return decl->ir->irAggr; } IrStruct *getIrAggr(StructDeclaration *sd, bool create) { return static_cast( getIrAggr(static_cast(sd), create)); } IrClass *getIrAggr(ClassDeclaration *cd, bool create) { return static_cast( getIrAggr(static_cast(cd), create)); } bool isIrAggrCreated(AggregateDeclaration *decl) { int t = decl->ir->type(); assert(t == IrDsymbol::AggrType || t == IrDsymbol::NotSet); return t == IrDsymbol::AggrType; } ldc-1.40.0-src/ir/irtypeclass.h0000644000000000000000000000472514727557031014771 0ustar rootroot//===-- ir/irtypeclass.h - IrType implementation for D classes --*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Provides the IrType subclass used to represent D classes. // //===----------------------------------------------------------------------===// #pragma once #include "ir/irtypeaggr.h" #include "llvm/IR/DerivedTypes.h" template struct Array; class ClassDeclaration; using FuncDeclarations = Array; class TypeClass; /// class IrTypeClass : public IrTypeAggr { public: /// static IrTypeClass *get(ClassDeclaration *cd); /// IrTypeClass *isClass() override { return this; } /// llvm::Type *getLLType() override; /// Returns the actual storage type, i.e. without the indirection /// for the class reference. llvm::Type *getMemoryLLType(); /// Returns the vtable type for this class. llvm::ArrayType *getVtblType() { return vtbl_type; } /// Get index to interface implementation. /// Returns the index of a specific interface implementation in this /// class or ~0 if not found. size_t getInterfaceIndex(ClassDeclaration *inter); /// Returns the number of interface implementations (vtables) in this /// class. unsigned getNumInterfaceVtbls(); protected: /// explicit IrTypeClass(ClassDeclaration *cd); /// ClassDeclaration *cd = nullptr; /// TypeClass *tc = nullptr; /// Vtable type. llvm::ArrayType *vtbl_type = nullptr; /// Number of interface implementations (vtables) in this class. unsigned num_interface_vtbls = 0; /// std::map type mapping ClassDeclaration* to size_t. using ClassIndexMap = std::map; /// Map for mapping the index of a specific interface implementation /// in this class to its ClassDeclaration. ClassIndexMap interfaceMap; ////////////////////////////////////////////////////////////////////////// const VarGEPIndices &getVarGEPIndices() override; /// Adds the data members for the given class to the type builder, including /// those inherited from base classes/interfaces. void addClassData(AggrTypeBuilder &builder, ClassDeclaration *currCd); /// Adds the interface and all it's base interface to the interface /// to index map. void addInterfaceToMap(ClassDeclaration *inter, size_t index); }; ldc-1.40.0-src/ir/iraggr.h0000644000000000000000000001502614727557031013676 0ustar rootroot//===-- ir/iraggr.h - Codegen state for D aggregates ------------*- C++ -*-===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // Represents the state of a D aggregate (struct/class) on its way through // codegen, also managing the associated init and RTTI symbols. // //===----------------------------------------------------------------------===// #pragma once #include "dmd/aggregate.h" #include "llvm/ADT/SmallVector.h" #include #include class StructInitializer; namespace llvm { class Constant; class DIType; class GlobalVariable; class StructType; } ////////////////////////////////////////////////////////////////////////////// /// Represents a struct or class/interface. /// It is used during codegen to hold all the vital info we need. class IrAggr { public: ////////////////////////////////////////////////////////////////////////// // public fields, // FIXME this is basically stuff I just haven't gotten around to yet. /// The D aggregate. AggregateDeclaration *aggrdecl = nullptr; /// Aggregate D type. Type *type = nullptr; /// Composite type debug description. This is not only to cache, but also /// used for resolving forward references. llvm::DIType *diCompositeType = nullptr; ////////////////////////////////////////////////////////////////////////// /// Returns the static default initializer of a field. static llvm::Constant *getDefaultInitializer(VarDeclaration *field); ////////////////////////////////////////////////////////////////////////// virtual ~IrAggr() = default; /// Creates the __initZ symbol lazily. llvm::GlobalVariable *getInitSymbol(bool define = false); /// Builds the __initZ initializer constant lazily. llvm::Constant *getDefaultInit(); /// Return the LLVM type of this Aggregate (w/o the reference for classes) llvm::StructType *getLLStructType(); /// Whether to suppress the TypeInfo definition for the aggregate via /// `-betterC` / `-fno-rtti`, no `object.TypeInfo`, or /// `pragma(LDC_no_typeinfo)`. bool suppressTypeInfo() const; ////////////////////////////////////////////////////////////////////////// using VarInitMap = std::map; /// Creates an initializer constant for the struct type with the given /// fields set to the provided constants. The remaining space (not /// explicitly specified fields, padding) is default-initialized. /// /// Note that in the general case (if e.g. unions are involved), the /// returned type is not necessarily the same as getLLType(). llvm::Constant * createInitializerConstant(const VarInitMap &explicitInitializers); protected: /// Static default initializer global. llvm::GlobalVariable *init = nullptr; /// Static default initializer constant. llvm::Constant *constInit = nullptr; /// TypeInfo global. llvm::GlobalVariable *typeInfo = nullptr; /// TypeInfo initializer constant. llvm::Constant *constTypeInfo = nullptr; explicit IrAggr(AggregateDeclaration *aggr) : aggrdecl(aggr), type(aggr->type) {} // Use dllimport for *declared* init symbol, TypeInfo and vtable? bool useDLLImport() const; private: llvm::StructType *llStructType = nullptr; /// Recursively adds all the initializers for the given aggregate and, in /// case of a class type, all its base classes. void addFieldInitializers(llvm::SmallVectorImpl &constants, const VarInitMap &explicitInitializers, AggregateDeclaration *decl, unsigned &offset, unsigned &interfaceVtblIndex, bool &isPacked); }; /// Represents a struct. class IrStruct : public IrAggr { public: explicit IrStruct(StructDeclaration *sd) : IrAggr(sd) {} /// Creates the TypeInfo_Struct symbol lazily. llvm::GlobalVariable *getTypeInfoSymbol(bool define = false); private: /// Builds the TypeInfo_Struct initializer constant lazily. llvm::Constant *getTypeInfoInit(); }; /// Represents a class/interface. class IrClass : public IrAggr { public: explicit IrClass(ClassDeclaration *cd); /// Creates the __ClassZ/__InterfaceZ symbol lazily. llvm::GlobalVariable *getClassInfoSymbol(bool define = false); /// Creates the __vtblZ symbol lazily. llvm::GlobalVariable *getVtblSymbol(bool define = false); /// Defines all interface vtbls. void defineInterfaceVtbls(); private: /// Vtbl global. llvm::GlobalVariable *vtbl = nullptr; /// Vtbl initializer constant. llvm::Constant *constVtbl = nullptr; /// Map from pairs of to global variable, implemented /// by this class. The same interface can appear multiple times, so index is /// another way to specify the thunk offset std::map, llvm::GlobalVariable *> interfaceVtblMap; /// Interface info array global. /// Basically: static object.Interface[num_interfaces] llvm::GlobalVariable *classInterfacesArray = nullptr; /// Array of all interface vtbl implementations - in order - implemented /// by this class. /// Corresponds to the Interface instances needed to be output. std::vector interfacesWithVtbls; void addInterfaceVtbls(ClassDeclaration *cd); /// Builds the __ClassZ/__InterfaceZ initializer constant lazily. llvm::Constant *getClassInfoInit(); /// Builds the __vtblZ initializer constant lazily. llvm::Constant *getVtblInit(); /// Returns the vtbl for an interface implementation. llvm::GlobalVariable *getInterfaceVtblSymbol(BaseClass *b, size_t interfaces_index, bool define = false); /// Defines the vtbl for an interface implementation. llvm::Constant *getInterfaceVtblInit(BaseClass *b, size_t interfaces_index); /// Creates the __interfaceInfos symbol lazily. llvm::GlobalVariable *getInterfaceArraySymbol(); /// Create the Interface[] interfaces ClassInfo field initializer. llvm::Constant *getClassInfoInterfaces(); // FIXME: IrAggr::createInitializerConstant() needs full access friend class IrAggr; }; ////////////////////////////////////////////////////////////////////////////// IrAggr *getIrAggr(AggregateDeclaration *decl, bool create = false); IrStruct *getIrAggr(StructDeclaration *sd, bool create = false); IrClass *getIrAggr(ClassDeclaration *cd, bool create = false); bool isIrAggrCreated(AggregateDeclaration *decl); ldc-1.40.0-src/ir/irclass.cpp0000644000000000000000000006063714727557031014426 0ustar rootroot//===-- irclass.cpp -------------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "dmd/aggregate.h" #include "dmd/declaration.h" #include "dmd/errors.h" #include "dmd/hdrgen.h" // for parametersTypeToChars() #include "dmd/identifier.h" #include "dmd/mangle.h" #include "dmd/mtype.h" #include "dmd/target.h" #include "gen/abi/abi.h" #include "gen/arrays.h" #include "gen/classes.h" #include "gen/funcgenstate.h" #include "gen/functions.h" #include "gen/irstate.h" #include "gen/logger.h" #include "gen/llvmhelpers.h" #include "gen/mangling.h" #include "gen/passes/metadata.h" #include "gen/rttibuilder.h" #include "gen/runtime.h" #include "gen/tollvm.h" #include "gen/typinf.h" #include "ir/iraggr.h" #include "ir/irdsymbol.h" #include "ir/irfunction.h" #include "ir/irtypeclass.h" #include "llvm/ADT/SmallString.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/Support/Endian.h" #include "llvm/Support/MD5.h" #ifndef NDEBUG #include "llvm/Support/raw_ostream.h" #endif using namespace dmd; ////////////////////////////////////////////////////////////////////////////// IrClass::IrClass(ClassDeclaration *cd) : IrAggr(cd) { addInterfaceVtbls(cd); assert(interfacesWithVtbls.size() == getIrType(type)->isClass()->getNumInterfaceVtbls() && "inconsistent number of interface vtables in this class"); } void IrClass::addInterfaceVtbls(ClassDeclaration *cd) { // No interface vtables in Objective-C if (cd->classKind == ClassKind::objc) return; if (cd->baseClass && !cd->isInterfaceDeclaration()) { addInterfaceVtbls(cd->baseClass); } if (cd->vtblInterfaces) { for (auto bc : *cd->vtblInterfaces) { interfacesWithVtbls.push_back(bc); } } } ////////////////////////////////////////////////////////////////////////////// LLGlobalVariable *IrClass::getVtblSymbol(bool define) { if (!vtbl) { const auto irMangle = getIRMangledVTableSymbolName(aggrdecl); LLType *vtblTy = getIrType(type)->isClass()->getVtblType(); vtbl = declareGlobal(aggrdecl->loc, gIR->module, vtblTy, irMangle, /*isConstant=*/true, false, useDLLImport()); if (!define) define = defineOnDeclare(aggrdecl, /*isFunction=*/false); } if (define) { auto init = getVtblInit(); // might define vtbl if (!vtbl->hasInitializer()) defineGlobal(vtbl, init, aggrdecl); } return vtbl; } ////////////////////////////////////////////////////////////////////////////// LLGlobalVariable *IrClass::getClassInfoSymbol(bool define) { if (!typeInfo) { const auto irMangle = getIRMangledClassInfoSymbolName(aggrdecl); // The type is also ClassInfo for interfaces – the actual TypeInfo for them // is a TypeInfo_Interface instance that references __ClassZ in its "base" // member. IrTypeClass *tc = getIrType(getClassInfoType(), true)->isClass(); assert(tc && "invalid ClassInfo type"); // We need to keep the symbol mutable as the type is not declared as // immutable on the D side, and e.g. synchronized() can be used on the // implicit monitor. typeInfo = declareGlobal(aggrdecl->loc, gIR->module, tc->getMemoryLLType(), irMangle, /*isConstant=*/false, false, useDLLImport()); // Generate some metadata on this ClassInfo if it's for a class. if (!aggrdecl->isInterfaceDeclaration()) { // regular TypeInfo metadata emitTypeInfoMetadata(typeInfo, aggrdecl->type); // Gather information LLType *bodyType = getLLStructType(); bool hasDestructor = (aggrdecl->dtor != nullptr); // Construct the fields llvm::Metadata *mdVals[CD_NumFields]; mdVals[CD_BodyType] = llvm::ConstantAsMetadata::get(llvm::UndefValue::get(bodyType)); mdVals[CD_Finalize] = llvm::ConstantAsMetadata::get( LLConstantInt::get(LLType::getInt1Ty(gIR->context()), hasDestructor)); // Construct the metadata and insert it into the module. const auto metaname = getMetadataName(CD_PREFIX, typeInfo); llvm::NamedMDNode *node = gIR->module.getOrInsertNamedMetadata(metaname); node->addOperand(llvm::MDNode::get(gIR->context(), mdVals)); } if (!define) define = defineOnDeclare(aggrdecl, /*isFunction=*/false); } if (define) { auto init = getClassInfoInit(); if (!typeInfo->hasInitializer()) defineGlobal(typeInfo, init, aggrdecl); } return typeInfo; } ////////////////////////////////////////////////////////////////////////////// static Type *getInterfacesArrayType() { getClassInfoType(); // check declaration in object.d return Type::typeinfoclass->fields[3]->type; } LLGlobalVariable *IrClass::getInterfaceArraySymbol() { if (classInterfacesArray) { return classInterfacesArray; } ClassDeclaration *cd = aggrdecl->isClassDeclaration(); size_t n = getIrType(type)->isClass()->getNumInterfaceVtbls(); assert(n > 0 && "getting ClassInfo.interfaces storage symbol, but we " "don't implement any interfaces"); LLType *InterfaceTy = DtoType(getInterfacesArrayType()->nextOf()); // create Interface[N] const auto irMangle = getIRMangledInterfaceInfosSymbolName(cd); LLArrayType *array_type = llvm::ArrayType::get(InterfaceTy, n); classInterfacesArray = declareGlobal(cd->loc, gIR->module, array_type, irMangle, /*isConstant=*/true, false, false); return classInterfacesArray; } ////////////////////////////////////////////////////////////////////////////// LLConstant *IrClass::getVtblInit() { if (constVtbl) { return constVtbl; } IF_LOG Logger::println("Building vtbl initializer"); LOG_SCOPE; ClassDeclaration *cd = aggrdecl->isClassDeclaration(); assert(cd && "not class"); std::vector constants; constants.reserve(cd->vtbl.length); // start with the classinfo llvm::Constant *c; if (!cd->isCPPclass()) { if (!suppressTypeInfo()) { c = getClassInfoSymbol(); } else { // use null if there are no TypeInfos c = getNullPtr(); } constants.push_back(c); } // add virtual function pointers size_t n = cd->vtbl.length; for (size_t i = cd->vtblOffset(); i < n; i++) { Dsymbol *dsym = cd->vtbl[i]; assert(dsym && "null vtbl member"); FuncDeclaration *fd = dsym->isFuncDeclaration(); assert(fd && "vtbl entry not a function"); if (cd->isAbstract() || (fd->isAbstract() && !fd->fbody)) { c = getNullPtr(); } else { // If inferring return type and semantic3 has not been run, do it now. // This pops up in some other places in the frontend as well, however // it is probably a bug that it still occurs that late. if (fd->inferRetType() && !fd->type->nextOf()) { Logger::println("Running late functionSemantic to infer return type."); if (!functionSemantic(fd)) { if (fd->hasSemantic3Errors()) { Logger::println( "functionSemantic failed; using null for vtbl entry."); constants.push_back(getNullPtr()); continue; } error(fd->loc, "failed to infer return type of `%s` for vtbl initializer", fd->toPrettyChars()); fatal(); } } c = DtoCallee(fd); if (cd->isFuncHidden(fd) && !fd->isFuture()) { // fd is hidden from the view of this class. If fd overlaps with any // function in the vtbl[], issue error. for (size_t j = cd->vtblOffset(); j < n; j++) { if (j == i) { continue; } auto fd2 = cd->vtbl[j]->isFuncDeclaration(); if (!fd2->ident->equals(fd->ident)) { continue; } if (fd2->isFuture()) { continue; } if (leastAsSpecialized(fd, fd2, nullptr) != MATCH::nomatch || leastAsSpecialized(fd2, fd, nullptr) != MATCH::nomatch) { TypeFunction *tf = static_cast(fd->type); if (tf->ty == TY::Tfunction) { error(cd->loc, "%s `%s` use of `%s%s` is hidden by `%s`; use `alias %s = " "%s.%s;` to introduce base class overload set", cd->kind(), cd->toPrettyChars(), fd->toPrettyChars(), parametersTypeToChars(tf->parameterList), cd->toChars(), fd->toChars(), fd->parent->toChars(), fd->toChars()); } else { error(cd->loc, "%s `%s` use of `%s` is hidden by `%s`", cd->kind(), cd->toPrettyChars(), fd->toPrettyChars(), cd->toChars()); } fatal(); break; } } } } constants.push_back(c); } // build the constant array LLArrayType *vtblTy = LLArrayType::get(getOpaquePtrType(), constants.size()); constVtbl = LLConstantArray::get(vtblTy, constants); return constVtbl; } ////////////////////////////////////////////////////////////////////////////// /** * The following code should be kept in sync with upstream, dmd/toobj.d: * - genClassInfoForClass() * - genClassInfoForInterface() */ namespace { unsigned buildClassinfoFlags(ClassDeclaration *cd) { auto flags = ClassFlags::hasOffTi | ClassFlags::hasTypeInfo | ClassFlags::hasNameSig; if (cd->isInterfaceDeclaration()) { if (cd->isCOMinterface()) { flags |= ClassFlags::isCOMclass; } return flags; } if (cd->isCOMclass()) { flags |= ClassFlags::isCOMclass; } if (cd->isCPPclass()) { flags |= ClassFlags::isCPPclass; } flags |= ClassFlags::hasGetMembers; if (cd->ctor) { flags |= ClassFlags::hasCtor; } for (ClassDeclaration *pc = cd; pc; pc = pc->baseClass) { if (pc->dtor) { flags |= ClassFlags::hasDtor; break; } } if (cd->isAbstract()) { flags |= ClassFlags::isAbstract; } for (ClassDeclaration *pc = cd; pc; pc = pc->baseClass) { if (pc->members) { for (Dsymbol *sm : *pc->members) { // printf("sm = %s %s\n", sm->kind(), sm->toChars()); if (sm->hasPointers()) { return flags; } } } } flags |= ClassFlags::noPointers; return flags; } } LLConstant *IrClass::getClassInfoInit() { if (constTypeInfo) { return constTypeInfo; } auto cd = aggrdecl->isClassDeclaration(); IF_LOG Logger::println("Defining ClassInfo for: %s", cd->toChars()); LOG_SCOPE; Type *const cinfoType = getClassInfoType(); // check declaration in object.d ClassDeclaration *const cinfo = Type::typeinfoclass; if (cinfo->fields.length != 14) { error(Loc(), "Unexpected number of fields in `object.ClassInfo`; " "druntime version does not match compiler (see -v)"); fatal(); } const bool isInterface = cd->isInterfaceDeclaration(); RTTIBuilder b(cinfoType); // adapted from original dmd code // byte[] m_init if (isInterface) { b.push_null_void_array(); } else { b.push_void_array(cd->size(Loc()), getInitSymbol()); } // string name const char *name = cd->ident->toChars(); if (strncmp(name, "TypeInfo_", 9) != 0) { name = cd->toPrettyChars(/*QualifyTypes=*/true); } b.push_string(name); // void*[] vtbl if (isInterface) { b.push_array(0, getNullPtr()); } else { b.push_array(cd->vtbl.length, getVtblSymbol()); } // Interface[] interfaces b.push(getClassInfoInterfaces()); // TypeInfo_Class base assert(!isInterface || !cd->baseClass); if (cd->baseClass) { DtoResolveClass(cd->baseClass); b.push(getIrAggr(cd->baseClass)->getClassInfoSymbol()); } else { b.push_null(cinfoType); } // void* destructor assert(!isInterface || !cd->tidtor); b.push_funcptr(cd->tidtor); // void function(Object) classInvariant assert(!isInterface || !cd->inv); b.push_funcptr(cd->inv); // ClassFlags m_flags const auto flags = buildClassinfoFlags(cd); b.push(DtoConstUshort(flags)); // ushort depth { uint16_t depth = 0; for (auto pc = cd; pc; pc = pc->baseClass) ++depth; // distance to Object b.push(DtoConstUshort(depth)); } // void* deallocator b.push_null_vp(); // OffsetTypeInfo[] m_offTi VarDeclaration *offTiVar = cinfo->fields[10]; b.push_null(offTiVar->type); // void function(Object) defaultConstructor CtorDeclaration *defConstructor = cd->defaultCtor; if (defConstructor && (defConstructor->storage_class & STCdisable)) { defConstructor = nullptr; } assert(!isInterface || !defConstructor); b.push_funcptr(defConstructor); // immutable(void)* m_RTInfo if (cd->getRTInfo) { b.push(toConstElem(cd->getRTInfo, gIR)); } else if (isInterface || (flags & ClassFlags::noPointers)) { b.push_size_as_vp(0); // no pointers } else { b.push_size_as_vp(1); // has pointers } // uint[4] nameSig { llvm::MD5 hasher; hasher.update(name); llvm::MD5::MD5Result result; hasher.final(result); LLConstant *dwords[4]; for (int i = 0; i < 4; ++i) { // make sure the 4 dwords don't depend on the endianness of the *host* dwords[i] = DtoConstUint(llvm::support::endian::read32le(&result[4 * i])); } auto t = llvm::ArrayType::get(dwords[0]->getType(), 4); b.push(LLConstantArray::get(t, dwords)); } // build the initializer LLType *initType = getClassInfoSymbol()->getValueType(); constTypeInfo = b.get_constant(isaStruct(initType)); return constTypeInfo; } ////////////////////////////////////////////////////////////////////////////// llvm::GlobalVariable *IrClass::getInterfaceVtblSymbol(BaseClass *b, size_t interfaces_index, bool define) { LLGlobalVariable *gvar = nullptr; const auto it = interfaceVtblMap.find({b->sym, interfaces_index}); if (it != interfaceVtblMap.end()) { gvar = it->second; } else { llvm::Type *vtblType = LLArrayType::get(getOpaquePtrType(), b->sym->vtbl.length); // Thunk prefix char thunkPrefix[16]; int thunkLen = snprintf(thunkPrefix, 16, "Thn%d_", b->offset); char thunkPrefixLen[16]; snprintf(thunkPrefixLen, 16, "%d", thunkLen); OutBuffer mangledName; mangledName.writestring("_D"); mangleToBuffer(aggrdecl, mangledName); mangledName.writestring("11__interface"); mangleToBuffer(b->sym, mangledName); mangledName.writestring(thunkPrefixLen); mangledName.writestring(thunkPrefix); mangledName.writestring("6__vtblZ"); const auto irMangle = getIRMangledVarName(mangledName.peekChars(), LINK::d); gvar = declareGlobal(aggrdecl->loc, gIR->module, vtblType, irMangle, /*isConstant=*/true, false, false); // insert into the vtbl map interfaceVtblMap.insert({{b->sym, interfaces_index}, gvar}); if (!define) define = defineOnDeclare(aggrdecl, /*isFunction=*/false); } if (define && !gvar->hasInitializer()) { auto init = getInterfaceVtblInit(b, interfaces_index); defineGlobal(gvar, init, aggrdecl); } return gvar; } LLConstant *IrClass::getInterfaceVtblInit(BaseClass *b, size_t interfaces_index) { IF_LOG Logger::println( "Defining vtbl for implementation of interface %s in class %s", b->sym->toPrettyChars(), aggrdecl->toPrettyChars()); LOG_SCOPE; ClassDeclaration *cd = aggrdecl->isClassDeclaration(); assert(cd && "not a class aggregate"); FuncDeclarations vtbl_array; const bool new_instance = b->sym == cd; b->fillVtbl(cd, &vtbl_array, new_instance); std::vector constants; constants.reserve(vtbl_array.length); char thunkPrefix[16]; snprintf(thunkPrefix, 16, "Thn%d_", b->offset); if (!b->sym->isCPPinterface()) { // skip interface info for CPP interfaces if (!suppressTypeInfo()) { // index into the interfaces array llvm::Constant *idxs[2] = {DtoConstSize_t(0), DtoConstSize_t(interfaces_index)}; llvm::GlobalVariable *interfaceInfosZ = getInterfaceArraySymbol(); llvm::Constant *c = llvm::ConstantExpr::getGetElementPtr( interfaceInfosZ->getValueType(), interfaceInfosZ, idxs, true); constants.push_back(c); } else { // use null if there are no TypeInfos constants.push_back(getNullPtr()); } } // add virtual function pointers size_t n = vtbl_array.length; for (size_t i = b->sym->vtblOffset(); i < n; i++) { FuncDeclaration *fd = vtbl_array[i]; if (!fd) { // FIXME // why is this null? // happens for mini/s.d constants.push_back(getNullPtr()); continue; } assert((!fd->isAbstract() || fd->fbody) && "null symbol in interface implementation vtable"); DtoResolveFunction(fd); assert(isIrFuncCreated(fd) && "invalid vtbl function"); IrFunction *irFunc = getIrFunc(fd); assert(irFunc->irFty.arg_this); int thunkOffset = b->offset; if (fd->interfaceVirtual) thunkOffset -= fd->interfaceVirtual->offset; if (thunkOffset == 0) { constants.push_back(irFunc->getLLVMCallee()); continue; } // Create the thunk function if it does not already exist in this // module. OutBuffer nameBuf; const auto mangledTargetName = mangleExact(fd); nameBuf.write(mangledTargetName, 2); nameBuf.writestring(thunkPrefix); nameBuf.writestring(mangledTargetName + 2); const auto thunkIRMangle = getIRMangledFuncName(nameBuf.peekChars(), fd->resolvedLinkage()); llvm::Function *thunk = gIR->module.getFunction(thunkIRMangle); if (!thunk) { const LinkageWithCOMDAT lwc(LLGlobalValue::LinkOnceODRLinkage, needsCOMDAT()); const auto callee = irFunc->getLLVMCallee(); thunk = LLFunction::Create( callee->getFunctionType(), lwc.first, thunkIRMangle, &gIR->module); setLinkage(lwc, thunk); thunk->copyAttributesFrom(callee); // Thunks themselves don't have an identity, only the target function has. thunk->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); // thunks don't need exception handling themselves thunk->setPersonalityFn(nullptr); // It is necessary to add debug information to the thunk in case it is // subject to inlining. See https://llvm.org/bugs/show_bug.cgi?id=26833 IF_LOG Logger::println("Doing function body for thunk to: %s", fd->toChars()); // Create a dummy FuncDeclaration with enough information to satisfy the // DIBuilder FuncDeclaration *thunkFd = reinterpret_cast( memcpy(new char[sizeof(FuncDeclaration)], (void *)fd, sizeof(FuncDeclaration))); thunkFd->ir = new IrDsymbol(); auto thunkFunc = getIrFunc(thunkFd, true); // create the IrFunction thunkFunc->setLLVMFunc(thunk); thunkFunc->type = irFunc->type; gIR->funcGenStates.emplace_back(new FuncGenState(*thunkFunc, *gIR)); // debug info thunkFunc->diSubprogram = nullptr; thunkFunc->diSubprogram = gIR->DBuilder.EmitThunk(thunk, thunkFd); // create entry and end blocks llvm::BasicBlock *beginbb = llvm::BasicBlock::Create(gIR->context(), "", thunk); // set up the IRBuilder scope for the thunk const auto savedIRBuilderScope = gIR->setInsertPoint(beginbb); gIR->DBuilder.EmitFuncStart(thunkFd); // Copy the function parameters, so later we can pass them to the // real function and set their names from the original function (the // latter being just for IR readablilty). std::vector args; llvm::Function::arg_iterator thunkArg = thunk->arg_begin(); llvm::Function::arg_iterator origArg = callee->arg_begin(); for (; thunkArg != thunk->arg_end(); ++thunkArg, ++origArg) { thunkArg->setName(origArg->getName()); args.push_back(&(*thunkArg)); } // cast 'this' to Object const int thisArgIndex = (!irFunc->irFty.arg_sret || gABI->passThisBeforeSret(irFunc->type)) ? 0 : 1; LLValue *&thisArg = args[thisArgIndex]; thisArg = DtoGEP1(getI8Type(), thisArg, DtoConstInt(-thunkOffset)); // all calls that might be subject to inlining into a caller with debug // info should have debug info, too gIR->DBuilder.EmitStopPoint(fd->loc); // call the real vtbl function. llvm::CallInst *call = gIR->ir->CreateCall(callee, args); call->setCallingConv(callee->getCallingConv()); call->setAttributes(callee->getAttributes()); call->setTailCallKind(callee->isVarArg() ? llvm::CallInst::TCK_MustTail : llvm::CallInst::TCK_Tail); // return from the thunk if (thunk->getReturnType() == LLType::getVoidTy(gIR->context())) { llvm::ReturnInst::Create(gIR->context(), beginbb); } else { llvm::ReturnInst::Create(gIR->context(), call, beginbb); } gIR->funcGenStates.pop_back(); } constants.push_back(thunk); } // build the vtbl constant llvm::Constant *vtbl_constant = LLConstantArray::get( LLArrayType::get(getOpaquePtrType(), constants.size()), constants); return vtbl_constant; } void IrClass::defineInterfaceVtbls() { const size_t n = interfacesWithVtbls.size(); assert(n == getIrType(type)->isClass()->getNumInterfaceVtbls() && "inconsistent number of interface vtables in this class"); for (size_t i = 0; i < n; ++i) { auto baseClass = interfacesWithVtbls[i]; getInterfaceVtblSymbol(baseClass, i, /*define=*/true); } } ////////////////////////////////////////////////////////////////////////////// LLConstant *IrClass::getClassInfoInterfaces() { IF_LOG Logger::println("Building ClassInfo.interfaces"); LOG_SCOPE; ClassDeclaration *cd = aggrdecl->isClassDeclaration(); assert(cd); size_t n = interfacesWithVtbls.size(); assert(getIrType(type)->isClass()->getNumInterfaceVtbls() == n && "inconsistent number of interface vtables in this class"); Type *interfacesArrayType = getInterfacesArrayType(); if (n == 0) { return getNullValue(DtoType(interfacesArrayType)); } // Build array of: // // struct Interface // { // ClassInfo classinfo; // void*[] vtbl; // ptrdiff_t offset; // } LLSmallVector constants; constants.reserve(cd->vtblInterfaces->length); LLStructType *interface_type = isaStruct(DtoType(interfacesArrayType->nextOf())); assert(interface_type); for (size_t i = 0; i < n; ++i) { BaseClass *it = interfacesWithVtbls[i]; IF_LOG Logger::println("Adding interface %s", it->sym->toPrettyChars()); IrClass *irinter = getIrAggr(it->sym); assert(irinter && "interface has null IrStruct"); IrTypeClass *itc = getIrType(irinter->type)->isClass(); assert(itc && "null interface IrTypeClass"); // classinfo LLConstant *ci = irinter->getClassInfoSymbol(); // vtbl LLConstant *vtb; // interface get a null if (cd->isInterfaceDeclaration()) { vtb = DtoConstSlice(DtoConstSize_t(0), getNullPtr()); } else { vtb = getInterfaceVtblSymbol(it, i); auto vtblSize = itc->getVtblType()->getNumContainedTypes(); vtb = DtoConstSlice(DtoConstSize_t(vtblSize), vtb); } // offset LLConstant *off = DtoConstSize_t(it->offset); // create Interface struct LLConstant *inits[3] = {ci, vtb, off}; LLConstant *entry = LLConstantStruct::get(interface_type, inits); constants.push_back(entry); } // create Interface[N] LLArrayType *array_type = llvm::ArrayType::get(interface_type, n); // create and apply initializer LLConstant *arr = LLConstantArray::get(array_type, constants); auto ciarr = getInterfaceArraySymbol(); defineGlobal(ciarr, arr, cd); // return null, only baseclass provide interfaces if (cd->vtblInterfaces->length == 0) { return getNullValue(DtoType(interfacesArrayType)); } // only the interface explicitly implemented by this class // (not super classes) should show in ClassInfo LLConstant *idxs[2] = {DtoConstSize_t(0), DtoConstSize_t(n - cd->vtblInterfaces->length)}; LLConstant *ptr = llvm::ConstantExpr::getGetElementPtr(ciarr->getValueType(), ciarr, idxs, true); // return as a slice return DtoConstSlice(DtoConstSize_t(cd->vtblInterfaces->length), ptr); } ldc-1.40.0-src/ir/irfuncty.cpp0000644000000000000000000001045014727557031014615 0ustar rootroot//===-- irfuncty.cpp ------------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "ir/irfuncty.h" #include "dmd/mtype.h" #include "gen/abi/abi.h" #include "gen/dvalue.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" #include "gen/tollvm.h" using namespace dmd; IrFuncTyArg::IrFuncTyArg(Type *t, bool bref) : type(t), ltype(t != Type::tvoid && bref ? DtoType(pointerTo(t)) : DtoType(t)), attrs(getGlobalContext()), byref(bref) { mem.addRange(&type, sizeof(type)); } IrFuncTyArg::IrFuncTyArg(Type *t, bool bref, llvm::AttrBuilder a) : type(t), ltype(t != Type::tvoid && bref ? DtoType(pointerTo(t)) : DtoType(t)), attrs(std::move(a)), byref(bref) { mem.addRange(&type, sizeof(type)); } IrFuncTyArg::~IrFuncTyArg() { mem.removeRange(&type); } bool IrFuncTyArg::isInReg() const { return attrs.contains(LLAttribute::InReg); } bool IrFuncTyArg::isSRet() const { return attrs.contains(LLAttribute::StructRet); } bool IrFuncTyArg::isByVal() const { return attrs.contains(LLAttribute::ByVal); } llvm::Value *IrFuncTy::putRet(DValue *dval) { assert(!arg_sret); if (ret->rewrite) { Logger::println("Rewrite: putRet"); LOG_SCOPE // Choosing isLValueExp=true here is a fail-safe conservative choice. // Most rewrites don't care, and those which do are usually not applied to // the return value (as more complex types are returned via sret and so not // rewritten at all). return ret->rewrite->put(dval, /*isLValueExp=*/true, /*isLastArgExp=*/true); } if (ret->byref || DtoIsInMemoryOnly(dval->type)) return DtoLVal(dval); return DtoRVal(dval); } llvm::Value *IrFuncTy::getRetRVal(Type *dty, LLValue *val) { assert(!arg_sret); if (ret->rewrite) { Logger::println("Rewrite: getRetRVal"); LOG_SCOPE return ret->rewrite->getRVal(dty, val); } return val; } llvm::Value *IrFuncTy::getRetLVal(Type *dty, LLValue *val) { assert(!arg_sret); if (ret->rewrite) { Logger::println("Rewrite: getRetLVal"); LOG_SCOPE return ret->rewrite->getLVal(dty, val); } return DtoAllocaDump(val, dty); } llvm::Value *IrFuncTy::putArg(const IrFuncTyArg &arg, DValue *dval, bool isLValueExp, bool isLastArgExp) { if (arg.rewrite) { Logger::println("Rewrite: putArg (%s expression%s)", isLValueExp ? "lvalue" : "rvalue", isLastArgExp ? ", last argument" : ""); LOG_SCOPE return arg.rewrite->put(dval, isLValueExp, isLastArgExp); } if (arg.byref || DtoIsInMemoryOnly(dval->type)) { if (isLValueExp && !isLastArgExp && arg.isByVal()) { // copy to avoid visibility of potential side effects of later argument // expressions return DtoAllocaDump(dval, ".lval_copy_for_byval"); } return DtoLVal(dval); } return DtoRVal(dval); } LLValue *IrFuncTy::getParamLVal(Type *dty, size_t idx, LLValue *val) { assert(idx < args.size() && "invalid getParam"); if (args[idx]->rewrite) { Logger::println("Rewrite: getParamLVal"); LOG_SCOPE return args[idx]->rewrite->getLVal(dty, val); } return DtoAllocaDump(val, dty); } AttrSet IrFuncTy::getParamAttrs(bool passThisBeforeSret) { AttrSet newAttrs; if (ret) { newAttrs.addToReturn(ret->attrs); } int idx = 0; // handle implicit args #define ADD_PA(X) \ if (X) { \ newAttrs.addToParam(idx, (X)->attrs); \ idx++; \ } if (arg_sret && arg_this && passThisBeforeSret) { ADD_PA(arg_this) ADD_PA(arg_sret) } else { ADD_PA(arg_sret) ADD_PA(arg_this) } ADD_PA(arg_nest) ADD_PA(arg_objcSelector) ADD_PA(arg_arguments) #undef ADD_PA // Set attributes on the explicit parameters. const size_t n = args.size(); for (size_t k = 0; k < n; k++) { newAttrs.addToParam(idx + k, args[k]->attrs); } return newAttrs; } ldc-1.40.0-src/ir/irdsymbol.cpp0000644000000000000000000000320614727557031014757 0ustar rootroot//===-- irdsymbol.cpp -----------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "gen/llvm.h" #include "gen/logger.h" #include "ir/irdsymbol.h" #include "ir/irvar.h" // Callbacks for constructing/destructing Dsymbol.ir member. void* newIrDsymbol() { return static_cast(new IrDsymbol()); } void deleteIrDsymbol(void* sym) { delete static_cast(sym); } std::vector IrDsymbol::list; void IrDsymbol::resetAll() { Logger::println("resetting %llu Dsymbols", static_cast(list.size())); for (auto s : list) { s->reset(); } } IrDsymbol::IrDsymbol() : irData(nullptr) { list.push_back(this); } IrDsymbol::IrDsymbol(const IrDsymbol &s) { list.push_back(this); irData = s.irData; m_type = s.m_type; m_state = s.m_state; } IrDsymbol::~IrDsymbol() { if (this == list.back()) { list.pop_back(); return; } auto it = std::find(list.rbegin(), list.rend(), this).base(); // base() returns the iterator _after_ the found position list.erase(--it); } void IrDsymbol::reset() { irData = nullptr; m_type = Type::NotSet; m_state = State::Initial; } void IrDsymbol::setResolved() { if (m_state < Resolved) { m_state = Resolved; } } void IrDsymbol::setDeclared() { if (m_state < Declared) { m_state = Declared; } } void IrDsymbol::setDefined() { if (m_state < Defined) { m_state = Defined; } } ldc-1.40.0-src/docs/0000755000000000000000000000000014727557031012564 5ustar rootrootldc-1.40.0-src/docs/dynamic_compile_bind.md0000644000000000000000000000352114727557031017237 0ustar rootroot# Dynamic compile bind ## D part * bind function returns `BindPtr` object which is reference counted internally * `BindPtr` have `opCall` and `toDelegate` methods to call directly or create D delegate * On creation `BindPtr` call `void registerBindPayload(void* handle, void* originalFunc, void* exampleFunc, const ParamSlice* params, size_t paramsSize)` function * `handle` - pointer to pointer to function, which uniquely identifies bind object, actual function pointer to generated code will be written here during `compileDynamicCode` call. * `originalFunc` - pointer to `original` function, this is special function, generated inside `bind`, which just forwards parameter to user function or delegate, this function always have `@dynamicCompileEmit` attribute, so jit runtime will fins it even if user function wasn't marked `@dynamicCompile` * `exampleFunc` - special function with parameters matched to original user function, runtime will extracts parameters types from it, never called * `params` - list of slices to bind parameters, will be null for placeholders * `paramsSize` - items count in `params` * On destruction `BindPtr` call `void unregisterBindPayload(void* handle);` * `handle` - same handle as passed in `registerBindPayload` previously ## Runtime part * `registerBindPayload` add handle to internal list * During `compileDynamicCode` * `generateBind` - Generate new function for each bind handle * Parse each bind parameter into llvm constant using existing `parseInitializer` (previously used for `@dynamicCompileConst`) * If parameter is function pointer from another bind handle, replace with direct reference to that function * Generate call to original function (this call will be inlined) * Generate and optimize module as usual * `applyBind` - update handles to generated code ldc-1.40.0-src/docs/dynamic_compile.md0000644000000000000000000001133114727557031016241 0ustar rootroot# Dynamic compilation facilities design overview `@dynamicCompile` attribute can be applied to any function and non thread-local variable (including lambdas and class virtual methods) ## Compiler part: * In DtoDeclareFunction read function uda's, if there is a `@dynamicCompile` attribute set `IrFunction::dynamicCompile = true` * If `IrFunction::dynamicCompile` is `true`, call `declareDynamicCompiledFunction` for this function which will create thunk function and save it to `IrFunction::rtCompileFunc` * `IrFunction::rtCompileFunc` has same signature and attributes as the original function and any attemt to call or take address of the this function will be redirected to rtCompileFunc in DtoCallee * Call `defineDynamicCompiledFunction` for any function which have `dynamicCompile == true` * In `defineDynamicCompiledFunction` create global variable which will hold pointer to jitted function and create thunk function body which just calls function pointer from this global var * (TODO: It is possible to get rid of this global var, can hotpatch code in runtime with compiled code address, does it worth it? How to do this in llvm?) * For each `@dynamicCompileConst` variable calls `addDynamicCompiledVar` * In `writeAndFreeLLModule` call function `generateBitcodeForDynamicCompile` for each module * In `generateBitcodeForDynamicCompile` first recursively search functions calls inside functions body, starting with `@dynamicCompile` functions. * After that we have two lists of functions: * Directly marked by user `@dynamicCompile` * Not marked by user but used by `@dynamicCompile` functions directly or indirectly and with body available * Both lists are subject to dynamic compilation. For the first list dynamic version will be always used. For the second static version will be used when called from static code and dynamic version when called from dynamic code. * Search for thread local valiables access in jitted code (required for TLS workaround) * Also we now have a list of symbols required by dynamic functions (variables and external functions) * (TODO: Option for limit recursion depth?) * Clone module via `llvm::CloneModule` copying functions marked for dynamic compilations (directly or indirectly) and `@dynamicCompileConst` variables * Remove `target-cpu` and `target-features` attributes from all jitted functions except those user set explicitly (these attributes will be set back before jit to host) * Apply thread local storage workaround to jitted functions if enabled (`replaceDynamicThreadLocals` function, controlled via `-runtime-compile-tls-workaround` command line switch, default on) * For each thread local variable accessed in jit code generate static accessor function which returns variable address. * Replace direct access to thread local variables with call to accessor function * Replace calls to jit thunks in jitted functions with direct calls (`fixRtModule`) * Create module data structures and bitcode array required for dynamic compilation and add module constructor which registers `RtComileModuleList` into global linked list (`setupModuleBitcodeData`) ``` /// Symbol required by dynamic code struct RtCompileSymList { const char *name; // Symbol name void *sym; // Symbol address }; /// Dynamic function struct RtCompileFuncList { const char *name; // Function name void *func; // Thunk global var address to store compiled function pointer }; /// @dynamicCompileConst variable struct RtCompileVarList { const char *name; const void *init; }; /// Runtime compiled functions info per module /// organized in linked list /// starting with `DynamicCompileModulesHeadName` (defined in runtime) struct RtComileModuleList { RtComileModuleList *next; // Next module const void *irData; // Ir data int32 irDataSize; // Ir data size in bytes RtCompileFuncList *funcList; // Dynamic functions int32 funcListSize; // Dynamic functions count RtCompileSymList *symList; // Symbols int32 symListSize; // Symbols count }; ``` ## Runtime part: * Defines runtime compiled modules list head * Defines `rtCompileProcessImplSo` function which do actual compilation * `rtCompileProcessImplSo` for each module in list: * Parses ir data into llvm module * Set `target-cpu` and `target-features` attributes to host for all function which don't have it * For each `@dynamicCompileConst` variable * Parse variable value from host process and create initializer * Set variable const so llvm can optimize it * Merge all modules into one via `llvm::Linker::linkModules` * Optimizes resulting module (TODO: optimization setup taken from LDC but not all options available to compiler available in jit) * Compile module, resolve functions using RtComileModuleList data and update thunk vars ldc-1.40.0-src/.clang-format0000644000000000000000000000010314727557031014201 0ustar rootrootBasedOnStyle: LLVM SortIncludes: false FixNamespaceComments: false ldc-1.40.0-src/utils/0000755000000000000000000000000014727557031012774 5ustar rootrootldc-1.40.0-src/utils/split-file.cpp0000644000000000000000000001702214727557031015552 0ustar rootroot//===- split-file.cpp - Input splitting utility ---------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Split input into multipe parts separated by regex '^(.|//)--- ' and extract // the specified part. // //===----------------------------------------------------------------------===// // // The LDC version of this code is based on LLVM 16 and modified such that it // compiles with earlier LLVM versions. // //===----------------------------------------------------------------------===// #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/WithColor.h" #include #include using namespace llvm; static cl::OptionCategory cat("split-file Options"); static cl::opt input(cl::Positional, cl::desc("filename"), cl::cat(cat)); static cl::opt output(cl::Positional, cl::desc("directory"), cl::value_desc("directory"), cl::cat(cat)); static cl::opt leadingLines("leading-lines", cl::desc("Preserve line numbers"), cl::cat(cat)); static cl::opt noLeadingLines("no-leading-lines", cl::desc("Don't preserve line numbers (default)"), cl::cat(cat)); static StringRef toolName; static int errorCount; [[noreturn]] static void fatal(StringRef filename, const Twine &message) { if (filename.empty()) WithColor::error(errs(), toolName) << message << '\n'; else WithColor::error(errs(), toolName) << filename << ": " << message << '\n'; exit(1); } static void error(StringRef filename, int64_t line, const Twine &message) { ++errorCount; errs() << filename << ':' << line << ": "; WithColor::error(errs()) << message << '\n'; } namespace { struct Part { const char *begin = nullptr; const char *end = nullptr; int64_t leadingLines = 0; }; /// Checks whether character \p C is whitespace in the "C" locale. /// /// Locale-independent version of the C standard library isspace. inline bool isSpace_ldc(char C) { return C == ' ' || C == '\f' || C == '\n' || C == '\r' || C == '\t' || C == '\v'; } /// Detect the line ending style of the string. /// /// If the string contains a line ending, return the line ending character /// sequence that is detected. Otherwise return '\n' for unix line endings. /// /// \return - The line ending character sequence. [[nodiscard]] StringRef detectEOL(StringRef const &str) { auto Length = str.size(); auto Data = str.data(); size_t Pos = str.find('\r'); if (Pos == str.npos) { // If there is no carriage return, assume unix return "\n"; } if (Pos + 1 < Length && Data[Pos + 1] == '\n') return "\r\n"; // Windows if (Pos > 0 && Data[Pos - 1] == '\n') return "\n\r"; // You monster! return "\r"; // Classic Mac } } // namespace static int handle(MemoryBuffer &inputBuf, StringRef input) { #if LDC_LLVM_VER >= 1800 #define startswith starts_with #endif DenseMap partToBegin; StringRef lastPart, separator; StringRef EOL = detectEOL(inputBuf.getBuffer()); for (line_iterator i(inputBuf, /*SkipBlanks=*/false, '\0'); !i.is_at_eof();) { const int64_t lineNo = i.line_number(); const StringRef line = *i++; const size_t markerLen = line.startswith("//") ? 6 : 5; if (!(line.size() >= markerLen && line.substr(markerLen - 4).startswith("--- "))) continue; separator = line.substr(0, markerLen); const StringRef partName = line.substr(markerLen); if (partName.empty()) { error(input, lineNo, "empty part name"); continue; } if (isSpace_ldc(partName.front()) || isSpace_ldc(partName.back())) { error(input, lineNo, "part name cannot have leading or trailing space"); continue; } auto res = partToBegin.try_emplace(partName); if (!res.second) { error(input, lineNo, "'" + separator + partName + "' occurs more than once"); continue; } if (!lastPart.empty()) partToBegin[lastPart].end = line.data(); Part &cur = res.first->second; if (!i.is_at_eof()) cur.begin = i->data(); // If --leading-lines is specified, numEmptyLines is 0. Append newlines so // that the extracted part preserves line numbers. cur.leadingLines = leadingLines ? i.line_number() - 1 : 0; lastPart = partName; } if (lastPart.empty()) fatal(input, "no part separator was found"); if (errorCount) return 1; partToBegin[lastPart].end = inputBuf.getBufferEnd(); std::vector> outputFiles; SmallString<256> partPath; for (auto &keyValue : partToBegin) { partPath.clear(); sys::path::append(partPath, output, keyValue.first); std::error_code ec = sys::fs::create_directories(sys::path::parent_path(partPath)); if (ec) fatal(input, ec.message()); using namespace std; auto f = make_unique(partPath.str(), ec, llvm::sys::fs::OF_None); if (!f) fatal(input, ec.message()); Part &part = keyValue.second; for (int64_t i = 0; i != part.leadingLines; ++i) (*f).os() << EOL; if (part.begin) (*f).os().write(part.begin, part.end - part.begin); outputFiles.push_back(std::move(f)); } for (std::unique_ptr &outputFile : outputFiles) outputFile->keep(); return 0; #if LDC_LLVM_VER >= 1800 #undef startswith #endif } int main(int argc, const char **argv) { toolName = sys::path::stem(argv[0]); cl::HideUnrelatedOptions({&cat}); cl::ParseCommandLineOptions( argc, argv, "Split input into multiple parts separated by regex '^(.|//)--- ' and " "extract the part specified by '^(.|//)--- '\n", nullptr, /*EnvVar=*/nullptr, /*LongOptionsUseDoubleDash=*/true); if (input.empty()) fatal("", "input filename is not specified"); if (output.empty()) fatal("", "output directory is not specified"); ErrorOr> bufferOrErr = MemoryBuffer::getFileOrSTDIN(input); if (std::error_code ec = bufferOrErr.getError()) fatal(input, ec.message()); // Delete output if it is a file or an empty directory, so that we can create // a directory. sys::fs::file_status status; if (std::error_code ec = sys::fs::status(output, status)) if (ec.value() != static_cast(std::errc::no_such_file_or_directory)) fatal(output, ec.message()); if (status.type() != sys::fs::file_type::file_not_found && status.type() != sys::fs::file_type::directory_file && status.type() != sys::fs::file_type::regular_file) fatal(output, "output cannot be a special file"); if (std::error_code ec = sys::fs::remove(output, /*IgnoreNonExisting=*/true)) if (ec.value() != static_cast(std::errc::directory_not_empty) && ec.value() != static_cast(std::errc::file_exists)) fatal(output, ec.message()); return handle(**bufferOrErr, input); } ldc-1.40.0-src/utils/FileCheck-16.cpp0000644000000000000000000011002414727557031015537 0ustar rootroot//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ldc-1.40.0-src/utils/FileCheck-18.cpp0000644000000000000000000010717114727557031015552 0ustar rootroot//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (const FileCheckDiag &Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (const InputAnnotation &M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ldc-1.40.0-src/utils/CMakeLists.txt0000644000000000000000000000713214727557031015537 0ustar rootroot# Build gen_gccbuiltins tools to generate D module from LLVM's list of # GCC-style intrinsics. if(CMAKE_CROSSCOMPILING) message(STATUS "NOT building gen_gccbuiltins utility when cross-compiling (you can use host LDC's ldc/gccbuiltins_*.di files)") return() endif() # The LLVM_INCLUDE_DIR definition is not always set, e.g. on Windows. # strip off anything but the first path string(REGEX REPLACE ";.*$" "" LLVM_INC_DIR "${LLVM_INCLUDE_DIRS}") find_path(LLVM_INTRINSIC_TD_PATH "Intrinsics.td" PATHS ${LLVM_INC_DIR}/llvm ${LLVM_INC_DIR}/llvm/IR NO_DEFAULT_PATH) if (${LLVM_INTRINSIC_TD_PATH} STREQUAL "LLVM_INTRINSIC_TD_PATH-NOTFOUND") message(SEND_ERROR "File Intrinsics.td not found") else() string(REGEX REPLACE "/llvm(/IR)?$" "" LLVM_INTRINSIC_TD_PATH ${LLVM_INTRINSIC_TD_PATH}) message(STATUS "Using path for Intrinsics.td: ${LLVM_INTRINSIC_TD_PATH}") endif() add_executable(gen_gccbuiltins gen_gccbuiltins.cpp) set_target_properties( gen_gccbuiltins PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS} \"-DLLVM_INTRINSIC_TD_PATH=R\\\"(${LLVM_INTRINSIC_TD_PATH})\\\"\"" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) target_link_libraries(gen_gccbuiltins ${LLVM_TABLEGEN_LIBRARY} ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(gen_gccbuiltins dl) endif() if ((TARGET FileCheck) OR (EXISTS ${LLVM_ROOT_DIR}/bin/FileCheck)) # already provided by LLVM message(STATUS "Skip building FileCheck, it is already provided by LLVM") else() # Build FileCheck for testing (build source version depending on LLVM version) set(FILECHECK_SRC FileCheck-${LLVM_VERSION_MAJOR}.cpp) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${FILECHECK_SRC}) add_executable(FileCheck ${FILECHECK_SRC}) set_target_properties( FileCheck PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) target_link_libraries(FileCheck LLVMFileCheck) target_link_libraries(FileCheck ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) message(STATUS "Building FileCheck from LDC source tree") else() message(STATUS "Skip building FileCheck (source not found), assuming it can be found in LLVM bin directory") endif() endif() if ((TARGET not) OR (EXISTS ${LLVM_ROOT_DIR}/bin/not)) # already provided by LLVM else() # Build `not` for testing add_executable(not not.cpp) set_target_properties( not PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) target_link_libraries(not ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) endif() if ((TARGET split-file) OR (EXISTS ${LLVM_ROOT_DIR}/bin/split-file)) # already provided by LLVM message(STATUS "Skip building split-file, it is already provided by LLVM") else() # Build split-file for testing set(SPLITFILE_SRC split-file.cpp) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${SPLITFILE_SRC}) add_executable(split-file ${SPLITFILE_SRC}) set_target_properties( split-file PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) target_link_libraries(split-file ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) message(STATUS "Building split-file from LDC source tree") else() message(STATUS "Skip building split-file (source not found), assuming it can be found in LLVM bin directory") endif() endif() ldc-1.40.0-src/utils/FileCheck-15.cpp0000644000000000000000000011002414727557031015536 0ustar rootroot//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ldc-1.40.0-src/utils/FileCheck-19.cpp0000644000000000000000000010660114727557031015550 0ustar rootroot//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (const FileCheckDiag &Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLines, LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (const InputAnnotation &M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLines, LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *llvm::max_element(DumpInputs); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *llvm::max_element(DumpInputFilters); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *llvm::max_element(DumpInputContexts); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ldc-1.40.0-src/utils/not.cpp0000644000000000000000000000352314727557031014303 0ustar rootroot//===- not.cpp - The 'not' testing tool -----------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // Usage: // not cmd // Will return true if cmd doesn't crash and returns false. // not --crash cmd // Will return true if cmd crashes (e.g. for testing crash reporting). #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; int main(int argc, const char **argv) { bool ExpectCrash = false; ++argv; --argc; if (argc > 0 && StringRef(argv[0]) == "--crash") { ++argv; --argc; ExpectCrash = true; } if (argc == 0) return 1; auto Program = sys::findProgramByName(argv[0]); if (!Program) { errs() << "Error: Unable to find `" << argv[0] << "' in PATH: " << Program.getError().message() << "\n"; return 1; } std::vector Argv; Argv.reserve(argc); for (int i = 0; i < argc; ++i) Argv.push_back(argv[i]); #if LDC_LLVM_VER < 1600 auto Env = llvm::None; #else auto Env = std::nullopt; #endif std::string ErrMsg; int Result = sys::ExecuteAndWait(*Program, Argv, Env, {}, 0, 0, &ErrMsg); #ifdef _WIN32 // Handle abort() in msvcrt -- It has exit code as 3. abort(), aka // unreachable, should be recognized as a crash. However, some binaries use // exit code 3 on non-crash failure paths, so only do this if we expect a // crash. if (ExpectCrash && Result == 3) Result = -3; #endif if (Result < 0) { errs() << "Error: " << ErrMsg << "\n"; if (ExpectCrash) return 0; return 1; } if (ExpectCrash) return 1; return Result == 0; } ldc-1.40.0-src/utils/gen_gccbuiltins.cpp0000644000000000000000000001151514727557031016642 0ustar rootroot//===-- gen_gccbuiltins.cpp - GCC builtin module generator ----------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // This tool reads the GCC builtin definitions from LLVM's Intrinsics.td for // a given architecture and accordingly generates a ldc.gccbuiltins_ // module for using them from D code. // //===----------------------------------------------------------------------===// #include "llvm/TableGen/Main.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Path.h" #include "llvm/TableGen/Record.h" #include #include #include #include #include #include using namespace std; using namespace llvm; #define BUILTIN_NAME_STRING "ClangBuiltinName" string dtype(Record* rec, bool readOnlyMem) { Init* typeInit = rec->getValueInit("VT"); if(!typeInit) return ""; string type = typeInit->getAsString(); if(type == "iPTR") return readOnlyMem ? "const void*" : "void*"; string vec = ""; if(type[0] == 'v') { size_t i = 1; while(i != type.size() && type[i] <= '9' && type[i] >= '0') i++; vec = type.substr(1, i - 1); type = type.substr(i); } if(type == "i1" && vec.empty()) return "bool"; else if(type == "i8") return "byte" + vec; else if(type == "i16") return "short" + vec; else if(type == "i32") return "int" + vec; else if(type == "i64") return "long" + vec; else if(type == "f32") return "float" + vec; else if(type == "f64") return "double" + vec; else return ""; } StringRef attributes(ListInit* propertyList) { const auto prop = propertyList->size() ? propertyList->getElementAsRecord(0)->getName() : ""; if (prop == "IntrNoMem") return " pure @safe"; if (prop == "IntrReadArgMem" || prop == "IntrReadWriteArgMem") return " pure"; return ""; } void processRecord(raw_ostream& os, Record& rec, string arch) { if(!rec.getValue(BUILTIN_NAME_STRING)) return; const StringRef builtinName = rec.getValueAsString(BUILTIN_NAME_STRING); string name = rec.getName().str(); if(name.substr(0, 4) != "int_" || name.find(arch) == string::npos) return; name = name.substr(4); replace(name.begin(), name.end(), '_', '.'); name = string("llvm.") + name; ListInit* propsList = rec.getValueAsListInit("IntrProperties"); const StringRef prop = propsList->size() ? propsList->getElementAsRecord(0)->getName() : ""; bool readOnlyMem = prop == "IntrReadArgMem" || prop == "IntrReadMem"; ListInit* paramsList = rec.getValueAsListInit("ParamTypes"); vector params; for(unsigned int i = 0; i < paramsList->size(); i++) { string t = dtype(paramsList->getElementAsRecord(i), readOnlyMem); if(t == "") return; params.push_back(t); } ListInit* retList = rec.getValueAsListInit("RetTypes"); string ret; size_t sz = retList->size(); if(sz == 0) ret = "void"; else if(sz == 1) { ret = dtype(retList->getElementAsRecord(0), false); if(ret == "") return; } else return; os << "pragma(LDC_intrinsic, \"" << name << "\")\n "; os << ret << " " << builtinName << "("; if(params.size()) os << params[0]; for(size_t i = 1; i < params.size(); i++) os << ", " << params[i]; os << ")" << attributes(propsList) << ";\n\n"; } std::string arch; bool emit(raw_ostream& os, RecordKeeper& records) { os << "module ldc.gccbuiltins_"; os << arch; os << "; \n\nimport core.simd;\n\nnothrow @nogc:\n\n"; const auto &defs = records.getDefs(); for (const auto& d : defs) processRecord(os, *d.second, arch); return false; } int main(int argc, char** argv) { if(argc != 3) { fprintf(stderr, "There must be exactly two command line arguments\n"); return 1; } llvm::SmallString<128> file(LLVM_INTRINSIC_TD_PATH); sys::path::append(file, "llvm"); sys::path::append(file, "IR"); sys::path::append(file, "Intrinsics.td"); string iStr = string("-I=") + LLVM_INTRINSIC_TD_PATH; string oStr = string("-o=") + argv[1]; vector args2(argv, argv + 1); args2.push_back(const_cast(file.c_str())); args2.push_back(const_cast(iStr.c_str())); args2.push_back(const_cast(oStr.c_str())); cl::ParseCommandLineOptions(args2.size(), &args2[0]); arch = argv[2]; return TableGenMain(argv[0], emit); } ldc-1.40.0-src/utils/FileCheck-17.cpp0000644000000000000000000011002414727557031015540 0ustar rootroot//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ldc-1.40.0-src/utils/README.md0000644000000000000000000000117414727557031014256 0ustar rootrootLDC – Utils =============================== The `/utils` directory contains utilities that are used in building LDC (`gen_gccbuiltins.cpp`) and testing LDC (`not` and `FileCheck`). `not` is copied from LLVM `FileCheck` is copied from LLVM, and versioned for each LLVM version that we support (for example, FileCheck-3.9.cpp does not compile with LLVM 3.5). Older versions of FileCheck contain modifications such that they contain new features/bugfixes but still compile with older LLVM versions. How `not` and `FileCheck` are used is decribed here: [LDC Lit-based testsuite](http://wiki.dlang.org/?title=LDC_Lit-based_testsuite). ldc-1.40.0-src/cmake/0000755000000000000000000000000014727557031012714 5ustar rootrootldc-1.40.0-src/cmake/VisualD.props0000644000000000000000000000224614727557031015354 0ustar rootroot LDC true $(MSBuildThisFileDirectory).. $(MSBuildThisFileDirectory)..\dmd\res IN_LLVM true true Release Off None true true Release Off ldc-1.40.0-src/cmake/Modules/0000755000000000000000000000000014727557031014324 5ustar rootrootldc-1.40.0-src/cmake/Modules/HandleLTOPGOBuildOptions.cmake0000644000000000000000000000304514727557031022004 0ustar rootroot# Handles the LDC_BUILD_WITH_LTO build option. # For example `cmake -DLDC_BUILD_WITH_LTO=thin`. # # LTO is enabled for the C++ and D compilers, provided that they accept the `-flto` flag. # TODO: implement a LDC_BUILD_WITH_PGO build option (or something similar) to generate/use an LDC PGO profile. set(__LTO_FLAG) set(LDC_BUILD_WITH_LTO OFF CACHE STRING "Build LDC with LTO. May be specified as Thin or Full to use a particular kind of LTO") string(TOUPPER "${LDC_BUILD_WITH_LTO}" uppercase_LDC_BUILD_WITH_LTO) if(uppercase_LDC_BUILD_WITH_LTO STREQUAL "THIN") set(__LTO_FLAG "-flto=thin") elseif(uppercase_LDC_BUILD_WITH_LTO STREQUAL "FULL") set(__LTO_FLAG "-flto=full") elseif(LDC_BUILD_WITH_LTO) set(__LTO_FLAG "-flto") endif() if(__LTO_FLAG) message(STATUS "Building LDC using LTO: ${__LTO_FLAG}") check_cxx_compiler_flag(${__LTO_FLAG} CXX_COMPILER_ACCEPTS_FLTO_${uppercase_LDC_BUILD_WITH_LTO}) if (CXX_COMPILER_ACCEPTS_FLTO_${uppercase_LDC_BUILD_WITH_LTO}) append(${__LTO_FLAG} LDC_CXXFLAGS) list(APPEND LLVM_LDFLAGS ${__LTO_FLAG}) endif() check_d_source_compiles("void main(){}" D_COMPILER_ACCEPTS_FLTO_${uppercase_LDC_BUILD_WITH_LTO} FLAGS ${__LTO_FLAG}) if(D_COMPILER_ACCEPTS_FLTO_${uppercase_LDC_BUILD_WITH_LTO}) append(${__LTO_FLAG} DFLAGS_LDC) endif() if(uppercase_LDC_BUILD_WITH_LTO STREQUAL "THIN") # On darwin, enable the lto cache. if(APPLE) list(APPEND LLVM_LDFLAGS "-Wl,-cache_path_lto,${PROJECT_BINARY_DIR}/lto.cache") endif() endif() endif() ldc-1.40.0-src/cmake/Modules/CheckLinkFlag.cmake0000644000000000000000000000111414727557031017750 0ustar rootroot# Check whether the linker supports a given flag. # # CHECK_LINK_FLAG( ) # # - Commandline flag passed to the linker # - variable to store whether the source code compiled # Will be created as an internal cache variable. include(CheckCSourceCompiles) macro (CHECK_LINK_FLAG FLAG VAR) set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") set(CMAKE_REQUIRED_FLAGS "-Wl,${FLAG}") CHECK_C_SOURCE_COMPILES("int main(void) { return 0; }" ${VAR}) set (CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") endmacro () ldc-1.40.0-src/cmake/Modules/FindLLVM.cmake0000644000000000000000000002103414727557031016701 0ustar rootroot# - Find LLVM headers and libraries. # This module locates LLVM and adapts the llvm-config output for use with # CMake. # # A given list of COMPONENTS is passed to llvm-config. # # The following variables are defined: # LLVM_FOUND - true if LLVM was found # LLVM_CXXFLAGS - C++ compiler flags for files that include LLVM headers. # LLVM_ENABLE_ASSERTIONS - Whether LLVM was built with enabled assertions (ON/OFF). # LLVM_INCLUDE_DIRS - Directory containing LLVM include files. # LLVM_IS_SHARED - Whether LLVM is going to be linked dynamically (ON) or statically (OFF). # LLVM_LDFLAGS - Linker flags to add when linking against LLVM # (includes -LLLVM_LIBRARY_DIRS). # LLVM_LIBRARIES - Full paths to the library files to link against. # LLVM_LIBRARY_DIRS - Directory containing LLVM libraries. # LLVM_NATIVE_ARCH - Backend corresponding to LLVM_HOST_TARGET, e.g., # X86 for x86_64 and i686 hosts. # LLVM_ROOT_DIR - The root directory of the LLVM installation. # llvm-config is searched for in ${LLVM_ROOT_DIR}/bin. # LLVM_TARGETS_TO_BUILD - List of built LLVM targets. # LLVM_VERSION_MAJOR - Major version of LLVM. # LLVM_VERSION_MINOR - Minor version of LLVM. # LLVM_VERSION_STRING - Full LLVM version string (e.g. 6.0.0svn). # LLVM_VERSION_BASE_STRING - Base LLVM version string without git/svn suffix (e.g. 6.0.0). # # Note: The variable names were chosen in conformance with the offical CMake # guidelines, see ${CMAKE_ROOT}/Modules/readme.txt. # Try suffixed versions to pick up the newest LLVM install available on Debian # derivatives. # We also want an user-specified LLVM_ROOT_DIR to take precedence over the # system default locations such as /usr/local/bin. Executing find_program() # multiples times is the approach recommended in the docs. set(llvm_config_names llvm-config-19.1 llvm-config191 llvm-config-19 llvm-config-18.1 llvm-config181 llvm-config-18 llvm-config-17.0 llvm-config170 llvm-config-17 llvm-config-16.0 llvm-config160 llvm-config-16 llvm-config-15.0 llvm-config150 llvm-config-15 llvm-config) find_program(LLVM_CONFIG NAMES ${llvm_config_names} PATHS ${LLVM_ROOT_DIR}/bin NO_DEFAULT_PATH DOC "Path to llvm-config tool.") find_program(LLVM_CONFIG NAMES ${llvm_config_names}) if(APPLE) # extra fallbacks for MacPorts & Homebrew find_program(LLVM_CONFIG NAMES ${llvm_config_names} PATHS /opt/local/libexec/llvm-19/bin /opt/local/libexec/llvm-18/bin /opt/local/libexec/llvm-17/bin /opt/local/libexec/llvm-16/bin /opt/local/libexec/llvm-15/bin /opt/local/libexec/llvm/bin /usr/local/opt/llvm@19/bin /usr/local/opt/llvm@18/bin /usr/local/opt/llvm@17/bin /usr/local/opt/llvm@16/bin /usr/local/opt/llvm@15/bin /usr/local/opt/llvm/bin NO_DEFAULT_PATH) endif() # Prints a warning/failure message depending on the required/quiet flags. Copied # from FindPackageHandleStandardArgs.cmake because it doesn't seem to be exposed. macro(_LLVM_FAIL _msg) if(LLVM_FIND_REQUIRED) message(FATAL_ERROR "${_msg}") else() if(NOT LLVM_FIND_QUIETLY) message(WARNING "${_msg}") endif() endif() endmacro() if(NOT LLVM_CONFIG) if(NOT LLVM_FIND_QUIETLY) _LLVM_FAIL("No LLVM installation (>= ${LLVM_FIND_VERSION}) found. Try manually setting the 'LLVM_ROOT_DIR' or 'LLVM_CONFIG' variables.") endif() else() macro(llvm_set var flag) if(LLVM_FIND_QUIETLY) set(_quiet_arg ERROR_QUIET) endif() set(result_code) execute_process( COMMAND ${LLVM_CONFIG} --${flag} RESULT_VARIABLE result_code OUTPUT_VARIABLE LLVM_${var} OUTPUT_STRIP_TRAILING_WHITESPACE ${_quiet_arg} ) if(result_code) _LLVM_FAIL("Failed to execute llvm-config ('${LLVM_CONFIG}', result code: '${result_code})'") else() if(${ARGV2}) file(TO_CMAKE_PATH "${LLVM_${var}}" LLVM_${var}) endif() endif() endmacro() macro(llvm_set_libs var flag components) if(LLVM_FIND_QUIETLY) set(_quiet_arg ERROR_QUIET) endif() set(result_code) execute_process( COMMAND ${LLVM_CONFIG} --${flag} ${components} RESULT_VARIABLE result_code OUTPUT_VARIABLE tmplibs OUTPUT_STRIP_TRAILING_WHITESPACE ${_quiet_arg} ) if(result_code) _LLVM_FAIL("Failed to execute llvm-config ('${LLVM_CONFIG}', result code: '${result_code})'") else() file(TO_CMAKE_PATH "${tmplibs}" tmplibs) string(REGEX MATCHALL "${pattern}[^ ]+" LLVM_${var} ${tmplibs}) endif() endmacro() llvm_set(VERSION_STRING version) llvm_set(CXXFLAGS cxxflags) llvm_set(INCLUDE_DIRS includedir true) llvm_set(ROOT_DIR prefix true) llvm_set(ENABLE_ASSERTIONS assertion-mode) # The LLVM version string _may_ contain a git/svn suffix, so match only the x.y.z part string(REGEX MATCH "^[0-9]+[.][0-9]+[.][0-9]+" LLVM_VERSION_BASE_STRING "${LLVM_VERSION_STRING}") string(REGEX REPLACE "([0-9]+).*" "\\1" LLVM_VERSION_MAJOR "${LLVM_VERSION_STRING}" ) string(REGEX REPLACE "[0-9]+\\.([0-9]+).*[A-Za-z]*" "\\1" LLVM_VERSION_MINOR "${LLVM_VERSION_STRING}" ) llvm_set(SHARED_MODE shared-mode) if(LLVM_SHARED_MODE STREQUAL "shared") set(LLVM_IS_SHARED ON) else() set(LLVM_IS_SHARED OFF) endif() llvm_set(LDFLAGS ldflags) llvm_set(SYSTEM_LIBS system-libs) string(REPLACE "\n" " " LLVM_LDFLAGS "${LLVM_LDFLAGS} ${LLVM_SYSTEM_LIBS}") if(APPLE) # unclear why/how this happens string(REPLACE "-llibxml2.tbd" "-lxml2" LLVM_LDFLAGS ${LLVM_LDFLAGS}) endif() llvm_set(LIBRARY_DIRS libdir true) llvm_set_libs(LIBRARIES libs "${LLVM_FIND_COMPONENTS}") # LLVM bug: llvm-config --libs tablegen returns -lLLVM-3.8.0 # but code for it is not in shared library if("${LLVM_FIND_COMPONENTS}" MATCHES "tablegen") if (NOT "${LLVM_LIBRARIES}" MATCHES "LLVMTableGen") set(LLVM_LIBRARIES "${LLVM_LIBRARIES};-lLLVMTableGen") endif() endif() llvm_set(CMAKEDIR cmakedir) llvm_set(TARGETS_TO_BUILD targets-built) string(REGEX MATCHALL "${pattern}[^ ]+" LLVM_TARGETS_TO_BUILD ${LLVM_TARGETS_TO_BUILD}) # Parse LLVM_NATIVE_ARCH manually from LLVMConfig.cmake; including it leads to issues like # https://github.com/ldc-developers/ldc/issues/3079. file(STRINGS "${LLVM_CMAKEDIR}/LLVMConfig.cmake" LLVM_NATIVE_ARCH LIMIT_COUNT 1 REGEX "^set\\(LLVM_NATIVE_ARCH (.+)\\)$") string(REGEX MATCH "set\\(LLVM_NATIVE_ARCH (.+)\\)" LLVM_NATIVE_ARCH "${LLVM_NATIVE_ARCH}") set(LLVM_NATIVE_ARCH ${CMAKE_MATCH_1}) message(STATUS "LLVM_NATIVE_ARCH: ${LLVM_NATIVE_ARCH}") # On CMake builds of LLVM, the output of llvm-config --cxxflags does not # include -fno-rtti, leading to linker errors. Be sure to add it. if(NOT MSVC AND (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang"))) if(NOT ${LLVM_CXXFLAGS} MATCHES "-fno-rtti") set(LLVM_CXXFLAGS "${LLVM_CXXFLAGS} -fno-rtti") endif() endif() # Remove some clang-specific flags for gcc. if(CMAKE_COMPILER_IS_GNUCXX) string(REPLACE "-Wcovered-switch-default " "" LLVM_CXXFLAGS ${LLVM_CXXFLAGS}) string(REPLACE "-Wstring-conversion " "" LLVM_CXXFLAGS ${LLVM_CXXFLAGS}) string(REPLACE "-fcolor-diagnostics " "" LLVM_CXXFLAGS ${LLVM_CXXFLAGS}) # this requires more recent gcc versions (not supported by 4.9) string(REPLACE "-Werror=unguarded-availability-new " "" LLVM_CXXFLAGS ${LLVM_CXXFLAGS}) endif() # Remove gcc-specific flags for clang. if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") string(REPLACE "-Wno-maybe-uninitialized " "" LLVM_CXXFLAGS ${LLVM_CXXFLAGS}) endif() if (${LLVM_VERSION_STRING} VERSION_LESS ${LLVM_FIND_VERSION}) _LLVM_FAIL("Unsupported LLVM version ${LLVM_VERSION_STRING} found (${LLVM_CONFIG}). At least version ${LLVM_FIND_VERSION} is required. You can also set variables 'LLVM_ROOT_DIR' or 'LLVM_CONFIG' to use a different LLVM installation.") endif() endif() # Use the default CMake facilities for handling QUIET/REQUIRED. include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LLVM REQUIRED_VARS LLVM_ROOT_DIR VERSION_VAR LLVM_VERSION_STRING) ldc-1.40.0-src/cmake/Modules/ExtractDMDSystemLinker.cmake0000644000000000000000000000646614727557031021653 0ustar rootroot# Determines the system linker program and default command line arguments # used by a (CLI-wise, DMD-compatible) D compiler to link executables. # # The following variables are read: # - D_COMPILER: The D compiler command (i.e., executable) to use. # # The following variables are set: # - D_LINKER_COMMAND: The system linker command used (gcc, clang, …) # internally by ${D_COMPILER}. # - D_LINKER_ARGS: The additional command line arguments the ${D_COMPILER} # passes to the linker (apart from those specifying the input object file and # the output file). # # Create a temporary file with an empty main. We do not use the `-main` compiler # switch as it might behave differently (e.g. some versions of LDC emit the dummy # module into a separate __main.o file). set(source_name cmakeExtractDMDSystemLinker) set(source_file ${CMAKE_BINARY_DIR}/${source_name}.d) file(WRITE ${source_file} "void main() {}") # Compile & link the file in verbose mode and capture the compiler's stdout. set(result_code) set(stdout) set(stderr) if(UNIX) separate_arguments(cmdflags UNIX_COMMAND "${D_COMPILER_FLAGS} ${DFLAGS_BASE}") else() separate_arguments(cmdflags WINDOWS_COMMAND "${D_COMPILER_FLAGS} ${DFLAGS_BASE}") endif() execute_process( COMMAND ${D_COMPILER} ${cmdflags} -v ${source_file} RESULT_VARIABLE result_code OUTPUT_VARIABLE stdout OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ERROR_VARIABLE stderr ERROR_STRIP_TRAILING_WHITESPACE ) if(NOT "${result_code}" STREQUAL "0") message(FATAL_ERROR "Failed to link empty D program using '${D_COMPILER} ${D_COMPILER_FLAGS} ${DFLAGS_BASE}':\n${stderr}") endif() if("${D_COMPILER_ID}" STREQUAL "GDMD") # Extract second to last line, which (due to -v) contains the linker command line. string(REGEX REPLACE "\n" ";" stderr_lines "${stderr}") list(GET stderr_lines -2 linker_line) string(REGEX REPLACE "^ +" "" linker_line "${linker_line}") else() # Extract last line, which (due to -v) contains the linker command line. string(REGEX REPLACE "\n" ";" stdout_lines "${stdout}") list(GET stdout_lines -1 linker_line) endif() # Remove object file/output file arguments. This of course makes assumptions on # the object file names used by the compilers. Another option would be to swallow # all .o/-o arguments. string(REPLACE "${source_name}.o" "" linker_line "${linker_line}") string(REPLACE "-o ${source_name}" "" linker_line "${linker_line}") # Split up remaining part into executable and arguments. separate_arguments(linker_line) list(GET linker_line 0 D_LINKER_COMMAND) list(REMOVE_AT linker_line 0) # Fixup known flags with spaces, such as "-target triple" argument, which would be turned # into "-target;triple" by `separate_arguments`. Repair by merging into one list item. string(REGEX REPLACE ";-target;([A-Za-z0-9_-]+)(;?)" ";-target \\1\\2" linker_line "${linker_line}") string(REGEX REPLACE ";-arch;([A-Za-z0-9_-]+)(;?)" ";-arch \\1\\2" linker_line "${linker_line}") if("${D_COMPILER_ID}" STREQUAL "GDMD") # Filter linker arguments for those we know can be safely reused set(D_LINKER_ARGS) foreach(arg ${linker_line}) if("${arg}" MATCHES ^-L.*|^-l.*|^-B.*) list(APPEND D_LINKER_ARGS "${arg}") endif() endforeach() else() set(D_LINKER_ARGS ${linker_line}) endif() ldc-1.40.0-src/cmake/Modules/CheckDSourceCompiles.cmake0000644000000000000000000000442714727557031021333 0ustar rootroot# This file is modified from CMake's CheckCxxSourceCompiles.cmake. # Distributed under the OSI-approved BSD 3-Clause License. See https://cmake.org/licensing for details. # Check if given D source compiles and links into an executable # # CHECK_D_SOURCE_COMPILES( [FLAGS ]) # # :: # # - source code to try to compile # - variable to store whether the source code compiled # Will be created as an internal cache variable. # - Extra commandline flags passed to the compiler. # # The D_COMPILER variable is read and must point to the D compiler executable. macro(CHECK_D_SOURCE_COMPILES SOURCE VAR) if(NOT DEFINED "${VAR}") set(_FLAGS) set(_key) foreach(arg ${ARGN}) if("${arg}" MATCHES "^(FLAGS)$") set(_key "${arg}") elseif(_key) list(APPEND _${_key} "${arg}") set(_key 0) else() message(FATAL_ERROR "Unknown argument:\n ${arg}\n") endif() endforeach() file(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.d" "${SOURCE}\n") if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Performing Test ${VAR}") endif() execute_process(COMMAND ${D_COMPILER} ${_FLAGS} ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.d OUTPUT_VARIABLE OUTPUT ERROR_VARIABLE OUTPUT RESULT_VARIABLE RETVAL) if(${RETVAL}) set(${VAR} 0) else() set(${VAR} 1) endif() if(${VAR}) set(${VAR} 1 CACHE INTERNAL "Test ${VAR}") if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Performing Test ${VAR} - Success") endif() file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log "Performing D SOURCE FILE Test ${VAR} succeeded with the following output:\n" "${OUTPUT}\n" "Source was:\n${SOURCE}\n") else() if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Performing Test ${VAR} - Failed") endif() set(${VAR} "" CACHE INTERNAL "Test ${VAR}") file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log "Performing D SOURCE FILE Test ${VAR} failed with the following output:\n" "${OUTPUT}\n" "Source was:\n${SOURCE}\n") endif() endif() endmacro() ldc-1.40.0-src/cmake/Modules/GetLinuxDistribution.cmake0000644000000000000000000000044214727557031021465 0ustar rootroot# Gets the linux distribution # # This module defines: # LINUX_DISTRIBUTION_IS_GENTOO # Check: Can /usr/bin/lsb_release -a be used? set(LINUX_DISTRIBUTION_IS_GENTOO FALSE) if (UNIX) if (EXISTS "/etc/gentoo-release") set(LINUX_DISTRIBUTION_IS_GENTOO TRUE) endif() endif() ldc-1.40.0-src/cmake/Modules/GetGitRevisionDescription.cmake0000644000000000000000000000742014727557031022437 0ustar rootroot# - Returns a version string from Git # # These functions force a re-configure on each git commit so that you can # trust the values of the variables in your build system. # # get_git_head_revision( [ ...]) # # Returns the refspec and sha hash of the current head revision # # git_describe( [ ...]) # # Returns the results of git describe on the source tree, and adjusting # the output so that it tests false if an error occurs. # # git_get_exact_tag( [ ...]) # # Returns the results of git describe --exact-match on the source tree, # and adjusting the output so that it tests false if there was no exact # matching tag. # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) return() endif() set(__get_git_revision_description YES) # We must run the following at "include" time, not at function call time, # to find the path to this module rather than the path to a calling list file get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) function(get_git_head_revision _refspecvar _hashvar _use_recursion) set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(GIT_DIR "${GIT_PARENT_DIR}/.git") # if we're not recursing and GIT_DIR does not exist, # we are not in git if(NOT _use_recursion AND NOT EXISTS "${GIT_DIR}") set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() # otherwise, either GIT_DIR exists, or we recurse # until we find it or we reach the root dir while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) # We have reached the root directory, we are not in git set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() set(GIT_DIR "${GIT_PARENT_DIR}/.git") endwhile() # check if this is a submodule if(NOT IS_DIRECTORY ${GIT_DIR}) file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) endif() set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") if(NOT EXISTS "${GIT_DATA}") file(MAKE_DIRECTORY "${GIT_DATA}") endif() if(NOT EXISTS "${GIT_DIR}/HEAD") return() endif() set(HEAD_FILE "${GIT_DATA}/HEAD") configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_describe _var) execute_process(COMMAND git describe ${ARGN} WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) git_describe(out HEAD --exact-match --tag ${ARGN}) set(${_var} "${out}" PARENT_SCOPE) endfunction() ldc-1.40.0-src/cmake/Modules/BuildDExecutable.cmake0000644000000000000000000001213714727557031020477 0ustar rootroot# Translates linker args for usage in DMD-compatible command-line. macro(translate_linker_args in_var out_var) set(${out_var} "") foreach(f IN LISTS "${in_var}") if(NOT "${f}" STREQUAL "") if(MSVC) string(REPLACE "-LIBPATH:" "/LIBPATH:" f ${f}) list(APPEND ${out_var} "-L${f}") else() # Work around `-Xcc=-Wl,...` issue for older ldmd2 host compilers. if("${D_COMPILER_ID}" STREQUAL "LDMD" AND "${f}" MATCHES "^-Wl,") list(APPEND ${out_var} "-L${f}") else() list(APPEND ${out_var} "-Xcc=${f}") endif() endif() endif() endforeach() endmacro() # Depends on these global variables: # - D_COMPILER # - D_COMPILER_ID # - D_COMPILER_FLAGS # - DFLAGS_BASE # - LDC_LINK_MANUALLY # - D_LINKER_ARGS # - LDC_ENABLE_PLUGINS function(build_d_executable target_name output_exe d_src_files compiler_args linker_args extra_compile_deps link_deps compile_separately) set(dflags "${D_COMPILER_FLAGS} ${DFLAGS_BASE} ${compiler_args}") if(UNIX) separate_arguments(dflags UNIX_COMMAND "${dflags}") else() separate_arguments(dflags WINDOWS_COMMAND "${dflags}") endif() get_filename_component(output_dir ${output_exe} DIRECTORY) set(object_files) if(NOT compile_separately) # Compile all D modules to a single object. set(object_file ${PROJECT_BINARY_DIR}/obj/${target_name}${CMAKE_CXX_OUTPUT_EXTENSION}) # Default to -linkonce-templates with LDMD host compiler, to speed-up optimization. if("${target_name}" STREQUAL "ldc2" AND LDC_ENABLE_PLUGINS) # For plugin support we need ldc2's symbols to be global, don't use -linkonce-templates. elseif("${D_COMPILER_ID}" STREQUAL "LDMD") set(dflags -linkonce-templates ${dflags}) endif() add_custom_command( OUTPUT ${object_file} COMMAND ${D_COMPILER} -c ${dflags} -of${object_file} ${d_src_files} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS ${d_src_files} ${extra_compile_deps} ) set(object_files ${object_file}) else() # Compile each D module separately. foreach(f ${d_src_files}) file(RELATIVE_PATH object_file ${PROJECT_SOURCE_DIR} ${f}) # make path relative to PROJECT_SOURCE_DIR string(REGEX REPLACE "[/\\\\]" "." object_file "${object_file}") # replace path separators with '.' string(REGEX REPLACE "^\\.+" "" object_file "${object_file}") # strip leading dots (e.g., from original '../dir/file.d' => '...dir.file.d' => 'dir.file.d') set(object_file ${PROJECT_BINARY_DIR}/obj/${target_name}/${object_file}${CMAKE_CXX_OUTPUT_EXTENSION}) add_custom_command( OUTPUT ${object_file} COMMAND ${D_COMPILER} -c ${dflags} -of${object_file} ${f} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS ${f} ${extra_compile_deps} ) list(APPEND object_files ${object_file}) endforeach() endif() # Link to an executable. if(LDC_LINK_MANUALLY) add_executable(${target_name} ${object_files}) set_target_properties(${target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_dir} LINKER_LANGUAGE CXX ) target_link_libraries(${target_name} ${link_deps} ${linker_args} ${D_LINKER_ARGS}) else() # Use a response file on Windows when compiling separately, in order not to # exceed the max command-line length. set(objects_args "${object_files}") if(WIN32 AND compile_separately) string(REPLACE ";" " " objects_args "${object_files}") file(WRITE ${output_exe}.rsp ${objects_args}) set(objects_args "@${output_exe}.rsp") endif() set(dep_libs "") foreach(l ${link_deps}) list(APPEND dep_libs "-L$") endforeach() set(full_linker_args ${CMAKE_EXE_LINKER_FLAGS} ${linker_args}) translate_linker_args(full_linker_args translated_linker_args) # We need to link against the C++ runtime library. if(NOT MSVC AND "${D_COMPILER_ID}" STREQUAL "LDMD" AND NOT "${dflags}" MATCHES "(^|;)-gcc=") set(translated_linker_args "-gcc=${CMAKE_CXX_COMPILER}" ${translated_linker_args}) endif() # Use an extra custom target as dependency for the executable in # addition to the object files directly to improve parallelization. # See https://github.com/ldc-developers/ldc/pull/3575. add_custom_target(${target_name}_d_objects DEPENDS ${object_files}) add_custom_command( OUTPUT ${output_exe} COMMAND ${D_COMPILER} ${dflags} -of${output_exe} ${objects_args} ${dep_libs} ${translated_linker_args} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS ${target_name}_d_objects ${object_files} ${link_deps} ) add_custom_target(${target_name} ALL DEPENDS ${output_exe}) endif() endfunction() ldc-1.40.0-src/cmake/Modules/FindDCompiler.cmake0000644000000000000000000001004714727557031020007 0ustar rootroot# Find a D compiler! # # Use variable D_COMPILER first if defined by user, then try the environment variable DMD, next use a list of common compiler executables. # # The following variables are defined: # D_COMPILER_FOUND - true if a D compiler with DMD-compatible cmdline interface was found # D_COMPILER - D compiler # D_COMPILER_FLAGS - D compiler flags (could be passed in the DMD environment variable) # D_COMPILER_ID = {"DigitalMars", "LDMD", "GDMD"} # D_COMPILER_VERSION_STRING - String containing the compiler version, e.g. "DMD64 D Compiler v2.070.2" # D_COMPILER_FE_VERSION - compiler front-end version, e.g. "2070" set(D_COMPILER_FOUND "FALSE") set(COMMON_D_COMPILERS "ldmd2" "dmd" "gdmd") set(COMMON_D_COMPILER_PATHS "/usr/bin" "/usr/local/bin" "C:\\d\\dmd2\\windows\\bin") if (D_COMPILER) get_filename_component(D_COMPILER ${D_COMPILER} PROGRAM PROGRAM_ARGS D_COMPILER_FLAGS_ENV_INIT) if(D_COMPILER_FLAGS_ENV_INIT) set(D_COMPILER_FLAGS "${D_COMPILER_FLAGS_ENV_INIT}" CACHE STRING "Default flags for D compiler") endif() if(NOT EXISTS ${D_COMPILER}) message(FATAL_ERROR "Could not find compiler set in variable D_COMPILER: ${D_COMPILER}.") endif() elseif($ENV{DMD} MATCHES ".+") get_filename_component(D_COMPILER $ENV{DMD} PROGRAM PROGRAM_ARGS D_COMPILER_FLAGS_ENV_INIT CACHE) if(D_COMPILER_FLAGS_ENV_INIT) set(D_COMPILER_FLAGS "${D_COMPILER_FLAGS_ENV_INIT}" CACHE STRING "Default flags for D compiler") endif() if(NOT EXISTS ${D_COMPILER}) message(FATAL_ERROR "Could not find compiler set in environment variable DMD: $ENV{DMD}.") endif() else() # "NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH" is necessary, otherwise CMake will find the compiler in the install prefix path! find_program(D_COMPILER NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NAMES ${COMMON_D_COMPILERS} PATHS ${COMMON_D_COMPILER_PATHS} DOC "D compiler") endif() # TODO: Test compiler and set compiler ID if (D_COMPILER) set(D_COMPILER_FOUND "TRUE") get_filename_component(__D_COMPILER_NAME ${D_COMPILER} NAME_WE) if (__D_COMPILER_NAME STREQUAL "dmd") set(D_COMPILER_ID "DigitalMars") elseif (__D_COMPILER_NAME STREQUAL "ldmd2") set(D_COMPILER_ID "LDMD") elseif (__D_COMPILER_NAME MATCHES "gdmd") set(D_COMPILER_ID "GDMD") elseif (NOT D_COMPILER_ID) message(FATAL_ERROR "Unsupported D compiler filename (try 'ldmd2' or 'gdmd', or set D_COMPILER_ID manually).") endif() # Older versions of ldmd do not have --version cmdline option, but the error message still contains the version info in the first line. execute_process(COMMAND ${D_COMPILER} --version OUTPUT_VARIABLE D_COMPILER_VERSION_STRING ERROR_VARIABLE D_COMPILER_VERSION_STRING ERROR_QUIET) if ("${D_COMPILER_ID}" STREQUAL "GDMD") execute_process(COMMAND "${CMAKE_COMMAND}" -E echo "pragma(msg, int(__VERSION__));" COMMAND "${D_COMPILER}" - -o- ERROR_VARIABLE D_COMPILER_FE_VERSION OUTPUT_QUIET) string(REGEX MATCH "^[^\r\n:]*" D_COMPILER_FE_VERSION "${D_COMPILER_FE_VERSION}") else() string(REGEX MATCH " (D Compiler|based on DMD) v([0-9]+)\\.([0-9]+)" D_COMPILER_FE_VERSION "${D_COMPILER_VERSION_STRING}") math(EXPR D_COMPILER_FE_VERSION ${CMAKE_MATCH_2}*1000+${CMAKE_MATCH_3}) # e.g., 2079 endif() string(REGEX MATCH "^[^\r\n:]*" D_COMPILER_VERSION_STRING "${D_COMPILER_VERSION_STRING}") endif() if (D_COMPILER_FOUND) message(STATUS "Found host D compiler ${D_COMPILER}, with default flags '${D_COMPILER_FLAGS}'") message(STATUS "Host D compiler ID: ${D_COMPILER_ID}") message(STATUS "Host D compiler version: ${D_COMPILER_VERSION_STRING}") message(STATUS "Host D compiler front-end version: ${D_COMPILER_FE_VERSION}") else() message(FATAL_ERROR "No supported D compiler (ldmd2, dmd, gdmd) found! Try setting the 'D_COMPILER' variable or 'DMD' environment variable.") endif() ldc-1.40.0-src/cmake/Modules/GetGitRevisionDescription.cmake.in0000644000000000000000000000240314727557031023040 0ustar rootroot# # Internal file for GetGitRevisionDescription.cmake # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) else() configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() ldc-1.40.0-src/cmake/Modules/FindMLIR.cmake0000644000000000000000000000436014727557031016675 0ustar rootroot# - Try to find MLIR project at LLVM # # The following are set after configuration is done: # MLIR_FOUND ON if MLIR installation was found # MLIR_ROOT_DIR # MLIR_INCLUDE_DIR # MLIR_LIB_DIR # MLIR_LIBRARIES # MLIR_TABLEGEN The mlir-tblgen executable set(MLIR_FOUND OFF) # We only want to find an MLIR version that is compatible with our LLVM version, # so for now only look in the same installation dir as LLVM. find_program(MLIR_TABLEGEN NAMES mlir-tblgen PATHS ${MLIR_ROOT_DIR}/bin ${LLVM_ROOT_DIR}/bin NO_DEFAULT_PATH DOC "Path to mlir-tblgen tool.") if(NOT MLIR_TABLEGEN) message(STATUS "Could not find mlir-tblgen. Try manually setting MLIR_ROOT_DIR or MLIR_TABLEGEN.") else() set(MLIR_FOUND ON) message(STATUS "Found mlir-tblgen: ${MLIR_TABLEGEN}") get_filename_component(MLIR_BIN_DIR ${MLIR_TABLEGEN} DIRECTORY CACHE) get_filename_component(MLIR_ROOT_DIR "${MLIR_BIN_DIR}/.." ABSOLUTE CACHE) set(MLIR_INCLUDE_DIR ${MLIR_ROOT_DIR}/include) set(MLIR_LIB_DIR ${MLIR_ROOT_DIR}/lib) # To be done: add the required MLIR libraries. Hopefully we don't have to manually list all MLIR libs. if(EXISTS "${MLIR_LIB_DIR}/MLIRIR.lib") set(MLIR_LIBRARIES ${MLIR_LIB_DIR}/MLIRIR.lib ${MLIR_LIB_DIR}/MLIRSupport.lib) elseif(EXISTS "${MLIR_LIB_DIR}/libMLIRIR.a") set(MLIR_LIBRARIES ${MLIR_LIB_DIR}/libMLIRIR.a ${MLIR_LIB_DIR}/libMLIRSupport.a) endif() # XXX: This function is untested and will need adjustment. function(mlir_tablegen) cmake_parse_arguments( ARG "NAME" "TARGET;OUTS;FLAG;SRCS" ${ARGN} ) MESSAGE(STATUS "Setting target for Ops_" ${ARG_TARGET}) set(TABLEGEN_OUTPUT ${TABLEGEN_OUTPUT} ${CMAKE_CURRENT_SOURCE_DIR}/${ARG_SRCS} PARENT_SCOPE) #mlir-tblgen ops.td --gen-op-* -I*-o=ops.*.inc add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/${ARG_OUTS} COMMAND ${MLIR_TABLEGEN} ${CMAKE_CURRENT_SOURCE_DIR}/${ARG_SRCS} -I${MLIR_INCLUDE_DIR} -o=${CMAKE_CURRENT_SOURCE_DIR}/${ARG_OUTS} ARGS ${ARG_FLAG} ) add_custom_target(Ops_${ARG_TARGET} ALL DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${ARG_OUTS}) endfunction() endif() ldc-1.40.0-src/ldc2_install.conf.in0000644000000000000000000000266714727557031015475 0ustar rootroot// See comments in driver/config.d in ldc source tree for grammar description of // this config file. // For cross-compilation, you can add sections for specific target triples by // naming the sections as (quoted) regex patterns. See LDC's `-v` output // (`config` line) to figure out your normalized triple, depending on the used // `-mtriple`, `-m32` etc. E.g.: // // "^arm.*-linux-gnueabihf$": { … }; // "86(_64)?-.*-linux": { … }; // "i[3-6]86-.*-windows-msvc": { … }; // // Later sections take precedence and override settings from previous matching // sections while inheriting unspecified settings from previous sections. // A `default` section always matches (treated as ".*") and is therefore usually // the first section. default: { // default switches injected before all explicit command-line switches switches = [ "-defaultlib=phobos2-ldc,druntime-ldc",@ADDITIONAL_DEFAULT_LDC_SWITCHES@ ]; // default switches appended after all explicit command-line switches post-switches = [ "-I@INCLUDE_INSTALL_DIR@", ]; // default directories to be searched for libraries when linking lib-dirs = [ "@CMAKE_INSTALL_LIBDIR@",@OPTIONAL_COMPILER_RT_DIR@ ]; // default rpath when linking against the shared default libs rpath = "@SHARED_LIBS_INSTALL_RPATH@"; }; "^wasm(32|64)-": { switches = [ "-defaultlib=",@WASM_DEFAULT_LDC_SWITCHES@ ]; lib-dirs = []; }; ldc-1.40.0-src/.circleci/0000755000000000000000000000000014727557031013467 5ustar rootrootldc-1.40.0-src/.circleci/config.yml0000644000000000000000000001032014727557031015453 0ustar rootrootcommonSteps: &commonSteps steps: # Each step starts in the checked-out source directory. - run: name: Install prerequisites command: | set -ux cd .. if [ "$CI_OS" = "linux" ]; then export DEBIAN_FRONTEND=noninteractive if [[ "${EXTRA_CMAKE_FLAGS:-}" = *-DMULTILIB?ON* ]]; then sudo dpkg --add-architecture i386 gcc_pkg="g++-multilib" libcurl_pkg="libcurl4t64 libcurl4t64:i386" else gcc_pkg="g++" libcurl_pkg="libcurl4t64" fi sudo apt-get -q update sudo apt-get -yq install \ git-core $gcc_pkg cmake \ llvm-$LLVM_MAJOR-dev libclang-common-$LLVM_MAJOR-dev zlib1g-dev \ $libcurl_pkg curl gdb python3 python3-pip tzdata unzip zip \ ${EXTRA_APT_PACKAGES:-} # Download & extract Ninja curl -fL --retry 3 --max-time 60 -O https://github.com/symmetryinvestments/ninja/releases/download/v1.11.1-sym1/ninja-linux.zip mkdir ninja unzip ninja-linux.zip -d ninja rm ninja-linux.zip # Add Ninja to PATH for future steps echo "export PATH=$PWD/ninja:\$PATH" >> $BASH_ENV fi # Install lit python3 --version python3 -m pip install --user lit python3 -c "import lit.main; lit.main.main();" --version . | head -n 1 # Download & extract host LDC if HOST_LDC_VERSION is set if [[ -v HOST_LDC_VERSION ]]; then curl -fL --retry 3 --max-time 300 -o ldc2.tar.xz https://github.com/ldc-developers/ldc/releases/download/v$HOST_LDC_VERSION/ldc2-$HOST_LDC_VERSION-$CI_OS-x86_64.tar.xz mkdir host-ldc tar -xf ldc2.tar.xz --strip 1 -C host-ldc rm ldc2.tar.xz fi - checkout - run: name: Checkout git submodules command: git submodule update --init - run: name: Build LDC & LDC D unittests & defaultlib unittest runners command: | set -ux cd .. cmake --version ninja --version mkdir build cd build cmake -G Ninja $CIRCLE_WORKING_DIRECTORY \ -DCMAKE_BUILD_TYPE=Release \ ${HOST_LDC_VERSION:+-DD_COMPILER=$PWD/../host-ldc/bin/ldmd2} \ -DLDC_LINK_MANUALLY=OFF \ ${EXTRA_CMAKE_FLAGS:-} ninja -j$PARALLELISM obj/ldc2.o all ldc2-unittest all-test-runners bin/ldc2 -version - run: name: Run LDC D unittests when: always command: cd ../build && ctest --output-on-failure -R ldc2-unittest - run: name: Run LIT testsuite when: always command: | set -ux if [ "$CI_OS" = "linux" ]; then # FIXME: lsan_interceptors.cpp:82 "((!lsan_init_is_running)) != (0)" rm tests/sanitizers/lsan_memleak.d fi cd ../build/tests python3 runlit.py -v -j $PARALLELISM . - run: name: Run DMD testsuite when: always command: cd ../build && DMD_TESTSUITE_MAKE_ARGS=-j$PARALLELISM ctest -V -R dmd-testsuite - run: name: Run defaultlib unittests & druntime integration tests when: always command: cd ../build && ctest -j$PARALLELISM --output-on-failure -E "dmd-testsuite|ldc2-unittest|lit-tests" version: 2 jobs: Ubuntu-24.04-multilib-rtSanitizers: <<: *commonSteps machine: image: ubuntu-2404:current resource_class: large environment: - PARALLELISM: 4 - CI_OS: linux - LLVM_MAJOR: 18 - HOST_LDC_VERSION: 1.39.0 - EXTRA_CMAKE_FLAGS: "-DMULTILIB=ON -DRT_SUPPORT_SANITIZERS=ON -DBUILD_LTO_LIBS=ON" Ubuntu-24.04-sharedLibsOnly-gdmd: <<: *commonSteps machine: image: ubuntu-2404:current resource_class: large environment: - PARALLELISM: 4 - CI_OS: linux - LLVM_MAJOR: 18 - EXTRA_APT_PACKAGES: gdmd - EXTRA_CMAKE_FLAGS: "-DBUILD_SHARED_LIBS=ON -DBUILD_LTO_LIBS=ON -DD_COMPILER=gdmd -DLDC_LINK_MANUALLY=ON" workflows: version: 2 build: jobs: - Ubuntu-24.04-multilib-rtSanitizers - Ubuntu-24.04-sharedLibsOnly-gdmd ldc-1.40.0-src/CMakeLists.txt0000644000000000000000000012171414727557031014402 0ustar rootrootcmake_minimum_required(VERSION 3.4.3) if(POLICY CMP0025) cmake_policy(SET CMP0025 NEW) endif() if(${CMAKE_VERSION} VERSION_GREATER "3.26.9") # Prevent implicit dependencies for custom commands, e.g., # `obj/ldc2.o` depending on `lib/libldc.a` with LDC_LINK_MANUALLY=ON. # Only supported since CMake v3.27 unfortunately. set(CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY ON) endif() project(ldc) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules") include(FindDCompiler) include(CheckCXXCompilerFlag) include(CheckDSourceCompiles) include(CheckLinkFlag) include(BuildDExecutable) # Helper function function(append value) foreach(variable ${ARGN}) if(${variable} STREQUAL "") set(${variable} "${value}" PARENT_SCOPE) else() set(${variable} "${${variable}} ${value}" PARENT_SCOPE) endif() endforeach(variable) endfunction() # # Locate LLVM. # find_package(LLVM 15.0 REQUIRED all-targets analysis asmparser asmprinter bitreader bitwriter codegen core debuginfodwarf debuginfomsf debuginfopdb demangle instcombine ipo instrumentation irreader libdriver linker lto mc mcdisassembler mcparser objcarcopts object option profiledata scalaropts selectiondag support tablegen target transformutils vectorize windowsdriver windowsmanifest symbolize ${EXTRA_LLVM_MODULES}) math(EXPR LDC_LLVM_VER ${LLVM_VERSION_MAJOR}*100+${LLVM_VERSION_MINOR}) message(STATUS "Using LLVM Version ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}") # Remove LLVMTableGen library from list of libraries string(REGEX MATCH "[^;]*LLVMTableGen[^;]*" LLVM_TABLEGEN_LIBRARY "${LLVM_LIBRARIES}") string(REGEX REPLACE "[^;]*LLVMTableGen[^;]*;?" "" LLVM_LIBRARIES "${LLVM_LIBRARIES}") # Information about which targets LLVM was built to target foreach(LLVM_SUPPORTED_TARGET ${LLVM_TARGETS_TO_BUILD}) add_definitions("-DLDC_LLVM_SUPPORTED_TARGET_${LLVM_SUPPORTED_TARGET}=1") endforeach() # Set MLIR support variables if it is found. # FIXME: LLVM 14+ (`mlir::OwningModuleRef` replacement) if(NOT LDC_WITH_MLIR STREQUAL "OFF" AND LDC_LLVM_VER LESS 1400) include(FindMLIR) if(MLIR_FOUND) message(STATUS "-- Building LDC with MLIR support (${MLIR_ROOT_DIR})") include_directories(${MLIR_INCLUDE_DIR}) add_definitions("-DLDC_MLIR_ENABLED") set(LLVM_LIBRARIES "${MLIR_LIBRARIES}" ${LLVM_LIBRARIES}) else() message(STATUS "-- Building LDC without MLIR support: not found") endif() endif() # Check and adapt for LLVMSPIRVLib (Khronos SPIRV-LLVM-Translator) set(LLVM_SPIRV_FOUND OFF) if (LDC_LLVM_VER LESS 1600) if(MSVC) if(EXISTS "${LLVM_LIBRARY_DIRS}/LLVMSPIRVLib.lib") set(LLVM_SPIRV_FOUND ON) set(LLVM_LIBRARIES "${LLVM_LIBRARY_DIRS}/LLVMSPIRVLib.lib" ${LLVM_LIBRARIES}) endif() else() if((EXISTS "${LLVM_LIBRARY_DIRS}/libLLVMSPIRVLib.a") OR (EXISTS "${LLVM_LIBRARY_DIRS}/libLLVMSPIRVLib.so") OR (EXISTS "${LLVM_LIBRARY_DIRS}/libLLVMSPIRVLib.dylib")) set(LLVM_SPIRV_FOUND ON) set(LLVM_LIBRARIES -lLLVMSPIRVLib ${LLVM_LIBRARIES}) endif() endif() if(NOT LLVM_SPIRV_FOUND) find_package(PkgConfig) if(PkgConfig_FOUND) if(MSVC) # make pkg-config use -LC:\path\to\build\LLVMSPIRVLib.lib not -L-lLLVMSPIRVLib set(PKG_CONFIG_EXECUTABLE "${PKG_CONFIG_EXECUTABLE} --msvc-syntax") endif() pkg_check_modules(LLVM_SPIRV LLVMSPIRVLib) if(LLVM_SPIRV_FOUND) set(LLVM_SPIRV_FOUND ON) # translate 1 to ON include_directories(${LLVM_SPIRV_INCLUDE_DIRS}) else() set(LLVM_SPIRV_FOUND OFF) endif() endif() endif() if(LLVM_SPIRV_FOUND) message(STATUS "-- Building LDC with SPIR-V support") add_definitions("-DLDC_LLVM_SUPPORTED_TARGET_SPIRV=1") else() message(STATUS "-- Building LDC without SPIR-V support: not found") endif() endif() # # Get info about used Linux distribution. # include(GetLinuxDistribution) # # Main configuration. # # Version information set(LDC_VERSION "1.40.0") # May be overridden by git hash tag set(DMDFE_MAJOR_VERSION 2) set(DMDFE_MINOR_VERSION 110) set(DMDFE_PATCH_VERSION 0) set(DMD_VERSION ${DMDFE_MAJOR_VERSION}.${DMDFE_MINOR_VERSION}.${DMDFE_PATCH_VERSION}) # Generally, we want to install everything into CMAKE_INSTALL_PREFIX, but when # it is /usr, put the config files into /etc to meet common practice. if(NOT DEFINED SYSCONF_INSTALL_DIR) if(CMAKE_INSTALL_PREFIX STREQUAL "/usr") set(SYSCONF_INSTALL_DIR "/etc") else() set(SYSCONF_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/etc") endif() endif() set(D_VERSION ${DMDFE_MAJOR_VERSION} CACHE STRING "D language version") set(PROGRAM_PREFIX "" CACHE STRING "Prepended to ldc/ldmd binary names") set(PROGRAM_SUFFIX "" CACHE STRING "Appended to ldc/ldmd binary names") set(CONF_INST_DIR ${SYSCONF_INSTALL_DIR} CACHE PATH "Directory ldc.conf is installed to") # Note: LIB_SUFFIX should perhaps be renamed to LDC_LIBDIR_SUFFIX. set(LIB_SUFFIX "" CACHE STRING "Appended to the library installation directory. Set to '64' to install libraries into ${PREFIX}/lib64.") set(COMPILE_D_MODULES_SEPARATELY OFF CACHE BOOL "Compile each D module separately (instead of all at once). Useful for many CPU cores and/or iterative development; generated executables will be somewhat slower.") set(LDC_ENABLE_ASSERTIONS "${LLVM_ENABLE_ASSERTIONS}" CACHE BOOL "Enable LDC assertions. Defaults to the LLVM assertions mode; overriding may cause LDC segfaults!") # Allow user to specify mimalloc.o location, to be linked with `ldc2` only set(ALTERNATIVE_MALLOC_O "" CACHE STRING "If specified, adds ALTERNATIVE_MALLOC_O object file to LDC link, to override the CRT malloc.") # Most linux distributions have a policy of not bundling dependencies like zlib set(PHOBOS_SYSTEM_ZLIB OFF CACHE BOOL "Use system zlib instead of Phobos' vendored version") if(D_VERSION EQUAL 1) message(FATAL_ERROR "D version 1 is no longer supported. Please consider using D version 2 or checkout the 'd1' git branch for the last version supporting D version 1.") elseif(D_VERSION EQUAL 2) set(LDC_EXE ldc2) set(LDMD_EXE ldmd2) set(RUNTIME druntime) append("-DDMDV2" CMAKE_CXX_FLAGS) else() message(FATAL_ERROR "unsupported D version") endif() set(LDC_EXE_NAME ${PROGRAM_PREFIX}${LDC_EXE}${PROGRAM_SUFFIX}) set(LDMD_EXE_NAME ${PROGRAM_PREFIX}${LDMD_EXE}${PROGRAM_SUFFIX}) # Setup D compiler flags (DMD syntax, which also works with LDMD). set(DFLAGS_BASE "-wi") set(DFLAGS_BUILD_TYPE "") # DFLAGS derived from CMAKE_BUILD_TYPE if(NOT MSVC_IDE) # for multi-config builds, these options have to be added later to the custom command if(CMAKE_BUILD_TYPE MATCHES "Debug") append("-g" DFLAGS_BUILD_TYPE) if(${D_COMPILER_ID} STREQUAL "LDMD") append("-link-debuglib" DFLAGS_BASE) endif() elseif(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo") append("-g -O -inline -release" DFLAGS_BUILD_TYPE) else() # Default to a Release build type append("-O -inline -release" DFLAGS_BUILD_TYPE) endif() if(LDC_ENABLE_ASSERTIONS) string(REPLACE " -release" "" DFLAGS_BUILD_TYPE "${DFLAGS_BUILD_TYPE}") endif() endif() if(MSVC) if(CMAKE_SIZEOF_VOID_P EQUAL 8) message(STATUS "Let D host compiler output 64-bit object files") append("-m64" DFLAGS_BASE) else() message(STATUS "Let D host compiler output 32-bit COFF object files") if(${D_COMPILER_ID} STREQUAL "DigitalMars") append("-m32mscoff" DFLAGS_BASE) else() append("-m32" DFLAGS_BASE) endif() endif() if(${D_COMPILER_ID} STREQUAL "DigitalMars" AND (MSVC_VERSION GREATER 1800)) # VS 2015+ append("-Llegacy_stdio_definitions.lib" DFLAGS_BASE) endif() # Link against the static MSVC runtime; CMake's C(++) flags apparently default to the dynamic one. # Host DMD/LDMD already defaults to linking against the static MSVC runtime. if(${LLVM_CXXFLAGS} MATCHES "(^| )/MDd?( |$)") message(FATAL_ERROR "LLVM must be built with CMake option LLVM_USE_CRT_=MT[d]") endif() set(llvm_ob_flag) string(REGEX MATCH "/Ob[0-2]" llvm_ob_flag "${LLVM_CXXFLAGS}") foreach(flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") # CMake defaults to /W3, LLVM uses /W4 => MS compiler warns about overridden option. # Simply replace with /W4. string(REGEX REPLACE "/W[0-3]" "/W4" ${flag_var} "${${flag_var}}") # Some CMake configs default to /Ob1, LLVM uses /Ob2. Replace with LLVM's option. if(NOT llvm_ob_flag STREQUAL "") string(REGEX REPLACE "/Ob[0-2]" "${llvm_ob_flag}" ${flag_var} "${${flag_var}}") endif() endforeach() endif() # Use separate compiler flags for the frontend and for the LDC-specific parts, # as enabling warnings on the DMD frontend only leads to a lot of clutter in # the output (LLVM_CXXFLAGS sometimes already includes -Wall). set(LDC_CXXFLAGS) if(CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")) if(NOT MSVC) # not for Windows-clang append("-Wall -Wextra" LDC_CXXFLAGS) endif() # Disable some noisy warnings: # * -Wunused-parameter and -Wcomment trigger for LLVM headers # * -Wmissing-field-initializer leads to reams of warnings in gen/asm-*.h # * -Wnon-virtual-dtor is something Walter has declined to let us fix upstream # and it triggers for the visitors we need in our glue code # * -Wpedantic warns on trailing commas in initializer lists and casting # function pointers to void*. # * -Wgnu-anonymous-struct and -Wnested-anon-types trigger for tokens.h. # * -Wgnu-redeclared-enum triggers for various frontend headers. # * -Wunused-private-field triggers for expression.h. append("-Wno-unused-parameter -Wno-comment -Wno-missing-field-initializers -Wno-non-virtual-dtor" LDC_CXXFLAGS) if ((${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")) append("-Wno-gnu-anonymous-struct -Wno-nested-anon-types -Wno-gnu-redeclared-enum -Wno-unused-private-field" LDC_CXXFLAGS) # clang trying to eagerly anticipate linker errors wrt. static class template # members leads to false positives (e.g., instantiated/defined in D): # 'instantiation of variable required here, but no definition is available' append("-Wno-undefined-var-template" LDC_CXXFLAGS) endif() if(CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS "4.7.0") append("-Wno-pedantic" LDC_CXXFLAGS) endif() endif() if(MSVC) # Remove flags here, for exceptions and RTTI. # CL.EXE complains to override flags like "/GR /GR-". string(REGEX REPLACE "(^| )[/-]EH[-cs]*( |$)" "\\2" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") string(REGEX REPLACE "(^| )[/-]GR-?( |$)" "\\2" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") append("/GR- /EHs-c-" CMAKE_CXX_FLAGS) append("/D_HAS_EXCEPTIONS=0" CMAKE_CXX_FLAGS) # disable warning C4201: nonstandard extension used: nameless struct/union append("/wd4201" LDC_CXXFLAGS) endif() # Append -mminimal-toc for gcc 4.0.x - 4.5.x on ppc64 if( CMAKE_COMPILER_IS_GNUCXX AND CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64|powerpc64" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "4.6.0" ) append("-mminimal-toc" LDC_CXXFLAGS) endif() # Do not use doubledouble on ppc if( CMAKE_SYSTEM_PROCESSOR MATCHES "ppc|powerpc") append("-mlong-double-64" LDC_CXXFLAGS) endif() if(UNIX) append("-DLDC_POSIX" LDC_CXXFLAGS) endif() set(SANITIZE_CXXFLAGS) set(SANITIZE_LDFLAGS) if(SANITIZE) if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") append("-fsanitize=address" SANITIZE_CXXFLAGS) append("-fsanitize=address" SANITIZE_LDFLAGS) else() message(WARNING "Option SANITIZE specified but compiler is not clang.") endif() endif() append("${SANITIZE_CXXFLAGS}" LDC_CXXFLAGS) if(PHOBOS_SYSTEM_ZLIB) append("-DPHOBOS_SYSTEM_ZLIB" LDC_CXXFLAGS) endif() # LLVM_CXXFLAGS may contain -Werror which causes compile errors with dmd source string(REPLACE "-Werror " "" LLVM_CXXFLAGS ${LLVM_CXXFLAGS}) if (UNIX AND NOT "${LLVM_LDFLAGS}" STREQUAL "") # LLVM_LDFLAGS may contain -l-lld which is a wrong library reference (AIX) string(REPLACE "-l-lld " "-lld " LLVM_LDFLAGS ${LLVM_LDFLAGS}) endif() if(MSVC) separate_arguments(LLVM_LDFLAGS WINDOWS_COMMAND "${LLVM_LDFLAGS}") if(NOT MSVC_IDE) # apparently not needed for VS (and spaces in path are problematic) if(CMAKE_SIZEOF_VOID_P EQUAL 8) list(APPEND LLVM_LDFLAGS "$ENV{VSINSTALLDIR}DIA SDK\\lib\\amd64\\diaguids.lib") else() list(APPEND LLVM_LDFLAGS "$ENV{VSINSTALLDIR}DIA SDK\\lib\\diaguids.lib") endif() endif() else() separate_arguments(LLVM_LDFLAGS UNIX_COMMAND "${LLVM_LDFLAGS}") endif() # Suppress superfluous randlib warnings about "*.a" having no symbols on MacOSX. if (APPLE) set(CMAKE_C_ARCHIVE_CREATE " Scr ") set(CMAKE_CXX_ARCHIVE_CREATE " Scr ") set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") endif() # # Gather source files. # include(GetGitRevisionDescription) git_get_exact_tag(TAG) if(NOT TAG MATCHES "NOTFOUND") if(TAG MATCHES "v[0-9].*") # For a version tag, remove the leading 'v'. CMake 2.8.0 (e.g. Ubuntu # 10.04 LTS) doesn't support -1 in string(SUBSTRING ...), so spell it # out. string(LENGTH "${TAG}" taglen) MATH(EXPR taglen "${taglen} - 1") string(SUBSTRING "${TAG}" 1 ${taglen} LDC_VERSION) else() set(LDC_VERSION "${TAG}") endif() else() get_git_head_revision(REFSPEC HASH FALSE) if(NOT HASH STREQUAL "GITDIR-NOTFOUND") # Append git hash to LDC_VERSION string(SUBSTRING "${HASH}" 0 7 LDC_VERSION_HASH) set(LDC_VERSION "${LDC_VERSION}-git-${LDC_VERSION_HASH}") # Append "-dirty" when the working copy is dirty git_describe(GIT_DIRTY --dirty) if (GIT_DIRTY MATCHES ".*-dirty") set(LDC_VERSION "${LDC_VERSION}-dirty") endif() endif() endif() message(STATUS "LDC version identifier: ${LDC_VERSION}") configure_file(driver/ldc-version.cpp.in driver/ldc-version.cpp) configure_file(driver/ldc_version.d.in driver/ldc_version.d) # Also add the header files to the build so that they are available in IDE # project files generated via CMake. file(GLOB_RECURSE FE_SRC_D dmd/*.d) file(GLOB_RECURSE FE_HDR dmd/*.h) file(GLOB_RECURSE FE_RES dmd/res/*.*) file(GLOB_RECURSE GEN_SRC gen/*.cpp gen/abi/*.cpp) file(GLOB_RECURSE GEN_HDR gen/*.h gen/abi/*.h) file(GLOB_RECURSE GEN_SRC_D gen/*.d) file(GLOB_RECURSE IR_SRC ir/*.cpp) file(GLOB_RECURSE IR_HDR ir/*.h) file(GLOB_RECURSE IR_SRC_D ir/*.d) file(GLOB_RECURSE DRV_SRC_D driver/*.d) set(DRV_SRC driver/args.cpp driver/cache.cpp driver/cl_helpers.cpp driver/cl_options.cpp driver/cl_options_instrumentation.cpp driver/cl_options_sanitizers.cpp driver/cl_options-llvm.cpp driver/codegenerator.cpp driver/configfile.cpp driver/cpreprocessor.cpp driver/dcomputecodegenerator.cpp driver/exe_path.cpp driver/targetmachine.cpp driver/toobj.cpp driver/tool.cpp driver/archiver.cpp driver/linker.cpp driver/linker-gcc.cpp driver/linker-msvc.cpp driver/main.cpp driver/plugins.cpp ) set(DRV_SRC_EXTRA ${CMAKE_BINARY_DIR}/driver/ldc-version.cpp) set(DRV_HDR driver/args.h driver/cache.h driver/cache_pruning.h driver/cl_helpers.h driver/cl_options.h driver/cl_options_instrumentation.h driver/cl_options_sanitizers.h driver/cl_options-llvm.h driver/codegenerator.h driver/configfile.h driver/dcomputecodegenerator.h driver/exe_path.h driver/ldc-version.h driver/archiver.h driver/linker.h driver/plugins.h driver/targetmachine.h driver/timetrace.h driver/toobj.h driver/tool.h ) # exclude man.d from ldc (only required by ldmd) list(REMOVE_ITEM FE_SRC_D ${PROJECT_SOURCE_DIR}/dmd/root/man.d ) set(LDC_CXX_SOURCE_FILES ${FE_HDR} ${GEN_SRC} ${GEN_HDR} ${IR_SRC} ${IR_HDR} ${DRV_SRC} ${DRV_SRC_EXTRA} ${DRV_HDR} ) set(LDC_D_SOURCE_FILES ${FE_SRC_D} ${GEN_SRC_D} ${IR_SRC_D} ${DRV_SRC_D} ) # source_group(TREE ...) requires CMake v3.8+ IF("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER 3.7) source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Source Files" FILES ${FE_SRC_D} ${GEN_SRC} ${GEN_SRC_D} ${IR_SRC} ${IR_SRC_D} ${DRV_SRC} ${DRV_SRC_D}) source_group("Source Files\\driver" FILES ${DRV_SRC_EXTRA}) source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Header Files" FILES ${FE_HDR} ${GEN_HDR} ${IR_HDR} ${DRV_HDR}) endif() # # Configure the build system to use LTO and/or PGO while building LDC # include(HandleLTOPGOBuildOptions) # # Enable Dynamic compilation if supported for this platform and LLVM version. # set(LDC_DYNAMIC_COMPILE "AUTO" CACHE STRING "Support dynamic compilation (ON|OFF). Enabled by default; not supported for LLVM >= 12.") option(LDC_DYNAMIC_COMPILE_USE_CUSTOM_PASSES "Use custom LDC passes in jit" ON) if(LDC_DYNAMIC_COMPILE STREQUAL "AUTO") if(LDC_LLVM_VER LESS 1200) set(LDC_DYNAMIC_COMPILE ON) else() # TODO: port from ORCv1 API (dropped with LLVM 12) to ORCv2 (added with LLVM 7) set(LDC_DYNAMIC_COMPILE OFF) endif() endif() message(STATUS "-- Building LDC with dynamic compilation support (LDC_DYNAMIC_COMPILE): ${LDC_DYNAMIC_COMPILE}") if(LDC_DYNAMIC_COMPILE) add_definitions(-DLDC_DYNAMIC_COMPILE) add_definitions(-DLDC_DYNAMIC_COMPILE_API_VERSION=3) endif() # # Includes, defines. # include_directories(. dmd) append("-I${PROJECT_SOURCE_DIR}" DFLAGS_LDC) append("-I${PROJECT_BINARY_DIR}" DFLAGS_LDC) append("-J${PROJECT_SOURCE_DIR}/dmd/res" DFLAGS_LDC) append("-version=IN_LLVM" DFLAGS_LDC) append("-DIN_LLVM" LDC_CXXFLAGS) append("-DOPAQUE_VTBLS" LDC_CXXFLAGS) # Predefine LDC_INSTALL_PREFIX as raw string literal, requiring shell + CMake escaping. # E.g., for CMAKE_INSTALL_PREFIX=`C:\dir with space`: # g++ "-DLDC_INSTALL_PREFIX=R\"(C:\dir with space)\"" ... # => LDC_INSTALL_PREFIX defined as `R"(C:\dir with space)"` append("\"-DLDC_INSTALL_PREFIX=R\\\"(${CMAKE_INSTALL_PREFIX})\\\"\"" LDC_CXXFLAGS) append("-DLDC_LLVM_VER=${LDC_LLVM_VER}" LDC_CXXFLAGS) append("\"-DLDC_LIBDIR_SUFFIX=R\\\"(${LIB_SUFFIX})\\\"\"" LDC_CXXFLAGS) append("-DLDC_HOST_${D_COMPILER_ID}=1" LDC_CXXFLAGS) append("-DLDC_HOST_FE_VER=${D_COMPILER_FE_VERSION}" LDC_CXXFLAGS) # If the LLVM is shared, add its lib dir to the hardcoded list used for library lookups. if(LLVM_IS_SHARED) append("\"-DLDC_LLVM_LIBDIR=R\\\"(${LLVM_LIBRARY_DIRS})\\\"\"" LDC_CXXFLAGS) endif() # # LLD integration (requires headers & libs) # if(NOT DEFINED LDC_WITH_LLD) if(EXISTS "${LLVM_INCLUDE_DIRS}/lld/Common/Driver.h") set(LDC_WITH_LLD ON) else() set(LDC_WITH_LLD OFF) endif() endif() if(LDC_WITH_LLD) append("-DLDC_WITH_LLD" LDC_CXXFLAGS) endif() message(STATUS "-- Building LDC with integrated LLD linker (LDC_WITH_LLD): ${LDC_WITH_LLD}") message(STATUS "-- Building LDC with enabled assertions (LDC_ENABLE_ASSERTIONS): ${LDC_ENABLE_ASSERTIONS}") if(LDC_ENABLE_ASSERTIONS) append("-UNDEBUG" LDC_CXXFLAGS) # avoid MSVC warning D9025 about "-DNDEBUG ... -UNDEBUG" string(REGEX REPLACE "(^| )[/-]D *NDEBUG( |$)" "\\1-UNDEBUG\\2" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") string(REGEX REPLACE "(^| )[/-]D *NDEBUG( |$)" "\\1-UNDEBUG\\2" CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}") string(REGEX REPLACE "(^| )[/-]D *NDEBUG( |$)" "\\1-UNDEBUG\\2" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") else() append("-DNDEBUG" LDC_CXXFLAGS) endif() # # Enable instrumentation for code coverage analysis # set(TEST_COVERAGE OFF CACHE BOOL "instrument compiler for code coverage analysis") if(TEST_COVERAGE) if(CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")) append("-O0 -g -fprofile-arcs -ftest-coverage" EXTRA_CXXFLAGS) list(APPEND LLVM_LDFLAGS "-lgcov") else() message(WARNING "Coverage testing is not available.") endif() endif() # # Set up the main ldc/ldc2 target. # set(LDC_LIB_LANGUAGE CXX) if(BUILD_SHARED) set(LDC_LIB_TYPE SHARED) else() set(LDC_LIB_TYPE STATIC) if("${D_COMPILER_ID}" STREQUAL "LDMD" AND D_COMPILER_FE_VERSION GREATER 2074) # Define a 'HOST_D' CMake linker language for the static LDCShared # library, using the host ldmd2 compiler ≥ v1.5 as archiver, which # supports LTO objects and cross-archiving. set(CMAKE_HOST_D_CREATE_STATIC_LIBRARY "${D_COMPILER} -lib ${D_COMPILER_FLAGS} ${DFLAGS_BASE} -of= ") set(LDC_LIB_LANGUAGE HOST_D) endif() endif() set(LDC_LIB LDCShared) set(LDC_LIB_EXTRA_SOURCES "") if(MSVC_IDE) # Visual Studio generator # Add the .d files as (Visual D) source files to this lib, so that they show up somewhere. set(LDC_LIB_EXTRA_SOURCES ${LDC_D_SOURCE_FILES}) set_property(SOURCE ${LDC_LIB_EXTRA_SOURCES} PROPERTY VS_TOOL_OVERRIDE "DCompile") # 'Clear' the original list for the custom commands below, producing ldc2.exe and ldc2-unittest.exe - # we still need a dummy .d file. set(LDC_D_SOURCE_FILES "${PROJECT_SOURCE_DIR}/dmd/root/man.d") # Mark this main library target as (bold) startup project for the generated Visual Studio solution. set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${LDC_LIB}) endif() add_library(${LDC_LIB} ${LDC_LIB_TYPE} ${LDC_CXX_SOURCE_FILES} ${LDC_LIB_EXTRA_SOURCES}) set_target_properties( ${LDC_LIB} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib${LIB_SUFFIX} ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib${LIB_SUFFIX} ARCHIVE_OUTPUT_NAME ldc LIBRARY_OUTPUT_NAME ldc RUNTIME_OUTPUT_NAME ldc COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS} ${EXTRA_CXXFLAGS}" LINKER_LANGUAGE ${LDC_LIB_LANGUAGE} LINK_FLAGS "${SANITIZE_LDFLAGS}" # Use a custom .props file to set up Visual D (import paths, predefined versions...). VS_USER_PROPS "${PROJECT_SOURCE_DIR}/cmake/VisualD.props" ) # LDFLAGS should actually be in target property LINK_FLAGS, but this works, and gets around linking problems target_link_libraries(${LDC_LIB} ${LLVM_LIBRARIES} ${LLVM_LDFLAGS}) if(WIN32) target_link_libraries(${LDC_LIB} imagehlp psapi) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(${LDC_LIB} dl) endif() if(LLVM_SPIRV_FOUND) target_link_libraries(${LDC_LIB} ${LLVM_SPIRV_LIBRARIES} ${LLVM_SPIRV_LDFLAGS}) endif() set(LDC_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDC_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX}) set(LDMD_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDMD_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX}) # Figure out how to link the main LDC executable, for which we need to take the # LLVM flags into account. set(LDC_LINKERFLAG_LIST ${SANITIZE_LDFLAGS} ${LLVM_LIBRARIES} ${LLVM_LDFLAGS}) if(MSVC) # Issue 1297 – set LDC's stack to 16 MiB for Windows builds (default: 1 MiB). list(APPEND LDC_LINKERFLAG_LIST "/STACK:16777216") # VS 2017+: Use undocumented /NOOPTTLS MS linker switch to keep on emitting # a .tls section. Required for older host druntime versions, otherwise the # GC TLS ranges are garbage starting with VS 2017 Update 15.3. if(MSVC_VERSION GREATER 1900 AND D_COMPILER_FE_VERSION LESS 2076) list(APPEND LDC_LINKERFLAG_LIST "/NOOPTTLS") endif() endif() if(LDC_WITH_LLD) if(MSVC) list(APPEND LDC_LINKERFLAG_LIST LLVMSymbolize.lib) else() set(LDC_LINKERFLAG_LIST -lLLVMSymbolize ${LDC_LINKERFLAG_LIST}) endif() set(LLD_MACHO lldMachO) if(MSVC) list(APPEND LDC_LINKERFLAG_LIST lldMinGW.lib lldCOFF.lib lldELF.lib ${LLD_MACHO}.lib lldWasm.lib lldCommon.lib) else() set(LDC_LINKERFLAG_LIST -llldMinGW -llldCOFF -llldELF -l${LLD_MACHO} -llldWasm -llldCommon ${LDC_LINKERFLAG_LIST}) endif() if(APPLE) # LLD 13.0.0 on Mac needs libxar list(APPEND LDC_LINKERFLAG_LIST -lxar) endif() endif() if(NOT DEFINED LDC_LINK_MANUALLY) if(MSVC) # Use the D host compiler for linking D executables. set(LDC_LINK_MANUALLY OFF) else() # On Unix-like systems, default to having CMake link the D executables via the C++ compiler. # (Using the D compiler needs -Xcc and -gcc support, see file BuildDExecutable.cmake.) set(LDC_LINK_MANUALLY ON) endif() endif() if(LDC_LINK_MANUALLY AND NOT DEFINED D_LINKER_ARGS) include(ExtractDMDSystemLinker) message(STATUS "Host D compiler linker program: ${D_LINKER_COMMAND}") message(STATUS "Host D compiler linker flags: ${D_LINKER_ARGS}") endif() # Plugin support if(UNIX) set(LDC_ENABLE_PLUGINS_DEFAULT ON) else() set(LDC_ENABLE_PLUGINS_DEFAULT OFF) endif() set(LDC_ENABLE_PLUGINS ${LDC_ENABLE_PLUGINS_DEFAULT} CACHE BOOL "Build LDC with plugin support (increases binary size)") if(LDC_ENABLE_PLUGINS) add_definitions(-DLDC_ENABLE_PLUGINS) if(APPLE) # Need to disable dead_strip with LDC host compilers. if("${D_COMPILER_ID}" STREQUAL "LDMD") if(LDC_LINK_MANUALLY) # suboptimal - applies to all D executables (incl. ldmd2, ldc-build-runtime, ldc-prune-cache) list(REMOVE_ITEM D_LINKER_ARGS "-Wl,-dead_strip") else() # just for ldc2 (and ldc2-unittest) append("-disable-linker-strip-dead" DFLAGS_LDC) endif() endif() elseif(UNIX) # For plugin support, we need to link with --export-dynamic on Unix. # Make sure the linker supports --export-dynamic (on Solaris it is not supported and also not needed). set(CMAKE_REQUIRED_QUIET_BAK ${CMAKE_REQUIRED_QUIET}) set(CMAKE_REQUIRED_QUIET ON) # suppress status messages CHECK_LINK_FLAG("--export-dynamic" LINKER_ACCEPTS_EXPORT_DYNAMIC_FLAG) set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_BAK}) if(LINKER_ACCEPTS_EXPORT_DYNAMIC_FLAG) set(LDC_LINKERFLAG_LIST "${LDC_LINKERFLAG_LIST};-Wl,--export-dynamic") else() message(WARNING "Linker does not accept --export-dynamic, user plugins may give missing symbol errors upon load") endif() endif() endif() message(STATUS "-- Building LDC with plugin support (LDC_ENABLE_PLUGINS): ${LDC_ENABLE_PLUGINS}") message(STATUS "-- Linking LDC with flags: ${ALTERNATIVE_MALLOC_O};${LDC_LINKERFLAG_LIST}") if(NOT WIN32 AND NOT CYGWIN) # Unify symbol visibility with LLVM to silence linker warning "direct access in function X to global # weak symbol Y means the weak symbol cannot be overridden at runtime. This was likely caused by # different translation units being compiled with different visibility settings." # See LLVM's cmake/modules/HandleLLVMOptions.cmake. check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG) if (LDC_ENABLE_PLUGINS AND NOT APPLE) # For plugins, we shouldn't apply this flag because it hides the inline methods of e.g. Visitor. On macOS it's OK to add. elseif (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG}) append("-fvisibility-inlines-hidden" LDC_CXXFLAGS) endif() endif() build_d_executable( "${LDC_EXE}" "${LDC_EXE_FULL}" "${LDC_D_SOURCE_FILES}" "${DFLAGS_BUILD_TYPE} ${DFLAGS_LDC}" "${ALTERNATIVE_MALLOC_O};${LDC_LINKERFLAG_LIST}" "${FE_RES}" "${LDC_LIB}" ${COMPILE_D_MODULES_SEPARATELY} ) if(MSVC_IDE) # the IDE generator is a multi-config one # so copy the config file into the correct bin subfolder # (different outputs no longer feasible for custom commands, so disabled) # add_custom_command(TARGET ${LDC_EXE} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/bin/${LDC_EXE}.conf $ COMMENT "Copy config file ${LDC_EXE}.conf") endif() # # LDMD # set_source_files_properties(driver/args.cpp driver/exe_path.cpp driver/ldmd.cpp driver/response.cpp PROPERTIES COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" COMPILE_DEFINITIONS LDC_EXE_NAME="${LDC_EXE_NAME}" ) add_library(LDMD_CXX_LIB ${LDC_LIB_TYPE} driver/args.cpp driver/exe_path.cpp driver/ldmd.cpp driver/response.cpp driver/args.h driver/exe_path.h) set_target_properties( LDMD_CXX_LIB PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib${LIB_SUFFIX} ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib${LIB_SUFFIX} ARCHIVE_OUTPUT_NAME ldmd LIBRARY_OUTPUT_NAME ldmd ) set(LDMD_D_SOURCE_FILES ${PROJECT_SOURCE_DIR}/dmd/root/man.d ${PROJECT_SOURCE_DIR}/driver/main.d) build_d_executable( "${LDMD_EXE}" "${LDMD_EXE_FULL}" "${LDMD_D_SOURCE_FILES}" "${DFLAGS_BUILD_TYPE}" "${LDC_LINKERFLAG_LIST}" "" "LDMD_CXX_LIB" ${COMPILE_D_MODULES_SEPARATELY} ) # Little helper. function(copy_and_rename_file source_path target_path) get_filename_component(source_name ${source_path} NAME) get_filename_component(target_dir ${target_path} DIRECTORY) file(MAKE_DIRECTORY ${target_dir}) # don't preserve source file permissions, see https://github.com/ldc-developers/ldc/issues/2337 file(COPY ${source_path} DESTINATION ${target_dir} NO_SOURCE_PERMISSIONS) file(RENAME ${target_dir}/${source_name} ${target_path}) endfunction() function(copy_and_install_llvm_library llvm_lib_path ldc_lib_name fixup_dylib) set(ldc_lib_path ${PROJECT_BINARY_DIR}/lib${LIB_SUFFIX}/${ldc_lib_name}) copy_and_rename_file(${llvm_lib_path} ${ldc_lib_path}) if (APPLE AND fixup_dylib) execute_process(COMMAND install_name_tool -id @rpath/${ldc_lib_name} ${ldc_lib_path} ERROR_VARIABLE INSTALL_NAME_TOOL_STDERR) if(${INSTALL_NAME_TOOL_STDERR} MATCHES "warning: changes being made to the file will invalidate the code signature") # Eat the warning, it's ok. elseif("${INSTALL_NAME_TOOL_STDERR}" STREQUAL "") else() message(WARNING "install_name_tool stderr: ${INSTALL_NAME_TOOL_STDERR}") endif() execute_process(COMMAND codesign --force -s - ${ldc_lib_path}) endif() install(FILES ${ldc_lib_path} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}) endfunction() # # Locate LLVM's LTO binary and use it # if(WIN32 OR LLVM_IS_SHARED) set(LDC_INSTALL_LTOPLUGIN_DEFAULT OFF) else() set(LDC_INSTALL_LTOPLUGIN_DEFAULT ON) endif() set(LDC_INSTALL_LTOPLUGIN ${LDC_INSTALL_LTOPLUGIN_DEFAULT} CACHE BOOL "Copy/install the LTO plugin from the LLVM package when available.") if (LDC_INSTALL_LTOPLUGIN) if(APPLE) set(LLVM_LTO_BINARY ${LLVM_LIBRARY_DIRS}/libLTO.dylib) set(LDC_LTO_BINARY_NAME libLTO.dylib) elseif(UNIX) set(LLVM_LTO_BINARY ${LLVM_LIBRARY_DIRS}/LLVMgold.so) set(LDC_LTO_BINARY_NAME LLVMgold-ldc.so) endif() if(EXISTS ${LLVM_LTO_BINARY}) message(STATUS "-- Including LTO linker plugin (LDC_INSTALL_LTOPLUGIN): ON (${LLVM_LTO_BINARY})") copy_and_install_llvm_library(${LLVM_LTO_BINARY} ${LDC_LTO_BINARY_NAME} TRUE) else() message(STATUS "-- Including LTO linker plugin (LDC_INSTALL_LTOPLUGIN): OFF (cannot find ${LLVM_LTO_BINARY})") endif() else() message(STATUS "-- Including LTO linker plugin (LDC_INSTALL_LTOPLUGIN): ${LDC_INSTALL_LTOPLUGIN}") endif() # # Locate ASan and other LLVM compiler-rt libraries, and copy them to our lib # folder or save that folder in the config files. Location is typically # LLVM_LIBRARY_DIRS/clang//lib// , for example # LLVM_LIBRARY_DIRS/clang/4.0.0/lib/darwin/ , but we allow the user to specify # another directory. set(COMPILER_RT_BASE_DIR "${LLVM_LIBRARY_DIRS}" CACHE PATH "Base path of compiler-rt libraries. If they in are /usr/lib/clang/17/lib/linux/libclang_rt* you should set this value to /usr/lib") # If it's different than the default it will need to be added to the config files if(COMPILER_RT_BASE_DIR STREQUAL LLVM_LIBRARY_DIRS) set(WANT_COMPILER_RT_LIBDIR_CONFIG FALSE) else() set(WANT_COMPILER_RT_LIBDIR_CONFIG TRUE) endif() set(COMPILER_RT_LIBDIR "${COMPILER_RT_BASE_DIR}/clang") if(LDC_LLVM_VER LESS 1600) set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${LLVM_VERSION_BASE_STRING}") else() set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${LLVM_VERSION_MAJOR}") endif() set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/lib") if(APPLE) set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/darwin") elseif(UNIX) set(COMPILER_RT_LIBDIR_OS_DEFAULT "x86_64-unknown-linux-gnu") set(COMPILER_RT_LIBDIR_OS "${COMPILER_RT_LIBDIR_OS_DEFAULT}" CACHE STRING "Non-Mac Posix: OS used as directory name for the compiler-rt source libraries, e.g., 'freebsd'.") set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${COMPILER_RT_LIBDIR_OS}") elseif(WIN32) set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/windows") endif() if(LLVM_IS_SHARED) set(LDC_INSTALL_LLVM_RUNTIME_LIBS_DEFAULT OFF) else() set(LDC_INSTALL_LLVM_RUNTIME_LIBS_DEFAULT ON) endif() set(LDC_INSTALL_LLVM_RUNTIME_LIBS ${LDC_INSTALL_LLVM_RUNTIME_LIBS_DEFAULT} CACHE BOOL "Copy/install LLVM compiler-rt libraries (ASan, libFuzzer, ...) from LLVM/Clang into LDC lib dir when available.") function(copy_compilerrt_lib llvm_lib_name ldc_lib_name fixup_dylib) set(llvm_lib_path ${COMPILER_RT_LIBDIR}/${llvm_lib_name}) if(EXISTS ${llvm_lib_path}) message(STATUS "-- - ${llvm_lib_path} --> ${ldc_lib_name}") copy_and_install_llvm_library(${llvm_lib_path} ${ldc_lib_name} ${fixup_dylib}) else() message(STATUS "-- - not found: ${llvm_lib_path}") endif() endfunction() message(STATUS "-- Including LLVM compiler-rt libraries (LDC_INSTALL_LLVM_RUNTIME_LIBS): ${LDC_INSTALL_LLVM_RUNTIME_LIBS}") if (LDC_INSTALL_LLVM_RUNTIME_LIBS) # Locate LLVM sanitizer runtime libraries, and copy them to our lib folder # No need to add another libdir, the default ldc one will have the libraries set(WANT_COMPILER_RT_LIBDIR_CONFIG FALSE) if(APPLE) copy_compilerrt_lib("libclang_rt.asan_osx_dynamic.dylib" "libldc_rt.asan.dylib" TRUE) copy_compilerrt_lib("libclang_rt.lsan_osx_dynamic.dylib" "libldc_rt.lsan.dylib" TRUE) copy_compilerrt_lib("libclang_rt.tsan_osx_dynamic.dylib" "libldc_rt.tsan.dylib" TRUE) copy_compilerrt_lib("libclang_rt.osx.a" "libldc_rt.builtins.a" FALSE) copy_compilerrt_lib("libclang_rt.profile_osx.a" "libldc_rt.profile.a" FALSE) copy_compilerrt_lib("libclang_rt.fuzzer_osx.a" "libldc_rt.fuzzer.a" FALSE) copy_compilerrt_lib("libclang_rt.xray_osx.a" "libldc_rt.xray.a" FALSE) copy_compilerrt_lib("libclang_rt.xray-basic_osx.a" "libldc_rt.xray-basic.a" FALSE) copy_compilerrt_lib("libclang_rt.xray-fdr_osx.a" "libldc_rt.xray-fdr.a" FALSE) copy_compilerrt_lib("libclang_rt.xray-profiling_osx.a" "libldc_rt.xray-profiling.a" FALSE) elseif(UNIX) set(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH "" CACHE STRING "Non-Mac Posix: architecture used as libname suffix for the compiler-rt source libraries, e.g., 'aarch64'.") if(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH STREQUAL "") set(compilerrt_suffix "") else() set(compilerrt_suffix "-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}") endif() copy_compilerrt_lib("libclang_rt.asan${compilerrt_suffix}.a" "libldc_rt.asan.a" FALSE) copy_compilerrt_lib("libclang_rt.lsan${compilerrt_suffix}.a" "libldc_rt.lsan.a" FALSE) copy_compilerrt_lib("libclang_rt.msan${compilerrt_suffix}.a" "libldc_rt.msan.a" FALSE) copy_compilerrt_lib("libclang_rt.tsan${compilerrt_suffix}.a" "libldc_rt.tsan.a" FALSE) copy_compilerrt_lib("libclang_rt.builtins${compilerrt_suffix}.a" "libldc_rt.builtins.a" FALSE) copy_compilerrt_lib("libclang_rt.profile${compilerrt_suffix}.a" "libldc_rt.profile.a" FALSE) copy_compilerrt_lib("libclang_rt.xray${compilerrt_suffix}.a" "libldc_rt.xray.a" FALSE) copy_compilerrt_lib("libclang_rt.fuzzer${compilerrt_suffix}.a" "libldc_rt.fuzzer.a" FALSE) copy_compilerrt_lib("libclang_rt.xray-basic${compilerrt_suffix}.a" "libldc_rt.xray-basic.a" FALSE) copy_compilerrt_lib("libclang_rt.xray-fdr${compilerrt_suffix}.a" "libldc_rt.xray-fdr.a" FALSE) copy_compilerrt_lib("libclang_rt.xray-profiling${compilerrt_suffix}.a" "libldc_rt.xray-profiling.a" FALSE) elseif(WIN32) set(compilerrt_arch_suffix "x86_64") if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(compilerrt_arch_suffix "i386") endif() copy_compilerrt_lib("clang_rt.asan-${compilerrt_arch_suffix}.lib" "ldc_rt.asan.lib" FALSE) copy_compilerrt_lib("clang_rt.lsan-${compilerrt_arch_suffix}.lib" "ldc_rt.lsan.lib" FALSE) copy_compilerrt_lib("clang_rt.builtins-${compilerrt_arch_suffix}.lib" "ldc_rt.builtins.lib" FALSE) copy_compilerrt_lib("clang_rt.profile-${compilerrt_arch_suffix}.lib" "ldc_rt.profile.lib" FALSE) copy_compilerrt_lib("clang_rt.fuzzer-${compilerrt_arch_suffix}.lib" "ldc_rt.fuzzer.lib" FALSE) endif() endif() if(WANT_COMPILER_RT_LIBDIR_CONFIG) message(STATUS "Adding ${COMPILER_RT_LIBDIR} to libdir in configuration files") set(OPTIONAL_COMPILER_RT_DIR "\n \"${COMPILER_RT_LIBDIR}\",") endif() # # Auxiliary build and test utils. # add_subdirectory(utils) # # Auxiliary tools. # add_subdirectory(tools) # # Test and runtime targets. Note that enable_testing() is order-sensitive! # enable_testing() # LDC unittest executable (D unittests only). set(LDC_UNITTEST_EXE ${LDC_EXE}-unittest) set(LDC_UNITTEST_EXE_NAME ${PROGRAM_PREFIX}${LDC_UNITTEST_EXE}${PROGRAM_SUFFIX}) set(LDC_UNITTEST_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDC_UNITTEST_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX}) build_d_executable( "${LDC_UNITTEST_EXE}" "${LDC_UNITTEST_EXE_FULL}" "${LDC_D_SOURCE_FILES}" "-g -unittest ${DFLAGS_LDC}" "${LDC_LINKERFLAG_LIST}" "" "${LDC_LIB}" ${COMPILE_D_MODULES_SEPARATELY} ) set_target_properties("${LDC_UNITTEST_EXE}" PROPERTIES EXCLUDE_FROM_ALL ON) add_test(NAME build-ldc2-unittest COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target ldc2-unittest) add_test(NAME ldc2-unittest COMMAND ${LDC_UNITTEST_EXE_FULL} --version) set_tests_properties(ldc2-unittest PROPERTIES DEPENDS build-ldc2-unittest) if(EXISTS "${PROJECT_SOURCE_DIR}/runtime/druntime/src/object.d") add_subdirectory(runtime) else() message(STATUS "Runtime file runtime/druntime/src/object.d not found, will build ldc binaries but not the standard library.") endif() if(D_VERSION EQUAL 2) add_subdirectory(tests/dmd) endif() add_subdirectory(tests) # ldc-build-runtime tool configure_file(${PROJECT_SOURCE_DIR}/runtime/ldc-build-runtime.d.in ${PROJECT_BINARY_DIR}/ldc-build-runtime.d @ONLY) set(LDC_BUILD_RUNTIME_EXE ldc-build-runtime) set(LDC_BUILD_RUNTIME_EXE_NAME ${PROGRAM_PREFIX}${LDC_BUILD_RUNTIME_EXE}${PROGRAM_SUFFIX}) set(LDC_BUILD_RUNTIME_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDC_BUILD_RUNTIME_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX}) build_d_executable( "${LDC_BUILD_RUNTIME_EXE}" "${LDC_BUILD_RUNTIME_EXE_FULL}" "${PROJECT_BINARY_DIR}/ldc-build-runtime.d" "${DFLAGS_BUILD_TYPE}" "" "${PROJECT_SOURCE_DIR}/runtime/ldc-build-runtime.d.in" "" ${COMPILE_D_MODULES_SEPARATELY} ) # # Install target. # install(PROGRAMS ${LDC_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(PROGRAMS ${LDMD_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(PROGRAMS ${LDC_BUILD_RUNTIME_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) if(${BUILD_SHARED}) # For now, only install libldc if explicitly building the shared library. # While it might theoretically be possible to use LDC as a static library # as well, for the time being this just bloats the normal packages. install(TARGETS ${LDC_LIB} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}) endif() install(FILES ${PROJECT_BINARY_DIR}/bin/${LDC_EXE}_install.conf DESTINATION ${CONF_INST_DIR} RENAME ${LDC_EXE}.conf) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(NOT DEFINED BASH_COMPLETION_COMPLETIONSDIR) find_package(bash-completion QUIET) if((NOT BASH_COMPLETION_FOUND) OR (NOT BASH_COMPLETION_PREFIX STREQUAL CMAKE_INSTALL_PREFIX)) set(BASH_COMPLETION_COMPLETIONSDIR "${CONF_INST_DIR}/bash_completion.d") if(LINUX_DISTRIBUTION_IS_GENTOO AND CMAKE_INSTALL_PREFIX STREQUAL "/usr") set(BASH_COMPLETION_COMPLETIONSDIR "/usr/share/bash-completion") endif() endif() endif() install(DIRECTORY packaging/bash_completion.d/ DESTINATION ${BASH_COMPLETION_COMPLETIONSDIR}) endif() # # Packaging # include (CMakeCPack.cmake) include (CPack) ldc-1.40.0-src/dmd/0000755000000000000000000000000014727557031012400 5ustar rootrootldc-1.40.0-src/dmd/cparse.d0000644000000000000000000063024314727557031014032 0ustar rootroot/** * Takes a token stream from the lexer, and parses it into an abstract syntax tree. * * Specification: C11 * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/cparse.d, _cparse.d) * Documentation: https://dlang.org/phobos/dmd_cparse.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/cparse.d */ module dmd.cparse; import core.stdc.stdio; import core.stdc.string : memcpy; import dmd.astenums; import dmd.errorsink; import dmd.id; import dmd.identifier; import dmd.lexer; import dmd.location; import dmd.parse; import dmd.root.array; import dmd.common.outbuffer; import dmd.root.rmem; import dmd.tokens; import dmd.typesem : size; version (LDC) private enum LDC_pre_2084 = __VERSION__ < 2084; // workaround bug with LDC < v1.14 host compilers else private enum LDC_pre_2084 = false; /*********************************************************** */ final class CParser(AST) : Parser!AST { AST.Dsymbols* symbols; // symbols declared in current scope bool addFuncName; /// add declaration of __func__ to function symbol table bool importBuiltins; /// seen use of C compiler builtins, so import __importc_builtins; private { structalign_t packalign; // current state of #pragma pack alignment // #pragma pack stack Array!Identifier* records; // identifers (or null) Array!structalign_t* packs; // parallel alignment values } /* C cannot be parsed without determining if an identifier is a type or a variable. * For expressions like `(T)-3`, is it a cast or a minus expression? * It also occurs with `typedef int (F)(); F fun;` * but to build the AST we need to distinguish `fun` being a function as opposed to a variable. * To fix, build a symbol table for the typedefs. * Symbol table of typedefs indexed by Identifier cast to void*. * 1. if an identifier is a typedef, then it will return a non-null Type * 2. if an identifier is not a typedef, then it will return null */ Array!(void*) typedefTab; /// Array of AST.Type[Identifier], typedef's indexed by Identifier /* This is passed in as a list of #define lines, as generated by the C preprocessor with the * appropriate switch to emit them. We append to it any #define's and #undef's encountered in the source * file, as cpp with the -dD embeds them in the preprocessed output file. * Once the file is parsed, then the #define's are converted to D symbols and appended to the array * of Dsymbols returned by parseModule(). */ OutBuffer* defines; extern (D) this(TARGET)(AST.Module _module, const(char)[] input, bool doDocComment, ErrorSink errorSink, const ref TARGET target, OutBuffer* defines, const CompileEnv* compileEnv) scope { const bool doUnittests = false; super(_module, input, doDocComment, errorSink, compileEnv, doUnittests); //printf("CParser.this()\n"); mod = _module; linkage = LINK.c; Ccompile = true; this.packalign.setDefault(); this.defines = defines; // Configure sizes for C `long`, `long double`, `wchar_t`, ... this.boolsize = target.boolsize; this.shortsize = target.shortsize; this.intsize = target.intsize; this.longsize = target.longsize; this.long_longsize = target.long_longsize; this.long_doublesize = target.long_doublesize; this.wchar_tsize = target.wchar_tsize; // C `char` is always unsigned in ImportC // We know that we are parsing out C, due the parent not knowing this, we have to setup tables here. charLookup = compileEnv.cCharLookupTable; } /******************************************** * Parse translation unit. * C11 6.9 * translation-unit: * external-declaration * translation-unit external-declaration * * external-declaration: * function-definition * declaration * Returns: * array of Dsymbols that were declared */ override AST.Dsymbols* parseModule() { //printf("cparseTranslationUnit()\n"); symbols = new AST.Dsymbols(); typedefTab.push(null); // C11 6.2.1-3 symbol table for "file scope" while (1) { if (token.value == TOK.endOfFile) { addDefines(); // convert #define's to Dsymbols // wrap the symbols in `extern (C) { symbols }` auto wrap = new AST.Dsymbols(); auto ld = new AST.LinkDeclaration(token.loc, LINK.c, symbols); wrap.push(ld); if (importBuiltins) { /* Seen references to C builtin functions. * Import their definitions */ auto s = new AST.Import(Loc.initial, null, Id.importc_builtins, null, false); wrap.push(s); } // end of file scope typedefTab.pop(); assert(typedefTab.length == 0); return wrap; } /* GNU Extensions * external-declaration: * simple-asm-expr ; */ if (token.value == TOK.asm_) { nextToken(); // move past asm check(TOK.leftParenthesis); if (token.value != TOK.string_) error("string literal expected for Asm Definition, not `%s`", token.toChars()); auto code = cparsePrimaryExp(); check(TOK.rightParenthesis); symbols.push(new AST.CAsmDeclaration(code)); check(TOK.semicolon); continue; } cparseDeclaration(LVL.global); } } /******************************************************************************/ /********************************* Statement Parser ***************************/ //{ /********************** * C11 6.8 * statement: * labeled-statement * compound-statement * expression-statement * selection-statement * iteration-statement * jump-statement * * Params: * flags = PSxxxx * endPtr = store location of closing brace * pEndloc = if { ... statements ... }, store location of closing brace, otherwise loc of last token of statement * Returns: * parsed statement */ AST.Statement cparseStatement(int flags, const(char)** endPtr = null, Loc* pEndloc = null) { AST.Statement s; const loc = token.loc; //printf("cparseStatement()\n"); const typedefTabLengthSave = typedefTab.length; auto symbolsSave = symbols; if (flags & ParseStatementFlags.scope_) { typedefTab.push(null); // introduce new block scope } if (!(flags & (ParseStatementFlags.scope_ | ParseStatementFlags.curlyScope))) { symbols = new AST.Dsymbols(); } switch (token.value) { case TOK.identifier: /* A leading identifier can be a declaration, label, or expression. * A quick check of the next token can disambiguate most cases. */ switch (peekNext()) { case TOK.colon: { // It's a label auto ident = token.ident; nextToken(); // advance to `:` nextToken(); // advance past `:` if (token.value == TOK.rightCurly) s = null; else if (token.value == TOK.leftCurly) s = cparseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); else s = cparseStatement(0); s = new AST.LabelStatement(loc, ident, s); break; } case TOK.dot: case TOK.arrow: case TOK.plusPlus: case TOK.minusMinus: case TOK.leftBracket: case TOK.question: case TOK.assign: case TOK.addAssign: case TOK.minAssign: case TOK.mulAssign: case TOK.divAssign: case TOK.modAssign: case TOK.andAssign: case TOK.orAssign: case TOK.xorAssign: case TOK.leftShiftAssign: case TOK.rightShiftAssign: goto Lexp; case TOK.leftParenthesis: if (auto pt = lookupTypedef(token.ident)) { if (*pt) goto Ldeclaration; } goto Lexp; // function call case TOK.semicolon: goto Lexp; default: { /* If tokens look like a declaration, assume it is one */ auto tk = &token; if (isCDeclaration(tk)) goto Ldeclaration; goto Lexp; } } break; case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.leftParenthesis: case TOK.and: case TOK.mul: case TOK.min: case TOK.add: case TOK.tilde: case TOK.not: case TOK.plusPlus: case TOK.minusMinus: case TOK.sizeof_: case TOK._Generic: case TOK._assert: Lexp: auto exp = cparseExpression(); if (token.value == TOK.identifier && exp.op == EXP.identifier) { error(token.loc, "found `%s` when expecting `;` or `=`, did you mean `%s %s = %s`?", peek(&token).toChars(), exp.toChars(), token.toChars(), peek(peek(&token)).toChars()); nextToken(); } else check(TOK.semicolon, "statement"); s = new AST.ExpStatement(loc, exp); break; // type-specifiers case TOK.void_: case TOK.char_: case TOK.int16: case TOK.int32: case TOK.int64: case TOK.__int128: case TOK.float32: case TOK.float64: case TOK.signed: case TOK.unsigned: case TOK._Bool: //case TOK._Imaginary: case TOK._Complex: case TOK.struct_: case TOK.union_: case TOK.enum_: case TOK.typeof_: // storage-class-specifiers case TOK.typedef_: case TOK.extern_: case TOK.static_: case TOK._Thread_local: case TOK.__thread: case TOK.auto_: case TOK.register: // function-specifiers case TOK.inline: case TOK._Noreturn: // type-qualifiers case TOK.const_: case TOK.volatile: case TOK.restrict: case TOK.__stdcall: // alignment-specifier case TOK._Alignas: // atomic-type-specifier or type_qualifier case TOK._Atomic: case TOK.__attribute__: case TOK.__declspec: Ldeclaration: { cparseDeclaration(LVL.local); if (symbols.length > 1) { auto as = new AST.Statements(); as.reserve(symbols.length); foreach (d; (*symbols)[]) { s = new AST.ExpStatement(loc, d); as.push(s); } s = new AST.CompoundDeclarationStatement(loc, as); symbols.setDim(0); } else if (symbols.length == 1) { auto d = (*symbols)[0]; s = new AST.ExpStatement(loc, d); symbols.setDim(0); } else s = new AST.ExpStatement(loc, cast(AST.Expression)null); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } case TOK._Static_assert: // _Static_assert ( constant-expression, string-literal ) ; s = new AST.StaticAssertStatement(cparseStaticAssert()); break; case TOK.leftCurly: { /* C11 6.8.2 * compound-statement: * { block-item-list (opt) } * * block-item-list: * block-item * block-item-list block-item * * block-item: * declaration * statement */ nextToken(); auto statements = new AST.Statements(); while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) { statements.push(cparseStatement(ParseStatementFlags.curlyScope)); } if (endPtr) *endPtr = token.ptr; endloc = token.loc; if (pEndloc) { *pEndloc = token.loc; pEndloc = null; // don't set it again } s = new AST.CompoundStatement(loc, statements); if (flags & (ParseStatementFlags.scope_ | ParseStatementFlags.curlyScope)) s = new AST.ScopeStatement(loc, s, token.loc); check(TOK.rightCurly, "compound statement"); break; } case TOK.while_: { nextToken(); check(TOK.leftParenthesis); auto condition = cparseExpression(); check(TOK.rightParenthesis); Loc endloc; auto _body = cparseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.WhileStatement(loc, condition, _body, endloc, null); break; } case TOK.semicolon: /* C11 6.8.3 null statement */ nextToken(); s = new AST.ExpStatement(loc, cast(AST.Expression)null); break; case TOK.do_: { nextToken(); auto _body = cparseStatement(ParseStatementFlags.scope_); check(TOK.while_); check(TOK.leftParenthesis); auto condition = cparseExpression(); check(TOK.rightParenthesis); check(TOK.semicolon, "terminating `;` required after do-while statement"); s = new AST.DoStatement(loc, _body, condition, token.loc); break; } case TOK.for_: { AST.Statement _init; AST.Expression condition; AST.Expression increment; nextToken(); check(TOK.leftParenthesis); if (token.value == TOK.semicolon) { _init = null; nextToken(); } else { _init = cparseStatement(0); } if (token.value == TOK.semicolon) { condition = null; nextToken(); } else { condition = cparseExpression(); check(TOK.semicolon, "`for` condition"); } if (token.value == TOK.rightParenthesis) { increment = null; nextToken(); } else { increment = cparseExpression(); check(TOK.rightParenthesis); } Loc endloc; auto _body = cparseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.ForStatement(loc, _init, condition, increment, _body, endloc); break; } case TOK.if_: { nextToken(); check(TOK.leftParenthesis); auto condition = cparseExpression(); check(TOK.rightParenthesis); auto ifbody = cparseStatement(ParseStatementFlags.scope_); AST.Statement elsebody; if (token.value == TOK.else_) { nextToken(); elsebody = cparseStatement(ParseStatementFlags.scope_); } else elsebody = null; if (condition && ifbody) s = new AST.IfStatement(loc, null, condition, ifbody, elsebody, token.loc); else s = null; // don't propagate parsing errors break; } case TOK.else_: error("found `else` without a corresponding `if` statement"); goto Lerror; case TOK.switch_: { nextToken(); check(TOK.leftParenthesis); auto condition = cparseExpression(); check(TOK.rightParenthesis); auto _body = cparseStatement(ParseStatementFlags.scope_); s = new AST.SwitchStatement(loc, null, condition, _body, false, token.loc); break; } case TOK.case_: { nextToken(); auto exp = cparseAssignExp(); AST.Expression expHigh; if (token.value == TOK.dotDotDot) { /* Case Ranges https://gcc.gnu.org/onlinedocs/gcc/Case-Ranges.html */ nextToken(); expHigh = cparseAssignExp(); } check(TOK.colon); if (flags & ParseStatementFlags.curlyScope) { auto statements = new AST.Statements(); while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly) { auto cur = cparseStatement(ParseStatementFlags.curlyScope); statements.push(cur); // https://issues.dlang.org/show_bug.cgi?id=21739 // Stop at the last break s.t. the following non-case statements are // not merged into the current case. This can happen for // case 1: ... break; // debug { case 2: ... } if (cur && cur.isBreakStatement()) break; } s = new AST.CompoundStatement(loc, statements); } else { s = cparseStatement(0); } s = new AST.ScopeStatement(loc, s, token.loc); if (expHigh) s = new AST.CaseRangeStatement(loc, exp, expHigh, s); else s = new AST.CaseStatement(loc, exp, s); break; } case TOK.default_: { nextToken(); check(TOK.colon); if (flags & ParseStatementFlags.curlyScope) { auto statements = new AST.Statements(); while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly) { statements.push(cparseStatement(ParseStatementFlags.curlyScope)); } s = new AST.CompoundStatement(loc, statements); } else s = cparseStatement(0); s = new AST.ScopeStatement(loc, s, token.loc); s = new AST.DefaultStatement(loc, s); break; } case TOK.return_: { /* return ; * return expression ; */ nextToken(); auto exp = token.value == TOK.semicolon ? null : cparseExpression(); check(TOK.semicolon, "`return` statement"); s = new AST.ReturnStatement(loc, exp); break; } case TOK.break_: nextToken(); check(TOK.semicolon, "`break` statement"); s = new AST.BreakStatement(loc, null); break; case TOK.continue_: nextToken(); check(TOK.semicolon, "`continue` statement"); s = new AST.ContinueStatement(loc, null); break; case TOK.goto_: { Identifier ident; nextToken(); if (token.value == TOK.identifier) { ident = token.ident; nextToken(); } else if (token.value == TOK.mul) { /* https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html */ error("`goto *` computed goto extension is not supported"); ident = null; cparseUnaryExp(); // parse and throw away } else { error("identifier expected following `goto`"); ident = null; } s = new AST.GotoStatement(loc, ident); check(TOK.semicolon, "`goto` statement"); break; } case TOK.asm_: switch (peekNext()) { case TOK.goto_: case TOK.inline: case TOK.volatile: case TOK.leftParenthesis: s = cparseGnuAsm(); break; default: // ImportC extensions: parse as a D asm block. s = parseAsm(compileEnv.masm); break; } break; default: error("found `%s` instead of statement", token.toChars()); goto Lerror; Lerror: panic(); if (token.value == TOK.semicolon) nextToken(); s = null; break; } if (pEndloc) *pEndloc = prevloc; symbols = symbolsSave; typedefTab.setDim(typedefTabLengthSave); return s; } //} /*******************************************************************************/ /********************************* Expression Parser ***************************/ //{ /************** * C11 6.5.17 * expression: * assignment-expression * expression , assignment-expression */ AST.Expression cparseExpression() { auto loc = token.loc; //printf("cparseExpression() loc = %d\n", loc.linnum); auto e = cparseAssignExp(); while (token.value == TOK.comma) { nextToken(); auto e2 = cparseAssignExp(); e = new AST.CommaExp(loc, e, e2, false); loc = token.loc; } return e; } /********************* * C11 6.5.1 * primary-expression: * identifier * constant * string-literal * ( expression ) * generic-selection * __builtin_va_arg(assign_expression, type) */ AST.Expression cparsePrimaryExp() { AST.Expression e; const loc = token.loc; //printf("parsePrimaryExp(): loc = %d\n", loc.linnum); switch (token.value) { case TOK.identifier: const id = token.ident.toString(); if (id.length > 2 && id[0] == '_' && id[1] == '_') // leading double underscore { if (token.ident is Id.__func__) { addFuncName = true; // implicitly declare __func__ } else if (token.ident is Id.builtin_va_arg) { e = cparseBuiltin_va_arg(); break; } else importBuiltins = true; // probably one of those compiler extensions } e = new AST.IdentifierExp(loc, token.ident); nextToken(); break; case TOK.charLiteral: case TOK.int32Literal: e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint32); nextToken(); break; case TOK.wcharLiteral: e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tuns16); nextToken(); break; case TOK.dcharLiteral: case TOK.uns32Literal: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns32); nextToken(); break; case TOK.int64Literal: e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint64); nextToken(); break; case TOK.uns64Literal: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns64); nextToken(); break; case TOK.float32Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat32); nextToken(); break; case TOK.float64Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat64); nextToken(); break; case TOK.float80Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat80); nextToken(); break; case TOK.imaginary32Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary32); nextToken(); break; case TOK.imaginary64Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary64); nextToken(); break; case TOK.imaginary80Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary80); nextToken(); break; case TOK.string_: { // cat adjacent strings auto s = token.ustring; auto len = token.len; auto postfix = token.postfix; while (1) { nextToken(); if (token.value == TOK.string_) { if (token.postfix) { if (token.postfix != postfix) error(token.loc, "mismatched string literal postfixes `'%c'` and `'%c'`", postfix, token.postfix); postfix = token.postfix; } const len1 = len; const len2 = token.len; len = len1 + len2; auto s2 = cast(char*)mem.xmalloc_noscan(len * char.sizeof); memcpy(s2, s, len1 * char.sizeof); memcpy(s2 + len1, token.ustring, len2 * char.sizeof); s = s2; } else break; } e = new AST.StringExp(loc, s[0 .. len], len, 1, postfix); break; } case TOK.leftParenthesis: nextToken(); if (token.value == TOK.leftCurly) e = cparseStatementExpression(); // gcc extension else e = cparseExpression(); check(TOK.rightParenthesis); break; case TOK._Generic: e = cparseGenericSelection(); break; case TOK._assert: // __check(assign-exp) extension nextToken(); check(TOK.leftParenthesis, "`__check`"); e = parseAssignExp(); check(TOK.rightParenthesis); e = new AST.AssertExp(loc, e, null); break; default: error("expression expected, not `%s`", token.toChars()); // Anything for e, as long as it's not NULL e = new AST.IntegerExp(loc, 0, AST.Type.tint32); nextToken(); break; } return e; } /********************************* * C11 6.5.2 * postfix-expression: * primary-expression * postfix-expression [ expression ] * postfix-expression ( argument-expression-list (opt) ) * postfix-expression . identifier * postfix-expression -> identifier * postfix-expression ++ * postfix-expression -- * ( type-name ) { initializer-list } * ( type-name ) { initializer-list , } * * argument-expression-list: * assignment-expression * argument-expression-list , assignment-expression */ private AST.Expression cparsePostfixExp(AST.Expression e) { e = cparsePrimaryExp(); return cparsePostfixOperators(e); } /******************************** * C11 6.5.2 * Parse a series of operators for a postfix expression after already parsing * a primary-expression or compound literal expression. * Params: * e = parsed primary or compound literal expression * Returns: * parsed postfix expression */ private AST.Expression cparsePostfixOperators(AST.Expression e) { while (1) { const loc = token.loc; switch (token.value) { case TOK.dot: nextToken(); if (token.value == TOK.identifier) { Identifier id = token.ident; e = new AST.DotIdExp(loc, e, id); break; } error("identifier expected following `.`, not `%s`", token.toChars()); break; case TOK.arrow: nextToken(); if (token.value == TOK.identifier) { Identifier id = token.ident; auto die = new AST.DotIdExp(loc, e, id); die.arrow = true; e = die; break; } error("identifier expected following `->`, not `%s`", token.toChars()); break; case TOK.plusPlus: e = new AST.PostExp(EXP.plusPlus, loc, e); break; case TOK.minusMinus: e = new AST.PostExp(EXP.minusMinus, loc, e); break; case TOK.leftParenthesis: e = new AST.CallExp(loc, e, cparseArguments()); continue; case TOK.leftBracket: { // array dereferences: // array[index] AST.Expression index; auto arguments = new AST.Expressions(); inBrackets++; nextToken(); index = cparseAssignExp(); arguments.push(index); check(TOK.rightBracket); inBrackets--; e = new AST.ArrayExp(loc, e, arguments); continue; } default: return e; } nextToken(); } } /************************ * C11 6.5.3 * unary-expression: * postfix-expression * ++ unary-expression * -- unary-expression * unary-operator cast-expression * sizeof unary-expression * sizeof ( type-name ) * _Alignof ( type-name ) * * unary-operator: * & * + - ~ ! */ private AST.Expression cparseUnaryExp() { AST.Expression e; const loc = token.loc; switch (token.value) { case TOK.plusPlus: nextToken(); // Parse `++` as an unary operator so that cast expressions only give // an error for being non-lvalues. e = cparseCastExp(); e = new AST.PreExp(EXP.prePlusPlus, loc, e); break; case TOK.minusMinus: nextToken(); // Parse `--` as an unary operator, same as prefix increment. e = cparseCastExp(); e = new AST.PreExp(EXP.preMinusMinus, loc, e); break; case TOK.and: nextToken(); e = cparseCastExp(); e = new AST.AddrExp(loc, e); break; case TOK.mul: nextToken(); e = cparseCastExp(); e = new AST.PtrExp(loc, e); break; case TOK.min: nextToken(); e = cparseCastExp(); e = new AST.NegExp(loc, e); break; case TOK.add: nextToken(); e = cparseCastExp(); e = new AST.UAddExp(loc, e); break; case TOK.not: nextToken(); e = cparseCastExp(); e = new AST.NotExp(loc, e); break; case TOK.tilde: nextToken(); e = cparseCastExp(); e = new AST.ComExp(loc, e); break; case TOK.sizeof_: { nextToken(); if (token.value == TOK.leftParenthesis) { auto tk = peek(&token); if (isTypeName(tk)) { /* Expression may be either be requesting the sizeof a type-name * or a compound literal, which requires checking whether * the next token is leftCurly */ nextToken(); auto t = cparseTypeName(); check(TOK.rightParenthesis); if (token.value == TOK.leftCurly) { // ( type-name ) { initializer-list } auto ci = cparseInitializer(); e = new AST.CompoundLiteralExp(loc, t, ci); e = cparsePostfixOperators(e); } else { // ( type-name ) e = new AST.TypeExp(loc, t); } } else { // must be an expression e = cparseUnaryExp(); } } else { //C11 6.5.3 e = cparseUnaryExp(); } e = new AST.DotIdExp(loc, e, Id.__sizeof); break; } case TOK.andAnd: /* https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html */ error("unary `&&` computed goto extension is not supported"); nextToken(); e = cparseCastExp(); break; case TOK._Alignof: { nextToken(); check(TOK.leftParenthesis); auto t = cparseTypeName(); check(TOK.rightParenthesis); e = new AST.TypeExp(loc, t); e = new AST.DotIdExp(loc, e, Id.__xalignof); break; } default: e = cparsePostfixExp(e); break; } assert(e); return e; } /************** * C11 6.5.4 * cast-expression * unary-expression * ( type-name ) cast-expression */ private AST.Expression cparseCastExp() { if (token.value == TOK.leftParenthesis) { //printf("cparseCastExp()\n"); auto tk = peek(&token); bool iscast; bool isexp; if (tk.value == TOK.identifier) { iscast = isTypedef(tk.ident); isexp = !iscast; } if (isexp) { // ( identifier ) is an expression return cparseUnaryExp(); } // If ( type-name ) auto pt = &token; if (isCastExpression(pt)) { // Expression may be either a cast or a compound literal, which // requires checking whether the next token is leftCurly const loc = token.loc; nextToken(); auto t = cparseTypeName(); check(TOK.rightParenthesis); pt = &token; if (token.value == TOK.leftCurly) { // C11 6.5.2.5 ( type-name ) { initializer-list } auto ci = cparseInitializer(); auto ce = new AST.CompoundLiteralExp(loc, t, ci); return cparsePostfixOperators(ce); } if (iscast) { // ( type-name ) cast-expression auto ce = cparseCastExp(); return new AST.CastExp(loc, ce, t); } if (t.isTypeIdentifier() && isexp && token.value == TOK.leftParenthesis && !isCastExpression(pt)) { /* (t)(...)... might be a cast expression or a function call, * with different grammars: a cast would be cparseCastExp(), * a function call would be cparsePostfixExp(CallExp(cparseArguments())). * We can't know until t is known. So, parse it as a function call * and let semantic() rewrite the AST as a CastExp if it turns out * to be a type. */ auto ie = new AST.IdentifierExp(loc, t.isTypeIdentifier().ident); ie.parens = true; // let semantic know it might be a CastExp AST.Expression e = new AST.CallExp(loc, ie, cparseArguments()); return cparsePostfixOperators(e); } // ( type-name ) cast-expression auto ce = cparseCastExp(); return new AST.CastExp(loc, ce, t); } } return cparseUnaryExp(); } /************** * C11 6.5.5 * multiplicative-expression * cast-expression * multiplicative-expression * cast-expression * multiplicative-expression / cast-expression * multiplicative-expression % cast-expression */ private AST.Expression cparseMulExp() { const loc = token.loc; auto e = cparseCastExp(); while (1) { switch (token.value) { case TOK.mul: nextToken(); auto e2 = cparseCastExp(); e = new AST.MulExp(loc, e, e2); continue; case TOK.div: nextToken(); auto e2 = cparseCastExp(); e = new AST.DivExp(loc, e, e2); continue; case TOK.mod: nextToken(); auto e2 = cparseCastExp(); e = new AST.ModExp(loc, e, e2); continue; default: break; } break; } return e; } /************** * C11 6.5.6 * additive-expression * multiplicative-expression * additive-expression + multiplicative-expression * additive-expression - multiplicative-expression */ private AST.Expression cparseAddExp() { const loc = token.loc; auto e = cparseMulExp(); while (1) { switch (token.value) { case TOK.add: nextToken(); auto e2 = cparseMulExp(); e = new AST.AddExp(loc, e, e2); continue; case TOK.min: nextToken(); auto e2 = cparseMulExp(); e = new AST.MinExp(loc, e, e2); continue; default: break; } break; } return e; } /************** * C11 6.5.7 * shift-expression * additive-expression * shift-expression << additive-expression * shift-expression >> additive-expression */ private AST.Expression cparseShiftExp() { const loc = token.loc; auto e = cparseAddExp(); while (1) { switch (token.value) { case TOK.leftShift: nextToken(); auto e2 = cparseAddExp(); e = new AST.ShlExp(loc, e, e2); continue; case TOK.rightShift: nextToken(); auto e2 = cparseAddExp(); e = new AST.ShrExp(loc, e, e2); continue; default: break; } break; } return e; } /************** * C11 6.5.8 * relational-expression * shift-expression * relational-expression < shift-expression * relational-expression > shift-expression * relational-expression <= shift-expression * relational-expression >= shift-expression */ private AST.Expression cparseRelationalExp() { const loc = token.loc; auto e = cparseShiftExp(); EXP op = EXP.reserved; switch (token.value) { case TOK.lessThan: op = EXP.lessThan; goto Lcmp; case TOK.lessOrEqual: op = EXP.lessOrEqual; goto Lcmp; case TOK.greaterThan: op = EXP.greaterThan; goto Lcmp; case TOK.greaterOrEqual: op = EXP.greaterOrEqual; goto Lcmp; Lcmp: nextToken(); auto e2 = cparseShiftExp(); e = new AST.CmpExp(op, loc, e, e2); break; default: break; } return e; } /************** * C11 6.5.9 * equality-expression * relational-expression * equality-expression == relational-expression * equality-expression != relational-expression */ private AST.Expression cparseEqualityExp() { const loc = token.loc; auto e = cparseRelationalExp(); EXP op = EXP.reserved; switch (token.value) { case TOK.equal: op = EXP.equal; goto Lequal; case TOK.notEqual: op = EXP.notEqual; goto Lequal; Lequal: nextToken(); auto e2 = cparseRelationalExp(); e = new AST.EqualExp(op, loc, e, e2); break; default: break; } return e; } /************** * C11 6.5.10 * AND-expression * equality-expression * AND-expression & equality-expression */ private AST.Expression cparseAndExp() { Loc loc = token.loc; auto e = cparseEqualityExp(); while (token.value == TOK.and) { nextToken(); auto e2 = cparseEqualityExp(); e = new AST.AndExp(loc, e, e2); loc = token.loc; } return e; } /************** * C11 6.5.11 * exclusive-OR-expression * AND-expression * exclusive-OR-expression ^ AND-expression */ private AST.Expression cparseXorExp() { const loc = token.loc; auto e = cparseAndExp(); while (token.value == TOK.xor) { nextToken(); auto e2 = cparseAndExp(); e = new AST.XorExp(loc, e, e2); } return e; } /************** * C11 6.5.12 * inclusive-OR-expression * exclusive-OR-expression * inclusive-OR-expression | exclusive-OR-expression */ private AST.Expression cparseOrExp() { const loc = token.loc; auto e = cparseXorExp(); while (token.value == TOK.or) { nextToken(); auto e2 = cparseXorExp(); e = new AST.OrExp(loc, e, e2); } return e; } /************** * C11 6.5.13 * logical-AND-expression * inclusive-OR-expression * logical-AND-expression && inclusive-OR-expression */ private AST.Expression cparseAndAndExp() { const loc = token.loc; auto e = cparseOrExp(); while (token.value == TOK.andAnd) { nextToken(); auto e2 = cparseOrExp(); e = new AST.LogicalExp(loc, EXP.andAnd, e, e2); } return e; } /************** * C11 6.5.14 * logical-OR-expression * logical-AND-expression * logical-OR-expression || logical-AND-expression */ private AST.Expression cparseOrOrExp() { const loc = token.loc; auto e = cparseAndAndExp(); while (token.value == TOK.orOr) { nextToken(); auto e2 = cparseAndAndExp(); e = new AST.LogicalExp(loc, EXP.orOr, e, e2); } return e; } /************** * C11 6.5.15 * conditional-expression: * logical-OR-expression * logical-OR-expression ? expression : conditional-expression */ private AST.Expression cparseCondExp() { const loc = token.loc; auto e = cparseOrOrExp(); if (token.value == TOK.question) { nextToken(); auto e1 = cparseExpression(); check(TOK.colon); auto e2 = cparseCondExp(); e = new AST.CondExp(loc, e, e1, e2); } return e; } /************** * C11 6.5.16 * assignment-expression: * conditional-expression * unary-expression assignment-operator assignment-expression * * assignment-operator: * = *= /= %= += -= <<= >>= &= ^= |= */ AST.Expression cparseAssignExp() { AST.Expression e; e = cparseCondExp(); // constrain it to being unary-expression in semantic pass if (e is null) return e; const loc = token.loc; switch (token.value) { case TOK.assign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.AssignExp(loc, e, e2); break; case TOK.addAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.AddAssignExp(loc, e, e2); break; case TOK.minAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.MinAssignExp(loc, e, e2); break; case TOK.mulAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.MulAssignExp(loc, e, e2); break; case TOK.divAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.DivAssignExp(loc, e, e2); break; case TOK.modAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.ModAssignExp(loc, e, e2); break; case TOK.andAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.AndAssignExp(loc, e, e2); break; case TOK.orAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.OrAssignExp(loc, e, e2); break; case TOK.xorAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.XorAssignExp(loc, e, e2); break; case TOK.leftShiftAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.ShlAssignExp(loc, e, e2); break; case TOK.rightShiftAssign: nextToken(); auto e2 = cparseAssignExp(); e = new AST.ShrAssignExp(loc, e, e2); break; default: break; } return e; } /*********************** * C11 6.5.1.1 * _Generic ( assignment-expression, generic-assoc-list ) * * generic-assoc-list: * generic-association * generic-assoc-list generic-association * * generic-association: * type-name : assignment-expression * default : assignment-expression */ private AST.Expression cparseGenericSelection() { const loc = token.loc; nextToken(); check(TOK.leftParenthesis); auto cntlExp = cparseAssignExp(); check(TOK.comma); auto types = new AST.Types(); auto exps = new AST.Expressions(); bool sawDefault; while (1) { AST.Type t; if (token.value == TOK.default_) { nextToken(); if (sawDefault) error("only one `default` allowed in generic-assoc-list"); sawDefault = true; t = null; } else t = cparseTypeName(); types.push(t); check(TOK.colon); auto e = cparseAssignExp(); exps.push(e); if (token.value == TOK.rightParenthesis || token.value == TOK.endOfFile) break; check(TOK.comma); } check(TOK.rightParenthesis); return new AST.GenericExp(loc, cntlExp, types, exps); } /*********************** * C11 6.6 Constant expressions * constant-expression: * conditional-expression */ private AST.Expression cparseConstantExp() { return cparseAssignExp(); } /***************************** * gcc extension: * type __builtin_va_arg(assign-expression, type) * Rewrite as `va_arg` template from `core.stdc.stdarg`: * va_arg!(type)(assign-expression); * Lexer is on `__builtin_va_arg` */ private AST.Expression cparseBuiltin_va_arg() { importBuiltins = true; // need core.stdc.stdarg nextToken(); check(TOK.leftParenthesis); auto arguments = new AST.Expressions(); auto arg = cparseAssignExp(); arguments.push(arg); check(TOK.comma); auto t = cparseTypeName(); auto tiargs = new AST.Objects(); tiargs.push(t); const loc = loc; auto ti = new AST.TemplateInstance(loc, Id.va_arg, tiargs); auto tie = new AST.ScopeExp(loc, ti); AST.Expression e = new AST.CallExp(loc, tie, arguments); check(TOK.rightParenthesis); return e; } /***************************** * gcc extension: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html * Represent as a function literal, then call the function literal. * Parser is on opening curly brace. */ private AST.Expression cparseStatementExpression() { AST.ParameterList parameterList; StorageClass stc = 0; const loc = token.loc; auto symbolsSave = symbols; symbols = new AST.Dsymbols(); typedefTab.push(null); auto fbody = cparseStatement(ParseStatementFlags.scope_); typedefTab.pop(); // end of function scope symbols = symbolsSave; // Rewrite last ExpStatement (if there is one) as a ReturnStatement auto ss = fbody.isScopeStatement(); auto cs = ss.statement.isCompoundStatement(); assert(cs); if (const len = (*cs.statements).length) { auto s = (*cs.statements)[len - 1]; if (s) // error recovery should be with ErrorStatement, not null { if (auto es = s.isExpStatement()) (*cs.statements)[len - 1] = new AST.ReturnStatement(es.loc, es.exp); } } auto tf = new AST.TypeFunction(parameterList, null, LINK.d, stc); auto fd = new AST.FuncLiteralDeclaration(loc, token.loc, tf, TOK.delegate_, null, null, 0); fd.fbody = fbody; auto fe = new AST.FuncExp(loc, fd); auto args = new AST.Expressions(); auto e = new AST.CallExp(loc, fe, args); // call the function literal return e; } //} /********************************************************************************/ /********************************* Declaration Parser ***************************/ //{ /************************************* * C11 6.7 * declaration: * declaration-specifiers init-declarator-list (opt) ; * static_assert-declaration * * init-declarator-list: * init-declarator * init-declarator-list , init-declarator * * init-declarator: * declarator * declarator = initializer * * Params: * level = declaration context */ void cparseDeclaration(LVL level) { //printf("cparseDeclaration(level = %d)\n", level); if (token.value == TOK._Static_assert) { auto s = cparseStaticAssert(); symbols.push(s); return; } if (token.value == TOK.__pragma) { uupragmaDirective(scanloc); return; } if (token.value == TOK._import) // import declaration extension { auto a = parseImport(); if (a && a.length) symbols.append(a); return; } const typedefTabLengthSave = typedefTab.length; auto symbolsSave = symbols; Specifier specifier; specifier.packalign = this.packalign; auto tspec = cparseDeclarationSpecifiers(level, specifier); AST.Dsymbol declareTag(AST.TypeTag tt, ref Specifier specifier) { /* `struct tag;` and `struct tag { ... };` * always result in a declaration in the current scope */ auto stag = (tt.tok == TOK.struct_) ? new AST.StructDeclaration(tt.loc, tt.id, false) : (tt.tok == TOK.union_) ? new AST.UnionDeclaration(tt.loc, tt.id) : new AST.EnumDeclaration(tt.loc, tt.id, tt.base); if (!tt.packalign.isUnknown()) { // saw `struct __declspec(align(N)) Tag ...` auto st = stag.isStructDeclaration(); st.alignment = tt.packalign; } stag.members = tt.members; tt.members = null; if (!symbols) symbols = new AST.Dsymbols(); auto stags = applySpecifier(stag, specifier); symbols.push(stags); return stags; } /* If a declarator does not follow, it is unnamed */ if (token.value == TOK.semicolon) { if (!tspec) { nextToken(); return; // accept empty declaration as an extension } if (auto ti = tspec.isTypeIdentifier()) { // C11 6.7.2-2 error("type-specifier missing for declaration of `%s`", ti.ident.toChars()); nextToken(); return; } nextToken(); auto tt = tspec.isTypeTag(); if (!tt || !tt.id && (tt.tok == TOK.struct_ || tt.tok == TOK.union_)) return; // legal but meaningless empty declaration, ignore it auto stags = declareTag(tt, specifier); if (0 && tt.tok == TOK.enum_) // C11 proscribes enums with no members, but we allow it { if (!tt.members) error(tt.loc, "`enum %s` has no members", stags.toChars()); } return; } if (!tspec) { error("no type for declarator before `%s`", token.toChars()); panic(); nextToken(); return; } if (tspec && specifier.mod & MOD.xconst) { tspec = toConst(tspec); specifier.mod &= ~MOD.xnone; // 'used' it } void scanPastSemicolon() { while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); } if (token.value == TOK.assign && tspec && tspec.isTypeIdentifier()) { /* C11 6.7.2-2 * Special check for `const b = 1;` because some compilers allow it */ error("type-specifier omitted for declaration of `%s`", tspec.isTypeIdentifier().ident.toChars()); return scanPastSemicolon(); } bool first = true; while (1) { Identifier id; AST.StringExp asmName; auto dt = cparseDeclarator(DTR.xdirect, tspec, id, specifier); if (!dt) { panic(); nextToken(); break; // error recovery } /* GNU Extensions * init-declarator: * declarator simple-asm-expr (opt) gnu-attributes (opt) * declarator simple-asm-expr (opt) gnu-attributes (opt) = initializer * * Clang also allows simple-asm-expr after gnu-attributes. */ while (1) { if (token.value == TOK.asm_) { asmName = cparseGnuAsmLabel(); /* This is a data definition, there cannot now be a * function definition. */ first = false; } else if (token.value == TOK.__attribute__) cparseGnuAttributes(specifier); else break; } switch (token.value) { case TOK.assign: case TOK.comma: case TOK.semicolon: /* This is a data definition, there cannot now be a * function definition. */ first = false; break; default: break; } if (specifier.alignExps && dt.isTypeFunction()) error("no alignment-specifier for function declaration"); // C11 6.7.5-2 if (specifier.alignExps && specifier.scw == SCW.xregister) error("no alignment-specifier for `register` storage class"); // C11 6.7.5-2 /* C11 6.9.1 Function Definitions * function-definition: * declaration-specifiers declarator declaration-list (opt) compound-statement * * declaration-list: * declaration * declaration-list declaration */ auto t = &token; if (first && // first declarator id && dt.isTypeFunction() && // function type not inherited from a typedef isDeclarationList(t) && // optional declaration-list level == LVL.global && // function definitions only at global scope t.value == TOK.leftCurly) // start of compound-statement { auto s = cparseFunctionDefinition(id, dt.isTypeFunction(), specifier); typedefTab.setDim(typedefTabLengthSave); symbols = symbolsSave; if (specifier.mod & MOD.x__stdcall) { // If this function is __stdcall, wrap it in a LinkDeclaration so that // it's extern(Windows) when imported in D. auto decls = new AST.Dsymbols(1); (*decls)[0] = s; s = new AST.LinkDeclaration(s.loc, LINK.windows, decls); } symbols.push(s); return; } AST.Dsymbol s = null; typedefTab.setDim(typedefTabLengthSave); symbols = symbolsSave; if (!symbols) symbols = new AST.Dsymbols; // lazilly create it if (level != LVL.global && !tspec && !specifier.scw && !specifier.mod) error("declaration-specifier-seq required"); else if (specifier.scw == SCW.xtypedef) { if (token.value == TOK.assign) error("no initializer for typedef declaration"); if (specifier.alignExps) error("no alignment-specifier for typedef declaration"); // C11 6.7.5-2 if (specifier.vector_size) { auto length = new AST.IntegerExp(token.loc, specifier.vector_size / dt.size(), AST.Type.tuns32); auto tsa = new AST.TypeSArray(dt, length); dt = new AST.TypeVector(tsa); specifier.vector_size = 0; // used it up } bool isalias = true; Identifier idt; if (auto ts = dt.isTypeStruct()) { if (ts.sym.isAnonymous()) { // This is a typedef for an anonymous struct-or-union. // Directly set the ident for the struct-or-union. ts.sym.ident = id; isalias = false; } idt = ts.sym.ident; } else if (auto te = dt.isTypeEnum()) { if (te.sym.isAnonymous()) { // This is a typedef for an anonymous enum. te.sym.ident = id; isalias = false; } idt = te.sym.ident; } else if (auto tt = dt.isTypeTag()) { if (!tt.id && id) /* This applies for enums declared as * typedef enum {A} E; */ tt.id = id; Specifier spec; declareTag(tt, spec); idt = tt.id; } if (isalias) { //printf("AliasDeclaration %s %s\n", id.toChars(), dt.toChars()); auto ad = new AST.AliasDeclaration(token.loc, id, dt); if (id == idt) ad.adFlags |= ad.hidden; // do not print when generating .di files s = ad; } insertTypedefToTypedefTab(id, dt); // remember typedefs } else if (id) { if (auto tt = dt.isTypeTag()) { if (tt.members && (tt.id || tt.tok == TOK.enum_)) { Specifier spec; declareTag(tt, spec); } } if (level == LVL.prototype) break; // declared later as Parameter, not VarDeclaration if (dt.ty == AST.Tvoid) error("`void` has no value"); AST.Initializer initializer; bool hasInitializer; if (token.value == TOK.assign) { nextToken(); hasInitializer = true; initializer = cparseInitializer(); } // declare the symbol assert(id); if (isFunctionTypedef(dt)) { if (hasInitializer) error("no initializer for function declaration"); if (specifier.scw & SCW.x_Thread_local) error("functions cannot be `_Thread_local`"); // C11 6.7.1-4 StorageClass stc = specifiersToSTC(level, specifier); stc &= ~STC.gshared; // no gshared functions auto fd = new AST.FuncDeclaration(token.loc, Loc.initial, id, stc, dt, specifier.noreturn); specifiersToFuncDeclaration(fd, specifier); s = fd; } else { // Give non-extern variables an implicit void initializer // if one has not been explicitly set. if (!hasInitializer && !(specifier.scw & (SCW.xextern | SCW.xstatic | SCW.x_Thread_local) || level == LVL.global)) initializer = new AST.VoidInitializer(token.loc); auto vd = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(level, specifier)); specifiersToVarDeclaration(vd, specifier); s = vd; } if (level != LVL.global) insertIdToTypedefTab(id); // non-typedef declarations can hide typedefs in outer scopes } if (s !is null) { // Saw `asm("name")` in the function, type, or variable definition. // This is equivalent to `pragma(mangle, "name")` in D if (asmName) { /* https://issues.dlang.org/show_bug.cgi?id=23012 Ideally this would be translated to a pragma(mangle) decl. This is not possible because ImportC symbols are (currently) merged before semantic analysis is performed, so the pragma(mangle) never effects any change on the declarations it pertains too. Writing to mangleOverride directly avoids this, and is possible because C only a StringExp is allowed unlike a full fat pragma(mangle) which is more liberal. */ if (auto p = s.isDeclaration()) { auto str = asmName.peekString(); p.mangleOverride = str; // p.adFlags |= AST.VarDeclaration.nounderscore; p.adFlags |= 4; // cannot get above line to compile on Ubuntu } } s = applySpecifier(s, specifier); if (level == LVL.local || (specifier.mod & MOD.x__stdcall)) { // Wrap the declaration in `extern (C/Windows) { declaration }` // Necessary for function pointers, but harmless to apply to all. auto decls = new AST.Dsymbols(1); (*decls)[0] = s; const lkg = specifier.mod & MOD.x__stdcall ? LINK.windows : linkage; s = new AST.LinkDeclaration(s.loc, lkg, decls); } symbols.push(s); } if (level == LVL.global && !id) error("expected identifier for declaration"); first = false; switch (token.value) { case TOK.identifier: if (s) { error(token.loc, "missing comma or semicolon after declaration of `%s`, found `%s` instead", s.toChars(), token.toChars()); goto Lend; } goto default; case TOK.semicolon: nextToken(); return; case TOK.comma: if (!symbolsSave) symbolsSave = symbols; nextToken(); break; default: error("`=`, `;` or `,` expected to end declaration instead of `%s`", token.toChars()); Lend: return scanPastSemicolon(); } } } /*************************************** * C11 Function Definitions * function-definition * declaration-specifiers declarator declaration-list (opt) compound-statement * * declaration-list: * declaration * declaration-list declaration * * It's already been parsed up to the declaration-list (opt). * Pick it up from there. * Params: * id = function identifier * ft = function type * specifier = function specifiers * Returns: * Dsymbol for the function */ AST.Dsymbol cparseFunctionDefinition(Identifier id, AST.TypeFunction ft, ref Specifier specifier) { /* Start function scope */ typedefTab.push(null); if (token.value != TOK.leftCurly) // if not start of a compound-statement { // Do declaration-list do { cparseDeclaration(LVL.parameter); } while (token.value != TOK.leftCurly); /* Since there were declarations, the parameter-list must have been * an identifier-list. */ ft.parameterList.hasIdentifierList = true; // semantic needs to know to adjust parameter types auto pl = ft.parameterList; if (pl.varargs != AST.VarArg.none && pl.length) error("function identifier-list cannot end with `...`"); ft.parameterList.varargs = AST.VarArg.KRvariadic; // but C11 allows extra arguments auto plLength = pl.length; if (symbols && symbols.length != plLength) error(token.loc, "%d identifiers does not match %d declarations", cast(int)plLength, cast(int)symbols.length); /* Transfer the types and storage classes from symbols[] to pl[] */ foreach (i; 0 .. plLength) { auto p = pl[i]; // yes, quadratic // Convert typedef-identifier to identifier if (p.type) { if (auto t = p.type.isTypeIdentifier()) { p.ident = t.ident; p.type = null; } } if (p.type || !(p.storageClass & STC.parameter)) error("storage class and type are not allowed in identifier-list"); if (!symbols) { // Error already given in cparseDeclaration p.type = AST.Type.terror; continue; } foreach (s; (*symbols)[]) // yes, quadratic { auto ad = s.isAttribDeclaration(); if (ad) s = (*ad.decl)[0]; // AlignDeclaration wrapping the declaration auto d = s.isDeclaration(); if (d && p.ident == d.ident && d.type) { p.type = d.type; p.storageClass = d.storage_class; d.type = null; // don't reuse break; } } if (!p.type) { error("no declaration for identifier `%s`", p.ident.toChars()); p.type = AST.Type.terror; } } } addFuncName = false; // gets set to true if somebody references __func__ in this function const locFunc = token.loc; auto body = cparseStatement(ParseStatementFlags.curly); // don't start a new scope; continue with parameter scope typedefTab.pop(); // end of function scope StorageClass stc = specifiersToSTC(LVL.global, specifier); stc &= ~STC.gshared; // no gshared functions auto fd = new AST.FuncDeclaration(locFunc, prevloc, id, stc, ft, specifier.noreturn); specifiersToFuncDeclaration(fd, specifier); if (addFuncName) { auto s = createFuncName(locFunc, id); body = new AST.CompoundStatement(locFunc, s, body); } fd.fbody = body; // TODO add `symbols` to the function's local symbol table `sc2` in FuncDeclaration::semantic3() return fd; } /*************************************** * C11 Initialization * initializer: * assignment-expression * { } // C23 6.7.10 addition * { initializer-list } * { initializer-list , } * * initializer-list: * designation (opt) initializer * initializer-list , designation (opt) initializer * * designation: * designator-list = * * designator-list: * designator * designator-list designator * * designator: * [ constant-expression ] * . identifier * Returns: * initializer */ AST.Initializer cparseInitializer() { if (token.value != TOK.leftCurly) { auto ae = cparseAssignExp(); // assignment-expression return new AST.ExpInitializer(token.loc, ae); } nextToken(); const loc = token.loc; if (token.value == TOK.rightCurly) // { } { nextToken(); return new AST.DefaultInitializer(loc); } /* Collect one or more `designation (opt) initializer` * into ci.initializerList, but lazily create ci */ AST.CInitializer ci; while (1) { /* There can be 0 or more designators preceding an initializer. * Collect them in desigInit */ AST.DesigInit desigInit; while (1) { if (token.value == TOK.leftBracket) // [ constant-expression ] { nextToken(); auto e = cparseConstantExp(); check(TOK.rightBracket); if (!desigInit.designatorList) desigInit.designatorList = new AST.Designators; desigInit.designatorList.push(AST.Designator(e)); } else if (token.value == TOK.dot) // . identifier { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `.` designator"); break; } if (!desigInit.designatorList) desigInit.designatorList = new AST.Designators; desigInit.designatorList.push(AST.Designator(token.ident)); nextToken(); } else { if (desigInit.designatorList) check(TOK.assign); break; } } desigInit.initializer = cparseInitializer(); if (!ci) ci = new AST.CInitializer(loc); ci.initializerList.push(desigInit); if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightCurly) continue; } break; } check(TOK.rightCurly); //printf("ci: %s\n", ci.toChars()); return ci; } /************************************* * C11 6.7 * declaration-specifier: * storage-class-specifier declaration-specifiers (opt) * type-specifier declaration-specifiers (opt) * type-qualifier declaration-specifiers (opt) * function-specifier declaration-specifiers (opt) * alignment-specifier declaration-specifiers (opt) * Params: * level = declaration context * specifier = specifiers in and out * Returns: * resulting type, null if not specified */ private AST.Type cparseDeclarationSpecifiers(LVL level, ref Specifier specifier) { enum TKW : uint { xnone = 0, xchar = 1, xsigned = 2, xunsigned = 4, xshort = 8, xint = 0x10, xlong = 0x20, xllong = 0x40, xfloat = 0x80, xdouble = 0x100, xldouble = 0x200, xtag = 0x400, xident = 0x800, xvoid = 0x1000, xbool = 0x4000, ximaginary = 0x8000, xcomplex = 0x10000, x_Atomic = 0x20000, xint128 = 0x40000, } AST.Type t; Loc loc; //printf("parseDeclarationSpecifiers()\n"); TKW tkw; SCW scw = specifier.scw & SCW.xtypedef; MOD mod; Identifier id; Identifier previd; Lwhile: while (1) { //printf("token %s\n", token.toChars()); TKW tkwx; SCW scwx; MOD modx; switch (token.value) { // Storage class specifiers case TOK.static_: scwx = SCW.xstatic; break; case TOK.extern_: scwx = SCW.xextern; break; case TOK.auto_: scwx = SCW.xauto; break; case TOK.register: scwx = SCW.xregister; break; case TOK.typedef_: scwx = SCW.xtypedef; break; case TOK.inline: scwx = SCW.xinline; break; case TOK._Noreturn: scwx = SCW.x_Noreturn; break; case TOK.__thread: case TOK._Thread_local: scwx = SCW.x_Thread_local; break; // Type qualifiers case TOK.const_: modx = MOD.xconst; break; case TOK.volatile: modx = MOD.xvolatile; break; case TOK.restrict: modx = MOD.xrestrict; break; case TOK.__stdcall: modx = MOD.x__stdcall; break; // Type specifiers case TOK.char_: tkwx = TKW.xchar; break; case TOK.signed: tkwx = TKW.xsigned; break; case TOK.unsigned: tkwx = TKW.xunsigned; break; case TOK.int16: tkwx = TKW.xshort; break; case TOK.int32: tkwx = TKW.xint; break; case TOK.int64: tkwx = TKW.xlong; break; case TOK.__int128: tkwx = TKW.xint128; break; case TOK.float32: tkwx = TKW.xfloat; break; case TOK.float64: tkwx = TKW.xdouble; break; case TOK.void_: tkwx = TKW.xvoid; break; case TOK._Bool: tkwx = TKW.xbool; break; case TOK._Imaginary: tkwx = TKW.ximaginary; break; case TOK._Complex: tkwx = TKW.xcomplex; break; case TOK.identifier: tkwx = TKW.xident; id = token.ident; break; case TOK.struct_: case TOK.union_: { const structOrUnion = token.value; const sloc = token.loc; nextToken(); Specifier tagSpecifier; /* GNU Extensions * struct-or-union-specifier: * struct-or-union gnu-attributes (opt) identifier (opt) { struct-declaration-list } gnu-attributes (opt) * struct-or-union gnu-attribute (opt) identifier */ while (1) { if (token.value == TOK.__attribute__) cparseGnuAttributes(tagSpecifier); else if (token.value == TOK.__declspec) cparseDeclspec(tagSpecifier); else if (token.value == TOK.__pragma) uupragmaDirective(sloc); else break; } t = cparseStruct(sloc, structOrUnion, tagSpecifier.packalign, symbols); tkwx = TKW.xtag; break; } case TOK.enum_: t = cparseEnum(symbols); tkwx = TKW.xtag; break; case TOK._Atomic: { // C11 6.7.2.4 // type-specifier if followed by `( type-name )` auto tk = peek(&token); if (tk.value == TOK.leftParenthesis) { tk = peek(tk); if (isTypeName(tk) && tk.value == TOK.rightParenthesis) { nextToken(); nextToken(); t = cparseTypeName(); tkwx = TKW.x_Atomic; break; } } // C11 6.7.3 type-qualifier if not modx = MOD.x_Atomic; break; } case TOK._Alignas: { /* C11 6.7.5 * _Alignas ( type-name ) * _Alignas ( constant-expression ) */ if (level & (LVL.parameter | LVL.prototype)) error("no alignment-specifier for parameters"); // C11 6.7.5-2 nextToken(); check(TOK.leftParenthesis); AST.Expression exp; auto tk = &token; if (isTypeName(tk)) // _Alignas ( type-name ) { auto talign = cparseTypeName(); /* Convert type to expression: `talign.alignof` */ auto e = new AST.TypeExp(loc, talign); exp = new AST.DotIdExp(loc, e, Id.__xalignof); } else // _Alignas ( constant-expression ) { exp = cparseConstantExp(); } if (!specifier.alignExps) specifier.alignExps = new AST.Expressions(0); specifier.alignExps.push(exp); check(TOK.rightParenthesis); break; } case TOK.__attribute__: { /* GNU Extensions * declaration-specifiers: * gnu-attributes declaration-specifiers (opt) */ cparseGnuAttributes(specifier); break; } case TOK.__declspec: { /* Microsoft extension */ cparseDeclspec(specifier); break; } case TOK.typeof_: { nextToken(); check(TOK.leftParenthesis); auto tk = &token; AST.Expression e; if (isTypeName(tk)) e = new AST.TypeExp(loc, cparseTypeName()); else e = cparseExpression(); t = new AST.TypeTypeof(loc, e); if(token.value == TOK.rightParenthesis) nextToken(); else { t = AST.Type.terror; error("`typeof` operator expects an expression or type name in parentheses"); // skipParens et. al expect to be on the opening parenthesis int parens; loop: while(1) { switch(token.value) { case TOK.leftParenthesis: parens++; break; case TOK.rightParenthesis: parens--; if(parens < 0) goto case; break; case TOK.endOfFile: break loop; default: } nextToken(); } } tkwx = TKW.xtag; break; } default: break Lwhile; } if (tkwx) { if (tkw & TKW.xlong && tkwx & TKW.xlong) { tkw &= ~TKW.xlong; tkwx = TKW.xllong; } if (tkw && tkwx & TKW.xident) { // 2nd identifier can't be a typedef break Lwhile; // leave parser on the identifier for the following declarator } else if (tkwx & TKW.xident) { // 1st identifier, save it for TypeIdentifier previd = id; } if (tkw & TKW.xident && tkwx || // typedef-name followed by type-specifier tkw & tkwx) // duplicate type-specifiers { error("illegal combination of type specifiers"); tkwx = TKW.init; } tkw |= tkwx; if (!(tkwx & TKW.xtag)) // if parser already advanced nextToken(); continue; } if (modx) { mod |= modx; nextToken(); continue; } if (scwx) { if (scw & scwx) error("duplicate storage class"); scw |= scwx; // C11 6.7.1-2 At most one storage-class may be given, except that // _Thread_local may appear with static or extern. const scw2 = scw & (SCW.xstatic | SCW.xextern | SCW.xauto | SCW.xregister | SCW.xtypedef); if (scw2 & (scw2 - 1) || scw & (SCW.x_Thread_local) && scw & (SCW.xauto | SCW.xregister | SCW.xtypedef)) { error("multiple storage classes in declaration specifiers"); scw &= ~scwx; } if (level == LVL.local && scw & (SCW.x_Thread_local) && scw & (SCW.xinline | SCW.x_Noreturn)) { error("`inline` and `_Noreturn` function specifiers not allowed for `_Thread_local`"); scw &= ~scwx; } if (level == LVL.local && scw & (SCW.x_Thread_local) && !(scw & (SCW.xstatic | SCW.xextern))) { error("`_Thread_local` in block scope must be accompanied with `static` or `extern`"); // C11 6.7.1-3 scw &= ~scwx; } if (level & (LVL.parameter | LVL.prototype) && scw & ~SCW.xregister) { error("only `register` storage class allowed for function parameters"); scw &= ~scwx; } if (level == LVL.global && scw & (SCW.xauto | SCW.xregister)) { error("`auto` and `register` storage class not allowed for global"); scw &= ~scwx; } nextToken(); continue; } } specifier.scw = scw; specifier.mod = mod; // Convert TKW bits to type t switch (tkw) { case TKW.xnone: t = null; break; case TKW.xchar: t = AST.Type.tchar; break; case TKW.xsigned | TKW.xchar: t = AST.Type.tint8; break; case TKW.xunsigned | TKW.xchar: t = AST.Type.tuns8; break; case TKW.xshort: case TKW.xsigned | TKW.xshort: case TKW.xsigned | TKW.xshort | TKW.xint: case TKW.xshort | TKW.xint: t = integerTypeForSize(shortsize); break; case TKW.xunsigned | TKW.xshort | TKW.xint: case TKW.xunsigned | TKW.xshort: t = unsignedTypeForSize(shortsize); break; case TKW.xint: case TKW.xsigned: case TKW.xsigned | TKW.xint: t = integerTypeForSize(intsize); break; case TKW.xunsigned: case TKW.xunsigned | TKW.xint: t = unsignedTypeForSize(intsize); break; case TKW.xlong: case TKW.xsigned | TKW.xlong: case TKW.xsigned | TKW.xlong | TKW.xint: case TKW.xlong | TKW.xint: t = integerTypeForSize(longsize); break; case TKW.xunsigned | TKW.xlong | TKW.xint: case TKW.xunsigned | TKW.xlong: t = unsignedTypeForSize(longsize); break; case TKW.xllong: case TKW.xsigned | TKW.xllong: case TKW.xsigned | TKW.xllong | TKW.xint: case TKW.xllong | TKW.xint: t = integerTypeForSize(long_longsize); break; case TKW.xunsigned | TKW.xllong | TKW.xint: case TKW.xunsigned | TKW.xllong: t = unsignedTypeForSize(long_longsize); break; case TKW.xint128: case TKW.xsigned | TKW.xint128: t = integerTypeForSize(16); break; case TKW.xunsigned | TKW.xint128: t = unsignedTypeForSize(16); break; case TKW.xvoid: t = AST.Type.tvoid; break; case TKW.xbool: t = boolsize == 1 ? AST.Type.tbool : integerTypeForSize(boolsize); break; case TKW.xfloat: t = AST.Type.tfloat32; break; case TKW.xdouble: t = AST.Type.tfloat64; break; case TKW.xlong | TKW.xdouble: t = realType(RTFlags.realfloat); break; case TKW.ximaginary | TKW.xfloat: t = AST.Type.timaginary32; break; case TKW.ximaginary | TKW.xdouble: t = AST.Type.timaginary64; break; case TKW.ximaginary | TKW.xlong | TKW.xdouble: t = realType(RTFlags.imaginary); break; case TKW.xcomplex | TKW.xfloat: t = AST.Type.tcomplex32; break; case TKW.xcomplex | TKW.xdouble: t = AST.Type.tcomplex64; break; case TKW.xcomplex | TKW.xlong | TKW.xdouble: t = realType(RTFlags.complex); break; case TKW.xident: { const idx = previd.toString(); if (idx.length > 2 && idx[0] == '_' && idx[1] == '_') // leading double underscore importBuiltins = true; // probably one of those compiler extensions t = null; /* Punch through to what the typedef is, to support things like: * typedef T* T; */ auto pt = lookupTypedef(previd); if (pt && *pt) // if previd is a known typedef t = *pt; if (!t) t = new AST.TypeIdentifier(loc, previd); break; } case TKW.xtag: case TKW.x_Atomic: // no atomics for you break; // t is already set default: error("illegal type combination"); t = AST.Type.terror; break; } return t; } /******************************** * C11 6.7.6 * Parse a declarator (including function definitions). * declarator: * pointer (opt) direct-declarator * * direct-declarator : * identifier * ( declarator ) * direct-declarator [ type-qualifier-list (opt) assignment-expression (opt) ] * direct-declarator [ static type-qualifier-list (opt) assignment-expression ] * direct-declarator [ type-qualifier-list static assignment-expression (opt) ] * direct-declarator [ type-qualifier-list (opt) * ] * direct-declarator ( parameter-type-list ) * direct-declarator ( identifier-list (opt) ) * * pointer : * * type-qualifier-list (opt) * * type-qualifier-list (opt) pointer * * type-qualifier-list : * type-qualifier * type-qualifier-list type-qualifier * * parameter-type-list : * parameter-list * parameter-list , ... * * parameter-list : * parameter-declaration * parameter-list , parameter-declaration * * parameter-declaration : * declaration-specifiers declarator * declaration-specifiers abstract-declarator (opt) * * identifier-list : * identifier * identifier-list , identifier * * Params: * declarator = declarator kind * tbase = base type to start with * pident = set to Identifier if there is one, null if not * specifier = specifiers in and out * Returns: * type declared. If a TypeFunction is returned, this.symbols is the * symbol table for the parameter-type-list, which will contain any * declared struct, union or enum tags. */ private AST.Type cparseDeclarator(DTR declarator, AST.Type tbase, out Identifier pident, ref Specifier specifier) { //printf("cparseDeclarator(%d, %s)\n", declarator, tbase.toChars()); AST.Types constTypes; // all the Types that will need `const` applied to them /* Insert tx -> t into * ts -> ... -> t * so that * ts -> ... -> tx -> t */ static void insertTx(ref AST.Type ts, AST.Type tx, AST.Type t) { AST.Type* pt; for (pt = &ts; *pt != t; pt = &(cast(AST.TypeNext)*pt).next) { } *pt = tx; } AST.Type parseDecl(AST.Type t) { //printf("parseDecl() t: %s\n", t.toChars()); AST.Type ts; while (1) { switch (token.value) { case TOK.identifier: // identifier //printf("identifier %s\n", token.ident.toChars()); if (declarator == DTR.xabstract) error("identifier not allowed in abstract-declarator"); pident = token.ident; ts = t; nextToken(); break; case TOK.leftParenthesis: // ( declarator ) //printf("leftParen\n"); /* like: T (*fp)(); * T ((*fp))(); */ auto tk = &token; if (!isCDeclarator(tk, declarator)) { /* Not ( declarator ), might be parameter-list */ ts = t; break; } nextToken(); if (token.value == TOK.__stdcall) // T (__stdcall*fp)(); { specifier.mod |= MOD.x__stdcall; nextToken(); } ts = parseDecl(t); check(TOK.rightParenthesis); break; case TOK.mul: // pointer //printf("star\n"); t = new AST.TypePointer(t); nextToken(); // add post fixes const/volatile/restrict/_Atomic const mod = cparseTypeQualifierList(); if (mod & MOD.xconst) constTypes.push(t); if (token.value == TOK.__attribute__) cparseGnuAttributes(specifier); continue; default: //printf("default %s\n", token.toChars()); if (declarator == DTR.xdirect) { if (!t || t.isTypeIdentifier()) { // const arr[1]; error("no type-specifier for declarator"); t = AST.Type.tint32; } else error("identifier or `(` expected"); // ) panic(); } ts = t; break; } break; } // parse DeclaratorSuffixes while (1) { switch (token.value) { case TOK.leftBracket: { // post [] syntax, pick up any leading type qualifiers, `static` and `*` AST.Type ta; nextToken(); auto mod = cparseTypeQualifierList(); // const/volatile/restrict/_Atomic bool isStatic; bool isVLA; if (token.value == TOK.static_) { isStatic = true; // `static` nextToken(); if (!mod) // type qualifiers after `static` mod = cparseTypeQualifierList(); } else if (token.value == TOK.mul) { if (peekNext() == TOK.rightBracket) { isVLA = true; // `*` nextToken(); } } if (isStatic || token.value != TOK.rightBracket) { //printf("It's a static array\n"); AST.Expression e = cparseAssignExp(); // [ expression ] ta = new AST.TypeSArray(t, e); } else { /* C11 6.7.6.2-4 An [ ] array is an incomplete array type */ ta = new AST.TypeSArray(t); } check(TOK.rightBracket); // Issue errors for unsupported types. if (isVLA) // C11 6.7.6.2 { error("variable length arrays are not supported"); } if (isStatic) // C11 6.7.6.3 { error("static array parameters are not supported"); } if (declarator != DTR.xparameter) { /* C11 6.7.6.2-4: '*' can only be used with function prototype scope. */ if (isVLA) error("variable length array used outside of function prototype"); /* C11 6.7.6.2-1: type qualifiers and 'static' shall only appear * in a declaration of a function parameter with an array type. */ if (isStatic || mod) error("static or type qualifier used outside of function prototype"); } if (ts.isTypeSArray() || ts.isTypeDArray()) { /* C11 6.7.6.2-1: type qualifiers and 'static' shall only appear * in the outermost array type derivation. */ if (isStatic || mod) error("static or type qualifier used in non-outermost array type derivation"); /* C11 6.7.6.2-1: the element type shall not be an incomplete or * function type. */ if (ta.isTypeSArray() && ta.isTypeSArray().isIncomplete() && !isVLA) error("array type has incomplete element type `%s`", ta.toChars()); } // Apply type qualifiers to the constructed type. if (mod & MOD.xconst) // ignore the other bits ta = toConst(ta); insertTx(ts, ta, t); // ts -> ... -> ta -> t continue; } case TOK.leftParenthesis: { // New symbol table for parameter-list auto symbolsSave = this.symbols; this.symbols = null; auto parameterList = cparseParameterList(); const lkg = specifier.mod & MOD.x__stdcall ? LINK.windows : linkage; StorageClass stc = specifier._nothrow ? STC.nothrow_ : 0; if (specifier._pure) stc |= STC.pure_; AST.Type tf = new AST.TypeFunction(parameterList, t, lkg, stc); //tf = tf.addSTC(storageClass); // TODO insertTx(ts, tf, t); // ts -> ... -> tf -> t if (ts != tf) this.symbols = symbolsSave; break; } default: break; } break; } if (declarator == DTR.xdirect && !pident) error("expected identifier for declarator"); return ts; } auto t = parseDecl(tbase); if (specifier.vector_size) { auto length = new AST.IntegerExp(token.loc, specifier.vector_size / tbase.size(), AST.Type.tuns32); auto tsa = new AST.TypeSArray(tbase, length); AST.Type tv = new AST.TypeVector(tsa); specifier.vector_size = 0; // used it up insertTx(t, tv, tbase); // replace tbase with tv } /* Because const is transitive, cannot assemble types from * fragments. Instead, types to be annotated with const are put * in constTypes[], and a bottom up scan of t is done to apply * const */ if (constTypes.length) { AST.Type constApply(AST.Type t) { if (t.nextOf()) { auto tn = cast(AST.TypeNext)t; // t.nextOf() should return a ref instead of this tn.next = constApply(tn.next); } foreach (tc; constTypes[]) { if (tc is t) { return toConst(t); } } return t; } if (declarator == DTR.xparameter && t.isTypePointer()) { /* Because there are instances in .h files of "const pointer to mutable", * skip applying transitive `const` * https://issues.dlang.org/show_bug.cgi?id=22534 */ auto tn = cast(AST.TypeNext)t; tn.next = constApply(tn.next); } else t = constApply(t); } //printf("result: %s\n", t.toChars()); return t; } /****************************** * C11 6.7.3 * type-qualifier: * const * restrict * volatile * _Atomic * __stdcall */ MOD cparseTypeQualifierList() { MOD mod; while (1) { switch (token.value) { case TOK.const_: mod |= MOD.xconst; break; case TOK.volatile: mod |= MOD.xvolatile; break; case TOK.restrict: mod |= MOD.xrestrict; break; case TOK._Atomic: mod |= MOD.x_Atomic; break; case TOK.__stdcall: mod |= MOD.x__stdcall; break; default: return mod; } nextToken(); } } /*********************************** * C11 6.7.7 */ AST.Type cparseTypeName() { Specifier specifier; specifier.packalign.setDefault(); auto tspec = cparseSpecifierQualifierList(LVL.global, specifier); if (!tspec) { error("type-specifier is missing"); tspec = AST.Type.tint32; } if (tspec && specifier.mod & MOD.xconst) { tspec = toConst(tspec); specifier.mod = MOD.xnone; // 'used' it } Identifier id; return cparseDeclarator(DTR.xabstract, tspec, id, specifier); } /*********************************** * C11 6.7.2.1 * specifier-qualifier-list: * type-specifier specifier-qualifier-list (opt) * type-qualifier specifier-qualifier-list (opt) * Params: * level = declaration context * specifier = specifiers in and out * Returns: * resulting type, null if not specified */ AST.Type cparseSpecifierQualifierList(LVL level, ref Specifier specifier) { auto t = cparseDeclarationSpecifiers(level, specifier); if (specifier.scw) error("storage class not allowed in specifier-qualified-list"); return t; } /*********************************** * C11 6.7.6.3 * ( parameter-type-list ) * ( identifier-list (opt) ) */ AST.ParameterList cparseParameterList() { auto parameters = new AST.Parameters(); AST.VarArg varargs = AST.VarArg.none; StorageClass varargsStc; check(TOK.leftParenthesis); if (token.value == TOK.void_ && peekNext() == TOK.rightParenthesis) // func(void) { nextToken(); nextToken(); return AST.ParameterList(parameters, varargs, varargsStc); } if (token.value == TOK.rightParenthesis) // func() { nextToken(); return AST.ParameterList(parameters, AST.VarArg.KRvariadic, varargsStc); } /* Create function prototype scope */ typedefTab.push(null); AST.ParameterList finish() { typedefTab.pop(); return AST.ParameterList(parameters, varargs, varargsStc); } /* The check for identifier-list comes later, * when doing the trailing declaration-list (opt) */ while (1) { if (token.value == TOK.rightParenthesis) break; if (token.value == TOK.dotDotDot) { if (parameters.length == 0) // func(...) error("named parameter required before `...`"); importBuiltins = true; // will need __va_list_tag varargs = AST.VarArg.variadic; // C-style variadics nextToken(); check(TOK.rightParenthesis); return finish(); } Specifier specifier; specifier.packalign.setDefault(); auto tspec = cparseDeclarationSpecifiers(LVL.prototype, specifier); if (!tspec) { error("no type-specifier for parameter"); tspec = AST.Type.tint32; } if (specifier.mod & MOD.xconst) { if ((token.value == TOK.rightParenthesis || token.value == TOK.comma) && tspec.isTypeIdentifier()) error("type-specifier omitted for parameter `%s`", tspec.isTypeIdentifier().ident.toChars()); tspec = toConst(tspec); specifier.mod = MOD.xnone; // 'used' it } Identifier id; const paramLoc = token.loc; auto t = cparseDeclarator(DTR.xparameter, tspec, id, specifier); if (token.value == TOK.__attribute__) cparseGnuAttributes(specifier); if (specifier.mod & MOD.xconst) t = toConst(t); auto param = new AST.Parameter(paramLoc, specifiersToSTC(LVL.parameter, specifier), t, id, null, null); parameters.push(param); if (token.value == TOK.rightParenthesis || token.value == TOK.endOfFile) break; check(TOK.comma); } check(TOK.rightParenthesis); return finish(); } /*********************************** * C11 6.7.10 * _Static_assert ( constant-expression , string-literal ) ; */ private AST.StaticAssert cparseStaticAssert() { const loc = token.loc; //printf("cparseStaticAssert()\n"); nextToken(); check(TOK.leftParenthesis); auto exp = cparseConstantExp(); check(TOK.comma); if (token.value != TOK.string_) error("string literal expected"); auto msg = cparsePrimaryExp(); check(TOK.rightParenthesis); check(TOK.semicolon); return new AST.StaticAssert(loc, exp, msg); } /************************* * Collect argument list. * Parser is on opening parenthesis. * Returns: * the arguments */ private AST.Expressions* cparseArguments() { nextToken(); auto arguments = new AST.Expressions(); while (token.value != TOK.rightParenthesis && token.value != TOK.endOfFile) { auto arg = cparseAssignExp(); arguments.push(arg); if (token.value != TOK.comma) break; nextToken(); // consume comma } check(TOK.rightParenthesis); return arguments; } /************************* * __declspec parser * https://docs.microsoft.com/en-us/cpp/cpp/declspec * decl-specifier: * __declspec ( extended-decl-modifier-seq ) * * extended-decl-modifier-seq: * extended-decl-modifier (opt) * extended-decl-modifier extended-decl-modifier-seq * * extended-decl-modifier: * align(number) * deprecated(depMsg) * dllimport * dllexport * naked * noinline * noreturn * nothrow * thread * Params: * specifier = filled in with the attribute(s) */ private void cparseDeclspec(ref Specifier specifier) { //printf("cparseDeclspec()\n"); /* Check for dllexport, dllimport * Ignore the rest */ nextToken(); // move past __declspec check(TOK.leftParenthesis); while (1) { if (token.value == TOK.rightParenthesis) { nextToken(); break; } else if (token.value == TOK.endOfFile) break; else if (token.value == TOK.identifier) { if (token.ident == Id.dllimport) { specifier.dllimport = true; nextToken(); } else if (token.ident == Id.dllexport) { specifier.dllexport = true; nextToken(); } else if (token.ident == Id.naked) { specifier.naked = true; nextToken(); } else if (token.ident == Id.noinline) { specifier.scw |= SCW.xnoinline; nextToken(); } else if (token.ident == Id.noreturn) { specifier.noreturn = true; nextToken(); } else if (token.ident == Id._nothrow) { specifier._nothrow = true; nextToken(); } else if (token.ident == Id.thread) { specifier.scw |= SCW.x_Thread_local; nextToken(); } else if (token.ident == Id._align) { // Microsoft spec is very imprecise as to how this actually works nextToken(); check(TOK.leftParenthesis); if (token.value == TOK.int32Literal) { const n = token.unsvalue; if (n < 1 || n & (n - 1) || 8192 < n) error("__decspec(align(%lld)) must be an integer positive power of 2 and be <= 8,192", cast(ulong)n); specifier.packalign.set(cast(uint)n); specifier.packalign.setPack(true); nextToken(); } else { error("alignment value expected, not `%s`", token.toChars()); nextToken(); } check(TOK.rightParenthesis); } else if (token.ident == Id._deprecated) { specifier._deprecated = true; nextToken(); if (token.value == TOK.leftParenthesis) // optional deprecation message { nextToken(); specifier.depMsg = cparseExpression(); check(TOK.rightParenthesis); } } else { nextToken(); if (token.value == TOK.leftParenthesis) cparseParens(); } } else if (token.value == TOK.restrict) // ImportC assigns no semantics to `restrict`, so just ignore the keyword. nextToken(); else { error("extended-decl-modifier expected after `__declspec(`, saw `%s` instead", token.toChars()); nextToken(); if (token.value != TOK.rightParenthesis) break; } } } /************************* * Parser for asm label. It appears after the declarator, and has apparently * nothing to do with inline assembler. * https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html * simple-asm-expr: * asm ( asm-string-literal ) * * asm-string-literal: * string-literal */ private AST.StringExp cparseGnuAsmLabel() { nextToken(); // move past asm check(TOK.leftParenthesis); if (token.value != TOK.string_) error("string literal expected for Asm Label, not `%s`", token.toChars()); auto label = cparsePrimaryExp(); check(TOK.rightParenthesis); return cast(AST.StringExp) label; } /******************** * Parse C inline assembler statement in Gnu format. * https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html * asm asm-qualifiers ( AssemblerTemplate : OutputOperands : InputOperands : Clobbers : GotoLabels ) * Current token is on the `asm`. * Returns: * inline assembler expression as a Statement */ private AST.Statement cparseGnuAsm() { // Defer parsing of AsmStatements until semantic processing. const loc = token.loc; nextToken(); // Consume all asm-qualifiers. As a future optimization, we could record // the `inline` and `volatile` storage classes against the statement. while (token.value == TOK.goto_ || token.value == TOK.inline || token.value == TOK.volatile) nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.string_) error("string literal expected for Assembler Template, not `%s`", token.toChars()); Token* toklist = null; Token** ptoklist = &toklist; //Identifier label = null; auto statements = new AST.Statements(); int parens; while (1) { switch (token.value) { case TOK.leftParenthesis: ++parens; goto default; case TOK.rightParenthesis: --parens; if (parens >= 0) goto default; break; case TOK.semicolon: error("matching `)` expected, not `;`"); break; case TOK.endOfFile: /* ( */ error("matching `)` expected, not end of file"); break; case TOK.colonColon: // treat as two separate : tokens for iasmgcc *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; *ptoklist = null; nextToken(); continue; default: *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); ptoklist = &(*ptoklist).next; *ptoklist = null; nextToken(); continue; } if (toklist) { // Create AsmStatement from list of tokens we've saved AST.Statement s = new AST.AsmStatement(token.loc, toklist); statements.push(s); } break; } nextToken(); auto s = new AST.CompoundAsmStatement(loc, statements, 0); return s; } /************************* * __attribute__ parser * https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html * gnu-attributes: * gnu-attributes gnu-attribute-specifier * * gnu-attribute-specifier: * __attribute__ (( gnu-attribute-list )) * * gnu-attribute-list: * gnu-attribute (opt) * gnu-attribute-list , gnu-attribute * * Params: * specifier = filled in with the attribute(s) */ private void cparseGnuAttributes(ref Specifier specifier) { while (token.value == TOK.__attribute__) { nextToken(); // move past __attribute__ check(TOK.leftParenthesis); check(TOK.leftParenthesis); if (token.value != TOK.rightParenthesis) { while (1) { cparseGnuAttribute(specifier); if (token.value != TOK.comma) break; nextToken(); } } check(TOK.rightParenthesis); check(TOK.rightParenthesis); } } /************************* * Parse a single GNU attribute * gnu-attribute: * gnu-attribute-name * gnu-attribute-name ( identifier ) * gnu-attribute-name ( identifier , expression-list ) * gnu-attribute-name ( expression-list (opt) ) * * gnu-attribute-name: * keyword * identifier * * expression-list: * constant-expression * expression-list , constant-expression * * Params: * specifier = filled in with the attribute(s) */ private void cparseGnuAttribute(ref Specifier specifier) { /* Check for dllimport, dllexport, naked, noreturn, vector_size(bytes) * Ignore the rest */ if (!isGnuAttributeName()) return; if (token.value == TOK.identifier) { if (token.ident == Id.aligned) { nextToken(); if (token.value == TOK.leftParenthesis) { nextToken(); if (token.value == TOK.int32Literal) { const n = token.unsvalue; if (n < 1 || n & (n - 1) || ushort.max < n) error("__attribute__((aligned(%lld))) must be an integer positive power of 2 and be <= 32,768", cast(ulong)n); specifier.packalign.set(cast(uint)n); specifier.packalign.setPack(true); nextToken(); } else { error("alignment value expected, not `%s`", token.toChars()); nextToken(); } check(TOK.rightParenthesis); } /* ignore __attribute__((aligned)), which sets the alignment to the largest value for any data * type on the target machine. It's the opposite of __attribute__((packed)) */ } else if (token.ident == Id.packed) { specifier.packalign.set(1); specifier.packalign.setPack(true); nextToken(); } else if (token.ident == Id.always_inline) // https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html { specifier.scw |= SCW.xinline; nextToken(); } else if (token.ident == Id._deprecated) { specifier._deprecated = true; nextToken(); if (token.value == TOK.leftParenthesis) // optional deprecation message { nextToken(); specifier.depMsg = cparseExpression(); check(TOK.rightParenthesis); } } else if (token.ident == Id.dllimport) { specifier.dllimport = true; nextToken(); } else if (token.ident == Id.dllexport) { specifier.dllexport = true; nextToken(); } else if (token.ident == Id.naked) { specifier.naked = true; nextToken(); } else if (token.ident == Id.noinline) { specifier.scw |= SCW.xnoinline; nextToken(); } else if (token.ident == Id.noreturn) { specifier.noreturn = true; nextToken(); } else if (token.ident == Id._nothrow) { specifier._nothrow = true; nextToken(); } else if (token.ident == Id._pure) { specifier._pure = true; nextToken(); } else if (token.ident == Id.vector_size) { nextToken(); check(TOK.leftParenthesis); if (token.value == TOK.int32Literal) { const n = token.unsvalue; if (n < 1 || n & (n - 1) || ushort.max < n) error("__attribute__((vector_size(%lld))) must be an integer positive power of 2 and be <= 32,768", cast(ulong)n); specifier.vector_size = cast(uint) n; nextToken(); } else { error("value for vector_size expected, not `%s`", token.toChars()); nextToken(); } check(TOK.rightParenthesis); } else { nextToken(); if (token.value == TOK.leftParenthesis) cparseParens(); } } else { nextToken(); if (token.value == TOK.leftParenthesis) cparseParens(); } } /************************* * See if match for GNU attribute name, which may be any identifier, * storage-class-specifier, type-specifier, or type-qualifier. * Returns: * true if a valid GNU attribute name */ private bool isGnuAttributeName() { switch (token.value) { case TOK.identifier: case TOK.static_: case TOK.unsigned: case TOK.int64: case TOK.const_: case TOK.extern_: case TOK.register: case TOK.typedef_: case TOK.int16: case TOK.inline: case TOK._Noreturn: case TOK.volatile: case TOK.signed: case TOK.auto_: case TOK.restrict: case TOK._Complex: case TOK._Thread_local: case TOK.int32: case TOK.__int128: case TOK.char_: case TOK.float32: case TOK.float64: case TOK.void_: case TOK._Bool: case TOK._Atomic: return true; default: return false; } } /*************************** * Like skipParens(), but consume the tokens. */ private void cparseParens() { check(TOK.leftParenthesis); int parens = 1; while (1) { switch (token.value) { case TOK.leftParenthesis: ++parens; break; case TOK.rightParenthesis: --parens; if (parens < 0) { error("extra right parenthesis"); return; } if (parens == 0) { nextToken(); return; } break; case TOK.endOfFile: error("end of file found before right parenthesis"); return; default: break; } nextToken(); } } //} /******************************************************************************/ /***************************** Struct & Enum Parser ***************************/ //{ /************************************* * C11 6.7.2.2 * enum-specifier: * enum identifier (opt) { enumerator-list } * enum identifier (opt) { enumerator-list , } * enum identifier * * enumerator-list: * enumerator * enumerator-list , enumerator * * enumerator: * enumeration-constant * enumeration-constant = constant-expression * * enumeration-constant: * identifier * * Params: * symbols = symbols to add enum declaration to * Returns: * type of the enum */ private AST.Type cparseEnum(ref AST.Dsymbols* symbols) { const loc = token.loc; nextToken(); /* GNU Extensions * enum-specifier: * enum gnu-attributes (opt) identifier (opt) { enumerator-list } gnu-attributes (opt) * enum gnu-attributes (opt) identifier (opt) { enumerator-list , } gnu-attributes (opt) * enum gnu-attributes (opt) identifier */ Specifier specifier; specifier.packalign.setDefault(); if (token.value == TOK.__attribute__) cparseGnuAttributes(specifier); Identifier tag; if (token.value == TOK.identifier) { tag = token.ident; nextToken(); } /* clang extension: add optional base type after the identifier * https://en.cppreference.com/w/cpp/language/enum * enum Identifier : Type */ //AST.Type base = AST.Type.tint32; // C11 6.7.2.2-4 implementation defined default base type AST.Type base = null; // C23 says base type is determined by enum member values if (token.value == TOK.colon) { nextToken(); base = cparseTypeName(); } AST.Dsymbols* members; if (token.value == TOK.leftCurly) { nextToken(); members = new AST.Dsymbols(); if (token.value == TOK.rightCurly) // C11 6.7.2.2-1 { if (tag) error("no members for `enum %s`", tag.toChars()); else error("no members for anonymous enum"); } while (token.value == TOK.identifier) { auto ident = token.ident; // enumeration-constant nextToken(); auto mloc = token.loc; if (token.value == TOK.__attribute__) { /* gnu-attributes can appear here, but just scan and ignore them * https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html */ Specifier specifierx; specifierx.packalign.setDefault(); cparseGnuAttributes(specifierx); } AST.Expression value; if (token.value == TOK.assign) { nextToken(); value = cparseConstantExp(); // TODO C11 6.7.2.2-2 value must fit into an int } if (token.value == TOK.__attribute__) { /* gnu-attributes can appear here, but just scan and ignore them * https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html */ Specifier specifierx; specifierx.packalign.setDefault(); cparseGnuAttributes(specifierx); } auto em = new AST.EnumMember(mloc, ident, value, null, 0, null, null); members.push(em); if (token.value == TOK.comma) { nextToken(); continue; } break; } check(TOK.rightCurly); /* GNU Extensions * Parse the postfix gnu-attributes (opt) */ if (token.value == TOK.__attribute__) cparseGnuAttributes(specifier); } else if (!tag) error("missing `identifier` after `enum`"); /* Need semantic information to determine if this is a declaration, * redeclaration, or reference to existing declaration. * Defer to the semantic() pass with a TypeTag. */ return new AST.TypeTag(loc, TOK.enum_, tag, structalign_t.init, base, members); } /************************************* * C11 6.7.2.1 * Parse struct and union specifiers. * Parser is advanced to the tag identifier or brace. * struct-or-union-specifier: * struct-or-union identifier (opt) { struct-declaration-list } * struct-or-union identifier * * struct-or-union: * struct * union * * struct-declaration-list: * struct-declaration * struct-declaration-list struct-declaration * * Params: * loc = location of `struct` or `union` * structOrUnion = TOK.struct_ or TOK.union_ * packalign = alignment to use for struct members * symbols = symbols to add struct-or-union declaration to * Returns: * type of the struct */ private AST.Type cparseStruct(Loc loc, TOK structOrUnion, structalign_t packalign, ref AST.Dsymbols* symbols) { Identifier tag; if (token.value == TOK.identifier) { tag = token.ident; nextToken(); } AST.Dsymbols* members; if (token.value == TOK.leftCurly) { nextToken(); members = new AST.Dsymbols(); // so `members` will be non-null even with 0 members while (token.value != TOK.rightCurly) { cparseStructDeclaration(members, packalign); if (token.value == TOK.endOfFile) break; } check(TOK.rightCurly); if ((*members).length == 0) // C11 6.7.2.1-8 { /* allow empty structs as an extension * struct-declarator-list: * struct-declarator (opt) */ } /* GNU Extensions * Parse the postfix gnu-attributes (opt) */ Specifier specifier; if (token.value == TOK.__attribute__) cparseGnuAttributes(specifier); if (!specifier.packalign.isUnknown) { packalign.set(specifier.packalign.get()); packalign.setPack(specifier.packalign.isPack()); foreach (ref d; (*members)[]) { auto decls = new AST.Dsymbols(1); (*decls)[0] = d; d = new AST.AlignDeclaration(d.loc, specifier.packalign, decls); } } } else if (!tag) error("missing tag `identifier` after `%s`", Token.toChars(structOrUnion)); // many ways and places to declare alignment if (packalign.isUnknown() && !this.packalign.isUnknown()) packalign.set(this.packalign.get()); /* Need semantic information to determine if this is a declaration, * redeclaration, or reference to existing declaration. * Defer to the semantic() pass with a TypeTag. */ return new AST.TypeTag(loc, structOrUnion, tag, packalign, null, members); } /************************************* * C11 6.7.2.1 * Parse a struct declaration member. * struct-declaration: * specifier-qualifier-list struct-declarator-list (opt) ; * static_assert-declaration * * struct-declarator-list: * struct-declarator * struct-declarator-list , struct-declarator * * struct-declarator: * declarator * declarator (opt) : constant-expression * Params: * members = where to put the fields (members) * packalign = alignment to use for struct members */ void cparseStructDeclaration(AST.Dsymbols* members, structalign_t packalign) { //printf("cparseStructDeclaration()\n"); if (token.value == TOK._Static_assert) { auto s = cparseStaticAssert(); members.push(s); return; } Specifier specifier; specifier.packalign = packalign.isUnknown ? this.packalign : packalign; auto tspec = cparseSpecifierQualifierList(LVL.member, specifier); if (!tspec) { error("no type-specifier for struct member"); tspec = AST.Type.tint32; } if (specifier.mod & MOD.xconst) { tspec = toConst(tspec); specifier.mod = MOD.xnone; // 'used' it } /* If a declarator does not follow, it is unnamed */ if (token.value == TOK.semicolon && tspec) { nextToken(); auto tt = tspec.isTypeTag(); if (!tt) { if (auto ti = tspec.isTypeIdentifier()) { error("type-specifier omitted before declaration of `%s`", ti.ident.toChars()); } return; // legal but meaningless empty declaration } /* If anonymous struct declaration * struct { ... members ... }; * C11 6.7.2.1-13 */ if (!tt.id && tt.members) { /* members of anonymous struct are considered members of * the containing struct */ auto ad = new AST.AnonDeclaration(tt.loc, tt.tok == TOK.union_, tt.members); auto s = applySpecifier(ad, specifier); members.push(s); return; } if (!tt.id && !tt.members) return; // already gave error in cparseStruct() /* `struct tag;` and `struct tag { ... };` * always result in a declaration in the current scope */ // TODO: merge in specifier auto stag = (tt.tok == TOK.struct_) ? new AST.StructDeclaration(tt.loc, tt.id, false) : new AST.UnionDeclaration(tt.loc, tt.id); stag.members = tt.members; if (!symbols) symbols = new AST.Dsymbols(); auto s = applySpecifier(stag, specifier); symbols.push(s); return; } while (1) { Identifier id; AST.Type dt; if (token.value == TOK.colon) { if (auto ti = tspec.isTypeIdentifier()) { error("type-specifier omitted before bit field declaration of `%s`", ti.ident.toChars()); tspec = AST.Type.tint32; } // C11 6.7.2.1-12 unnamed bit-field id = Identifier.generateAnonymousId("BitField"); dt = tspec; } else { dt = cparseDeclarator(DTR.xdirect, tspec, id, specifier); if (!dt) { panic(); nextToken(); break; // error recovery } } AST.Expression width; if (token.value == TOK.colon) { // C11 6.7.2.1-10 bit-field nextToken(); width = cparseConstantExp(); } /* GNU Extensions * struct-declarator: * declarator gnu-attributes (opt) * declarator (opt) : constant-expression gnu-attributes (opt) */ if (token.value == TOK.__attribute__) cparseGnuAttributes(specifier); if (!tspec && !specifier.scw && !specifier.mod) error("specifier-qualifier-list required"); else if (width) { if (specifier.alignExps) error("no alignment-specifier for bit field declaration"); // C11 6.7.5-2 auto s = new AST.BitFieldDeclaration(width.loc, dt, id, width); members.push(s); } else if (id) { if (dt.ty == AST.Tvoid) error("`void` has no value"); // declare the symbol // Give member variables an implicit void initializer auto initializer = new AST.VoidInitializer(token.loc); AST.Dsymbol s = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(LVL.member, specifier)); s = applySpecifier(s, specifier); members.push(s); } switch (token.value) { case TOK.identifier: error("missing comma"); goto default; case TOK.semicolon: nextToken(); return; case TOK.comma: nextToken(); break; default: error("`;` or `,` expected"); while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); return; } } } //} /******************************************************************************/ /********************************* Lookahead Parser ***************************/ //{ /************************************ * Determine if the scanner is sitting on the start of a declaration. * Params: * t = current token of the scanner * needId = flag with additional requirements for a declaration * endtok = ending token * pt = will be set ending token (if not null) * Returns: * true at start of a declaration */ private bool isCDeclaration(ref Token* pt) { auto t = pt; //printf("isCDeclaration() %s\n", t.toChars()); if (!isDeclarationSpecifiers(t)) return false; while (1) { if (t.value == TOK.semicolon) { t = peek(t); pt = t; return true; } if (!isCDeclarator(t, DTR.xdirect)) return false; if (t.value == TOK.asm_) { t = peek(t); if (t.value != TOK.leftParenthesis || !skipParens(t, &t)) return false; } if (t.value == TOK.__attribute__) { t = peek(t); if (t.value != TOK.leftParenthesis || !skipParens(t, &t)) return false; } if (t.value == TOK.assign) { t = peek(t); if (!isInitializer(t)) return false; } switch (t.value) { case TOK.comma: t = peek(t); break; case TOK.semicolon: t = peek(t); pt = t; return true; default: return false; } } } /******************************** * See if match for initializer. * Params: * pt = starting token, updated to one past end of initializer if true * Returns: * true if initializer */ private bool isInitializer(ref Token* pt) { //printf("isInitializer()\n"); auto t = pt; if (t.value == TOK.leftCurly) { if (!skipBraces(t)) return false; pt = t; return true; } // skip over assignment-expression, ending before comma or semiColon or EOF if (!isAssignmentExpression(t)) return false; pt = t; return true; } /******************************** * See if match for: * postfix-expression ( argument-expression-list(opt) ) * Params: * pt = starting token, updated to one past end of initializer if true * Returns: * true if function call */ private bool isFunctionCall(ref Token* pt) { //printf("isFunctionCall()\n"); auto t = pt; if (!isPrimaryExpression(t)) return false; if (t.value != TOK.leftParenthesis) return false; t = peek(t); while (1) { if (!isAssignmentExpression(t)) return false; if (t.value == TOK.comma) { t = peek(t); continue; } if (t.value == TOK.rightParenthesis) { t = peek(t); break; } return false; } if (t.value != TOK.semicolon) return false; pt = t; return true; } /******************************** * See if match for assignment-expression. * Params: * pt = starting token, updated to one past end of assignment-expression if true * Returns: * true if assignment-expression */ private bool isAssignmentExpression(ref Token* pt) { auto t = pt; //printf("isAssignmentExpression() %s\n", t.toChars()); /* This doesn't actually check for grammar matching an * assignment-expression. It just matches ( ) [ ] looking for * an ending token that would terminate one. */ bool any; while (1) { switch (t.value) { case TOK.comma: case TOK.semicolon: case TOK.rightParenthesis: case TOK.rightBracket: case TOK.endOfFile: case TOK.endOfLine: if (!any) return false; break; case TOK.leftParenthesis: if (!skipParens(t, &t)) return false; /* https://issues.dlang.org/show_bug.cgi?id=22267 If the parser encounters the following `identifier variableName = (expression);` the initializer is not identified as such since the parentheses cause the parser to keep walking indefinitely (whereas `(1) + 1` would not be affected.). */ any = true; continue; case TOK.leftBracket: if (!skipBrackets(t)) return false; continue; case TOK.leftCurly: if (!skipBraces(t)) return false; continue; default: any = true; // assume token was part of an a-e t = peek(t); continue; } pt = t; return true; } } /******************************** * See if match for constant-expression. * Params: * pt = starting token, updated to one past end of constant-expression if true * Returns: * true if constant-expression */ private bool isConstantExpression(ref Token* pt) { return isAssignmentExpression(pt); } /******************************** * See if match for declaration-specifiers. * No errors are diagnosed. * Params: * pt = starting token, updated to one past end of declaration-specifiers if true * Returns: * true if declaration-specifiers */ private bool isDeclarationSpecifiers(ref Token* pt) { //printf("isDeclarationSpecifiers()\n"); auto t = pt; bool seenType; bool any; while (1) { switch (t.value) { // type-specifiers case TOK.void_: case TOK.char_: case TOK.int16: case TOK.int32: case TOK.int64: case TOK.__int128: case TOK.float32: case TOK.float64: case TOK.signed: case TOK.unsigned: case TOK._Bool: //case TOK._Imaginary: case TOK._Complex: t = peek(t); seenType = true; any = true; continue; case TOK.identifier: // typedef-name if (!seenType) { t = peek(t); seenType = true; any = true; continue; } break; case TOK.struct_: case TOK.union_: case TOK.enum_: t = peek(t); if (t.value == TOK.__attribute__ || t.value == TOK.__declspec) { t = peek(t); if (!skipParens(t, &t)) return false; } if (t.value == TOK.identifier) { t = peek(t); if (t.value == TOK.leftCurly) { if (!skipBraces(t)) return false; } } else if (t.value == TOK.leftCurly) { if (!skipBraces(t)) return false; } else return false; any = true; continue; // storage-class-specifiers case TOK.typedef_: case TOK.extern_: case TOK.static_: case TOK.__thread: case TOK._Thread_local: case TOK.auto_: case TOK.register: // function-specifiers case TOK.inline: case TOK._Noreturn: // type-qualifiers case TOK.const_: case TOK.volatile: case TOK.restrict: case TOK.__stdcall: t = peek(t); any = true; continue; case TOK._Alignas: // alignment-specifier case TOK.__declspec: // decl-specifier case TOK.__attribute__: // attribute-specifier t = peek(t); if (!skipParens(t, &t)) return false; any = true; continue; // either atomic-type-specifier or type_qualifier case TOK._Atomic: // TODO _Atomic ( type-name ) t = peek(t); if (t.value == TOK.leftParenthesis) // maybe atomic-type-specifier { auto tsave = t; t = peek(t); if (!isTypeName(t) || t.value != TOK.rightParenthesis) { // it's a type-qualifier t = tsave; // back up parser any = true; continue; } t = peek(t); // move past right parenthesis of atomic-type-specifier } any = true; continue; default: break; } break; } if (any) { pt = t; return true; } return false; } /************************************** * See if declaration-list is present. * Returns: * true if declaration-list is present, even an empty one */ bool isDeclarationList(ref Token* pt) { auto t = pt; while (1) { if (t.value == TOK.leftCurly) { pt = t; return true; } if (!isCDeclaration(t)) return false; } } /******************************************* * Skip braces. * Params: * pt = enters on left brace, set to token past right bracket on true * Returns: * true if successful */ private bool skipBraces(ref Token* pt) { auto t = pt; if (t.value != TOK.leftCurly) return false; int braces = 0; while (1) { switch (t.value) { case TOK.leftCurly: ++braces; t = peek(t); continue; case TOK.rightCurly: --braces; if (braces == 0) { pt = peek(t); return true; } if (braces < 0) return false; t = peek(t); continue; case TOK.endOfFile: return false; default: t = peek(t); continue; } } } /******************************************* * Skip brackets. * Params: * pt = enters on left bracket, set to token past right bracket on true * Returns: * true if successful */ private bool skipBrackets(ref Token* pt) { auto t = pt; if (t.value != TOK.leftBracket) return false; int brackets = 0; while (1) { switch (t.value) { case TOK.leftBracket: ++brackets; t = peek(t); continue; case TOK.rightBracket: --brackets; if (brackets == 0) { pt = peek(t); return true; } if (brackets < 0) return false; t = peek(t); continue; case TOK.endOfFile: return false; default: t = peek(t); continue; } } } /********************************* * Check to see if tokens starting with *pt form a declarator. * Params: * pt = pointer to starting token, updated to point past declarator if true is returned * declarator = declarator kind * Returns: * true if it does */ private bool isCDeclarator(ref Token* pt, DTR declarator) { //printf("isCDeclarator()\n"); auto t = pt; while (1) { if (t.value == TOK.mul) // pointer { t = peek(t); if (!isTypeQualifierList(t)) return false; } else break; } if (t.value == TOK.identifier) { if (declarator == DTR.xabstract) return false; t = peek(t); } else if (t.value == TOK.leftParenthesis) { t = peek(t); if (t.value == TOK.__stdcall) t = peek(t); if (!isCDeclarator(t, declarator)) return false; if (t.value != TOK.rightParenthesis) return false; t = peek(t); } else if (declarator == DTR.xdirect) { return false; } while (1) { if (t.value == TOK.leftBracket) { if (!skipBrackets(t)) return false; } else if (t.value == TOK.leftParenthesis) { if (!skipParens(t, &t)) return false; } else break; } pt = t; return true; } /*************************** * Is this the start of a type-qualifier-list? * (Can be empty.) * Params: * pt = first token; updated with past end of type-qualifier-list if true * Returns: * true if start of type-qualifier-list */ private bool isTypeQualifierList(ref Token* pt) { auto t = pt; while (1) { switch (t.value) { case TOK.const_: case TOK.restrict: case TOK.volatile: case TOK._Atomic: case TOK.__stdcall: t = peek(t); continue; default: break; } break; } pt = t; return true; } /*************************** * Is this the start of a type-name? * Params: * pt = first token; updated with past end of type-name if true * Returns: * true if start of type-name */ private bool isTypeName(ref Token* pt) { auto t = pt; //printf("isTypeName() %s\n", t.toChars()); if (!isSpecifierQualifierList(t)) return false; if (!isCDeclarator(t, DTR.xabstract)) return false; if (t.value != TOK.rightParenthesis) return false; pt = t; return true; } /*************************** * Is this the start of a specifier-qualifier-list? * Params: * pt = first token; updated with past end of specifier-qualifier-list if true * Returns: * true if start of specifier-qualifier-list */ private bool isSpecifierQualifierList(ref Token* pt) { auto t = pt; bool result; while (1) { switch (t.value) { // Type Qualifiers case TOK.const_: case TOK.restrict: case TOK.volatile: case TOK.__stdcall: // Type Specifiers case TOK.char_: case TOK.signed: case TOK.unsigned: case TOK.int16: case TOK.int32: case TOK.int64: case TOK.__int128: case TOK.float32: case TOK.float64: case TOK.void_: case TOK._Bool: //case TOK._Imaginary: // ? missing in Spec case TOK._Complex: t = peek(t); break; case TOK.identifier: // Use typedef table to disambiguate if (isTypedef(t.ident)) { t = peek(t); break; } else { return false; } // struct-or-union-specifier // enum-specifier case TOK.struct_: case TOK.union_: case TOK.enum_: t = peek(t); if (t.value == TOK.identifier) { t = peek(t); if (t.value == TOK.leftCurly) { if (!skipBraces(t)) return false; } } else if (t.value == TOK.leftCurly) { if (!skipBraces(t)) return false; } else return false; break; // atomic-type-specifier case TOK._Atomic: case TOK.typeof_: case TOK.__attribute__: t = peek(t); if (t.value != TOK.leftParenthesis || !skipParens(t, &t)) return false; break; default: if (result) pt = t; return result; } result = true; } } /************************************ * Looking at the leading left parenthesis, and determine if it is * either of the following: * ( type-name ) cast-expression * ( type-name ) { initializer-list } * as opposed to: * ( expression ) * Params: * pt = starting token, updated to one past end of constant-expression if true * afterParenType = true if already seen `( type-name )` * Returns: * true if matches ( type-name ) ... */ private bool isCastExpression(ref Token* pt, bool afterParenType = false) { enum log = false; if (log) printf("isCastExpression(tk: `%s`, afterParenType: %d)\n", token.toChars(pt.value), afterParenType); auto t = pt; switch (t.value) { case TOK.leftParenthesis: auto tk = peek(t); // move past left parenthesis if (!isTypeName(tk) || tk.value != TOK.rightParenthesis) { if (afterParenType) goto default; // could be ( type-name ) ( unary-expression ) return false; } tk = peek(tk); // move past right parenthesis if (tk.value == TOK.leftCurly) { // ( type-name ) { initializer-list } if (!isInitializer(tk)) { return false; } t = tk; break; } if (tk.value == TOK.leftParenthesis && peek(tk).value == TOK.rightParenthesis) { return false; // (type-name)() is not a cast (it might be a function call) } if (!isCastExpression(tk, true)) { if (afterParenType) // could be ( type-name ) ( unary-expression ) goto default; // where unary-expression also matched type-name return true; } // ( type-name ) cast-expression t = tk; break; default: if (!afterParenType || !isUnaryExpression(t, afterParenType)) { return false; } // if we've already seen ( type-name ), then this is a cast break; } pt = t; if (log) printf("isCastExpression true\n"); return true; } /******************************** * See if match for unary-expression. * Params: * pt = starting token, updated to one past end of constant-expression if true * afterParenType = true if already seen ( type-name ) of a cast-expression * Returns: * true if unary-expression */ private bool isUnaryExpression(ref Token* pt, bool afterParenType = false) { auto t = pt; switch (t.value) { case TOK.plusPlus: case TOK.minusMinus: t = peek(t); if (!isUnaryExpression(t, afterParenType)) return false; break; case TOK.and: case TOK.mul: case TOK.min: case TOK.add: case TOK.not: case TOK.tilde: t = peek(t); if (!isCastExpression(t, afterParenType)) return false; break; case TOK.sizeof_: t = peek(t); if (t.value == TOK.leftParenthesis) { auto tk = peek(t); if (isTypeName(tk)) { if (tk.value != TOK.rightParenthesis) return false; t = peek(tk); break; } } if (!isUnaryExpression(t, afterParenType)) return false; break; case TOK._Alignof: t = peek(t); if (t.value != TOK.leftParenthesis) return false; t = peek(t); if (!isTypeName(t) || t.value != TOK.rightParenthesis) return false; break; default: // Compound literals are handled by cast and sizeof expressions, // so be content with just seeing a primary expression. if (!isPrimaryExpression(t)) return false; break; } pt = t; return true; } /******************************** * See if match for primary-expression. * Params: * pt = starting token, updated to one past end of constant-expression if true * Returns: * true if primary-expression */ private bool isPrimaryExpression(ref Token* pt) { auto t = pt; switch (t.value) { case TOK.identifier: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.string_: t = peek(t); break; case TOK.leftParenthesis: // ( expression ) if (!skipParens(t, &t)) return false; break; case TOK._Generic: t = peek(t); if (!skipParens(t, &t)) return false; break; default: return false; } pt = t; return true; } //} /******************************************************************************/ /********************************* More ***************************************/ //{ /************** * Declaration context */ enum LVL { global = 1, /// global parameter = 2, /// function parameter (declarations for function identifier-list) prototype = 4, /// function prototype local = 8, /// local member = 0x10, /// struct member } /// Types of declarator to parse enum DTR { xdirect = 1, /// C11 6.7.6 direct-declarator xabstract = 2, /// C11 6.7.7 abstract-declarator xparameter = 3, /// parameter declarator may be either direct or abstract } /// C11 6.7.1 Storage-class specifiers enum SCW : uint { xnone = 0, xtypedef = 1, xextern = 2, xstatic = 4, x_Thread_local = 8, xauto = 0x10, xregister = 0x20, // C11 6.7.4 Function specifiers xinline = 0x40, x_Noreturn = 0x80, xnoinline = 0x100, } /// C11 6.7.3 Type qualifiers enum MOD : uint { xnone = 0, xconst = 1, xvolatile = 2, xrestrict = 4, x_Atomic = 8, x__stdcall = 0x10, // Windows linkage extension } /********************************** * Aggregate for all the various specifiers */ struct Specifier { bool noreturn; /// noreturn attribute bool naked; /// naked attribute bool _nothrow; /// nothrow attribute bool _pure; /// pure attribute bool dllimport; /// dllimport attribute bool dllexport; /// dllexport attribute bool _deprecated; /// deprecated attribute AST.Expression depMsg; /// deprecated message uint vector_size; /// positive power of 2 multipe of base type size SCW scw; /// storage-class specifiers MOD mod; /// type qualifiers AST.Expressions* alignExps; /// alignment structalign_t packalign; /// #pragma pack alignment value } /*********************** * Convert from C specifiers to D storage class * Params: * level = declaration context * specifier = specifiers, context, etc. * Returns: * corresponding D storage class */ StorageClass specifiersToSTC(LVL level, const ref Specifier specifier) { StorageClass stc; if (specifier.scw & SCW.x_Thread_local) { if (level == LVL.global) { if (specifier.scw & SCW.xextern) stc = AST.STC.extern_; else if (specifier.scw & SCW.xstatic) stc = AST.STC.static_; } else if (level == LVL.local) { if (specifier.scw & SCW.xextern) stc = AST.STC.extern_; else if (specifier.scw & SCW.xstatic) stc = AST.STC.static_; } else if (level == LVL.member) { if (specifier.scw & SCW.xextern) stc = AST.STC.extern_; else if (specifier.scw & SCW.xstatic) stc = AST.STC.static_; } } else { if (level == LVL.global) { if (specifier.scw & SCW.xextern) stc = AST.STC.extern_ | AST.STC.gshared; else if (specifier.scw & SCW.xstatic) stc = AST.STC.gshared | AST.STC.static_; else stc = AST.STC.gshared; } else if (level == LVL.local) { if (specifier.scw & SCW.xextern) stc = AST.STC.extern_ | AST.STC.gshared; else if (specifier.scw & SCW.xstatic) stc = AST.STC.gshared; else if (specifier.scw & SCW.xregister) stc = AST.STC.register; } else if (level == LVL.parameter) { if (specifier.scw & SCW.xregister) stc = AST.STC.register | AST.STC.parameter; else stc = AST.STC.parameter; } else if (level == LVL.member) { if (specifier.scw & SCW.xextern) stc = AST.STC.extern_ | AST.STC.gshared; else if (specifier.scw & SCW.xstatic) stc = AST.STC.gshared; } } if (specifier._deprecated && !specifier.depMsg) stc |= AST.STC.deprecated_; return stc; } /*********************** * Add attributes from Specifier to function * Params: * fd = function to apply them to * specifier = specifiers */ void specifiersToFuncDeclaration(AST.FuncDeclaration fd, const ref Specifier specifier) { fd.isNaked = specifier.naked; fd.dllImport = specifier.dllimport; fd.dllExport = specifier.dllexport; if (specifier.scw & SCW.xnoinline) fd.inlining = PINLINE.never; else if (specifier.scw & SCW.xinline) fd.inlining = PINLINE.always; } /*********************** * Add attributes from Specifier to variable * Params: * vd = function to apply them to * specifier = specifiers */ void specifiersToVarDeclaration(AST.VarDeclaration vd, const ref Specifier specifier) { vd.dllImport = specifier.dllimport; vd.dllExport = specifier.dllexport; } /*********************** * Return suitable signed integer type for the given size * Params: * size = size of type * Returns: * corresponding signed D integer type */ private AST.Type integerTypeForSize(ubyte size) { if (size <= 1) return AST.Type.tint8; if (size <= 2) return AST.Type.tint16; if (size <= 4) return AST.Type.tint32; if (size <= 8) return AST.Type.tint64; if (size == 16) { error("__int128 not supported"); return AST.Type.terror; } error("unsupported integer type"); return AST.Type.terror; } /*********************** * Return suitable unsigned integer type for the given size * Params: * size = size of type * Returns: * corresponding unsigned D integer type */ private AST.Type unsignedTypeForSize(ubyte size) { if (size <= 1) return AST.Type.tuns8; if (size <= 2) return AST.Type.tuns16; if (size <= 4) return AST.Type.tuns32; if (size <= 8) return AST.Type.tuns64; if (size == 16) { error("unsigned __int128 not supported"); return AST.Type.terror; } error("unsupported integer type"); return AST.Type.terror; } /*********************** * Return suitable D float type for C `long double` * Params: * flags = kind of float to return (real, imaginary, complex). * Returns: * corresponding D type */ private AST.Type realType(RTFlags flags) { if (long_doublesize == AST.Type.tfloat80.size()) { // On GDC and LDC, D `real` types map to C `long double`, so never // return a double type when real.sizeof == double.sizeof. final switch (flags) { case RTFlags.realfloat: return AST.Type.tfloat80; case RTFlags.imaginary: return AST.Type.timaginary80; case RTFlags.complex: return AST.Type.tcomplex80; } } else { final switch (flags) { case RTFlags.realfloat: return long_doublesize == 8 ? AST.Type.tfloat64 : AST.Type.tfloat80; case RTFlags.imaginary: return long_doublesize == 8 ? AST.Type.timaginary64 : AST.Type.timaginary80; case RTFlags.complex: return long_doublesize == 8 ? AST.Type.tcomplex64 : AST.Type.tcomplex80; } } } /************** * Flags for realType */ private enum RTFlags { realfloat, imaginary, complex, } /******************** * C11 6.4.2.2 Create declaration to predefine __func__ * `static const char __func__[] = " function-name ";` * Params: * loc = location for this declaration * id = identifier of function * Returns: * statement representing the declaration of __func__ */ private AST.Statement createFuncName(Loc loc, Identifier id) { const fn = id.toString(); // function-name auto efn = new AST.StringExp(loc, fn, fn.length, 1, 'c'); auto ifn = new AST.ExpInitializer(loc, efn); auto lenfn = new AST.IntegerExp(loc, fn.length + 1, AST.Type.tuns32); // +1 for terminating 0 auto tfn = new AST.TypeSArray(AST.Type.tchar, lenfn); efn.type = tfn.makeImmutable(); efn.committed = true; auto sfn = new AST.VarDeclaration(loc, tfn, Id.__func__, ifn, STC.gshared | STC.immutable_); auto e = new AST.DeclarationExp(loc, sfn); return new AST.ExpStatement(loc, e); } /************************ * After encountering an error, scan forward until a right brace or ; is found * or the end of the file. */ void panic() { while (token.value != TOK.rightCurly && token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); } /************************** * Apply `const` to a type. * Params: * t = type to add const to * Returns: * resulting type */ private AST.Type toConst(AST.Type t) { // `const` is always applied to the return type, not the // type function itself. if (auto tf = t.isTypeFunction()) tf.next = tf.next.addSTC(STC.const_); else if (auto tt = t.isTypeTag()) tt.mod |= MODFlags.const_; else { /* Ignore const if the result would be const pointer to mutable */ auto tn = t.nextOf(); if (!tn || tn.isConst()) t = t.addSTC(STC.const_); } return t; } /*************************** * Apply specifier to a Dsymbol. * Params: * s = Dsymbol * specifier = specifiers to apply * Returns: * Dsymbol with specifiers applied */ private AST.Dsymbol applySpecifier(AST.Dsymbol s, ref Specifier specifier) { //printf("applySpecifier() %s\n", s.toChars()); if (specifier._deprecated) { if (specifier.depMsg) { // Wrap declaration in a DeprecatedDeclaration auto decls = new AST.Dsymbols(1); (*decls)[0] = s; s = new AST.DeprecatedDeclaration(specifier.depMsg, decls); } } if (specifier.alignExps) { //printf(" applying _Alignas %s, packalign %d\n", (*specifier.alignExps)[0].toChars(), cast(int)specifier.packalign); // Wrap declaration in an AlignDeclaration auto decls = new AST.Dsymbols(1); (*decls)[0] = s; s = new AST.AlignDeclaration(s.loc, specifier.alignExps, decls); } else if (!specifier.packalign.isDefault() && !specifier.packalign.isUnknown()) { //printf(" applying packalign %d\n", cast(int)specifier.packalign); // Wrap #pragma pack in an AlignDeclaration auto decls = new AST.Dsymbols(1); (*decls)[0] = s; s = new AST.AlignDeclaration(s.loc, specifier.packalign, decls); } return s; } //} /******************************************************************************/ /************************** typedefTab symbol table ***************************/ //{ /******************************** * Determines if type t is a function type. * Params: * t = type to test * Returns: * true if it represents a function */ bool isFunctionTypedef(AST.Type t) { //printf("isFunctionTypedef() %s\n", t.toChars()); if (t.isTypeFunction()) return true; if (auto tid = t.isTypeIdentifier()) { auto pt = lookupTypedef(tid.ident); if (pt && *pt) { return (*pt).isTypeFunction() !is null; } } return false; } /******************************** * Determine if `id` is a symbol for a Typedef. * Params: * id = possible typedef * Returns: * true if id is a Type */ bool isTypedef(Identifier id) { auto pt = lookupTypedef(id); return (pt && *pt); } /******************************* * Add `id` to typedefTab[], but only if it will mask an existing typedef. * Params: id = identifier for non-typedef symbol */ void insertIdToTypedefTab(Identifier id) { //printf("insertIdToTypedefTab(id: %s) level %d\n", id.toChars(), cast(int)typedefTab.length - 1); if (isTypedef(id)) // if existing typedef { /* Add id as null, so we can later distinguish it from a non-null typedef */ static if (LDC_pre_2084) auto tab = *cast(void*[void*]*) &(typedefTab[$ - 1]); else auto tab = cast(void*[void*])(typedefTab[$ - 1]); tab[cast(void*)id] = cast(void*)null; } } /******************************* * Add `id` to typedefTab[] * Params: * id = identifier for typedef symbol * t = type of the typedef symbol */ void insertTypedefToTypedefTab(Identifier id, AST.Type t) { //printf("insertTypedefToTypedefTab(id: %s, t: %s) level %d\n", id.toChars(), t ? t.toChars() : "null".ptr, cast(int)typedefTab.length - 1); if (auto tid = t.isTypeIdentifier()) { // Try to resolve the TypeIdentifier to its type auto pt = lookupTypedef(tid.ident); if (pt && *pt) t = *pt; } static if (LDC_pre_2084) auto tab = *cast(void*[void*]*) &(typedefTab[$ - 1]); else auto tab = cast(void*[void*])(typedefTab[$ - 1]); tab[cast(void*)id] = cast(void*)t; typedefTab[$ - 1] = cast(void*)tab; } /********************************* * Lookup id in typedefTab[]. * Returns: * if not found, then null. * if found, then Type*. Deferencing it will yield null if it is not * a typedef, and a type if it is a typedef. */ AST.Type* lookupTypedef(Identifier id) { foreach_reverse (tab; typedefTab[]) { static if (LDC_pre_2084) auto ctab = *cast(void*[void*]*) &tab; else auto ctab = cast(void*[void*])tab; if (auto pt = cast(void*)id in ctab) { return cast(AST.Type*)pt; } } return null; // not found } //} /******************************************************************************/ /********************************* Directive Parser ***************************/ //{ override bool parseSpecialTokenSequence() { Token n; scan(&n); if (n.value == TOK.int32Literal) { poundLine(n, true); return true; } if (n.value == TOK.identifier) { if (n.ident == Id.line) { poundLine(n, false); return true; } else if (defines && (n.ident == Id.define || n.ident == Id.undef)) { /* Append this line to `defines`. * Not canonicalizing it - assume it already is */ defines.writeByte('#'); defines.writestring(n.ident.toString()); skipToNextLine(defines); defines.writeByte(0); // each #define line is 0 terminated return true; } else if (n.ident == Id.__pragma) { pragmaDirective(scanloc); return true; } else if (n.ident == Id.ident) // #ident "string" { scan(&n); if (n.value == TOK.string_ && n.ptr[0] == '"' && n.postfix == 0) { /* gcc inserts string into the .comment section in the object file. * Just ignore it for now, but can support it later by writing * the string to obj_exestr() */ //auto comment = n.ustring; scan(&n); if (n.value == TOK.endOfFile || n.value == TOK.endOfLine) return true; } error("\"string\" expected after `#ident`"); return false; } } if (n.ident != Id.undef) error("C preprocessor directive `#%s` is not supported", n.toChars()); return false; } /********************************************* * VC __pragma * https://docs.microsoft.com/en-us/cpp/preprocessor/pragma-directives-and-the-pragma-keyword?view=msvc-170 * Scanner is on the `__pragma` * Params: * startloc = location to use for error messages */ private void uupragmaDirective(const ref Loc startloc) { const loc = startloc; nextToken(); // move past __pragma if (token.value != TOK.leftParenthesis) { error(loc, "left parenthesis expected to follow `__pragma` instead of `%s`", token.toChars()); nextToken(); return; } nextToken(); if (token.value == TOK.identifier) { if (token.ident == Id.pack) pragmaPack(startloc, false); else { nextToken(); if (token.value == TOK.leftParenthesis) cparseParens(); } } else if (token.value == TOK.endOfFile) { } else if (token.value == TOK.rightParenthesis) { } else error(loc, "unrecognized `__pragma(%s)`", token.toChars()); if (token.value != TOK.rightParenthesis) { error(loc, "right parenthesis expected to close `__pragma(...)` instead of `%s`", token.toChars()); return; } nextToken(); } /********************************************* * C11 6.10.6 Pragma directive * # pragma pp-tokens(opt) new-line * The C preprocessor sometimes leaves pragma directives in * the preprocessed output. Ignore them. * Upon return, p is at start of next line. */ private void pragmaDirective(const ref Loc loc) { Token n; scan(&n); if (n.value == TOK.identifier && n.ident == Id.pack) return pragmaPack(loc, true); if (n.value != TOK.endOfLine) skipToNextLine(); } /********* * # pragma pack * https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html * https://docs.microsoft.com/en-us/cpp/preprocessor/pack * Scanner is on the `pack` * Params: * startloc = location to use for error messages * useScan = use scan() to retrieve next token, instead of nextToken() */ private void pragmaPack(const ref Loc startloc, bool useScan) { const loc = startloc; /* Pull tokens from scan() or nextToken() */ void scan(Token* t) { if (useScan) { Lexer.scan(t); } else { nextToken(); *t = token; } } Token n; scan(&n); if (n.value != TOK.leftParenthesis) { error(loc, "left parenthesis expected to follow `#pragma pack`"); if (n.value != TOK.endOfLine) skipToNextLine(); return; } void closingParen() { if (n.value != TOK.rightParenthesis) { error(loc, "right parenthesis expected to close `#pragma pack(`"); } if (n.value != TOK.endOfLine) skipToNextLine(); } void setPackAlign(ref const Token t) { const n = t.unsvalue; if (n < 1 || n & (n - 1) || ushort.max < n) error(loc, "pack must be an integer positive power of 2, not 0x%llx", cast(ulong)n); packalign.set(cast(uint)n); packalign.setPack(true); } scan(&n); if (!records) { records = new Array!Identifier; packs = new Array!structalign_t; } /* # pragma pack ( show ) */ if (n.value == TOK.identifier && n.ident == Id.show) { if (packalign.isDefault()) eSink.warning(startloc, "current pack attribute is default"); else eSink.warning(startloc, "current pack attribute is %d", packalign.get()); scan(&n); return closingParen(); } /* # pragma pack ( push ) * # pragma pack ( push , identifier ) * # pragma pack ( push , integer ) * # pragma pack ( push , identifier , integer ) */ if (n.value == TOK.identifier && n.ident == Id.push) { scan(&n); Identifier record = null; if (n.value == TOK.comma) { scan(&n); if (n.value == TOK.identifier) { record = n.ident; scan(&n); if (n.value == TOK.comma) { scan(&n); if (n.value == TOK.int32Literal) { setPackAlign(n); scan(&n); } else error(loc, "alignment value expected, not `%s`", n.toChars()); } } else if (n.value == TOK.int32Literal) { setPackAlign(n); scan(&n); } else error(loc, "alignment value expected, not `%s`", n.toChars()); } this.records.push(record); this.packs.push(packalign); return closingParen(); } /* # pragma pack ( pop ) * # pragma pack ( pop PopList ) * PopList : * , IdentifierOrInteger * , IdentifierOrInteger PopList * IdentifierOrInteger: * identifier * integer */ if (n.value == TOK.identifier && n.ident == Id.pop) { scan(&n); size_t len = this.records.length; if (n.value == TOK.rightParenthesis) // #pragma pack ( pop ) { if (len == 0) // nothing to pop return closingParen(); this.records.setDim(len - 1); this.packs.setDim(len - 1); if (len == 1) // stack is now empty packalign.setDefault(); else packalign = (*this.packs)[len - 1]; return closingParen(); } while (n.value == TOK.comma) // #pragma pack ( pop , { scan(&n); if (n.value == TOK.identifier) { /* pragma pack(pop, identifier * Pop until identifier is found, pop that one too, and set * alignment to the new top of the stack. * If identifier is not found, do nothing. */ for ( ; len; --len) { if ((*this.records)[len - 1] == n.ident) { this.records.setDim(len - 1); this.packs.setDim(len - 1); if (len > 1) packalign = (*this.packs)[len - 2]; else packalign.setDefault(); // stack empty, use default break; } } scan(&n); } else if (n.value == TOK.int32Literal) { setPackAlign(n); scan(&n); } else { error(loc, "identifier or alignment value expected following `#pragma pack(pop,` not `%s`", n.toChars()); scan(&n); } } return closingParen(); } /* # pragma pack ( integer ) * Sets alignment to integer */ if (n.value == TOK.int32Literal) { setPackAlign(n); scan(&n); return closingParen(); } /* # pragma pack ( ) * Sets alignment to default */ if (n.value == TOK.rightParenthesis) { packalign.setDefault(); return closingParen(); } error(loc, "unrecognized `#pragma pack(%s)`", n.toChars()); if (n.value != TOK.endOfLine) skipToNextLine(); } //} /******************************************************************************/ /********************************* #define Parser *****************************/ //{ /** * Go through the #define's in the defines buffer and see what we can convert * to Dsymbols, which are then appended to symbols[] */ void addDefines() { if (!defines || defines.length < 10) // minimum length of a #define line return; OutBuffer* buf = defines; defines = null; // prevent skipToNextLine() and parseSpecialTokenSequence() // from appending to slice[] const length = buf.length; buf.writeByte(0); auto slice = buf.peekChars()[0 .. length]; auto scanlocSave = scanloc; resetDefineLines(slice); // reset lexer auto save = eSink; auto eLatch = new ErrorSinkLatch(); eSink = eLatch; const(char)* endp = &slice[length - 7]; AST.Dsymbols newSymbols; size_t[void*] defineTab; // hash table of #define's turned into Symbol's // indexed by Identifier, returns index into newSymbols[] // The memory for this is leaked void addSym(AST.Dsymbol s) { //printf("addSym() %s\n", s.toChars()); if (auto v = s.isVarDeclaration()) v.isCmacro(true); // mark it as coming from a C #define /* If it's already defined, replace the earlier * definition */ if (size_t* pd = cast(void*)s.ident in defineTab) { //printf("replacing %s\n", s.toChars()); newSymbols[*pd] = s; return; } defineTab[cast(void*)s.ident] = newSymbols.length; newSymbols.push(s); } void removeSym(Identifier ident) { //printf("removeSym() %s\n", ident.toChars()); if (size_t* pd = cast(void*)ident in defineTab) { //printf("removing %s\n", ident.toChars()); newSymbols[*pd] = null; } } while (p < endp) { //printf("|%s|\n", p); if (p[0 .. 7] == "#define") { p += 7; nextToken(); //printf("define %s\n", token.toChars()); if (token.value == TOK.identifier) { auto id = token.ident; const params = *p == '('; nextToken(); AST.Type t; Lswitch: switch (token.value) { case TOK.endOfFile: // #define identifier ++p; continue; case TOK.int32Literal: case TOK.charLiteral: t = AST.Type.tint32; goto Linteger; case TOK.wcharLiteral: t = AST.Type.tuns16; goto Linteger; case TOK.dcharLiteral: case TOK.uns32Literal: t = AST.Type.tuns32; goto Linteger; case TOK.int64Literal: t = AST.Type.tint64; goto Linteger; case TOK.uns64Literal: t = AST.Type.tuns64; goto Linteger; Linteger: const intvalue = token.intvalue; nextToken(); if (token.value == TOK.endOfFile) { /* Declare manifest constant: * enum id = intvalue; */ AST.Expression e = new AST.IntegerExp(scanloc, intvalue, t); auto v = new AST.VarDeclaration(scanloc, t, id, new AST.ExpInitializer(scanloc, e), STC.manifest); addSym(v); ++p; continue; } break; case TOK.float32Literal: t = AST.Type.tfloat32; goto Lfloat; case TOK.float64Literal: t = AST.Type.tfloat64; goto Lfloat; case TOK.float80Literal: t = AST.Type.tfloat80; goto Lfloat; case TOK.imaginary32Literal: t = AST.Type.timaginary32; goto Lfloat; case TOK.imaginary64Literal: t = AST.Type.timaginary64; goto Lfloat; case TOK.imaginary80Literal: t = AST.Type.timaginary80; goto Lfloat; Lfloat: const floatvalue = token.floatvalue; nextToken(); if (token.value == TOK.endOfFile) { /* Declare manifest constant: * enum id = floatvalue; */ AST.Expression e = new AST.RealExp(scanloc, floatvalue, t); auto v = new AST.VarDeclaration(scanloc, t, id, new AST.ExpInitializer(scanloc, e), STC.manifest); addSym(v); ++p; continue; } break; case TOK.string_: const str = token.ustring; const len = token.len; const postfix = token.postfix; nextToken(); if (token.value == TOK.endOfFile) { /* Declare manifest constant: * enum id = "string"; */ AST.Expression e = new AST.StringExp(scanloc, str[0 .. len], len, 1, postfix); auto v = new AST.VarDeclaration(scanloc, null, id, new AST.ExpInitializer(scanloc, e), STC.manifest); addSym(v); ++p; continue; } break; case TOK.leftParenthesis: { /* Look for: * #define ID ( expression ) * and rewrite it to a template function: * auto ID()() { return expression; } */ if (params) goto caseFunctionLike; // version with parameters nextToken(); eLatch.sawErrors = false; auto exp = cparseExpression(); if (eLatch.sawErrors) // parsing errors break; // abandon this #define if (token.value != TOK.rightParenthesis) break; nextToken(); if (token.value != TOK.endOfFile) break; auto ret = new AST.ReturnStatement(exp.loc, exp); auto parameterList = AST.ParameterList(new AST.Parameters(), VarArg.none, 0); StorageClass stc = STC.auto_; auto tf = new AST.TypeFunction(parameterList, null, LINK.d, stc); auto fd = new AST.FuncDeclaration(exp.loc, exp.loc, id, stc, tf, 0); fd.fbody = ret; AST.Dsymbols* decldefs = new AST.Dsymbols(); decldefs.push(fd); AST.TemplateParameters* tpl = new AST.TemplateParameters(); AST.Expression constraint = null; auto tempdecl = new AST.TemplateDeclaration(exp.loc, id, tpl, constraint, decldefs, false); addSym(tempdecl); ++p; continue; } caseFunctionLike: { /* Parse `( a, b ) expression` * Create template function: * auto id(__MP1, __MP2)(__MP1 a, __MP1 b) { return expression; } */ //printf("functionlike %s\n", id.toChars()); // Capture the parameter list VarArg varargs = VarArg.none; auto parameters = new AST.Parameters(); nextToken(); // skip past `(` Lwhile: while (1) { if (token.value == TOK.rightParenthesis) break; if (token.value == TOK.dotDotDot) { static if (0) // variadic macros not supported yet { varargs = AST.VarArg.variadic; // C-style variadics nextToken(); if (token.value == TOK.rightParenthesis) break Lwhile; } break Lswitch; } if (token.value != TOK.identifier) break Lswitch; auto param = new AST.Parameter(token.loc, 0, null, token.ident, null, null); parameters.push(param); nextToken(); if (token.value == TOK.comma) { nextToken(); continue; } break; } if (token.value != TOK.rightParenthesis) break; //auto pstart = p; nextToken(); auto parameterList = AST.ParameterList(parameters, varargs, 0); /* Create a type for each parameter. Add it to the template parameter list, * and the parameter list. */ auto tpl = new AST.TemplateParameters(); foreach (param; (*parameters)[]) { auto idtype = Identifier.generateId("__MP"); auto loc = param.loc; auto tp = new AST.TemplateTypeParameter(loc, idtype, null, null); tpl.push(tp); auto at = new AST.TypeIdentifier(loc, idtype); param.type = at; } eLatch.sawErrors = false; auto exp = cparseExpression(); //printf("exp: %s tok: %s\n", exp.toChars(), Token.toChars(token.value)); //printf("parsed: '%.*s'\n", cast(int)(p - pstart), pstart); assert(symbols); if (eLatch.sawErrors) // parsing errors break; // abandon this #define if (token.value != TOK.endOfFile) // did not consume the entire line break; // Generate function auto ret = new AST.ReturnStatement(exp.loc, exp); StorageClass stc = STC.auto_; auto tf = new AST.TypeFunction(parameterList, null, LINK.d, stc); auto fd = new AST.FuncDeclaration(exp.loc, exp.loc, id, stc, tf, 0); fd.fbody = ret; // Wrap it in an eponymous template AST.Dsymbols* decldefs = new AST.Dsymbols(); decldefs.push(fd); auto tempdecl = new AST.TemplateDeclaration(exp.loc, id, tpl, null, decldefs, false); addSym(tempdecl); ++p; continue; } default: break; } } } else if (p[0 .. 6] == "#undef") { p += 6; nextToken(); //printf("undef %s\n", token.toChars()); if (token.value == TOK.identifier) removeSym(token.ident); } // scan to end of line while (*p) ++p; ++p; // advance to start of next line scanloc.linnum = scanloc.linnum + 1; } if (newSymbols.length) { assert(symbols, "symbols is null"); symbols.reserve(newSymbols.length); foreach (sym; newSymbols) if (sym) // undefined entries are null symbols.push(sym); } scanloc = scanlocSave; eSink = save; defines = buf; } //} } ldc-1.40.0-src/dmd/identifier.h0000644000000000000000000000211614727557031014673 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/identifier.h */ #pragma once #include "root/dcompat.h" #include "rootobject.h" class Identifier final : public RootObject { private: int value; d_bool isAnonymous_; DString string; public: static Identifier* create(const char *string); const char *toChars() const override; int getValue() const; bool isAnonymous() const; const char *toHChars2() const; DYNCAST dyncast() const override; static Identifier *generateId(const char *prefix, size_t length, size_t suffix); static Identifier *idPool(const char *s, unsigned len); static inline Identifier *idPool(const char *s) { return idPool(s, static_cast(strlen(s))); } static bool isValidIdentifier(const char *p); }; ldc-1.40.0-src/dmd/target.d0000644000000000000000000016130014727557031014034 0ustar rootroot/** * Handles target-specific parameters * * In order to allow for cross compilation, when the compiler produces a binary * for a different platform than it is running on, target information needs * to be abstracted. This is done in this module, primarily through `Target`. * * Note: * While DMD itself does not support cross-compilation, GDC and LDC do. * Hence, this module is (sometimes heavily) modified by them, * and contributors should review how their changes affect them. * * See_Also: * - $(LINK2 https://wiki.osdev.org/Target_Triplet, Target Triplets) * - $(LINK2 https://github.com/ldc-developers/ldc, LDC repository) * - $(LINK2 https://github.com/D-Programming-GDC/gcc, GDC repository) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/target.d, _target.d) * Documentation: https://dlang.org/phobos/dmd_target.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/target.d */ module dmd.target; import core.stdc.stdio; import dmd.astenums : CHECKENABLE; import dmd.globals : Param; version (IN_LLVM) { import dmd.globals : IN_LLVM; import gen.llvmhelpers : isTargetWindowsMSVC; } enum CPU : ubyte { x87, mmx, sse, sse2, sse3, ssse3, sse4_1, sse4_2, avx, // AVX1 instruction set avx2, // AVX2 instruction set avx512, // AVX-512 instruction set // Special values that don't survive past the command line processing baseline, // (default) the minimum capability CPU native // the machine the compiler is being run on } Target.OS defaultTargetOS() @safe { version (Windows) return Target.OS.Windows; else version (linux) return Target.OS.linux; else version (OSX) return Target.OS.OSX; else version (FreeBSD) return Target.OS.FreeBSD; else version (OpenBSD) return Target.OS.OpenBSD; else version (Solaris) return Target.OS.Solaris; else version (DragonFlyBSD) return Target.OS.DragonFlyBSD; else static assert(0, "unknown TARGET"); } ubyte defaultTargetOSMajor() @safe { version (FreeBSD) { version (TARGET_FREEBSD10) return 10; else version (TARGET_FREEBSD11) return 11; else version (TARGET_FREEBSD12) return 12; else version (TARGET_FREEBSD13) return 13; else version (TARGET_FREEBSD14) return 14; else return 0; } else return 0; } version (IN_LLVM) {} else { /** * Add default `version` identifier for dmd, and set the * target platform in `params`. * https://dlang.org/spec/version.html#predefined-versions * * Needs to be run after all arguments parsing (command line, DFLAGS environment * variable and config file) in order to add final flags (such as `X86_64` or * the `CRuntime` used). * * Params: * params = which target to compile for (set by `setTarget()`) * tgt = target */ public void addDefaultVersionIdentifiers(const ref Param params, const ref Target tgt) { import dmd.cond : VersionCondition; import dmd.dmdparams : driverParams, PIC; VersionCondition.addPredefinedGlobalIdent("DigitalMars"); VersionCondition.addPredefinedGlobalIdent("LittleEndian"); VersionCondition.addPredefinedGlobalIdent("D_Version2"); VersionCondition.addPredefinedGlobalIdent("all"); addPredefinedGlobalIdentifiers(tgt); if (params.ddoc.doOutput) VersionCondition.addPredefinedGlobalIdent("D_Ddoc"); if (params.cov) VersionCondition.addPredefinedGlobalIdent("D_Coverage"); if (driverParams.pic != PIC.fixed) VersionCondition.addPredefinedGlobalIdent(driverParams.pic == PIC.pic ? "D_PIC" : "D_PIE"); if (params.useUnitTests) VersionCondition.addPredefinedGlobalIdent("unittest"); if (params.useAssert == CHECKENABLE.on) VersionCondition.addPredefinedGlobalIdent("assert"); if (params.useIn == CHECKENABLE.on) VersionCondition.addPredefinedGlobalIdent("D_PreConditions"); if (params.useOut == CHECKENABLE.on) VersionCondition.addPredefinedGlobalIdent("D_PostConditions"); if (params.useInvariants == CHECKENABLE.on) VersionCondition.addPredefinedGlobalIdent("D_Invariants"); if (params.useArrayBounds == CHECKENABLE.off) VersionCondition.addPredefinedGlobalIdent("D_NoBoundsChecks"); if (params.betterC) { VersionCondition.addPredefinedGlobalIdent("D_BetterC"); } else { if (params.useModuleInfo) VersionCondition.addPredefinedGlobalIdent("D_ModuleInfo"); if (params.useExceptions) VersionCondition.addPredefinedGlobalIdent("D_Exceptions"); if (params.useTypeInfo) VersionCondition.addPredefinedGlobalIdent("D_TypeInfo"); } VersionCondition.addPredefinedGlobalIdent("D_HardFloat"); if (params.tracegc) VersionCondition.addPredefinedGlobalIdent("D_ProfileGC"); if (driverParams.optimize) VersionCondition.addPredefinedGlobalIdent("D_Optimized"); } // /** // * Add predefined global identifiers that are determied by the target // */ private void addPredefinedGlobalIdentifiers(const ref Target tgt) { import dmd.cond : VersionCondition; alias predef = VersionCondition.addPredefinedGlobalIdent; if (tgt.cpu >= CPU.sse2) { predef("D_SIMD"); if (tgt.cpu >= CPU.avx) predef("D_AVX"); if (tgt.cpu >= CPU.avx2) predef("D_AVX2"); } with (Target) { if (tgt.os & OS.Posix) predef("Posix"); if (tgt.os & (OS.linux | OS.FreeBSD | OS.OpenBSD | OS.DragonFlyBSD | OS.Solaris)) predef("ELFv1"); switch (tgt.os) { case OS.none: { predef("FreeStanding"); break; } case OS.linux: { predef("linux"); break; } case OS.OpenBSD: { predef("OpenBSD"); break; } case OS.DragonFlyBSD: { predef("DragonFlyBSD"); break; } case OS.Solaris: { predef("Solaris"); break; } case OS.Windows: { predef("Windows"); VersionCondition.addPredefinedGlobalIdent(tgt.isX86_64 ? "Win64" : "Win32"); break; } case OS.OSX: { predef("OSX"); // For legacy compatibility predef("darwin"); break; } case OS.FreeBSD: { predef("FreeBSD"); if(tgt.osMajor != 0) { import core.stdc.stdio : snprintf; char["FreeBSD_100".length + 1] buffer; immutable len = snprintf(buffer.ptr, buffer.length, "FreeBSD_%u", uint(tgt.osMajor)); predef(buffer[0 .. len]); } break; } default: assert(0); } } addCRuntimePredefinedGlobalIdent(tgt.c); addCppRuntimePredefinedGlobalIdent(tgt.cpp); if (tgt.isX86_64) { VersionCondition.addPredefinedGlobalIdent("D_InlineAsm_X86_64"); VersionCondition.addPredefinedGlobalIdent("X86_64"); } else { VersionCondition.addPredefinedGlobalIdent("D_InlineAsm"); //legacy VersionCondition.addPredefinedGlobalIdent("D_InlineAsm_X86"); VersionCondition.addPredefinedGlobalIdent("X86"); } if (tgt.isLP64) VersionCondition.addPredefinedGlobalIdent("D_LP64"); else if (tgt.isX86_64) VersionCondition.addPredefinedGlobalIdent("X32"); } private void addCRuntimePredefinedGlobalIdent(const ref TargetC c) { import dmd.cond : VersionCondition; alias predef = VersionCondition.addPredefinedGlobalIdent; with (TargetC.Runtime) switch (c.runtime) { default: case Unspecified: return; case Bionic: return predef("CRuntime_Bionic"); case Glibc: return predef("CRuntime_Glibc"); case Microsoft: return predef("CRuntime_Microsoft"); case Musl: return predef("CRuntime_Musl"); case Newlib: return predef("CRuntime_Newlib"); case UClibc: return predef("CRuntime_UClibc"); case WASI: return predef("CRuntime_WASI"); } } private void addCppRuntimePredefinedGlobalIdent(const ref TargetCPP cpp) { import dmd.cond : VersionCondition; alias predef = VersionCondition.addPredefinedGlobalIdent; with (TargetCPP.Runtime) switch (cpp.runtime) { default: case Unspecified: return; case LLVM: predef("CppRuntime_LLVM"); predef("CppRuntime_Clang"); // legacy return; case GNU: predef("CppRuntime_GNU"); predef("CppRuntime_Gcc"); // legacy return; case Microsoft: predef("CppRuntime_Microsoft"); return; case Sun: predef("CppRuntime_Sun"); return; } } } // !IN_LLVM //////////////////////////////////////////////////////////////////////////////// /** * Describes a back-end target. At present it is incomplete, but in the future * it should grow to contain most or all target machine and target O/S specific * information. * * In many cases, calls to sizeof() can't be used directly for getting data type * sizes since cross compiling is supported and would end up using the host * sizes rather than the target sizes. */ extern (C++) struct Target { import dmd.dscope : Scope; import dmd.expression : Expression; import dmd.func : FuncDeclaration; import dmd.location; import dmd.astenums : LINK, TY; import dmd.mtype : Type, TypeFunction, TypeTuple; import dmd.typesem : pointerTo, size; import dmd.root.ctfloat : real_t; import dmd.statement : Statement; import dmd.tokens : EXP; /// Bit decoding of the Target.OS enum OS : ubyte { /* These are mutually exclusive; one and only one is set. * Match spelling and casing of corresponding version identifiers */ none = 0, linux = 1, Windows = 2, OSX = 4, OpenBSD = 8, FreeBSD = 0x10, Solaris = 0x20, DragonFlyBSD = 0x40, // Combination masks all = linux | Windows | OSX | OpenBSD | FreeBSD | Solaris | DragonFlyBSD, Posix = linux | OSX | OpenBSD | FreeBSD | Solaris | DragonFlyBSD, } extern(D) enum ObjectFormat : ubyte { elf, macho, coff, } OS os; ubyte osMajor; // D ABI ubyte ptrsize; /// size of a pointer in bytes version (IN_LLVM) { void* realType; /// llvm::Type* for `real` } ubyte realsize; /// size a real consumes in memory ubyte realpad; /// padding added to the CPU real size to bring it up to realsize ubyte realalignsize; /// alignment for reals ubyte classinfosize; /// size of `ClassInfo` ulong maxStaticDataSize; /// maximum size of static data /// C ABI TargetC c; /// C++ ABI TargetCPP cpp; /// Objective-C ABI TargetObjC objc; /// Architecture name const(char)[] architectureName; version (IN_LLVM) {} else { CPU cpu; // CPU instruction set to target bool isX86_64; // generate 64 bit code for x86_64; true by default for 64 bit dmd bool isX86; // generate 32 bit Intel x86 code } bool isLP64; // pointers are 64 bits // Environmental const(char)[] obj_ext; /// extension for object files const(char)[] lib_ext; /// extension for static library files const(char)[] dll_ext; /// extension for dynamic library files bool run_noext; /// allow -run sources without extensions /** * Values representing all properties for floating point types */ extern (C++) struct FPTypeProperties(T) { real_t max; /// largest representable value that's not infinity real_t min_normal; /// smallest representable normalized value that's not 0 real_t nan; /// NaN value real_t infinity; /// infinity value real_t epsilon; /// smallest increment to the value 1 long dig; /// number of decimal digits of precision long mant_dig; /// number of bits in mantissa long max_exp; /// maximum int value such that 2$(SUPERSCRIPT `max_exp-1`) is representable long min_exp; /// minimum int value such that 2$(SUPERSCRIPT `min_exp-1`) is representable as a normalized value long max_10_exp; /// maximum int value such that 10$(SUPERSCRIPT `max_10_exp` is representable) long min_10_exp; /// minimum int value such that 10$(SUPERSCRIPT `min_10_exp`) is representable as a normalized value extern (D) void initialize() { max = T.max; min_normal = T.min_normal; nan = T.nan; infinity = T.infinity; epsilon = T.epsilon; dig = T.dig; mant_dig = T.mant_dig; max_exp = T.max_exp; min_exp = T.min_exp; max_10_exp = T.max_10_exp; min_10_exp = T.min_10_exp; } } FPTypeProperties!float FloatProperties; /// FPTypeProperties!double DoubleProperties; /// FPTypeProperties!real_t RealProperties; /// private Type tvalist; // cached lazy result of va_listType() private const(Param)* params; // cached reference to global.params version (IN_LLVM) { extern (C++): private void initFPTypeProperties() { FloatProperties.initialize(); DoubleProperties.initialize(); RealProperties.initialize(); } // implemented in gen/target.cpp: void _init(ref const Param params); // unused: void deinitialize(); uint alignsize(Type type); uint fieldalign(Type type); Type va_listType(const ref Loc loc, Scope* sc); } else // !IN_LLVM { /** * Initialize the Target */ extern (C++) void _init(ref const Param params) { // isX86_64 and cpu are initialized in parseCommandLine isX86 = !isX86_64; this.params = ¶ms; FloatProperties.initialize(); DoubleProperties.initialize(); RealProperties.initialize(); isLP64 = isX86_64; // These have default values for 32 bit code, they get // adjusted for 64 bit code. ptrsize = 4; classinfosize = 0x4C+16; // 92 /* gcc uses int.max for 32 bit compilations, and long.max for 64 bit ones. * Set to int.max for both, because the rest of the compiler cannot handle * 2^64-1 without some pervasive rework. The trouble is that much of the * front and back end uses 32 bit ints for sizes and offsets. Since C++ * silently truncates 64 bit ints to 32, finding all these dependencies will be a problem. */ maxStaticDataSize = int.max; if (isLP64) { ptrsize = 8; classinfosize = 0x98+16; // 168 } if (os & (Target.OS.linux | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.DragonFlyBSD | Target.OS.Solaris)) { realsize = 12; realpad = 2; realalignsize = 4; } else if (os == Target.OS.OSX) { realsize = 16; realpad = 6; realalignsize = 16; } else if (os == Target.OS.Windows) { realsize = 10; realpad = 0; realalignsize = 2; } else assert(0); if (isX86_64) { if (os & (Target.OS.linux | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.DragonFlyBSD | Target.OS.Solaris)) { realsize = 16; realpad = 6; realalignsize = 16; } } c.initialize(params, this); cpp.initialize(params, this); objc.initialize(params, this); if (isX86_64) architectureName = "X86_64"; else architectureName = "X86"; if (os == Target.OS.Windows) { obj_ext = "obj"; lib_ext = "lib"; dll_ext = "dll"; run_noext = false; } else if (os & (Target.OS.linux | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.DragonFlyBSD | Target.OS.Solaris | Target.OS.OSX)) { obj_ext = "o"; lib_ext = "a"; if (os == Target.OS.OSX) dll_ext = "dylib"; else dll_ext = "so"; run_noext = true; } else assert(0, "unknown environment"); } /** Determine the object format to be used */ extern(D) Target.ObjectFormat objectFormat() const @safe { if (os == Target.OS.OSX) return Target.ObjectFormat.macho; else if (os & Target.OS.Posix) return Target.ObjectFormat.elf; else if (os == Target.OS.Windows) return Target.ObjectFormat.coff; else assert(0, "unkown object format"); } /** * Determine the instruction set to be used */ void setCPU() @safe { if(!isXmmSupported()) { cpu = CPU.x87; // cannot support other instruction sets return; } switch (cpu) { case CPU.baseline: cpu = CPU.sse2; break; case CPU.native: { import core.cpuid; cpu = core.cpuid.avx2 ? CPU.avx2 : core.cpuid.avx ? CPU.avx : CPU.sse2; break; } default: break; } } /** * Deinitializes the global state of the compiler. * * This can be used to restore the state set by `_init` to its original * state. */ void deinitialize() @safe { this = this.init; } /** * Requested target memory alignment size of the given type. * Params: * type = type to inspect * Returns: * alignment in bytes */ extern (C++) uint alignsize(Type type) { assert(type.isTypeBasic()); switch (type.ty) { case TY.Tfloat80: case TY.Timaginary80: case TY.Tcomplex80: return target.realalignsize; case TY.Tcomplex32: if (os & Target.OS.Posix) return 4; break; case TY.Tint64: case TY.Tuns64: case TY.Tfloat64: case TY.Timaginary64: case TY.Tcomplex64: if (os & Target.OS.Posix) return isX86 ? 4 : 8; break; default: break; } return cast(uint)type.size(Loc.initial); } /** * Requested target field alignment size of the given type. * Params: * type = type to inspect * Returns: * alignment in bytes */ extern (C++) uint fieldalign(Type type) { const size = type.alignsize(); if ((isX86_64 || os == Target.OS.OSX) && (size == 16 || size == 32)) return size; return (8 < size) ? 8 : size; } /** * Type for the `va_list` type for the target; e.g., required for `_argptr` * declarations. * NOTE: For Posix/x86_64 this returns the type which will really * be used for passing an argument of type va_list. * Returns: * `Type` that represents `va_list`. */ extern (C++) Type va_listType(const ref Loc loc, Scope* sc) { if (tvalist) return tvalist; if (os == Target.OS.Windows) { tvalist = Type.tchar.pointerTo(); } else if (os & Target.OS.Posix) { if (isX86_64) { import dmd.identifier : Identifier; import dmd.mtype : TypeIdentifier; import dmd.typesem : typeSemantic; tvalist = new TypeIdentifier(Loc.initial, Identifier.idPool("__va_list_tag")).pointerTo(); tvalist = typeSemantic(tvalist, loc, sc); } else { tvalist = Type.tchar.pointerTo(); } } else { assert(0); } return tvalist; } } // !IN_LLVM /** * Checks whether the target supports a vector type. * Params: * sz = vector type size in bytes * type = vector element type * Returns: * 0 vector type is supported, * 1 vector type is not supported on the target at all * 2 vector element type is not supported * 3 vector size is not supported */ extern (C++) int isVectorTypeSupported(int sz, Type type) @safe { // LDC_FIXME: Is it possible to query the LLVM target about supported vectors? static if (!IN_LLVM) { if (!isXmmSupported()) return 1; // not supported } switch (type.ty) { case TY.Tvoid: case TY.Tint8: case TY.Tuns8: case TY.Tint16: case TY.Tuns16: case TY.Tint32: case TY.Tuns32: case TY.Tfloat32: case TY.Tint64: case TY.Tuns64: case TY.Tfloat64: break; default: return 2; // wrong base type } static if (!IN_LLVM) { // Whether a vector is really supported depends on the CPU being targeted. if (sz == 16) { switch (type.ty) { case TY.Tint32: case TY.Tuns32: case TY.Tfloat32: if (cpu < CPU.sse) return 3; // no SSE vector support break; case TY.Tvoid: case TY.Tint8: case TY.Tuns8: case TY.Tint16: case TY.Tuns16: case TY.Tint64: case TY.Tuns64: case TY.Tfloat64: if (cpu < CPU.sse2) return 3; // no SSE2 vector support break; default: assert(0); } } else if (sz == 32) { if (cpu < CPU.avx) return 3; // no AVX vector support } else return 3; // wrong size } // !IN_LLVM return 0; } /** * Checks whether the target supports the given operation for vectors. * Params: * type = target type of operation * op = the unary or binary op being done on the `type` * t2 = type of second operand if `op` is a binary operation * Returns: * true if the operation is supported or type is not a vector */ extern (C++) bool isVectorOpSupported(Type type, EXP op, Type t2 = null) { import dmd.hdrgen : EXPtoString; auto tvec = type.isTypeVector(); if (tvec is null) return true; // not a vector op const vecsize = cast(int)tvec.basetype.size(); const elemty = cast(int)tvec.elementType().ty; // Only operations on these sizes are supported (see isVectorTypeSupported) if (!IN_LLVM && vecsize != 16 && vecsize != 32) return false; bool supported = false; switch (op) { case EXP.uadd: // Expression is a no-op, supported everywhere. supported = tvec.isscalar(); break; case EXP.negate: version (IN_LLVM) { supported = tvec.isscalar(); } else { if (vecsize == 16) { // float[4] negate needs SSE support ({V}SUBPS) if (elemty == TY.Tfloat32 && cpu >= CPU.sse) supported = true; // double[2] negate needs SSE2 support ({V}SUBPD) else if (elemty == TY.Tfloat64 && cpu >= CPU.sse2) supported = true; // (u)byte[16]/short[8]/int[4]/long[2] negate needs SSE2 support ({V}PSUB[BWDQ]) else if (tvec.isintegral() && cpu >= CPU.sse2) supported = true; } else if (vecsize == 32) { // float[8]/double[4] negate needs AVX support (VSUBP[SD]) if (tvec.isfloating() && cpu >= CPU.avx) supported = true; // (u)byte[32]/short[16]/int[8]/long[4] negate needs AVX2 support (VPSUB[BWDQ]) else if (tvec.isintegral() && cpu >= CPU.avx2) supported = true; } } // !IN_LLVM break; case EXP.identity, EXP.notIdentity: supported = IN_LLVM; break; case EXP.lessThan, EXP.greaterThan, EXP.lessOrEqual, EXP.greaterOrEqual: case EXP.equal: case EXP.notEqual: version (IN_LLVM) { supported = tvec.isscalar(); } else { if (vecsize == 16) { // float[4] comparison needs SSE support (CMP{EQ,NEQ,LT,LE}PS) if (elemty == TY.Tfloat32 && cpu >= CPU.sse) supported = true; // double[2] comparison needs SSE2 support (CMP{EQ,NEQ,LT,LE}PD) else if (elemty == TY.Tfloat64 && cpu >= CPU.sse2) supported = true; else if (tvec.isintegral()) { if (elemty == TY.Tint64 || elemty == TY.Tuns64) { // (u)long[2] equality needs SSE4.1 support (PCMPEQQ) if ((op == EXP.equal || op == EXP.notEqual) && cpu >= CPU.sse4_1) supported = true; // (u)long[2] comparison needs SSE4.2 support (PCMPGTQ) else if (cpu >= CPU.sse4_2) supported = true; } // (u)byte[16]/short[8]/int[4] comparison needs SSE2 support (PCMP{EQ,GT}[BWD]) else if (cpu >= CPU.sse2) supported = true; } } else if (vecsize == 32) { // float[8]/double[4] comparison needs AVX support (VCMP{EQ,NEQ,LT,LE}P[SD]) if (tvec.isfloating() && cpu >= CPU.avx) supported = true; // (u)byte[32]/short[16]/int[8]/long[4] comparison needs AVX2 support (VPCMP{EQ,GT}[BWDQ]) else if (tvec.isintegral() && cpu >= CPU.avx2) supported = true; } } // !IN_LLVM break; case EXP.leftShift, EXP.leftShiftAssign, EXP.rightShift, EXP.rightShiftAssign, EXP.unsignedRightShift, EXP.unsignedRightShiftAssign: supported = IN_LLVM && tvec.isintegral(); break; case EXP.add, EXP.addAssign, EXP.min, EXP.minAssign: version (IN_LLVM) { supported = tvec.isscalar(); } else { if (vecsize == 16) { // float[4] add/sub needs SSE support ({V}ADDPS, {V}SUBPS) if (elemty == TY.Tfloat32 && cpu >= CPU.sse) supported = true; // double[2] add/sub needs SSE2 support ({V}ADDPD, {V}SUBPD) else if (elemty == TY.Tfloat64 && cpu >= CPU.sse2) supported = true; // (u)byte[16]/short[8]/int[4]/long[2] add/sub needs SSE2 support ({V}PADD[BWDQ], {V}PSUB[BWDQ]) else if (tvec.isintegral() && cpu >= CPU.sse2) supported = true; } else if (vecsize == 32) { // float[8]/double[4] add/sub needs AVX support (VADDP[SD], VSUBP[SD]) if (tvec.isfloating() && cpu >= CPU.avx) supported = true; // (u)byte[32]/short[16]/int[8]/long[4] add/sub needs AVX2 support (VPADD[BWDQ], VPSUB[BWDQ]) else if (tvec.isintegral() && cpu >= CPU.avx2) supported = true; } } // !IN_LLVM break; case EXP.mul, EXP.mulAssign: version (IN_LLVM) { supported = tvec.isscalar(); } else { if (vecsize == 16) { // float[4] multiply needs SSE support ({V}MULPS) if (elemty == TY.Tfloat32 && cpu >= CPU.sse) supported = true; // double[2] multiply needs SSE2 support ({V}MULPD) else if (elemty == TY.Tfloat64 && cpu >= CPU.sse2) supported = true; // (u)short[8] multiply needs SSE2 support ({V}PMULLW) else if ((elemty == TY.Tint16 || elemty == TY.Tuns16) && cpu >= CPU.sse2) supported = true; // (u)int[4] multiply needs SSE4.1 support ({V}PMULLD) else if ((elemty == TY.Tint32 || elemty == TY.Tuns32) && cpu >= CPU.sse4_1) supported = true; } else if (vecsize == 32) { // float[8]/double[4] multiply needs AVX support (VMULP[SD]) if (tvec.isfloating() && cpu >= CPU.avx) supported = true; // (u)short[16] multiply needs AVX2 support (VPMULLW) else if ((elemty == TY.Tint16 || elemty == TY.Tuns16) && cpu >= CPU.avx2) supported = true; // (u)int[8] multiply needs AVX2 support (VPMULLD) else if ((elemty == TY.Tint32 || elemty == TY.Tuns32) && cpu >= CPU.avx2) supported = true; } } // !IN_LLVM break; case EXP.div, EXP.divAssign: version (IN_LLVM) { supported = tvec.isscalar(); } else { if (vecsize == 16) { // float[4] divide needs SSE support ({V}DIVPS) if (elemty == TY.Tfloat32 && cpu >= CPU.sse) supported = true; // double[2] divide needs SSE2 support ({V}DIVPD) else if (elemty == TY.Tfloat64 && cpu >= CPU.sse2) supported = true; } else if (vecsize == 32) { // float[8]/double[4] multiply needs AVX support (VDIVP[SD]) if (tvec.isfloating() && cpu >= CPU.avx) supported = true; } } // !IN_LLVM break; case EXP.mod, EXP.modAssign: supported = IN_LLVM && tvec.isscalar(); break; case EXP.and, EXP.andAssign, EXP.or, EXP.orAssign, EXP.xor, EXP.xorAssign: version (IN_LLVM) { supported = tvec.isintegral(); } else { // (u)byte[16]/short[8]/int[4]/long[2] bitwise ops needs SSE2 support ({V}PAND, {V}POR, {V}PXOR) if (vecsize == 16 && tvec.isintegral() && cpu >= CPU.sse2) supported = true; // (u)byte[32]/short[16]/int[8]/long[4] bitwise ops needs AVX2 support (VPAND, VPOR, VPXOR) else if (vecsize == 32 && tvec.isintegral() && cpu >= CPU.avx2) supported = true; } // !IN_LLVM break; case EXP.not: supported = false; break; case EXP.tilde: version (IN_LLVM) { supported = tvec.isintegral(); } else { // (u)byte[16]/short[8]/int[4]/long[2] logical exclusive needs SSE2 support ({V}PXOR) if (vecsize == 16 && tvec.isintegral() && cpu >= CPU.sse2) supported = true; // (u)byte[32]/short[16]/int[8]/long[4] logical exclusive needs AVX2 support (VPXOR) else if (vecsize == 32 && tvec.isintegral() && cpu >= CPU.avx2) supported = true; } // !IN_LLVM break; case EXP.pow, EXP.powAssign: supported = false; break; default: // import std.stdio : stderr, writeln; // stderr.writeln(op); assert(0, "unhandled op " ~ EXPtoString(cast(EXP)op)); } return supported; } /** * Default system linkage for the target. * Returns: * `LINK` to use for `extern(System)` */ extern (C++) LINK systemLinkage() @safe { return os == Target.OS.Windows ? LINK.windows : LINK.c; } version (IN_LLVM) { extern (C++): TypeTuple toArgTypes(Type t); bool isReturnOnStack(TypeFunction tf, bool needsThis); bool preferPassByRef(Type t); Expression getTargetInfo(const(char)* name, const ref Loc loc); bool isCalleeDestroyingArgs(TypeFunction tf); bool libraryObjectMonitors(FuncDeclaration fd, Statement fbody) { return true; } bool supportsLinkerDirective() const; } else // !IN_LLVM { /** * Describes how an argument type is passed to a function on target. * Params: * t = type to break down * Returns: * tuple of types if type is passed in one or more registers * empty tuple if type is always passed on the stack * null if the type is a `void` or argtypes aren't supported by the target */ extern (C++) TypeTuple toArgTypes(Type t) { import dmd.argtypes_x86 : toArgTypes_x86; import dmd.argtypes_sysv_x64 : toArgTypes_sysv_x64; if (isX86_64) { // no argTypes for Win64 yet return isPOSIX ? toArgTypes_sysv_x64(t) : null; } return toArgTypes_x86(t); } /** * Determine return style of function - whether in registers or * through a hidden pointer to the caller's stack. * Params: * tf = function type to check * needsThis = true if the function type is for a non-static member function * Returns: * true if return value from function is on the stack */ extern (C++) bool isReturnOnStack(TypeFunction tf, bool needsThis) { import dmd.id : Id; import dmd.argtypes_sysv_x64 : toArgTypes_sysv_x64; import dmd.typesem : castMod; if (tf.isref) { //printf(" ref false\n"); return false; // returns a pointer } Type tn = tf.next; if (auto te = tn.isTypeEnum()) { if (te.sym.isSpecial()) { // Special enums with target-specific return style if (te.sym.ident == Id.__c_complex_float) tn = Type.tcomplex32.castMod(tn.mod); else if (te.sym.ident == Id.__c_complex_double) tn = Type.tcomplex64.castMod(tn.mod); else if (te.sym.ident == Id.__c_complex_real) tn = Type.tcomplex80.castMod(tn.mod); } } tn = tn.toBasetype(); //printf("tn = %s\n", tn.toChars()); const sz = tn.size(); Type tns = tn; if (os == Target.OS.Windows && isX86_64) { // https://msdn.microsoft.com/en-us/library/7572ztz4%28v=vs.100%29.aspx if (tns.ty == TY.Tcomplex32) return true; if (tns.isscalar()) return false; tns = tns.baseElemOf(); if (auto ts = tns.isTypeStruct()) { auto sd = ts.sym; if (tf.linkage == LINK.cpp && needsThis) return true; if (tf.linkage == LINK.cpp && sd.ctor) return true; if (!sd.isPOD() || sz > 8) return true; if (sd.fields.length == 0) return true; } if (sz <= 16 && !(sz & (sz - 1))) return false; return true; } else if (os == Target.OS.Windows) { Type tb = tns.baseElemOf(); if (tb.ty == TY.Tstruct) { if (tf.linkage == LINK.cpp && needsThis) return true; } } else if (isX86_64 && isPOSIX) { TypeTuple tt = toArgTypes_sysv_x64(tn); if (!tt) return false; // void else return !tt.arguments.length; } Lagain: if (tns.ty == TY.Tsarray) { tns = tns.baseElemOf(); if (tns.ty != TY.Tstruct) { L2: if (os == Target.OS.linux && tf.linkage != LINK.d && isX86) { // 32 bit C/C++ structs always on stack } else { switch (sz) { case 1: case 2: case 4: case 8: //printf(" sarray false\n"); return false; // return small structs in regs // (not 3 byte structs!) default: break; } } //printf(" sarray true\n"); return true; } } if (auto ts = tns.isTypeStruct()) { auto sd = ts.sym; if (os == Target.OS.linux && tf.linkage != LINK.d && isX86) { //printf(" 2 true\n"); return true; // 32 bit C/C++ structs always on stack } if (os == Target.OS.Windows && tf.linkage == LINK.cpp && isX86 && sd.isPOD() && sd.ctor) { // win32 returns otherwise POD structs with ctors via memory return true; } if (sd.numArgTypes() == 1) { tns = sd.argType(0); if (tns.ty != TY.Tstruct) goto L2; goto Lagain; } else if (isX86_64 && sd.numArgTypes() == 0) return true; else if (sd.isPOD()) { switch (sz) { case 1: case 2: case 4: case 8: //printf(" 3 false\n"); return false; // return small structs in regs // (not 3 byte structs!) case 16: if (os & Target.OS.Posix && isX86_64) return false; break; default: break; } } //printf(" 3 true\n"); return true; } else if (os & Target.OS.Posix && (tf.linkage == LINK.c || tf.linkage == LINK.cpp) && tns.iscomplex()) { if (tns.ty == TY.Tcomplex32) return false; // in EDX:EAX, not ST1:ST0 else return true; } else if (os == Target.OS.Windows && isX86 && tf.linkage == LINK.cpp && tf.isfloating()) { /* See DMC++ function exp2_retmethod() * https://github.com/DigitalMars/Compiler/blob/master/dm/src/dmc/dexp2.d#L149 */ return true; } else { //assert(sz <= 16); //printf(" 4 false\n"); return false; } } /** * Decides whether an `in` parameter of the specified POD type is to be * passed by reference or by value. To be used with `-preview=in` only! * Params: * t = type of the `in` parameter, must be a POD * Returns: * `true` if the `in` parameter is to be passed by reference */ extern (C++) bool preferPassByRef(Type t) { const size = t.size(); if (isX86_64) { if (os == Target.OS.Windows) { // Win64 special case: by-value for slices and delegates due to // high number of usages in druntime/Phobos (compiled without // -preview=in but supposed to link against -preview=in code) const ty = t.toBasetype().ty; if (ty == TY.Tarray || ty == TY.Tdelegate) return false; // If size is larger than 8 or not a power-of-2, the Win64 ABI // would require a hidden reference anyway. return size > 8 || (size > 0 && (size & (size - 1)) != 0); } else // SysV x86_64 ABI { // Prefer a ref if the POD cannot be passed in registers, i.e., // would be passed on the stack, *and* the size is > 16. if (size <= 16) return false; TypeTuple getArgTypes() { import dmd.astenums : Sizeok; if (auto ts = t.toBasetype().isTypeStruct()) { auto sd = ts.sym; assert(sd.sizeok == Sizeok.done); return sd.argTypes; } return toArgTypes(t); } TypeTuple argTypes = getArgTypes(); assert(argTypes !is null, "size == 0 should already be handled"); return argTypes.arguments.length == 0; // cannot be passed in registers } } else // 32-bit x86 ABI { // Prefer a ref if the size is > 2 machine words. return size > 8; } } // this guarantees `getTargetInfo` and `allTargetInfos` remain in sync private enum TargetInfoKeys { cppRuntimeLibrary, cppStd, floatAbi, objectFormat, CET } /** * Get targetInfo by key * Params: * name = name of targetInfo to get * loc = location to use for error messages * Returns: * Expression for the requested targetInfo */ extern (C++) Expression getTargetInfo(const(char)* name, const ref Loc loc) { import dmd.dmdparams : driverParams; import dmd.expression : IntegerExp, StringExp; import dmd.root.string : toDString; StringExp stringExp(const(char)[] sval) { return new StringExp(loc, sval); } switch (name.toDString) with (TargetInfoKeys) { case objectFormat.stringof: if (os == Target.OS.Windows) return stringExp("coff"); else if (os == Target.OS.OSX) return stringExp("macho"); else return stringExp("elf"); case floatAbi.stringof: return stringExp("hard"); case cppRuntimeLibrary.stringof: if (os == Target.OS.Windows) return stringExp(driverParams.mscrtlib); return stringExp(""); case cppStd.stringof: return new IntegerExp(params.cplusplus); case CET.stringof: return new IntegerExp(driverParams.ibt); default: return null; } } /** * Params: * tf = type of function being called * Returns: `true` if the callee invokes destructors for arguments. */ extern (C++) bool isCalleeDestroyingArgs(TypeFunction tf) @safe { /* BUG preventing this from working: https://github.com/dlang/dmd/pull/16145 if (tf.linkage == LINK.d) return false; */ // On windows, the callee destroys arguments always regardless of function linkage, // and regardless of whether the caller or callee cleans the stack. return os == Target.OS.Windows || // C++ on non-Windows platforms has the caller destroying the arguments tf.linkage != LINK.cpp; } /** * Returns true if the implementation for object monitors is always defined * in the D runtime library (rt/monitor_.d). * Params: * fd = function with `synchronized` storage class. * fbody = entire function body of `fd` * Returns: * `false` if the target backend handles synchronizing monitors. */ extern (C++) bool libraryObjectMonitors(FuncDeclaration fd, Statement fbody) { if (isX86 && os == Target.OS.Windows && !fd.isStatic() && !fbody.usesEH() && !params.trace) { /* The back end uses the "jmonitor" hack for syncing; * no need to do the sync in the library. */ return false; } return true; } /** * Returns true if the target supports `pragma(linkerDirective)`. * Returns: * `false` if the target does not support `pragma(linkerDirective)`. */ extern (C++) bool supportsLinkerDirective() const @safe { return os == Target.OS.Windows; } //////////////////////////////////////////////////////////////////////////// /* All functions after this point are extern (D), as they are only relevant * for targets of DMD, and should not be used in front-end code. */ /****************** * Returns: * true if xmm usage is supported */ extern (D) bool isXmmSupported() @safe { return isX86_64 || (isX86 && os == Target.OS.OSX); } /** * Returns: * true if generating code for POSIX */ extern (D) @property bool isPOSIX() scope const nothrow @nogc @safe out(result) { assert(result || os == Target.OS.Windows); } do { return (os & Target.OS.Posix) != 0; } /********************* * Returns: * alignment of the stack */ extern (D) uint stackAlign() @safe { uint sz = isXmmSupported() ? 16 : isX86_64 ? 8 : isX86 ? 4 : 0; assert(sz); return sz; } } // !IN_LLVM } //////////////////////////////////////////////////////////////////////////////// /** * Functions and variables specific to interfacing with extern(C) ABI. */ struct TargetC { import dmd.declaration : BitFieldDeclaration; enum Runtime : ubyte { Unspecified, Bionic, Glibc, Microsoft, Musl, Newlib, UClibc, WASI, } enum BitFieldStyle : ubyte { Unspecified, MS, /// Microsoft 32 and 64 bit C compilers /// https://docs.microsoft.com/en-us/cpp/c-language/c-bit-fields?view=msvc-160 /// https://docs.microsoft.com/en-us/cpp/cpp/cpp-bit-fields?view=msvc-160 Gcc_Clang, /// gcc and clang } bool crtDestructorsSupported = true; /// Not all platforms support crt_destructor ubyte boolsize; /// size of a C `_Bool` type ubyte shortsize; /// size of a C `short` or `unsigned short` type ubyte intsize; /// size of a C `int` or `unsigned int` type ubyte longsize; /// size of a C `long` or `unsigned long` type ubyte long_longsize; /// size of a C `long long` or `unsigned long long` type ubyte long_doublesize; /// size of a C `long double` ubyte wchar_tsize; /// size of a C `wchar_t` type version (IN_LLVM) {} else { Runtime runtime; /// vendor of the C runtime to link against } BitFieldStyle bitFieldStyle; /// different C compilers do it differently version (IN_LLVM) { /* initialized in Target::_init() */ } else extern (D) void initialize(ref const Param params, ref const Target target) @safe { const os = target.os; boolsize = 1; shortsize = 2; intsize = 4; long_longsize = 8; if (os & (Target.OS.linux | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.DragonFlyBSD | Target.OS.Solaris)) longsize = 4; else if (os == Target.OS.OSX) longsize = 4; else if (os == Target.OS.Windows) longsize = 4; else assert(0); if (target.isX86_64) { if (os & (Target.OS.linux | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.DragonFlyBSD | Target.OS.Solaris)) longsize = 8; else if (os == Target.OS.OSX) longsize = 8; } if (target.isX86_64 && os == Target.OS.Windows) long_doublesize = 8; else long_doublesize = target.realsize; if (os == Target.OS.Windows) wchar_tsize = 2; else wchar_tsize = 4; if (os == Target.OS.Windows) runtime = Runtime.Microsoft; else if (os == Target.OS.linux) { // Note: This is overridden later by `-target=` if supplied. // For now, choose the sensible default. version (CRuntime_Musl) runtime = Runtime.Musl; else runtime = Runtime.Glibc; } if (os == Target.OS.Windows) bitFieldStyle = BitFieldStyle.MS; else if (os & (Target.OS.linux | Target.OS.FreeBSD | Target.OS.OSX | Target.OS.OpenBSD | Target.OS.DragonFlyBSD | Target.OS.Solaris)) bitFieldStyle = BitFieldStyle.Gcc_Clang; else assert(0); /* MacOS Monterey (12) does not support C runtime destructors. */ if (os == Target.OS.OSX) { crtDestructorsSupported = false; } } version (IN_LLVM) { // implemented in gen/target.cpp extern (C++) bool contributesToAggregateAlignment(BitFieldDeclaration bfd); } else { /** * Indicates whether the specified bit-field contributes to the alignment * of the containing aggregate. * E.g., (not all) ARM ABIs do NOT ignore anonymous (incl. 0-length) * bit-fields. */ extern (C++) bool contributesToAggregateAlignment(BitFieldDeclaration bfd) { if (bitFieldStyle == BitFieldStyle.MS) return true; if (bitFieldStyle == BitFieldStyle.Gcc_Clang) { // sufficient for DMD's currently supported architectures return !bfd.isAnonymous(); } assert(0); } } } //////////////////////////////////////////////////////////////////////////////// /** * Functions and variables specific to interface with extern(C++) ABI. */ struct TargetCPP { import dmd.dsymbol : Dsymbol; import dmd.dclass : ClassDeclaration; import dmd.func : FuncDeclaration; import dmd.mtype : Type; enum Runtime : ubyte { Unspecified, LLVM, GNU, Microsoft, Sun } bool reverseOverloads; /// set if overloaded functions are grouped and in reverse order (such as in dmc and cl) bool exceptions; /// set if catching C++ exceptions is supported bool twoDtorInVtable; /// target C++ ABI puts deleting and non-deleting destructor into vtable bool splitVBasetable; /// set if C++ ABI uses separate tables for virtual functions and virtual bases bool wrapDtorInExternD; /// set if C++ dtors require a D wrapper to be callable from runtime version (IN_LLVM) {} else { Runtime runtime; /// vendor of the C++ runtime to link against } version (IN_LLVM) { /* initialized in Target::_init() */ } else extern (D) void initialize(ref const Param params, ref const Target target) @safe { const os = target.os; if (os & (Target.OS.linux | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.DragonFlyBSD | Target.OS.Solaris)) twoDtorInVtable = true; else if (os == Target.OS.OSX) twoDtorInVtable = true; else if (os == Target.OS.Windows) { reverseOverloads = true; splitVBasetable = true; } else assert(0); exceptions = (os & Target.OS.Posix) != 0; if (os == Target.OS.Windows) runtime = Runtime.Microsoft; else if (os & (Target.OS.linux | Target.OS.DragonFlyBSD)) runtime = Runtime.GNU; else if (os & (Target.OS.OSX | Target.OS.FreeBSD | Target.OS.OpenBSD)) runtime = Runtime.LLVM; else if (os == Target.OS.Solaris) runtime = Runtime.GNU; else assert(0); // C++ and D ABI incompatible on all (?) x86 32-bit platforms wrapDtorInExternD = target.isX86; } /** * Mangle the given symbol for C++ ABI. * Params: * s = declaration with C++ linkage * Returns: * string mangling of symbol */ extern (C++) const(char)* toMangle(Dsymbol s) { import dmd.cppmangle : toCppMangleItanium; import dmd.cppmanglewin : toCppMangleMSVC; version (IN_LLVM) { if (isTargetWindowsMSVC()) return toCppMangleMSVC(s); else return toCppMangleItanium(s); } else { if (target.os & (Target.OS.linux | Target.OS.OSX | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.Solaris | Target.OS.DragonFlyBSD)) return toCppMangleItanium(s); if (target.os == Target.OS.Windows) return toCppMangleMSVC(s); else assert(0, "fix this"); } } /** * Get RTTI mangling of the given class declaration for C++ ABI. * Params: * cd = class with C++ linkage * Returns: * string mangling of C++ typeinfo */ extern (C++) const(char)* typeInfoMangle(ClassDeclaration cd) { import dmd.cppmangle : cppTypeInfoMangleItanium; import dmd.cppmanglewin : cppTypeInfoMangleMSVC; version (IN_LLVM) { if (isTargetWindowsMSVC()) return cppTypeInfoMangleMSVC(cd); else return cppTypeInfoMangleItanium(cd); } else { if (target.os & (Target.OS.linux | Target.OS.OSX | Target.OS.FreeBSD | Target.OS.OpenBSD | Target.OS.Solaris | Target.OS.DragonFlyBSD)) return cppTypeInfoMangleItanium(cd); if (target.os == Target.OS.Windows) return cppTypeInfoMangleMSVC(cd); else assert(0, "fix this"); } } /** * Get mangle name of a this-adjusting thunk to the given function * declaration for C++ ABI. * Params: * fd = function with C++ linkage * offset = call offset to the vptr * Returns: * string mangling of C++ thunk, or null if unhandled */ extern (C++) const(char)* thunkMangle(FuncDeclaration fd, int offset) { return null; } /** * Gets vendor-specific type mangling for C++ ABI. * Params: * t = type to inspect * Returns: * string if type is mangled specially on target * null if unhandled */ version (IN_LLVM) { extern (C++) const(char)* typeMangle(Type t); } else { extern (C++) const(char)* typeMangle(Type t) { return null; } } /** * Get the type that will really be used for passing the given argument * to an `extern(C++)` function, or `null` if unhandled. * Params: * t = type to be passed. * Returns: * `Type` to use for type `t`. */ extern (C++) Type parameterType(Type t) { return null; } /** * Checks whether type is a vendor-specific fundamental type. * Params: * t = type to inspect * isFundamental = where to store result * Returns: * true if isFundamental was set by function */ extern (C++) bool fundamentalType(const Type t, ref bool isFundamental) { return false; } /** * Get the starting offset position for fields of an `extern(C++)` class * that is derived from the given base class. * Params: * baseClass = base class with C++ linkage * Returns: * starting offset to lay out derived class fields */ extern (C++) uint derivedClassOffset(ClassDeclaration baseClass) { // MSVC adds padding between base and derived fields if required. // IN_LLVM: changed from `if (target.os == Target.OS.Windows)` if (isTargetWindowsMSVC()) return (baseClass.structsize + baseClass.alignsize - 1) & ~(baseClass.alignsize - 1); else return baseClass.structsize; } } //////////////////////////////////////////////////////////////////////////////// /** * Functions and variables specific to interface with extern(Objective-C) ABI. */ struct TargetObjC { bool supported; /// set if compiler can interface with Objective-C version (IN_LLVM) { /* initialized in Target::_init() */ } else extern (D) void initialize(ref const Param params, ref const Target target) @safe { if (target.os == Target.OS.OSX && target.isX86_64) supported = true; } } //////////////////////////////////////////////////////////////////////////////// extern (C++) __gshared Target target; ldc-1.40.0-src/dmd/argtypes_aarch64.d0000644000000000000000000001423414727557031015717 0ustar rootroot/** * Break down a D type into basic (register) types for the AArch64 ABI. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: Martin Kinkelin * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/argtypes_aarch64.d, _argtypes_aarch64.d) * Documentation: https://dlang.org/phobos/dmd_argtypes_aarch64.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/argtypes_aarch64.d */ module dmd.argtypes_aarch64; import dmd.astenums; import dmd.mtype; import dmd.typesem; /**************************************************** * This breaks a type down into 'simpler' types that can be passed to a function * in registers, and returned in registers. * This is the implementation for the AAPCS64 ABI, based on * $(LINK https://github.com/ARM-software/abi-aa/blob/master/aapcs64/aapcs64.rst). * Params: * t = type to break down * Returns: * tuple of 1 type if `t` can be passed in registers; e.g., a static array * for Homogeneous Floating-point/Vector Aggregates (HFVA). * A tuple of zero length means the type cannot be passed/returned in registers. * null indicates a `void`. */ TypeTuple toArgTypes_aarch64(Type t) { if (t == Type.terror) return new TypeTuple(t); const size = cast(size_t) t.size(); if (size == 0) return null; Type tb = t.toBasetype(); const isAggregate = tb.ty == Tstruct || tb.ty == Tsarray || tb.ty == Tarray || tb.ty == Tdelegate || tb.iscomplex(); if (!isAggregate) return new TypeTuple(t); Type hfvaType; enum maxNumHFVAElements = 4; const isHFVA = size > maxNumHFVAElements * 16 ? false : isHFVA(tb, maxNumHFVAElements, &hfvaType); // non-PODs and larger non-HFVA PODs are passed indirectly by value (pointer to caller-allocated copy) if ((size > 16 && !isHFVA) || !isPOD(tb)) return TypeTuple.empty; if (isHFVA) { // pass in SIMD registers return new TypeTuple(hfvaType); } // pass remaining aggregates in 1 or 2 GP registers static Type getGPType(size_t size) { switch (size) { case 1: return Type.tint8; case 2: return Type.tint16; case 4: return Type.tint32; case 8: return Type.tint64; default: import dmd.typesem : sarrayOf; return Type.tint64.sarrayOf((size + 7) / 8); } } return new TypeTuple(getGPType(size)); } /** * A Homogeneous Floating-point/Vector Aggregate (HFA/HVA) is an ARM/AArch64 * concept that consists of up to 4 elements of the same floating point/vector * type. It is the aggregate final data layout that matters so structs, unions, * static arrays and complex numbers can result in an HFVA. * * simple HFAs: struct F1 {float f;} struct D4 {double a,b,c,d;} * interesting HFA: struct {F1[2] vals; float weight;} * * If the type is an HFVA and `rewriteType` is specified, it is set to a * corresponding static array type. */ bool isHFVA(Type t, int maxNumElements = 4, Type* rewriteType = null) { t = t.toBasetype(); if ((t.ty != Tstruct && t.ty != Tsarray && !t.iscomplex()) || !isPOD(t)) return false; Type fundamentalType; const N = getNestedHFVA(t, fundamentalType); if (N < 1 || N > maxNumElements) return false; import dmd.typesem : sarrayOf; if (rewriteType) *rewriteType = fundamentalType.sarrayOf(N); return true; } private: bool isPOD(Type t) { auto baseType = t.baseElemOf(); if (auto ts = baseType.isTypeStruct()) return ts.sym.isPOD(); return true; } /** * Recursive helper. * Returns size_t.max if the type isn't suited as HFVA (element) or incompatible * to the specified fundamental type, otherwise the number of consumed elements * of that fundamental type. * If `fundamentalType` is null, it is set on the first occasion and then left * untouched. */ size_t getNestedHFVA(Type t, ref Type fundamentalType) { t = t.toBasetype(); if (auto tarray = t.isTypeSArray()) { const N = getNestedHFVA(tarray.nextOf(), fundamentalType); return N == size_t.max ? N : N * cast(size_t) tarray.dim.toUInteger(); // => T[0] may return 0 } if (auto tstruct = t.isTypeStruct()) { // check each field recursively and set fundamentalType bool isEmpty = true; foreach (field; tstruct.sym.fields) { const field_N = getNestedHFVA(field.type, fundamentalType); if (field_N == size_t.max) return field_N; if (field_N > 0) // might be 0 for empty static array isEmpty = false; } // an empty struct (no fields or only empty static arrays) is an undefined // byte, i.e., no HFVA if (isEmpty) return size_t.max; // due to possibly overlapping fields (for unions and nested anonymous // unions), use the overall struct size to determine N const structSize = t.size(); const fundamentalSize = fundamentalType.size(); assert(structSize % fundamentalSize == 0); return cast(size_t) (structSize / fundamentalSize); } Type thisFundamentalType; size_t N; if (t.isTypeVector()) { thisFundamentalType = t; N = 1; } else if (t.isfloating()) // incl. imaginary and complex { auto ftSize = t.size(); N = 1; if (t.iscomplex()) { ftSize /= 2; N = 2; } switch (ftSize) { case 4: thisFundamentalType = Type.tfloat32; break; case 8: thisFundamentalType = Type.tfloat64; break; case 16: thisFundamentalType = Type.tfloat80; break; // IEEE quadruple default: assert(0, "unexpected floating-point type size"); } } else { return size_t.max; // reject all other types } if (!fundamentalType) fundamentalType = thisFundamentalType; // initialize else if (fundamentalType != thisFundamentalType) return size_t.max; // incompatible fundamental types, reject return N; } ldc-1.40.0-src/dmd/denum.d0000644000000000000000000001361714727557031013665 0ustar rootroot/** * Define `enum` declarations and `enum` members. * * Specification: $(LINK2 https://dlang.org/spec/enum.html, Enums) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/denum.d, _denum.d) * Documentation: https://dlang.org/phobos/dmd_denum.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/denum.d * References: https://dlang.org/spec/enum.html */ module dmd.denum; import core.stdc.stdio; import dmd.astenums; import dmd.attrib; import dmd.gluelayer; import dmd.declaration; import dmd.dsymbol; import dmd.expression; import dmd.id; import dmd.identifier; import dmd.init; import dmd.location; import dmd.mtype; import dmd.visitor; /*********************************************************** * AST node for `EnumDeclaration` * https://dlang.org/spec/enum.html#EnumDeclaration */ extern (C++) final class EnumDeclaration : ScopeDsymbol { /* The separate, and distinct, cases are: * 1. enum { ... } * 2. enum : memtype { ... } * 3. enum id { ... } * 4. enum id : memtype { ... } * 5. enum id : memtype; * 6. enum id; */ Type type; // the TypeEnum Type memtype; // type of the members Visibility visibility; Expression maxval; Expression minval; Expression defaultval; // default initializer // `bool` fields that are compacted into bit fields in a string mixin private extern (D) static struct BitFields { bool isdeprecated; bool added; bool inuse; } import dmd.common.bitfields : generateBitFields; mixin(generateBitFields!(BitFields, ubyte)); version (IN_LLVM) {} else { Symbol* sinit; } extern (D) this(const ref Loc loc, Identifier ident, Type memtype) { super(loc, ident); //printf("EnumDeclaration() %p %s : %s\n", this, toChars(), memtype.toChars()); type = new TypeEnum(this); this.memtype = memtype; visibility = Visibility(Visibility.Kind.undefined); } override EnumDeclaration syntaxCopy(Dsymbol s) { assert(!s); auto ed = new EnumDeclaration(loc, ident, memtype ? memtype.syntaxCopy() : null); ScopeDsymbol.syntaxCopy(ed); return ed; } override bool oneMember(out Dsymbol ps, Identifier ident) { if (isAnonymous()) return Dsymbol.oneMembers(members, ps, ident); return Dsymbol.oneMember(ps, ident); } override Type getType() { return type; } override const(char)* kind() const { return "enum"; } // is Dsymbol deprecated? override bool isDeprecated() const { return isdeprecated; } override Visibility visible() pure nothrow @nogc @safe { return visibility; } /**************** * Determine if enum is a special one. * Returns: * `true` if special */ bool isSpecial() const nothrow @nogc { return isSpecialEnumIdent(ident) && memtype; } override inout(EnumDeclaration) isEnumDeclaration() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * AST node representing a member of an enum. * https://dlang.org/spec/enum.html#EnumMember * https://dlang.org/spec/enum.html#AnonymousEnumMember */ extern (C++) final class EnumMember : VarDeclaration { /* Can take the following forms: * 1. id * 2. id = value * 3. type id = value */ @property ref value() { return (cast(ExpInitializer)_init).exp; } // A cast() is injected to 'value' after dsymbolSemantic(), // but 'origValue' will preserve the original value, // or previous value + 1 if none was specified. Expression origValue; Type origType; EnumDeclaration ed; extern (D) this(const ref Loc loc, Identifier id, Expression value, Type origType) { super(loc, null, id ? id : Id.empty, new ExpInitializer(loc, value)); this.origValue = value; this.origType = origType; } extern(D) this(Loc loc, Identifier id, Expression value, Type memtype, StorageClass stc, UserAttributeDeclaration uad, DeprecatedDeclaration dd) { this(loc, id, value, memtype); storage_class = stc; userAttribDecl = uad; depdecl = dd; } override EnumMember syntaxCopy(Dsymbol s) { assert(!s); return new EnumMember( loc, ident, value ? value.syntaxCopy() : null, origType ? origType.syntaxCopy() : null, storage_class, userAttribDecl ? userAttribDecl.syntaxCopy(s) : null, depdecl ? depdecl.syntaxCopy(s) : null); } override const(char)* kind() const { return "enum member"; } override inout(EnumMember) isEnumMember() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /****************************************** * Check for special enum names. * * Special enum names are used by the C++ name mangler to represent * C++ types that are not basic D types. * Params: * ident = identifier to check for specialness * Returns: * `true` if it is special */ bool isSpecialEnumIdent(const Identifier ident) @nogc nothrow { return ident == Id.__c_long || ident == Id.__c_ulong || ident == Id.__c_longlong || ident == Id.__c_ulonglong || ident == Id.__c_long_double || ident == Id.__c_wchar_t || ident == Id.__c_complex_float || ident == Id.__c_complex_double || ident == Id.__c_complex_real; } ldc-1.40.0-src/dmd/template.h0000644000000000000000000002521514727557031014371 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/template.h */ #pragma once #include "arraytypes.h" #include "dsymbol.h" #include "expression.h" class Identifier; class TemplateInstance; class TemplateParameter; class TemplateTypeParameter; class TemplateThisParameter; class TemplateValueParameter; class TemplateAliasParameter; class TemplateTupleParameter; class Type; class TypeQualified; struct Scope; class Expression; class FuncDeclaration; class Parameter; class Tuple final : public RootObject { public: Objects objects; // kludge for template.isType() DYNCAST dyncast() const override { return DYNCAST_TUPLE; } const char *toChars() const override; }; struct TemplatePrevious { TemplatePrevious *prev; Scope *sc; Objects *dedargs; }; class TemplateDeclaration final : public ScopeDsymbol { public: TemplateParameters *parameters; // array of TemplateParameter's TemplateParameters *origParameters; // originals for Ddoc Expression *constraint; // Hash table to look up TemplateInstance's of this TemplateDeclaration void *instances; TemplateDeclaration *overnext; // next overloaded TemplateDeclaration TemplateDeclaration *overroot; // first in overnext list FuncDeclaration *funcroot; // first function in unified overload list Dsymbol *onemember; // if !=NULL then one member of this template d_bool literal; // this template declaration is a literal d_bool ismixin; // template declaration is only to be used as a mixin d_bool isstatic; // this is static template declaration d_bool isTrivialAliasSeq; // matches `template AliasSeq(T...) { alias AliasSeq = T; } d_bool isTrivialAlias; // matches pattern `template Alias(T) { alias Alias = qualifiers(T); }` d_bool deprecated_; // this template declaration is deprecated Visibility visibility; TemplatePrevious *previous; // threaded list of previous instantiation attempts on stack #if IN_LLVM const char *intrinsicName; #endif TemplateDeclaration *syntaxCopy(Dsymbol *) override; bool overloadInsert(Dsymbol *s) override; bool hasStaticCtorOrDtor() override; const char *kind() const override; const char *toChars() const override; Visibility visible() override; TemplateDeclaration *isTemplateDeclaration() override { return this; } bool isDeprecated() const override; bool isOverloadable() const override; void accept(Visitor *v) override { v->visit(this); } }; /* For type-parameter: * template Foo(ident) // specType is set to NULL * template Foo(ident : specType) * For value-parameter: * template Foo(valType ident) // specValue is set to NULL * template Foo(valType ident : specValue) * For alias-parameter: * template Foo(alias ident) * For this-parameter: * template Foo(this ident) */ class TemplateParameter : public ASTNode { public: Loc loc; Identifier *ident; /* True if this is a part of precedent parameter specialization pattern. * * template A(T : X!TL, alias X, TL...) {} * // X and TL are dependent template parameter * * A dependent template parameter should return MATCHexact in matchArg() * to respect the match level of the corresponding precedent parameter. */ d_bool dependent; virtual TemplateTypeParameter *isTemplateTypeParameter(); virtual TemplateValueParameter *isTemplateValueParameter(); virtual TemplateAliasParameter *isTemplateAliasParameter(); virtual TemplateThisParameter *isTemplateThisParameter(); virtual TemplateTupleParameter *isTemplateTupleParameter(); virtual TemplateParameter *syntaxCopy() = 0; virtual bool declareParameter(Scope *sc) = 0; virtual void print(RootObject *oarg, RootObject *oded) = 0; virtual RootObject *specialization() = 0; virtual RootObject *defaultArg(const Loc &instLoc, Scope *sc) = 0; virtual bool hasDefaultArg() = 0; DYNCAST dyncast() const override { return DYNCAST_TEMPLATEPARAMETER; } /* Create dummy argument based on parameter. */ virtual RootObject *dummyArg() = 0; void accept(Visitor *v) override { v->visit(this); } }; /* Syntax: * ident : specType = defaultType */ class TemplateTypeParameter : public TemplateParameter { public: Type *specType; // type parameter: if !=NULL, this is the type specialization Type *defaultType; TemplateTypeParameter *isTemplateTypeParameter() override final; TemplateTypeParameter *syntaxCopy() override; bool declareParameter(Scope *sc) override final; void print(RootObject *oarg, RootObject *oded) override final; RootObject *specialization() override final; RootObject *defaultArg(const Loc &instLoc, Scope *sc) override final; bool hasDefaultArg() override final; RootObject *dummyArg() override final; void accept(Visitor *v) override { v->visit(this); } }; /* Syntax: * this ident : specType = defaultType */ class TemplateThisParameter final : public TemplateTypeParameter { public: TemplateThisParameter *isTemplateThisParameter() override; TemplateThisParameter *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; /* Syntax: * valType ident : specValue = defaultValue */ class TemplateValueParameter final : public TemplateParameter { public: Type *valType; Expression *specValue; Expression *defaultValue; TemplateValueParameter *isTemplateValueParameter() override; TemplateValueParameter *syntaxCopy() override; bool declareParameter(Scope *sc) override; void print(RootObject *oarg, RootObject *oded) override; RootObject *specialization() override; RootObject *defaultArg(const Loc &instLoc, Scope *sc) override; bool hasDefaultArg() override; RootObject *dummyArg() override; void accept(Visitor *v) override { v->visit(this); } }; /* Syntax: * specType ident : specAlias = defaultAlias */ class TemplateAliasParameter final : public TemplateParameter { public: Type *specType; RootObject *specAlias; RootObject *defaultAlias; TemplateAliasParameter *isTemplateAliasParameter() override; TemplateAliasParameter *syntaxCopy() override; bool declareParameter(Scope *sc) override; void print(RootObject *oarg, RootObject *oded) override; RootObject *specialization() override; RootObject *defaultArg(const Loc &instLoc, Scope *sc) override; bool hasDefaultArg() override; RootObject *dummyArg() override; void accept(Visitor *v) override { v->visit(this); } }; /* Syntax: * ident ... */ class TemplateTupleParameter final : public TemplateParameter { public: TemplateTupleParameter *isTemplateTupleParameter() override; TemplateTupleParameter *syntaxCopy() override; bool declareParameter(Scope *sc) override; void print(RootObject *oarg, RootObject *oded) override; RootObject *specialization() override; RootObject *defaultArg(const Loc &instLoc, Scope *sc) override; bool hasDefaultArg() override; RootObject *dummyArg() override; void accept(Visitor *v) override { v->visit(this); } }; /* Given: * foo!(args) => * name = foo * tiargs = args */ class TemplateInstance : public ScopeDsymbol { public: Identifier *name; // Array of Types/Expressions of template // instance arguments [int*, char, 10*10] Objects *tiargs; // Array of Types/Expressions corresponding // to TemplateDeclaration.parameters // [int, char, 100] Objects tdtypes; // Modules imported by this template instance Modules importedModules; Dsymbol *tempdecl; // referenced by foo.bar.abc Dsymbol *enclosing; // if referencing local symbols, this is the context Dsymbol *aliasdecl; // !=NULL if instance is an alias for its sole member TemplateInstance *inst; // refer to existing instance ScopeDsymbol *argsym; // argument symbol table hash_t hash; // cached result of toHash() Expressions *fargs; // for function template, these are the function arguments Identifiers *fnames; // for function template, argument names TemplateInstances* deferred; Module *memberOf; // if !null, then this TemplateInstance appears in memberOf.members[] // Used to determine the instance needs code generation. // Note that these are inaccurate until semantic analysis phase completed. TemplateInstance *tinst; // enclosing template instance TemplateInstance *tnext; // non-first instantiated instances Module *minst; // the top module that instantiated this instance private: unsigned short _nest; // for recursive pretty printing detection, 3 MSBs reserved for flags public: unsigned char inuse; // for recursive expansion detection TemplateInstance *syntaxCopy(Dsymbol *) override; Dsymbol *toAlias() override final; // resolve real symbol const char *kind() const override; bool oneMember(Dsymbol *&ps, Identifier *ident) override; const char *toChars() const override; const char* toPrettyCharsHelper() override final; Identifier *getIdent() override final; bool isDiscardable(); bool needsCodegen(); TemplateInstance *isTemplateInstance() override final { return this; } void accept(Visitor *v) override { v->visit(this); } }; class TemplateMixin final : public TemplateInstance { public: TypeQualified *tqual; TemplateMixin *syntaxCopy(Dsymbol *s) override; const char *kind() const override; bool oneMember(Dsymbol *&ps, Identifier *ident) override; bool hasPointers() override; const char *toChars() const override; TemplateMixin *isTemplateMixin() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; namespace dmd { // in templateparamsem.d bool tpsemantic(TemplateParameter *tp, Scope *sc, TemplateParameters *parameters); Expression *isExpression(RootObject *o); Dsymbol *isDsymbol(RootObject *o); Type *isType(RootObject *o); Tuple *isTuple(RootObject *o); Parameter *isParameter(RootObject *o); TemplateParameter *isTemplateParameter(RootObject *o); bool isError(const RootObject *const o); void printTemplateStats(bool listInstances, ErrorSink* eSink); } ldc-1.40.0-src/dmd/dscope.d0000644000000000000000000007114614727557031014033 0ustar rootroot/** * A scope as defined by curly braces `{}`. * * Not to be confused with the `scope` storage class. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dscope.d, _dscope.d) * Documentation: https://dlang.org/phobos/dmd_dscope.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dscope.d */ module dmd.dscope; import core.stdc.stdio; import core.stdc.string; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.ctorflow; import dmd.dclass; import dmd.declaration; import dmd.dmodule; import dmd.doc; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.expression; import dmd.errors; import dmd.errorsink; import dmd.func; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.importc; import dmd.location; import dmd.common.outbuffer; import dmd.root.rmem; import dmd.root.speller; import dmd.statement; import dmd.target; import dmd.tokens; //version=LOGSEARCH; // List of flags that can be applied to this `Scope` enum SCOPE { ctor = 0x0001, /// constructor type noaccesscheck = 0x0002, /// don't do access checks condition = 0x0004, /// inside static if/assert condition debug_ = 0x0008, /// inside debug conditional constraint = 0x0010, /// inside template constraint invariant_ = 0x0020, /// inside invariant code require = 0x0040, /// inside in contract code ensure = 0x0060, /// inside out contract code contract = 0x0060, /// [mask] we're inside contract code ctfe = 0x0080, /// inside a ctfe-only expression compile = 0x0100, /// inside __traits(compile) ignoresymbolvisibility = 0x0200, /// ignore symbol visibility /// https://issues.dlang.org/show_bug.cgi?id=15907 Cfile = 0x0800, /// C semantics apply free = 0x8000, /// is on free list fullinst = 0x10000, /// fully instantiate templates ctfeBlock = 0x20000, /// inside a `if (__ctfe)` block dip1000 = 0x40000, /// dip1000 errors enabled for this scope dip25 = 0x80000, /// dip25 errors enabled for this scope } /// Flags that are carried along with a scope push() private enum PersistentFlags = SCOPE.contract | SCOPE.debug_ | SCOPE.ctfe | SCOPE.compile | SCOPE.constraint | SCOPE.noaccesscheck | SCOPE.ignoresymbolvisibility | SCOPE.Cfile | SCOPE.ctfeBlock | SCOPE.dip1000 | SCOPE.dip25; extern (C++) struct Scope { Scope* enclosing; /// enclosing Scope Module _module; /// Root module ScopeDsymbol scopesym; /// current symbol FuncDeclaration func; /// function we are in VarDeclaration varDecl; /// variable we are in during semantic2 Dsymbol parent; /// parent to use LabelStatement slabel; /// enclosing labelled statement SwitchStatement sw; /// enclosing switch statement Statement tryBody; /// enclosing _body of TryCatchStatement or TryFinallyStatement TryFinallyStatement tf; /// enclosing try finally statement ScopeGuardStatement os; /// enclosing scope(xxx) statement Statement sbreak; /// enclosing statement that supports "break" Statement scontinue; /// enclosing statement that supports "continue" ForeachStatement fes; /// if nested function for ForeachStatement, this is it Scope* callsc; /// used for __FUNCTION__, __PRETTY_FUNCTION__ and __MODULE__ Dsymbol inunion; /// != null if processing members of a union bool nofree; /// true if shouldn't free it bool inLoop; /// true if inside a loop (where constructor calls aren't allowed) bool inDefaultArg; /// true if inside a default argument (where __FILE__, etc are evaluated at the call site) int intypeof; /// in typeof(exp) VarDeclaration lastVar; /// Previous symbol used to prevent goto-skips-init ErrorSink eSink; /// sink for error messages /* If minst && !tinst, it's in definitely non-speculative scope (eg. module member scope). * If !minst && !tinst, it's in definitely speculative scope (eg. template constraint). * If minst && tinst, it's in instantiated code scope without speculation. * If !minst && tinst, it's in instantiated code scope with speculation. */ Module minst; /// root module where the instantiated templates should belong to TemplateInstance tinst; /// enclosing template instance CtorFlow ctorflow; /// flow analysis for constructors /// alignment for struct members AlignDeclaration aligndecl; /// C++ namespace this symbol is in CPPNamespaceDeclaration namespace; /// linkage for external functions LINK linkage = LINK.d; /// mangle type CPPMANGLE cppmangle = CPPMANGLE.def; version (IN_LLVM) { bool emitInstrumentation = true; // whether to emit instrumentation with -fprofile-instr-generate } /// inlining strategy for functions PragmaDeclaration inlining; /// visibility for class members Visibility visibility = Visibility(Visibility.Kind.public_); int explicitVisibility; /// set if in an explicit visibility attribute StorageClass stc; /// storage class DeprecatedDeclaration depdecl; /// customized deprecation message uint flags; // user defined attributes UserAttributeDeclaration userAttribDecl; DocComment* lastdc; /// documentation comment for last symbol at this scope uint[void*] anchorCounts; /// lookup duplicate anchor name count Identifier prevAnchor; /// qualified symbol name of last doc anchor AliasDeclaration aliasAsg; /// if set, then aliasAsg is being assigned a new value, /// do not set wasRead for it extern (D) __gshared Scope* freelist; extern (D) static Scope* alloc() { if (freelist) { Scope* s = freelist; freelist = s.enclosing; //printf("freelist %p\n", s); assert(s.flags & SCOPE.free); s.flags &= ~SCOPE.free; return s; } return new Scope(); } extern (D) static Scope* createGlobal(Module _module, ErrorSink eSink) { Scope* sc = Scope.alloc(); *sc = Scope.init; sc._module = _module; sc.minst = _module; sc.scopesym = new ScopeDsymbol(); sc.scopesym.symtab = new DsymbolTable(); sc.eSink = eSink; assert(eSink); // Add top level package as member of this global scope Dsymbol m = _module; while (m.parent) m = m.parent; m.addMember(null, sc.scopesym); m.parent = null; // got changed by addMember() if (global.params.useDIP1000 == FeatureState.enabled) sc.flags |= SCOPE.dip1000; if (global.params.useDIP25 == FeatureState.enabled) sc.flags |= SCOPE.dip25; if (_module.filetype == FileType.c) sc.flags |= SCOPE.Cfile; // Create the module scope underneath the global scope sc = sc.push(_module); sc.parent = _module; return sc; } extern (D) Scope* copy() { Scope* sc = Scope.alloc(); *sc = this; /* https://issues.dlang.org/show_bug.cgi?id=11777 * The copied scope should not inherit fieldinit. */ sc.ctorflow.fieldinit = null; return sc; } extern (D) Scope* push() { Scope* s = copy(); //printf("Scope::push(this = %p) new = %p\n", this, s); assert(!(flags & SCOPE.free)); s.scopesym = null; s.enclosing = &this; debug { if (enclosing) assert(!(enclosing.flags & SCOPE.free)); if (s == enclosing) { printf("this = %p, enclosing = %p, enclosing.enclosing = %p\n", s, &this, enclosing); } assert(s != enclosing); } s.slabel = null; s.nofree = false; s.ctorflow.fieldinit = ctorflow.fieldinit.arraydup; s.flags = (flags & PersistentFlags); s.lastdc = null; assert(&this != s); return s; } extern (D) Scope* push(ScopeDsymbol ss) { //printf("Scope::push(%s)\n", ss.toChars()); Scope* s = push(); s.scopesym = ss; return s; } extern (D) Scope* pop() { //printf("Scope::pop() %p nofree = %d\n", this, nofree); if (enclosing) enclosing.ctorflow.OR(ctorflow); ctorflow.freeFieldinit(); Scope* enc = enclosing; if (!nofree) { if (mem.isGCEnabled) this = this.init; enclosing = freelist; freelist = &this; flags |= SCOPE.free; } return enc; } /************************* * Similar to pop(), but the results in `this` are not folded * into `enclosing`. */ extern (D) void detach() { ctorflow.freeFieldinit(); enclosing = null; pop(); } extern (D) Scope* startCTFE() { Scope* sc = this.push(); sc.flags = this.flags | SCOPE.ctfe; version (none) { /* TODO: Currently this is not possible, because we need to * unspeculative some types and symbols if they are necessary for the * final executable. Consider: * * struct S(T) { * string toString() const { return "instantiated"; } * } * enum x = S!int(); * void main() { * // To call x.toString in runtime, compiler should unspeculative S!int. * assert(x.toString() == "instantiated"); * } * * This results in an undefined reference to `RTInfoImpl`: * class C { int a,b,c; int* p,q; } * void test() { C c = new C(); } */ // If a template is instantiated from CT evaluated expression, // compiler can elide its code generation. sc.tinst = null; sc.minst = null; } return sc; } extern (D) Scope* endCTFE() { assert(flags & SCOPE.ctfe); return pop(); } /******************************* * Merge results of `ctorflow` into `this`. * Params: * loc = for error messages * ctorflow = flow results to merge in */ extern (D) void merge(const ref Loc loc, const ref CtorFlow ctorflow) { if (!mergeCallSuper(this.ctorflow.callSuper, ctorflow.callSuper)) error(loc, "one path skips constructor"); const fies = ctorflow.fieldinit; if (this.ctorflow.fieldinit.length && fies.length) { FuncDeclaration f = func; if (fes) f = fes.func; auto ad = f.isMemberDecl(); assert(ad); foreach (i, v; ad.fields) { bool mustInit = (v.storage_class & STC.nodefaultctor || v.type.needsNested()); auto fieldInit = &this.ctorflow.fieldinit[i]; const fiesCurrent = fies[i]; if (fieldInit.loc is Loc.init) fieldInit.loc = fiesCurrent.loc; if (!mergeFieldInit(this.ctorflow.fieldinit[i].csx, fiesCurrent.csx) && mustInit) { error(loc, "one path skips field `%s`", v.toChars()); } } } } /************************************ * Perform unqualified name lookup by following the chain of scopes up * until found. * * Params: * loc = location to use for error messages * ident = name to look up * pscopesym = if supplied and name is found, set to scope that ident was found in, otherwise set to null * flags = modify search based on flags * * Returns: * symbol if found, null if not */ extern (C++) Dsymbol search(const ref Loc loc, Identifier ident, out Dsymbol pscopesym, SearchOptFlags flags = SearchOpt.all) { version (LOGSEARCH) { printf("Scope.search(%p, '%s' flags=x%x)\n", &this, ident.toChars(), flags); // Print scope chain for (Scope* sc = &this; sc; sc = sc.enclosing) { if (!sc.scopesym) continue; printf("\tscope %s\n", sc.scopesym.toChars()); } static void printMsg(string txt, Dsymbol s) { printf("%.*s %s.%s, kind = '%s'\n", cast(int)txt.length, txt.ptr, s.parent ? s.parent.toChars() : "", s.toChars(), s.kind()); } } // This function is called only for unqualified lookup assert(!(flags & (SearchOpt.localsOnly | SearchOpt.importsOnly))); /* If ident is "start at module scope", only look at module scope */ if (ident == Id.empty) { // Look for module scope for (Scope* sc = &this; sc; sc = sc.enclosing) { assert(sc != sc.enclosing); if (!sc.scopesym) continue; if (Dsymbol s = sc.scopesym.isModule()) { //printMsg("\tfound", s); pscopesym = sc.scopesym; return s; } } return null; } Dsymbol checkAliasThis(AggregateDeclaration ad, Identifier ident, SearchOptFlags flags, Expression* exp) { import dmd.mtype; if (!ad || !ad.aliasthis) return null; Declaration decl = ad.aliasthis.sym.isDeclaration(); if (!decl) return null; Type t = decl.type; ScopeDsymbol sds; TypeClass tc; TypeStruct ts; switch(t.ty) { case Tstruct: ts = cast(TypeStruct)t; sds = ts.sym; break; case Tclass: tc = cast(TypeClass)t; sds = tc.sym; break; case Tinstance: sds = (cast(TypeInstance)t).tempinst; break; case Tenum: sds = (cast(TypeEnum)t).sym; break; default: break; } if (!sds) return null; Dsymbol ret = sds.search(loc, ident, flags); if (ret) { *exp = new DotIdExp(loc, *exp, ad.aliasthis.ident); *exp = new DotIdExp(loc, *exp, ident); return ret; } if (!ts && !tc) return null; Dsymbol s; *exp = new DotIdExp(loc, *exp, ad.aliasthis.ident); if (ts && !(ts.att & AliasThisRec.tracing)) { ts.att = cast(AliasThisRec)(ts.att | AliasThisRec.tracing); s = checkAliasThis(sds.isAggregateDeclaration(), ident, flags, exp); ts.att = cast(AliasThisRec)(ts.att & ~AliasThisRec.tracing); } else if(tc && !(tc.att & AliasThisRec.tracing)) { tc.att = cast(AliasThisRec)(tc.att | AliasThisRec.tracing); s = checkAliasThis(sds.isAggregateDeclaration(), ident, flags, exp); tc.att = cast(AliasThisRec)(tc.att & ~AliasThisRec.tracing); } return s; } Dsymbol searchScopes(SearchOptFlags flags) { for (Scope* sc = &this; sc; sc = sc.enclosing) { assert(sc != sc.enclosing); if (!sc.scopesym) continue; //printf("\tlooking in scopesym '%s', kind = '%s', flags = x%x\n", sc.scopesym.toChars(), sc.scopesym.kind(), flags); if (sc.scopesym.isModule()) flags |= SearchOpt.unqualifiedModule; // tell Module.search() that SearchOpt.localsOnly is to be obeyed else if (sc.flags & SCOPE.Cfile && sc.scopesym.isStructDeclaration()) continue; // C doesn't have struct scope if (Dsymbol s = sc.scopesym.search(loc, ident, flags)) { if (flags & SearchOpt.tagNameSpace) { // ImportC: if symbol is not a tag, look for it in tag table if (!s.isScopeDsymbol()) { auto ps = cast(void*)s in sc._module.tagSymTab; if (!ps) goto NotFound; s = *ps; } } //printMsg("\tfound local", s); pscopesym = sc.scopesym; return s; } NotFound: if (global.params.fixAliasThis) { Expression exp = new ThisExp(loc); Dsymbol aliasSym = checkAliasThis(sc.scopesym.isAggregateDeclaration(), ident, flags, &exp); if (aliasSym) { //printf("found aliassym: %s\n", aliasSym.toChars()); pscopesym = new ExpressionDsymbol(exp); return aliasSym; } } // Stop when we hit a module, but keep going if that is not just under the global scope if (sc.scopesym.isModule() && !(sc.enclosing && !sc.enclosing.enclosing)) break; } return null; } if (this.flags & SCOPE.ignoresymbolvisibility) flags |= SearchOpt.ignoreVisibility; // First look in local scopes Dsymbol s = searchScopes(flags | SearchOpt.localsOnly); version (LOGSEARCH) if (s) printMsg("-Scope.search() found local", s); if (!s) { // Second look in imported modules s = searchScopes(flags | SearchOpt.importsOnly); version (LOGSEARCH) if (s) printMsg("-Scope.search() found import", s); } return s; } extern (D) Dsymbol search_correct(Identifier ident) { if (global.gag) return null; // don't do it for speculative compiles; too time consuming /************************************************ * Given the failed search attempt, try to find * one with a close spelling. * Params: * seed = identifier to search for * cost = set to the cost, which rises with each outer scope * Returns: * Dsymbol if found, null if not */ extern (D) Dsymbol scope_search_fp(const(char)[] seed, out int cost) { //printf("scope_search_fp('%s')\n", seed); /* If not in the lexer's string table, it certainly isn't in the symbol table. * Doing this first is a lot faster. */ if (!seed.length) return null; Identifier id = Identifier.lookup(seed); if (!id) return null; Scope* sc = &this; Module.clearCache(); Dsymbol scopesym; Dsymbol s = sc.search(Loc.initial, id, scopesym, SearchOpt.ignoreErrors); if (!s) return null; // Do not show `@disable`d declarations if (auto decl = s.isDeclaration()) if (decl.storage_class & STC.disable) return null; // Or `deprecated` ones if we're not in a deprecated scope if (s.isDeprecated() && !sc.isDeprecated()) return null; for (cost = 0; sc; sc = sc.enclosing, ++cost) if (sc.scopesym == scopesym) break; if (scopesym != s.parent) { ++cost; // got to the symbol through an import if (s.visible().kind == Visibility.Kind.private_) return null; } return s; } Dsymbol scopesym; // search for exact name first if (auto s = search(Loc.initial, ident, scopesym, SearchOpt.ignoreErrors)) return s; return speller!scope_search_fp(ident.toString()); } /************************************ * Maybe `ident` was a C or C++ name. Check for that, * and suggest the D equivalent. * Params: * ident = unknown identifier * Returns: * D identifier string if found, null if not */ extern (D) static const(char)* search_correct_C(Identifier ident) { import dmd.astenums : Twchar; TOK tok; if (ident == Id.NULL) tok = TOK.null_; else if (ident == Id.TRUE) tok = TOK.true_; else if (ident == Id.FALSE) tok = TOK.false_; else if (ident == Id.unsigned) tok = TOK.uns32; else if (ident == Id.wchar_t) tok = target.c.wchar_tsize == 2 ? TOK.wchar_ : TOK.dchar_; else return null; return Token.toChars(tok); } /*************************** * Find the innermost scope with a symbol table. * Returns: * innermost scope, null if none */ extern (D) Scope* inner() return @safe { for (Scope* sc = &this; sc; sc = sc.enclosing) { if (sc.scopesym) return sc; } return null; } /****************************** * Add symbol s to innermost symbol table. * Params: * s = symbol to insert * Returns: * null if already in table, `s` if not */ extern (D) Dsymbol insert(Dsymbol s) { //printf("insert() %s\n", s.toChars()); if (VarDeclaration vd = s.isVarDeclaration()) { if (lastVar) vd.lastVar = lastVar; lastVar = vd; } else if (WithScopeSymbol ss = s.isWithScopeSymbol()) { if (VarDeclaration vd = ss.withstate.wthis) { if (lastVar) vd.lastVar = lastVar; lastVar = vd; } return null; } auto scopesym = inner().scopesym; //printf("\t\tscopesym = %p\n", scopesym); if (!scopesym.symtab) scopesym.symtab = new DsymbolTable(); if (!(flags & SCOPE.Cfile)) return scopesym.symtabInsert(s); // ImportC insert if (!scopesym.symtabInsert(s)) // if already in table { Dsymbol s2 = scopesym.symtabLookup(s, s.ident); // s2 is existing entry return handleTagSymbols(this, s, s2, scopesym); } return s; // inserted } /******************************************** * Search enclosing scopes for ScopeDsymbol. */ extern (D) ScopeDsymbol getScopesym() @safe { for (Scope* sc = &this; sc; sc = sc.enclosing) { if (sc.scopesym) return sc.scopesym; } return null; // not found } /******************************************** * Search enclosing scopes for ClassDeclaration. */ extern (D) ClassDeclaration getClassScope() @safe { for (Scope* sc = &this; sc; sc = sc.enclosing) { if (!sc.scopesym) continue; if (ClassDeclaration cd = sc.scopesym.isClassDeclaration()) return cd; } return null; } /******************************************** * Search enclosing scopes for ClassDeclaration or StructDeclaration. */ extern (D) AggregateDeclaration getStructClassScope() @safe { for (Scope* sc = &this; sc; sc = sc.enclosing) { if (!sc.scopesym) continue; if (AggregateDeclaration ad = sc.scopesym.isClassDeclaration()) return ad; if (AggregateDeclaration ad = sc.scopesym.isStructDeclaration()) return ad; } return null; } /******************************************** * Find the lexically enclosing function (if any). * * This function skips through generated FuncDeclarations, * e.g. rewritten foreach bodies. * * Returns: the function or null */ extern (D) inout(FuncDeclaration) getEnclosingFunction() inout { if (!this.func) return null; auto fd = cast(FuncDeclaration) this.func; // Look through foreach bodies rewritten as delegates while (fd.fes) { assert(fd.fes.func); fd = fd.fes.func; } return cast(inout(FuncDeclaration)) fd; } /******************************************* * For TemplateDeclarations, we need to remember the Scope * where it was declared. So mark the Scope as not * to be free'd. */ extern (D) void setNoFree() @safe { //int i = 0; //printf("Scope::setNoFree(this = %p)\n", this); for (Scope* sc = &this; sc; sc = sc.enclosing) { //printf("\tsc = %p\n", sc); sc.nofree = true; assert(!(flags & SCOPE.free)); //assert(sc != sc.enclosing); //assert(!sc.enclosing || sc != sc.enclosing.enclosing); //if (++i == 10) // assert(0); } } /****************************** */ extern (D) structalign_t alignment() { if (aligndecl) { auto ad = aligndecl.getAlignment(&this); return ad.salign; } else { structalign_t sa; sa.setDefault(); return sa; } } @safe @nogc pure nothrow const: /********************************** * Checks whether the current scope (or any of its parents) is deprecated. * * Returns: `true` if this or any parent scope is deprecated, `false` otherwise` */ extern (D) bool isDeprecated() { for (const(Dsymbol)* sp = &(this.parent); *sp; sp = &(sp.parent)) { if (sp.isDeprecated()) return true; } for (const(Scope)* sc2 = &this; sc2; sc2 = sc2.enclosing) { if (sc2.scopesym && sc2.scopesym.isDeprecated()) return true; // If inside a StorageClassDeclaration that is deprecated if (sc2.stc & STC.deprecated_) return true; } if (_module.md && _module.md.isdeprecated) { return true; } return false; } /** * dmd relies on mutation of state during semantic analysis, however * sometimes semantic is being performed in a speculative context that should * not have any visible effect on the rest of the compilation: for example when compiling * a typeof() or __traits(compiles). * * Returns: `true` if this `Scope` is known to be from one of these speculative contexts */ extern (D) bool isFromSpeculativeSemanticContext() scope { return this.intypeof || this.flags & SCOPE.compile; } /** * Returns: true if the code needs to go all the way through to code generation. * This implies things like needing lowering to simpler forms. */ extern (D) bool needsCodegen() { return (flags & (SCOPE.ctfe | SCOPE.ctfeBlock | SCOPE.compile)) == 0; } /// Returns: whether to raise DIP1000 warnings (FeatureStabe.default) or errors (FeatureState.enabled) extern (D) FeatureState useDIP1000() { return (flags & SCOPE.dip1000 || hasEdition(Edition.v2024)) ? FeatureState.enabled : FeatureState.disabled; } /// Returns: whether to raise DIP25 warnings (FeatureStabe.default) or errors (FeatureState.enabled) extern (D) FeatureState useDIP25() { return (flags & SCOPE.dip25 || hasEdition(Edition.v2024)) ? FeatureState.enabled : FeatureState.disabled; } /// Returns: whether this scope compiles with `edition` or later extern (D) bool hasEdition(Edition edition) { return _module && _module.edition >= edition; } } ldc-1.40.0-src/dmd/typinf.h0000644000000000000000000000123614727557031014064 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/typinf.h */ #pragma once #include "globals.h" class Expression; class Type; struct Scope; namespace dmd { bool genTypeInfo(Expression *e, const Loc &loc, Type *torig, Scope *sc); bool isSpeculativeType(Type *t); bool builtinTypeInfo(Type *t); } Type *getTypeInfoType(const Loc &loc, Type *t, Scope *sc); ldc-1.40.0-src/dmd/errors.d0000644000000000000000000006542314727557031014073 0ustar rootroot/** * Functions for raising errors. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/errors.d, _errors.d) * Documentation: https://dlang.org/phobos/dmd_errors.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/errors.d */ module dmd.errors; public import core.stdc.stdarg; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; import dmd.errorsink; import dmd.globals; import dmd.location; import dmd.common.outbuffer; import dmd.root.rmem; import dmd.root.string; import dmd.console; nothrow: /// Constants used to discriminate kinds of error messages. enum ErrorKind { warning, deprecation, error, tip, message, } /*************************** * Error message sink for D compiler. */ class ErrorSinkCompiler : ErrorSink { nothrow: extern (C++): override: void error(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.error); va_end(ap); } void errorSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.error); va_end(ap); } void warning(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.warning); va_end(ap); } void warningSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.warning); va_end(ap); } void deprecation(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.deprecation); va_end(ap); } void deprecationSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.deprecation); va_end(ap); } void message(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.message); va_end(ap); } } /** * Color highlighting to classify messages */ enum Classification : Color { error = Color.brightRed, /// for errors gagged = Color.brightBlue, /// for gagged errors warning = Color.brightYellow, /// for warnings deprecation = Color.brightCyan, /// for deprecations tip = Color.brightGreen, /// for tip messages } static if (__VERSION__ < 2092) private extern (C++) void noop(const ref Loc loc, const(char)* format, ...) {} else pragma(printf) private extern (C++) void noop(const ref Loc loc, const(char)* format, ...) {} package auto previewErrorFunc(bool isDeprecated, FeatureState featureState) @safe @nogc pure nothrow { with (FeatureState) final switch (featureState) { case enabled: return &error; case disabled: return &noop; case default_: return isDeprecated ? &noop : &deprecation; } } package auto previewSupplementalFunc(bool isDeprecated, FeatureState featureState) @safe @nogc pure nothrow { with (FeatureState) final switch (featureState) { case enabled: return &errorSupplemental; case disabled: return &noop; case default_: return isDeprecated ? &noop : &deprecationSupplemental; } } /** * Print an error message, increasing the global error count. * Params: * loc = location of error * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void error(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.error); va_end(ap); } else pragma(printf) extern (C++) void error(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.error); va_end(ap); } /** * Same as above, but takes a filename and line information arguments as separate parameters. * Params: * filename = source file of error * linnum = line in the source file * charnum = column number on the line * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void error(const(char)* filename, uint linnum, uint charnum, const(char)* format, ...) { const loc = Loc(filename, linnum, charnum); va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.error); va_end(ap); } else pragma(printf) extern (C++) void error(const(char)* filename, uint linnum, uint charnum, const(char)* format, ...) { const loc = Loc(filename, linnum, charnum); va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.error); va_end(ap); } /** * Print additional details about an error message. * Doesn't increase the error count or print an additional error prefix. * Params: * loc = location of error * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void errorSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.error); va_end(ap); } else pragma(printf) extern (C++) void errorSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.error); va_end(ap); } /** * Print a warning message, increasing the global warning count. * Params: * loc = location of warning * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void warning(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.warning); va_end(ap); } else pragma(printf) extern (C++) void warning(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.warning); va_end(ap); } /** * Print additional details about a warning message. * Doesn't increase the warning count or print an additional warning prefix. * Params: * loc = location of warning * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void warningSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.warning); va_end(ap); } else pragma(printf) extern (C++) void warningSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.warning); va_end(ap); } /** * Print a deprecation message, may increase the global warning or error count * depending on whether deprecations are ignored. * Params: * loc = location of deprecation * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void deprecation(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.deprecation); va_end(ap); } else pragma(printf) extern (C++) void deprecation(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.deprecation); va_end(ap); } /** * Print additional details about a deprecation message. * Doesn't increase the error count, or print an additional deprecation prefix. * Params: * loc = location of deprecation * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void deprecationSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.deprecation); va_end(ap); } else pragma(printf) extern (C++) void deprecationSupplemental(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReportSupplemental(loc, format, ap, ErrorKind.deprecation); va_end(ap); } /** * Print a verbose message. * Doesn't prefix or highlight messages. * Params: * loc = location of message * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void message(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.message); va_end(ap); } else pragma(printf) extern (C++) void message(const ref Loc loc, const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(loc, format, ap, ErrorKind.message); va_end(ap); } /** * Same as above, but doesn't take a location argument. * Params: * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void message(const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(Loc.initial, format, ap, ErrorKind.message); va_end(ap); } else pragma(printf) extern (C++) void message(const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(Loc.initial, format, ap, ErrorKind.message); va_end(ap); } /** * The type of the diagnostic handler * see verrorReport for arguments * Returns: true if error handling is done, false to continue printing to stderr */ alias DiagnosticHandler = bool delegate(const ref Loc location, Color headerColor, const(char)* header, const(char)* messageFormat, va_list args, const(char)* prefix1, const(char)* prefix2); /** * The diagnostic handler. * If non-null it will be called for every diagnostic message issued by the compiler. * If it returns false, the message will be printed to stderr as usual. */ __gshared DiagnosticHandler diagnosticHandler; /** * Print a tip message with the prefix and highlighting. * Params: * format = printf-style format specification * ... = printf-style variadic arguments */ static if (__VERSION__ < 2092) extern (C++) void tip(const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(Loc.initial, format, ap, ErrorKind.tip); va_end(ap); } else pragma(printf) extern (C++) void tip(const(char)* format, ...) { va_list ap; va_start(ap, format); verrorReport(Loc.initial, format, ap, ErrorKind.tip); va_end(ap); } // Encapsulates an error as described by its location, format message, and kind. private struct ErrorInfo { this(const ref Loc loc, const ErrorKind kind, const(char)* p1 = null, const(char)* p2 = null) @safe @nogc pure nothrow { this.loc = loc; this.p1 = p1; this.p2 = p2; this.kind = kind; } const Loc loc; // location of error Classification headerColor; // color to set `header` output to const(char)* p1; // additional message prefix const(char)* p2; // additional message prefix const ErrorKind kind; // kind of error being printed bool supplemental; // true if supplemental error } /** * Implements $(D error), $(D warning), $(D deprecation), $(D message), and * $(D tip). Report a diagnostic error, taking a va_list parameter, and * optionally additional message prefixes. Whether the message gets printed * depends on runtime values of DiagnosticReporting and global gagging. * Params: * loc = location of error * format = printf-style format specification * ap = printf-style variadic arguments * kind = kind of error being printed * p1 = additional message prefix * p2 = additional message prefix */ extern (C++) void verrorReport(const ref Loc loc, const(char)* format, va_list ap, ErrorKind kind, const(char)* p1 = null, const(char)* p2 = null) { auto info = ErrorInfo(loc, kind, p1, p2); final switch (info.kind) { case ErrorKind.error: global.errors++; if (!global.gag) { info.headerColor = Classification.error; verrorPrint(format, ap, info); if (global.params.v.errorLimit && global.errors >= global.params.v.errorLimit) fatal(); // moderate blizzard of cascading messages } else { if (global.params.v.showGaggedErrors) { info.headerColor = Classification.gagged; verrorPrint(format, ap, info); } global.gaggedErrors++; } break; case ErrorKind.deprecation: if (global.params.useDeprecated == DiagnosticReporting.error) goto case ErrorKind.error; else if (global.params.useDeprecated == DiagnosticReporting.inform) { if (!global.gag) { global.deprecations++; if (global.params.v.errorLimit == 0 || global.deprecations <= global.params.v.errorLimit) { info.headerColor = Classification.deprecation; verrorPrint(format, ap, info); } } else { global.gaggedWarnings++; } } break; case ErrorKind.warning: if (global.params.warnings != DiagnosticReporting.off) { if (!global.gag) { info.headerColor = Classification.warning; verrorPrint(format, ap, info); if (global.params.warnings == DiagnosticReporting.error) global.warnings++; } else { global.gaggedWarnings++; } } break; case ErrorKind.tip: if (!global.gag) { info.headerColor = Classification.tip; verrorPrint(format, ap, info); } break; case ErrorKind.message: const p = info.loc.toChars(); if (*p) { fprintf(stdout, "%s: ", p); mem.xfree(cast(void*)p); } OutBuffer tmp; tmp.vprintf(format, ap); fputs(tmp.peekChars(), stdout); fputc('\n', stdout); fflush(stdout); // ensure it gets written out in case of compiler aborts break; } } /** * Implements $(D errorSupplemental), $(D warningSupplemental), and * $(D deprecationSupplemental). Report an addition diagnostic error, taking a * va_list parameter. Whether the message gets printed depends on runtime * values of DiagnosticReporting and global gagging. * Params: * loc = location of error * format = printf-style format specification * ap = printf-style variadic arguments * kind = kind of error being printed */ extern (C++) void verrorReportSupplemental(const ref Loc loc, const(char)* format, va_list ap, ErrorKind kind) { auto info = ErrorInfo(loc, kind); info.supplemental = true; switch (info.kind) { case ErrorKind.error: if (global.gag) { if (!global.params.v.showGaggedErrors) return; info.headerColor = Classification.gagged; } else info.headerColor = Classification.error; verrorPrint(format, ap, info); break; case ErrorKind.deprecation: if (global.params.useDeprecated == DiagnosticReporting.error) goto case ErrorKind.error; else if (global.params.useDeprecated == DiagnosticReporting.inform && !global.gag) { if (global.params.v.errorLimit == 0 || global.deprecations <= global.params.v.errorLimit) { info.headerColor = Classification.deprecation; verrorPrint(format, ap, info); } } break; case ErrorKind.warning: if (global.params.warnings != DiagnosticReporting.off && !global.gag) { info.headerColor = Classification.warning; verrorPrint(format, ap, info); } break; default: assert(false, "internal error: unhandled kind in error report"); } } /** * Just print to stderr, doesn't care about gagging. * (format,ap) text within backticks gets syntax highlighted. * Params: * format = printf-style format specification * ap = printf-style variadic arguments * info = context of error */ private void verrorPrint(const(char)* format, va_list ap, ref ErrorInfo info) { const(char)* header; // title of error message if (info.supplemental) header = " "; else { final switch (info.kind) { case ErrorKind.error: header = "Error: "; break; case ErrorKind.deprecation: header = "Deprecation: "; break; case ErrorKind.warning: header = "Warning: "; break; case ErrorKind.tip: header = " Tip: "; break; case ErrorKind.message: assert(0); } } if (diagnosticHandler !is null && diagnosticHandler(info.loc, info.headerColor, header, format, ap, info.p1, info.p2)) return; if (global.params.v.showGaggedErrors && global.gag) fprintf(stderr, "(spec:%d) ", global.gag); auto con = cast(Console) global.console; const p = info.loc.toChars(); if (con) con.setColorBright(true); if (*p) { fprintf(stderr, "%s: ", p); mem.xfree(cast(void*)p); } if (con) con.setColor(info.headerColor); fputs(header, stderr); if (con) con.resetColor(); OutBuffer tmp; if (info.p1) { tmp.writestring(info.p1); tmp.writestring(" "); } if (info.p2) { tmp.writestring(info.p2); tmp.writestring(" "); } tmp.vprintf(format, ap); if (con && strchr(tmp.peekChars(), '`')) { colorSyntaxHighlight(tmp); writeHighlights(con, tmp); } else fputs(tmp.peekChars(), stderr); fputc('\n', stderr); __gshared Loc old_loc; Loc loc = info.loc; if (global.params.v.printErrorContext && // ignore supplemental messages with same loc (loc != old_loc || !info.supplemental) && // ignore invalid files loc != Loc.initial && // ignore mixins for now !loc.filename.strstr(".d-mixin-") && !global.params.mixinOut.doOutput) { import dmd.root.filename : FileName; const fileName = FileName(loc.filename.toDString); if (auto text = global.fileManager.getFileContents(fileName)) { auto range = dmd.root.string.splitLines(cast(const(char[])) text); size_t linnum; foreach (line; range) { ++linnum; if (linnum != loc.linnum) continue; if (loc.charnum < line.length) { fprintf(stderr, "%.*s\n", cast(int)line.length, line.ptr); // The number of column bytes and the number of display columns // occupied by a character are not the same for non-ASCII charaters. // https://issues.dlang.org/show_bug.cgi?id=21849 size_t col = 0; while (col < loc.charnum - 1) { import dmd.root.utf : utf_decodeChar; dchar u; const msg = utf_decodeChar(line, col, u); assert(msg is null, msg); fputc(' ', stderr); } fputc('^', stderr); fputc('\n', stderr); } break; } } } old_loc = loc; fflush(stderr); // ensure it gets written out in case of compiler aborts } /** * The type of the fatal error handler * Returns: true if error handling is done, false to do exit(EXIT_FAILURE) */ alias FatalErrorHandler = bool delegate(); /** * The fatal error handler. * If non-null it will be called for every fatal() call issued by the compiler. */ __gshared FatalErrorHandler fatalErrorHandler; /** * Call this after printing out fatal error messages to clean up and exit the * compiler. You can also set a fatalErrorHandler to override this behaviour. */ extern (C++) void fatal() { if (fatalErrorHandler && fatalErrorHandler()) return; exit(EXIT_FAILURE); } /** * Try to stop forgetting to remove the breakpoints from * release builds. */ extern (C++) void halt() @safe { assert(0); } /** * Scan characters in `buf`. Assume text enclosed by `...` * is D source code, and color syntax highlight it. * Modify contents of `buf` with highlighted result. * Many parallels to ddoc.highlightText(). * Params: * buf = text containing `...` code to highlight */ private void colorSyntaxHighlight(ref OutBuffer buf) { //printf("colorSyntaxHighlight('%.*s')\n", cast(int)buf.length, buf[].ptr); bool inBacktick = false; size_t iCodeStart = 0; size_t offset = 0; for (size_t i = offset; i < buf.length; ++i) { char c = buf[i]; switch (c) { case '`': if (inBacktick) { inBacktick = false; OutBuffer codebuf; codebuf.write(buf[iCodeStart .. i]); codebuf.writeByte(0); // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL colorHighlightCode(codebuf); buf.remove(iCodeStart, i - iCodeStart); immutable pre = ""; i = buf.insert(iCodeStart, pre); i = buf.insert(i, codebuf[]); break; } inBacktick = true; iCodeStart = i + 1; break; default: break; } } } /** * Embed these highlighting commands in the text stream. * HIGHLIGHT.Escape indicates a Color follows. */ enum HIGHLIGHT : ubyte { Default = Color.black, // back to whatever the console is set at Escape = '\xFF', // highlight Color follows Identifier = Color.white, Keyword = Color.white, Literal = Color.white, Comment = Color.darkGray, Other = Color.cyan, // other tokens } /** * Highlight code for CODE section. * Rewrite the contents of `buf` with embedded highlights. * Analogous to doc.highlightCode2() */ private void colorHighlightCode(ref OutBuffer buf) { import dmd.lexer; import dmd.tokens; __gshared int nested; if (nested) { // Should never happen, but don't infinitely recurse if it does --nested; return; } ++nested; scope Lexer lex = new Lexer(null, cast(char*)buf[].ptr, 0, buf.length - 1, 0, 1, global.errorSinkNull, &global.compileEnv); OutBuffer res; const(char)* lastp = cast(char*)buf[].ptr; //printf("colorHighlightCode('%.*s')\n", cast(int)(buf.length - 1), buf[].ptr); res.reserve(buf.length); res.writeByte(HIGHLIGHT.Escape); res.writeByte(HIGHLIGHT.Other); while (1) { Token tok; lex.scan(&tok); res.writestring(lastp[0 .. tok.ptr - lastp]); HIGHLIGHT highlight; switch (tok.value) { case TOK.identifier: highlight = HIGHLIGHT.Identifier; break; case TOK.comment: highlight = HIGHLIGHT.Comment; break; case TOK.int32Literal: .. case TOK.dcharLiteral: case TOK.string_: highlight = HIGHLIGHT.Literal; break; default: if (tok.isKeyword()) highlight = HIGHLIGHT.Keyword; break; } if (highlight != HIGHLIGHT.Default) { res.writeByte(HIGHLIGHT.Escape); res.writeByte(highlight); res.writestring(tok.ptr[0 .. lex.p - tok.ptr]); res.writeByte(HIGHLIGHT.Escape); res.writeByte(HIGHLIGHT.Other); } else res.writestring(tok.ptr[0 .. lex.p - tok.ptr]); if (tok.value == TOK.endOfFile) break; lastp = lex.p; } res.writeByte(HIGHLIGHT.Escape); res.writeByte(HIGHLIGHT.Default); //printf("res = '%.*s'\n", cast(int)buf.length, buf[].ptr); buf.setsize(0); buf.write(&res); --nested; } /** * Write the buffer contents with embedded highlights to stderr. * Params: * buf = highlighted text */ private void writeHighlights(Console con, ref const OutBuffer buf) { bool colors; scope (exit) { /* Do not mess up console if highlighting aborts */ if (colors) con.resetColor(); } for (size_t i = 0; i < buf.length; ++i) { const c = buf[i]; if (c == HIGHLIGHT.Escape) { const color = buf[++i]; if (color == HIGHLIGHT.Default) { con.resetColor(); colors = false; } else if (color == Color.white) { con.resetColor(); con.setColorBright(true); colors = true; } else { con.setColor(cast(Color)color); colors = true; } } else fputc(c, con.fp); } } ldc-1.40.0-src/dmd/dversion.d0000644000000000000000000000654414727557031014407 0ustar rootroot/** * Defines a `Dsymbol` for `version = identifier` and `debug = identifier` statements. * * Specification: $(LINK2 https://dlang.org/spec/version.html#version-specification, Version Specification), * $(LINK2 https://dlang.org/spec/version.html#debug_specification, Debug Specification). * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dversion.d, _dversion.d) * Documentation: https://dlang.org/phobos/dmd_dversion.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dversion.d */ module dmd.dversion; import dmd.arraytypes; import dmd.cond; import dmd.dmodule; import dmd.dscope; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.errors; import dmd.globals; import dmd.identifier; import dmd.location; import dmd.common.outbuffer; import dmd.visitor; /*********************************************************** * DebugSymbol's happen for statements like: * debug = identifier; * debug = integer; */ extern (C++) final class DebugSymbol : Dsymbol { uint level; extern (D) this(const ref Loc loc, Identifier ident) @safe { super(loc, ident); } extern (D) this(const ref Loc loc, uint level) @safe { super(loc, null); this.level = level; } override DebugSymbol syntaxCopy(Dsymbol s) { assert(!s); auto ds = new DebugSymbol(loc, ident); ds.comment = comment; ds.level = level; return ds; } override const(char)* toChars() const nothrow { if (ident) return ident.toChars(); else { OutBuffer buf; buf.print(level); return buf.extractChars(); } } override const(char)* kind() const nothrow { return "debug"; } override inout(DebugSymbol) isDebugSymbol() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * VersionSymbol's happen for statements like: * version = identifier; * version = integer; */ extern (C++) final class VersionSymbol : Dsymbol { uint level; extern (D) this(const ref Loc loc, Identifier ident) @safe { super(loc, ident); } extern (D) this(const ref Loc loc, uint level) @safe { super(loc, null); this.level = level; } override VersionSymbol syntaxCopy(Dsymbol s) { assert(!s); auto ds = ident ? new VersionSymbol(loc, ident) : new VersionSymbol(loc, level); ds.comment = comment; return ds; } override const(char)* toChars() const nothrow { if (ident) return ident.toChars(); else { OutBuffer buf; buf.print(level); return buf.extractChars(); } } override const(char)* kind() const nothrow { return "version"; } override inout(VersionSymbol) isVersionSymbol() inout { return this; } override void accept(Visitor v) { v.visit(this); } } ldc-1.40.0-src/dmd/escape.d0000644000000000000000000025414214727557031014015 0ustar rootroot/** * Most of the logic to implement scoped pointers and scoped references is here. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/escape.d, _escape.d) * Documentation: https://dlang.org/phobos/dmd_escape.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/escape.d */ module dmd.escape; import core.stdc.stdio : printf; import core.stdc.stdlib; import core.stdc.string; import dmd.root.rmem; import dmd.aggregate; import dmd.astenums; import dmd.declaration; import dmd.dscope; import dmd.dsymbol; import dmd.errors; import dmd.expression; import dmd.func; import dmd.funcsem; import dmd.globals : FeatureState; import dmd.id; import dmd.identifier; import dmd.init; import dmd.location; import dmd.mtype; import dmd.printast; import dmd.rootobject; import dmd.tokens; import dmd.typesem : hasPointers, parameterStorageClass; import dmd.visitor; import dmd.arraytypes; private: /// Groups global state for escape checking together package(dmd) struct EscapeState { // Maps `sequenceNumber` of a `VarDeclaration` to an object that contains the // reason it failed to infer `scope` // https://issues.dlang.org/show_bug.cgi?id=23295 private __gshared RootObject[int] scopeInferFailure; /// Called by `initDMD` / `deinitializeDMD` to reset global state static void reset() { scopeInferFailure = null; } } /****************************************************** * Checks memory objects passed to a function. * Checks that if a memory object is passed by ref or by pointer, * all of the refs or pointers are const, or there is only one mutable * ref or pointer to it. * References: * DIP 1021 * Params: * sc = used to determine current function and module * fd = function being called * tf = fd's type * ethis = if not null, the `this` pointer * arguments = actual arguments to function * gag = do not print error messages * Returns: * `true` if error */ public bool checkMutableArguments(ref Scope sc, FuncDeclaration fd, TypeFunction tf, Expression ethis, Expressions* arguments, bool gag) { enum log = false; if (log) printf("[%s] checkMutableArguments, fd: `%s`\n", fd.loc.toChars(), fd.toChars()); if (log && ethis) printf("ethis: `%s`\n", ethis.toChars()); bool errors = false; /* Outer variable references are treated as if they are extra arguments * passed by ref to the function (which they essentially are via the static link). */ VarDeclaration[] outerVars = fd ? fd.outerVars[] : null; const len = arguments.length + (ethis !is null) + outerVars.length; if (len <= 1) return errors; struct EscapeBy { VarDeclarations byref; VarDeclarations byvalue; Parameter param; // null if no Parameter for this argument bool isMutable; // true if reference to mutable } auto escapeBy = new EscapeBy[len]; const paramLength = tf.parameterList.length; // Fill in escapeBy[] with arguments[], ethis, and outerVars[] foreach (const i, ref eb; escapeBy) { bool refs; Expression arg; if (i < arguments.length) { arg = (*arguments)[i]; if (i < paramLength) { eb.param = tf.parameterList[i]; refs = eb.param.isReference(); eb.isMutable = eb.param.isReferenceToMutable(arg.type); } else { eb.param = null; refs = false; eb.isMutable = arg.type.isReferenceToMutable(); } } else if (ethis) { /* ethis is passed by value if a class reference, * by ref if a struct value */ eb.param = null; arg = ethis; auto ad = fd.isThis(); assert(ad); assert(ethis); if (ad.isClassDeclaration()) { refs = false; eb.isMutable = arg.type.isReferenceToMutable(); } else { assert(ad.isStructDeclaration()); refs = true; eb.isMutable = arg.type.isMutable(); } } else { // outer variables are passed by ref eb.param = null; refs = true; auto var = outerVars[i - (len - outerVars.length)]; eb.isMutable = var.type.isMutable(); eb.byref.push(var); continue; } void onRef(VarDeclaration v, bool transition) { eb.byref.push(v); } void onValue(VarDeclaration v) { eb.byvalue.push(v); } void onFunc(FuncDeclaration fd, bool called) {} void onExp(Expression e, bool transition) {} scope EscapeByResults er = EscapeByResults(&onRef, &onValue, &onFunc, &onExp); if (refs) escapeByRef(arg, er); else escapeByValue(arg, er); } void checkOnePair(size_t i, ref EscapeBy eb, ref EscapeBy eb2, VarDeclaration v, VarDeclaration v2, bool of) { if (log) printf("v2: `%s`\n", v2.toChars()); if (v2 != v) return; //printf("v %d v2 %d\n", eb.isMutable, eb2.isMutable); if (!(eb.isMutable || eb2.isMutable)) return; if (!tf.islive && !(sc.useDIP1000 == FeatureState.enabled && sc.func && sc.func.setUnsafe())) return; if (!gag) { // int i; funcThatEscapes(ref int i); // funcThatEscapes(i); // error escaping reference _to_ `i` // int* j; funcThatEscapes2(int* j); // funcThatEscapes2(j); // error escaping reference _of_ `i` const(char)* referenceVerb = of ? "of" : "to"; const(char)* msg = eb.isMutable && eb2.isMutable ? "more than one mutable reference %s `%s` in arguments to `%s()`" : "mutable and const references %s `%s` in arguments to `%s()`"; sc.eSink.error((*arguments)[i].loc, msg, referenceVerb, v.toChars(), fd ? fd.toPrettyChars() : "indirectly"); } errors = true; } void escape(size_t i, ref EscapeBy eb, bool byval) { foreach (VarDeclaration v; byval ? eb.byvalue : eb.byref) { if (log) { const(char)* by = byval ? "byval" : "byref"; printf("%s %s\n", by, v.toChars()); } if (byval && !v.type.hasPointers()) continue; foreach (ref eb2; escapeBy[i + 1 .. $]) { foreach (VarDeclaration v2; byval ? eb2.byvalue : eb2.byref) { checkOnePair(i, eb, eb2, v, v2, byval); } } } } foreach (const i, ref eb; escapeBy[0 .. $ - 1]) { escape(i, eb, true); escape(i, eb, false); } return errors; } /****************************************** * Array literal is going to be allocated on the GC heap. * Check its elements to see if any would escape by going on the heap. * Params: * sc = used to determine current function and module * ae = array literal expression * gag = do not print error messages * Returns: * `true` if any elements escaped */ public bool checkArrayLiteralEscape(ref Scope sc, ArrayLiteralExp ae, bool gag) { bool errors; if (ae.basis) errors = checkNewEscape(sc, ae.basis, gag); foreach (ex; *ae.elements) { if (ex) errors |= checkNewEscape(sc, ex, gag); } return errors; } /****************************************** * Associative array literal is going to be allocated on the GC heap. * Check its elements to see if any would escape by going on the heap. * Params: * sc = used to determine current function and module * ae = associative array literal expression * gag = do not print error messages * Returns: * `true` if any elements escaped */ public bool checkAssocArrayLiteralEscape(ref Scope sc, AssocArrayLiteralExp ae, bool gag) { bool errors; foreach (ex; *ae.keys) { if (ex) errors |= checkNewEscape(sc, ex, gag); } foreach (ex; *ae.values) { if (ex) errors |= checkNewEscape(sc, ex, gag); } return errors; } /** * A `scope` variable was assigned to non-scope parameter `v`. * If applicable, print why the parameter was not inferred `scope`. * * Params: * printFunc = error/deprecation print function to use * v = parameter that was not inferred * recursionLimit = recursion limit for printing the reason */ private void printScopeFailure(E)(E printFunc, VarDeclaration v, int recursionLimit) { recursionLimit--; if (recursionLimit < 0 || !v) return; if (RootObject* o = v.sequenceNumber in EscapeState.scopeInferFailure) { switch ((*o).dyncast()) { case DYNCAST.expression: Expression e = cast(Expression) *o; printFunc(e.loc, "which is not `scope` because of `%s`", e.toChars()); break; case DYNCAST.dsymbol: VarDeclaration v1 = cast(VarDeclaration) *o; printFunc(v1.loc, "which is assigned to non-scope parameter `%s`", v1.toChars()); printScopeFailure(printFunc, v1, recursionLimit); break; default: assert(0); } } } /**************************************** * Function parameter `par` is being initialized to `arg`, * and `par` may escape. * Detect if scoped values can escape this way. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * fdc = function being called, `null` if called indirectly * parId = name of function parameter for error messages * vPar = `VarDeclaration` corresponding to `par` * parStc = storage classes of function parameter (may have added `scope` from `pure`) * arg = initializer for param * assertmsg = true if the parameter is the msg argument to assert(bool, msg). * gag = do not print error messages * Returns: * `true` if pointers to the stack can escape via assignment */ public bool checkParamArgumentEscape(ref Scope sc, FuncDeclaration fdc, Identifier parId, VarDeclaration vPar, STC parStc, Expression arg, bool assertmsg, bool gag) { enum log = false; if (log) printf("checkParamArgumentEscape(arg: %s par: %s parSTC: %llx)\n", arg ? arg.toChars() : "null", parId ? parId.toChars() : "null", parStc); //printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers()); if (!arg.type.hasPointers()) return false; bool result = false; /* 'v' is assigned unsafely to 'par' */ void unsafeAssign(string desc)(VarDeclaration v) { if (assertmsg) { result |= sc.setUnsafeDIP1000(gag, arg.loc, desc ~ " `%s` assigned to non-scope parameter calling `assert()`", v); return; } bool isThis = fdc && fdc.needThis() && fdc.vthis == vPar; // implicit `this` parameter to member function const(char)* msg = (isThis) ? (desc ~ " `%s` calling non-scope member function `%s.%s()`") : (fdc && parId) ? (desc ~ " `%s` assigned to non-scope parameter `%s` calling `%s`") : (fdc && !parId) ? (desc ~ " `%s` assigned to non-scope anonymous parameter calling `%s`") : (!fdc && parId) ? (desc ~ " `%s` assigned to non-scope parameter `%s`") : (desc ~ " `%s` assigned to non-scope anonymous parameter"); if (isThis ? sc.setUnsafeDIP1000(gag, arg.loc, msg, arg, fdc.toParent2(), fdc) : sc.setUnsafeDIP1000(gag, arg.loc, msg, v, parId ? parId : fdc, fdc)) { result = true; printScopeFailure(previewSupplementalFunc(sc.isDeprecated(), sc.useDIP1000), vPar, 10); } } void onValue(VarDeclaration v) { if (log) printf("byvalue %s\n", v.toChars()); if (parStc & STC.scope_ || v.isDataseg()) return; Dsymbol p = v.toParent2(); notMaybeScope(v, vPar); if (v.isScope()) { unsafeAssign!"scope variable"(v); } else if (v.isTypesafeVariadicArray && p == sc.func) { unsafeAssign!"variadic variable"(v); } } void onRef(VarDeclaration v, bool retRefTransition) { if (log) printf("byref %s\n", v.toChars()); if (v.isDataseg()) return; Dsymbol p = v.toParent2(); notMaybeScope(v, arg); if (checkScopeVarAddr(v, arg, sc, gag)) { result = true; return; } if (p == sc.func && !(parStc & STC.scope_)) { unsafeAssign!"reference to local variable"(v); return; } } void onFunc(FuncDeclaration fd, bool called) { //printf("fd = %s, %d\n", fd.toChars(), fd.tookAddressOf); if (parStc & STC.scope_) return; VarDeclarations vars; findAllOuterAccessedVariables(fd, &vars); foreach (v; vars) { //printf("v = %s\n", v.toChars()); assert(!v.isDataseg()); // these are not put in the closureVars[] Dsymbol p = v.toParent2(); notMaybeScope(v, arg); if ((v.isReference() || v.isScope()) && p == sc.func) { unsafeAssign!"reference to local"(v); return; } } } void onExp(Expression ee, bool retRefTransition) { if (parStc & STC.scope_) return; const(char)* msg = parId ? "reference to stack allocated value returned by `%s` assigned to non-scope parameter `%s`" : "reference to stack allocated value returned by `%s` assigned to non-scope anonymous parameter"; result |= sc.setUnsafeDIP1000(gag, ee.loc, msg, ee, parId); } scope EscapeByResults er = EscapeByResults(&onRef, &onValue, &onFunc, &onExp); escapeByValue(arg, er); return result; } /***************************************************** * Function argument initializes a `return` parameter, * and that parameter gets assigned to `firstArg`. * Essentially, treat as `firstArg = arg;` * Params: * sc = used to determine current function and module * firstArg = `ref` argument through which `arg` may be assigned * arg = initializer for parameter * param = parameter declaration corresponding to `arg` * gag = do not print error messages * Returns: * `true` if assignment to `firstArg` would cause an error */ public bool checkParamArgumentReturn(ref Scope sc, Expression firstArg, Expression arg, Parameter param, bool gag) { enum log = false; if (log) printf("checkParamArgumentReturn(firstArg: %s arg: %s)\n", firstArg.toChars(), arg.toChars()); //printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers()); if (!(param.storageClass & STC.return_)) return false; if (!arg.type.hasPointers() && !param.isReference()) return false; // `byRef` needed for `assign(ref int* x, ref int i) {x = &i};` // Note: taking address of scope pointer is not allowed // `assign(ref int** x, return ref scope int* i) {x = &i};` // Thus no return ref/return scope ambiguity here const byRef = param.isReference() && !(param.storageClass & STC.scope_) && !(param.storageClass & STC.returnScope); // fixme: it's possible to infer returnScope without scope with vaIsFirstRef auto e = new AssignExp(arg.loc, firstArg, arg); return checkAssignEscape(sc, e, gag, byRef); } /***************************************************** * Check struct constructor of the form `s.this(args)`, by * checking each `return` parameter to see if it gets * assigned to `s`. * Params: * sc = used to determine current function and module * ce = constructor call of the form `s.this(args)` * gag = do not print error messages * Returns: * `true` if construction would cause an escaping reference error */ public bool checkConstructorEscape(ref Scope sc, CallExp ce, bool gag) { enum log = false; if (log) printf("checkConstructorEscape(%s, %s)\n", ce.toChars(), ce.type.toChars()); Type tthis = ce.type.toBasetype(); assert(tthis.ty == Tstruct); if (!tthis.hasPointers()) return false; if (!ce.arguments && ce.arguments.length) return false; DotVarExp dve = ce.e1.isDotVarExp(); CtorDeclaration ctor = dve.var.isCtorDeclaration(); TypeFunction tf = ctor.type.isTypeFunction(); const nparams = tf.parameterList.length; const n = ce.arguments.length; // j=1 if _arguments[] is first argument const j = tf.isDstyleVariadic(); /* Attempt to assign each `return` arg to the `this` reference */ foreach (const i; 0 .. n) { Expression arg = (*ce.arguments)[i]; //printf("\targ[%d]: %s\n", i, arg.toChars()); if (i - j < nparams && i >= j) { Parameter p = tf.parameterList[i - j]; if (checkParamArgumentReturn(sc, dve.e1, arg, p, gag)) return true; } } return false; } /// How a `return` parameter escapes its pointer value public enum ReturnParamDest { returnVal, /// through return statement: `return x` this_, /// assigned to a struct instance: `this.x = x` firstArg, /// assigned to first argument: `firstArg = x` } /**************************************** * Find out if instead of returning a `return` parameter via a return statement, * it is returned via assignment to either `this` or the first parameter. * * This works the same as returning the value via a return statement. * Although the first argument must be `ref`, it is not regarded as returning by `ref`. * * See_Also: https://dlang.org.spec/function.html#return-ref-parameters * * Params: * tf = function type * tthis = type of `this` parameter, or `null` if none * Returns: What a `return` parameter should transfer the lifetime of the argument to */ public ReturnParamDest returnParamDest(TypeFunction tf, Type tthis) { assert(tf); if (tf.isctor) return ReturnParamDest.this_; if (!tf.nextOf() || (tf.nextOf().ty != Tvoid)) return ReturnParamDest.returnVal; if (tthis && tthis.toBasetype().ty == Tstruct) // class `this` is passed by value return ReturnParamDest.this_; if (tf.parameterList.length > 0 && tf.parameterList[0].isReference) return ReturnParamDest.firstArg; return ReturnParamDest.returnVal; } /**************************************** * Given an `AssignExp`, determine if the lvalue will cause * the contents of the rvalue to escape. * Print error messages when these are detected. * Infer `scope` attribute for the lvalue where possible, in order * to eliminate the error. * Params: * sc = used to determine current function and module * e = `AssignExp` or `CatAssignExp` to check for any pointers to the stack * gag = do not print error messages * byRef = set to `true` if `e1` of `e` gets assigned a reference to `e2` * Returns: * `true` if pointers to the stack can escape via assignment */ public bool checkAssignEscape(ref Scope sc, Expression e, bool gag, bool byRef) { enum log = false; if (log) printf("checkAssignEscape(e: %s, byRef: %d)\n", e.toChars(), byRef); if (e.op != EXP.assign && e.op != EXP.blit && e.op != EXP.construct && e.op != EXP.concatenateAssign && e.op != EXP.concatenateElemAssign && e.op != EXP.concatenateDcharAssign) return false; auto ae = cast(BinExp)e; Expression e1 = ae.e1; Expression e2 = ae.e2; //printf("type = %s, %d\n", e1.type.toChars(), e1.type.hasPointers()); if (!e1.type.hasPointers()) return false; if (e1.isSliceExp()) { if (VarDeclaration va = expToVariable(e1)) { if (!va.type.toBasetype().isTypeSArray() || // treat static array slice same as a variable !va.type.hasPointers()) return false; } else return false; } /* The struct literal case can arise from the S(e2) constructor call: * return S(e2); * and appears in this function as: * structLiteral = e2; * Such an assignment does not necessarily remove scope-ness. */ if (e1.isStructLiteralExp()) return false; VarDeclaration va = expToVariable(e1); if (va && e.op == EXP.concatenateElemAssign) { /* https://issues.dlang.org/show_bug.cgi?id=17842 * Draw an equivalence between: * *q = p; * and: * va ~= e; * since we are not assigning to va, but are assigning indirectly through va. */ va = null; } if (va && e1.isDotVarExp() && va.type.toBasetype().isTypeClass()) { /* https://issues.dlang.org/show_bug.cgi?id=17949 * Draw an equivalence between: * *q = p; * and: * va.field = e2; * since we are not assigning to va, but are assigning indirectly through class reference va. */ va = null; } if (log && va) printf("va: %s\n", va.toChars()); FuncDeclaration fd = sc.func; // Determine if va is a `ref` parameter, so it has a lifetime exceding the function scope const bool vaIsRef = va && va.isParameter() && va.isReference(); if (log && vaIsRef) printf("va is ref `%s`\n", va.toChars()); // Determine if va is the first parameter, through which other 'return' parameters // can be assigned. bool vaIsFirstRef = false; if (fd && fd.type) { final switch (returnParamDest(fd.type.isTypeFunction(), fd.vthis ? fd.vthis.type : null)) { case ReturnParamDest.this_: vaIsFirstRef = va == fd.vthis; break; case ReturnParamDest.firstArg: // While you'd expect fd.parameters[0] to exist in this case, the compiler-generated // expression that initializes an `out int* p = null` is analyzed before fd.parameters // is created, so we still do a null and length check vaIsFirstRef = fd.parameters && 0 < fd.parameters.length && (*fd.parameters)[0] == va; break; case ReturnParamDest.returnVal: break; } } if (log && vaIsFirstRef) printf("va is first ref `%s`\n", va.toChars()); bool result = false; void onValue(VarDeclaration v) { if (log) printf("byvalue: %s\n", v.toChars()); if (v.isDataseg()) return; if (v == va) return; Dsymbol p = v.toParent2(); if (va && !vaIsRef && !va.isScope() && !v.isScope() && !v.isTypesafeVariadicArray && !va.isTypesafeVariadicArray && (va.isParameter() && va.maybeScope && v.isParameter() && v.maybeScope) && p == fd) { /* Add v to va's list of dependencies */ va.addMaybe(v); return; } if (vaIsFirstRef && p == fd) { inferReturn(fd, v, /*returnScope:*/ true); } if (!(va && va.isScope()) || vaIsRef) notMaybeScope(v, e); if (v.isScope()) { if (vaIsFirstRef && v.isParameter() && v.isReturn()) { // va=v, where v is `return scope` if (inferScope(va)) return; } // If va's lifetime encloses v's, then error if (EnclosedBy eb = va.enclosesLifetimeOf(v)) { const(char)* msg; final switch (eb) { case EnclosedBy.none: assert(0); case EnclosedBy.returnScope: msg = "scope variable `%s` assigned to return scope `%s`"; break; case EnclosedBy.longerScope: if (v.storage_class & STC.temp) return; msg = "scope variable `%s` assigned to `%s` with longer lifetime"; break; case EnclosedBy.refVar: msg = "scope variable `%s` assigned to `ref` variable `%s` with longer lifetime"; break; case EnclosedBy.global: msg = "scope variable `%s` assigned to global variable `%s`"; break; } if (sc.setUnsafeDIP1000(gag, ae.loc, msg, v, va)) { result = true; return; } } // v = scope, va should be scope as well const vaWasScope = va && va.isScope(); if (inferScope(va)) { // In case of `scope local = returnScopeParam`, do not infer return scope for `x` if (!vaWasScope && v.isReturn() && !va.isReturn()) { if (log) printf("infer return for %s\n", va.toChars()); va.storage_class |= STC.return_ | STC.returninferred; // Added "return scope" so don't confuse it with "return ref" if (isRefReturnScope(va.storage_class)) va.storage_class |= STC.returnScope; } return; } result |= sc.setUnsafeDIP1000(gag, ae.loc, "scope variable `%s` assigned to non-scope `%s`", v, e1); } else if (v.isTypesafeVariadicArray && p == fd) { if (inferScope(va)) return; result |= sc.setUnsafeDIP1000(gag, ae.loc, "variadic variable `%s` assigned to non-scope `%s`", v, e1); } else { /* v is not 'scope', and we didn't check the scope of where we assigned it to. * It may escape via that assignment, therefore, v can never be 'scope'. */ //printf("no infer for %s in %s, %d\n", v.toChars(), fd.ident.toChars(), __LINE__); doNotInferScope(v, e); } } void onRef(VarDeclaration v, bool retRefTransition) { if (log) printf("byref: %s\n", v.toChars()); if (v.isDataseg()) return; if (checkScopeVarAddr(v, ae, sc, gag)) { result = true; return; } if (va && va.isScope() && !v.isReference()) { if (!va.isReturn()) { va.doNotInferReturn = true; } else { result |= sc.setUnsafeDIP1000(gag, ae.loc, "address of local variable `%s` assigned to return scope `%s`", v, va); } } Dsymbol p = v.toParent2(); if (vaIsFirstRef && p == fd) { //if (log) printf("inferring 'return' for parameter %s in function %s\n", v.toChars(), fd.toChars()); inferReturn(fd, v, /*returnScope:*/ false); } // If va's lifetime encloses v's, then error if (va && !(vaIsFirstRef && v.isReturn()) && va.enclosesLifetimeOf(v)) { if (sc.setUnsafeDIP1000(gag, ae.loc, "address of variable `%s` assigned to `%s` with longer lifetime", v, va)) { result = true; return; } } if (!(va && va.isScope())) notMaybeScope(v, e); if (p != sc.func) return; if (inferScope(va)) { if (v.isReturn() && !va.isReturn()) va.storage_class |= STC.return_ | STC.returninferred; return; } if (e1.op == EXP.structLiteral) return; result |= sc.setUnsafeDIP1000(gag, ae.loc, "reference to local variable `%s` assigned to non-scope `%s`", v, e1); } void onFunc(FuncDeclaration func, bool called) { if (log) printf("byfunc: %s, %d\n", func.toChars(), func.tookAddressOf); VarDeclarations vars; findAllOuterAccessedVariables(func, &vars); /* https://issues.dlang.org/show_bug.cgi?id=16037 * If assigning the address of a delegate to a scope variable, * then uncount that address of. This is so it won't cause a * closure to be allocated. */ if (va && va.isScope() && !va.isReturn() && func.tookAddressOf) --func.tookAddressOf; foreach (v; vars) { //printf("v = %s\n", v.toChars()); assert(!v.isDataseg()); // these are not put in the closureVars[] Dsymbol p = v.toParent2(); if (!(va && va.isScope())) notMaybeScope(v, e); if (!(v.isReference() || v.isScope()) || p != fd) return; if (va && !va.isDataseg() && (va.isScope() || va.maybeScope)) { /* Don't infer STC.scope_ for va, because then a closure * won't be generated for fd. */ //if (!va.isScope()) //va.storage_class |= STC.scope_ | STC.scopeinferred; return; } result |= sc.setUnsafeDIP1000(gag, ae.loc, "reference to local `%s` assigned to non-scope `%s` in @safe code", v, e1); } } void onExp(Expression ee, bool retRefTransition) { if (log) printf("byexp: %s\n", ee.toChars()); /* Do not allow slicing of a static array returned by a function */ if (ee.op == EXP.call && ee.type.toBasetype().isTypeSArray() && e1.type.toBasetype().isTypeDArray() && !(va && va.storage_class & STC.temp)) { if (!gag) sc.eSink.deprecation(ee.loc, "slice of static array temporary returned by `%s` assigned to longer lived variable `%s`", ee.toChars(), e1.toChars()); //result = true; return; } if (ee.op == EXP.call && ee.type.toBasetype().isTypeStruct() && (!va || !(va.storage_class & STC.temp) && !va.isScope())) { if (sc.setUnsafeDIP1000(gag, ee.loc, "address of struct temporary returned by `%s` assigned to longer lived variable `%s`", ee, e1)) { result = true; return; } } if (ee.op == EXP.structLiteral && (!va || !(va.storage_class & STC.temp))) { if (sc.setUnsafeDIP1000(gag, ee.loc, "address of struct literal `%s` assigned to longer lived variable `%s`", ee, e1)) { result = true; return; } } if (inferScope(va)) return; result |= sc.setUnsafeDIP1000(gag, ee.loc, "reference to stack allocated value returned by `%s` assigned to non-scope `%s`", ee, e1); } scope EscapeByResults er = EscapeByResults(&onRef, &onValue, &onFunc, &onExp); if (byRef) escapeByRef(e2, er); else escapeByValue(e2, er); return result; } /************************************ * Detect cases where pointers to the stack can escape the * lifetime of the stack frame when throwing `e`. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * e = expression to check for any pointers to the stack * gag = do not print error messages * Returns: * `true` if pointers to the stack can escape */ public bool checkThrowEscape(ref Scope sc, Expression e, bool gag) { //printf("[%s] checkThrowEscape, e = %s\n", e.loc.toChars(), e.toChars()); bool result = false; void onRef(VarDeclaration v, bool retRefTransition) {} void onValue(VarDeclaration v) { //printf("byvalue %s\n", v.toChars()); if (v.isDataseg()) return; if (v.isScope() && !v.iscatchvar) // special case: allow catch var to be rethrown // despite being `scope` { // https://issues.dlang.org/show_bug.cgi?id=17029 result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be thrown", v); return; } else { notMaybeScope(v, new ThrowExp(e.loc, e)); } } void onFunc(FuncDeclaration fd, bool called) {} void onExp(Expression exp, bool retRefTransition) {} scope EscapeByResults er = EscapeByResults(&onRef, &onValue, &onFunc, &onExp); escapeByValue(e, er); return result; } /************************************ * Detect cases where pointers to the stack can escape the * lifetime of the stack frame by being placed into a GC allocated object. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * e = expression to check for any pointers to the stack * gag = do not print error messages * Returns: * `true` if pointers to the stack can escape */ public bool checkNewEscape(ref Scope sc, Expression e, bool gag) { import dmd.globals: FeatureState; import dmd.errors: previewErrorFunc; //printf("[%s] checkNewEscape, e = %s\n", e.loc.toChars(), e.toChars()); enum log = false; if (log) printf("[%s] checkNewEscape, e: `%s`\n", e.loc.toChars(), e.toChars()); bool result = false; void onValue(VarDeclaration v) { if (log) printf("byvalue `%s`\n", v.toChars()); if (v.isDataseg()) return; Dsymbol p = v.toParent2(); if (v.isScope()) { if ( /* This case comes up when the ReturnStatement of a __foreachbody is * checked for escapes by the caller of __foreachbody. Skip it. * * struct S { static int opApply(int delegate(S*) dg); } * S* foo() { * foreach (S* s; S) // create __foreachbody for body of foreach * return s; // s is inferred as 'scope' but incorrectly tested in foo() * return null; } */ !(p.parent == sc.func)) { // https://issues.dlang.org/show_bug.cgi?id=20868 result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be copied into allocated memory", v); return; } } else if (v.isTypesafeVariadicArray && p == sc.func) { result |= sc.setUnsafeDIP1000(gag, e.loc, "copying `%s` into allocated memory escapes a reference to variadic parameter `%s`", e, v); } else { //printf("no infer for %s in %s, %d\n", v.toChars(), sc.func.ident.toChars(), __LINE__); notMaybeScope(v, e); } } void onRef(VarDeclaration v, bool retRefTransition) { if (log) printf("byref `%s`\n", v.toChars()); // 'featureState' tells us whether to emit an error or a deprecation, // depending on the flag passed to the CLI for DIP25 / DIP1000 bool escapingRef(VarDeclaration v, FeatureState fs) { const(char)* msg = v.isParameter() ? "copying `%s` into allocated memory escapes a reference to parameter `%s`" : "copying `%s` into allocated memory escapes a reference to local variable `%s`"; return setUnsafePreview(&sc, fs, gag, e.loc, msg, e, v); } if (v.isDataseg()) return; Dsymbol p = v.toParent2(); if (!v.isReference()) { if (p == sc.func) { result |= escapingRef(v, sc.useDIP1000); return; } } /* Check for returning a ref variable by 'ref', but should be 'return ref' * Infer the addition of 'return', or set result to be the offending expression. */ if (!v.isReference()) return; // https://dlang.org/spec/function.html#return-ref-parameters if (p == sc.func) { //printf("escaping reference to local ref variable %s\n", v.toChars()); //printf("storage class = x%llx\n", v.storage_class); result |= escapingRef(v, sc.useDIP25); return; } // Don't need to be concerned if v's parent does not return a ref FuncDeclaration func = p.isFuncDeclaration(); if (!func || !func.type) return; if (auto tf = func.type.isTypeFunction()) { if (!tf.isref) return; const(char)* msg = "storing reference to outer local variable `%s` into allocated memory causes it to escape"; if (!gag) { previewErrorFunc(sc.isDeprecated(), sc.useDIP25)(e.loc, msg, v.toChars()); } // If -preview=dip25 is used, the user wants an error // Otherwise, issue a deprecation result |= (sc.useDIP25 == FeatureState.enabled); } } void onFunc(FuncDeclaration fd, bool called) {} void onExp(Expression ee, bool retRefTransition) { if (log) printf("byexp %s\n", ee.toChars()); if (!gag) sc.eSink.error(ee.loc, "storing reference to stack allocated value returned by `%s` into allocated memory causes it to escape", ee.toChars()); result = true; } scope EscapeByResults er = EscapeByResults(&onRef, &onValue, &onFunc, &onExp); escapeByValue(e, er); return result; } /************************************ * Detect cases where pointers to the stack can escape the * lifetime of the stack frame by returning `e` by value. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * e = expression to check for any pointers to the stack * gag = do not print error messages * Returns: * `true` if pointers to the stack can escape */ public bool checkReturnEscape(ref Scope sc, Expression e, bool gag) { //printf("[%s] checkReturnEscape, e: %s\n", e.loc.toChars(), e.toChars()); return checkReturnEscapeImpl(sc, e, false, gag); } /************************************ * Detect cases where returning `e` by `ref` can result in a reference to the stack * being returned. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * e = expression to check * gag = do not print error messages * Returns: * `true` if references to the stack can escape */ public bool checkReturnEscapeRef(ref Scope sc, Expression e, bool gag) { version (none) { printf("[%s] checkReturnEscapeRef, e = %s\n", e.loc.toChars(), e.toChars()); printf("current function %s\n", sc.func.toChars()); printf("parent2 function %s\n", sc.func.toParent2().toChars()); } return checkReturnEscapeImpl(sc, e, true, gag); } /*************************************** * Implementation of checking for escapes in return expressions. * Params: * sc = used to determine current function and module * e = expression to check * refs = `true`: escape by value, `false`: escape by `ref` * gag = do not print error messages * Returns: * `true` if references to the stack can escape */ private bool checkReturnEscapeImpl(ref Scope sc, Expression e, bool refs, bool gag) { enum log = false; if (log) printf("[%s] checkReturnEscapeImpl, refs: %d e: `%s`\n", e.loc.toChars(), refs, e.toChars()); bool result = false; void onValue(VarDeclaration v) { if (log) printf("byvalue `%s`\n", v.toChars()); if (v.isDataseg()) return; const vsr = buildScopeRef(v.storage_class); Dsymbol p = v.toParent2(); if (p == sc.func && inferReturn(sc.func, v, /*returnScope:*/ true)) { return; } if (v.isScope()) { /* If `return scope` applies to v. */ if (vsr == ScopeRef.ReturnScope || vsr == ScopeRef.Ref_ReturnScope) { return; } auto pfunc = p.isFuncDeclaration(); if (pfunc && /* This case comes up when the ReturnStatement of a __foreachbody is * checked for escapes by the caller of __foreachbody. Skip it. * * struct S { static int opApply(int delegate(S*) dg); } * S* foo() { * foreach (S* s; S) // create __foreachbody for body of foreach * return s; // s is inferred as 'scope' but incorrectly tested in foo() * return null; } */ !(!refs && p.parent == sc.func && pfunc.fes) ) { /* * auto p(scope string s) { * string scfunc() { return s; } * } */ if (sc.func.isFuncDeclaration().getLevel(pfunc, sc.intypeof) > 0 && inferReturn(sc.func, sc.func.vthis, /*returnScope*/ !refs)) { return; } if (v.isParameter() && !v.isReturn()) { // https://issues.dlang.org/show_bug.cgi?id=23191 if (!gag) { previewErrorFunc(sc.isDeprecated(), sc.useDIP1000)(e.loc, "scope parameter `%s` may not be returned", v.toChars() ); result = true; return; } } else { // https://issues.dlang.org/show_bug.cgi?id=17029 result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be returned", v); return; } } } else if (v.isTypesafeVariadicArray && p == sc.func) { if (!gag) sc.eSink.error(e.loc, "returning `%s` escapes a reference to variadic parameter `%s`", e.toChars(), v.toChars()); result = false; } else { //printf("no infer for %s in %s, %d\n", v.toChars(), sc.func.ident.toChars(), __LINE__); doNotInferScope(v, e); } } void onRef(VarDeclaration v, bool retRefTransition) { if (log) { printf("byref `%s` %s\n", v.toChars(), ScopeRefToChars(buildScopeRef(v.storage_class))); } // 'featureState' tells us whether to emit an error or a deprecation, // depending on the flag passed to the CLI for DIP25 void escapingRef(VarDeclaration v, FeatureState featureState) { const(char)* msg = v.isParameter() ? "returning `%s` escapes a reference to parameter `%s`" : "returning `%s` escapes a reference to local variable `%s`"; if (v.isParameter() && v.isReference()) { if (setUnsafePreview(&sc, featureState, gag, e.loc, msg, e, v) || sc.func.isSafeBypassingInference()) { result = true; if (v.storage_class & STC.returnScope) { previewSupplementalFunc(sc.isDeprecated(), featureState)(v.loc, "perhaps change the `return scope` into `scope return`"); } else { const(char)* annotateKind = (v.ident is Id.This) ? "function" : "parameter"; previewSupplementalFunc(sc.isDeprecated(), featureState)(v.loc, "perhaps annotate the %s with `return`", annotateKind); } } } else { if (retRefTransition) { result |= sc.setUnsafeDIP1000(gag, e.loc, msg, e, v); } else { if (!gag) previewErrorFunc(sc.isDeprecated(), featureState)(e.loc, msg, e.toChars(), v.toChars()); result = true; } } } if (v.isDataseg()) return; const vsr = buildScopeRef(v.storage_class); Dsymbol p = v.toParent2(); // https://issues.dlang.org/show_bug.cgi?id=19965 if (!refs) { if (sc.func.vthis == v) notMaybeScope(v, e); if (checkScopeVarAddr(v, e, sc, gag)) { result = true; return; } } if (!v.isReference()) { if (p == sc.func) { escapingRef(v, FeatureState.enabled); return; } FuncDeclaration fd = p.isFuncDeclaration(); if (fd && sc.func.returnInprocess) { /* Code like: * int x; * auto dg = () { return &x; } * Making it: * auto dg = () return { return &x; } * Because dg.ptr points to x, this is returning dt.ptr+offset */ sc.func.storage_class |= STC.return_ | STC.returninferred; } } /* Check for returning a ref variable by 'ref', but should be 'return ref' * Infer the addition of 'return', or set result to be the offending expression. */ if ((vsr == ScopeRef.Ref || vsr == ScopeRef.RefScope || vsr == ScopeRef.Ref_ReturnScope) && !(v.storage_class & STC.foreach_)) { if (p == sc.func && (vsr == ScopeRef.Ref || vsr == ScopeRef.RefScope) && inferReturn(sc.func, v, /*returnScope:*/ false)) { return; } else { // https://dlang.org/spec/function.html#return-ref-parameters // Only look for errors if in module listed on command line if (p == sc.func) { //printf("escaping reference to local ref variable %s\n", v.toChars()); //printf("storage class = x%llx\n", v.storage_class); escapingRef(v, sc.useDIP25); return; } // Don't need to be concerned if v's parent does not return a ref FuncDeclaration fd = p.isFuncDeclaration(); if (fd && fd.type && fd.type.ty == Tfunction) { TypeFunction tf = fd.type.isTypeFunction(); if (tf.isref) { const(char)* msg = "escaping reference to outer local variable `%s`"; if (!gag) previewErrorFunc(sc.isDeprecated(), sc.useDIP25)(e.loc, msg, v.toChars()); result = true; return; } } } } } void onFunc(FuncDeclaration fd, bool called) { if (called && fd.isNested()) result |= sc.setUnsafeDIP1000(gag, e.loc, "escaping local variable through nested function `%s`", fd); } void onExp(Expression ee, bool retRefTransition) { if (log) printf("byexp %s\n", ee.toChars()); if (retRefTransition) { result |= sc.setUnsafeDIP1000(gag, ee.loc, "escaping reference to stack allocated value returned by `%s`", ee); } else { if (!gag) sc.eSink.error(ee.loc, "escaping reference to stack allocated value returned by `%s`", ee.toChars()); result = true; } } scope EscapeByResults er = EscapeByResults(&onRef, &onValue, &onFunc, &onExp); if (refs) escapeByRef(e, er); else escapeByValue(e, er); return result; } /*********************************** * Infer `scope` for a variable * * Params: * va = variable to infer scope for * Returns: `true` if succesful or already `scope` */ private bool inferScope(VarDeclaration va) { if (!va) return false; if (!va.isDataseg() && va.maybeScope && !va.isScope()) { //printf("inferring scope for %s\n", va.toChars()); va.maybeScope = false; va.storage_class |= STC.scope_ | STC.scopeinferred; return true; } return va.isScope(); } /************************************* * Variable v needs to have 'return' inferred for it. * Params: * fd = function that v is a parameter to * v = parameter that needs to be STC.return_ * returnScope = infer `return scope` instead of `return ref` * * Returns: whether the inference on `v` was successful or `v` already was `return` */ private bool inferReturn(FuncDeclaration fd, VarDeclaration v, bool returnScope) { if (v.isReturn()) return !!(v.storage_class & STC.returnScope) == returnScope; if (!v.isParameter() || v.isTypesafeVariadicArray || (returnScope && v.doNotInferReturn)) return false; if (!fd.returnInprocess) return false; if (returnScope && !(v.isScope() || v.maybeScope)) return false; //printf("for function '%s' inferring 'return' for variable '%s', returnScope: %d\n", fd.toChars(), v.toChars(), returnScope); auto newStcs = STC.return_ | STC.returninferred | (returnScope ? STC.returnScope : 0); v.storage_class |= newStcs; if (v == fd.vthis) { /* v is the 'this' reference, so mark the function */ fd.storage_class |= newStcs; if (auto tf = fd.type.isTypeFunction()) { //printf("'this' too %p %s\n", tf, sc.func.toChars()); tf.isreturnscope = returnScope; tf.isreturn = true; tf.isreturninferred = true; } } else { // Perform 'return' inference on parameter if (auto tf = fd.type.isTypeFunction()) { foreach (i, p; tf.parameterList) { if (p.ident == v.ident) { p.storageClass |= newStcs; break; // there can be only one } } } } return true; } /**************************************** * e is an expression to be returned by value, and that value contains pointers. * Walk e to determine which variables are possibly being * returned by value, such as: * int* function(int* p) { return p; } * If e is a form of &p, determine which variables have content * which is being returned as ref, such as: * int* function(int i) { return &i; } * Multiple variables can be inserted, because of expressions like this: * int function(bool b, int i, int* p) { return b ? &i : p; } * * No side effects. * * Params: * e = expression to be returned by value * er = where to place collected data * retRefTransition = if `e` is returned through a `return (ref) scope` function call */ public void escapeByValue(Expression e, ref scope EscapeByResults er, bool retRefTransition = false) { //printf("[%s] escapeByValue, e: %s\n", e.loc.toChars(), e.toChars()); void visit(Expression e) { } void visitAddr(AddrExp e) { /* Taking the address of struct literal is normally not * allowed, but CTFE can generate one out of a new expression, * but it'll be placed in static data so no need to check it. */ if (e.e1.op != EXP.structLiteral) escapeByRef(e.e1, er, retRefTransition); } void visitSymOff(SymOffExp e) { VarDeclaration v = e.var.isVarDeclaration(); if (v) er.byRef(v, retRefTransition); } void visitVar(VarExp e) { if (auto v = e.var.isVarDeclaration()) { if (v.type.hasPointers() || // not tracking non-pointers v.storage_class & STC.lazy_) // lazy variables are actually pointers er.byValue(v); } } void visitThis(ThisExp e) { if (e.var) er.byValue(e.var); } void visitPtr(PtrExp e) { if (er.live && e.type.hasPointers()) escapeByValue(e.e1, er, retRefTransition); } void visitDotVar(DotVarExp e) { auto t = e.e1.type.toBasetype(); if (e.type.hasPointers() && (er.live || t.ty == Tstruct)) { escapeByValue(e.e1, er, retRefTransition); } } void visitDelegate(DelegateExp e) { Type t = e.e1.type.toBasetype(); if (t.ty == Tclass || t.ty == Tpointer) escapeByValue(e.e1, er, retRefTransition); else escapeByRef(e.e1, er, retRefTransition); er.byFunc(e.func, false); } void visitFunc(FuncExp e) { if (e.fd.tok == TOK.delegate_) er.byFunc(e.fd, false); } void visitTuple(TupleExp e) { assert(0); // should have been lowered by now } void visitArrayLiteral(ArrayLiteralExp e) { Type tb = e.type.toBasetype(); if (tb.ty == Tsarray || tb.ty == Tarray) { if (e.basis) escapeByValue(e.basis, er, retRefTransition); foreach (el; *e.elements) { if (el) escapeByValue(el, er, retRefTransition); } } } void visitStructLiteral(StructLiteralExp e) { if (e.elements) { foreach (ex; *e.elements) { if (ex) escapeByValue(ex, er, retRefTransition); } } } void visitNew(NewExp e) { Type tb = e.newtype.toBasetype(); if (tb.ty == Tstruct && !e.member && e.arguments) { foreach (ex; *e.arguments) { if (ex) escapeByValue(ex, er, retRefTransition); } } } void visitCast(CastExp e) { if (!e.type.hasPointers()) return; Type tb = e.type.toBasetype(); if (tb.ty == Tarray && e.e1.type.toBasetype().ty == Tsarray) { escapeByRef(e.e1, er, retRefTransition); } else escapeByValue(e.e1, er, retRefTransition); } void visitSlice(SliceExp e) { if (auto ve = e.e1.isVarExp()) { VarDeclaration v = ve.var.isVarDeclaration(); Type tb = e.type.toBasetype(); if (v) { if (tb.ty == Tsarray) return; if (v.isTypesafeVariadicArray) { er.byValue(v); return; } } } Type t1b = e.e1.type.toBasetype(); if (t1b.ty == Tsarray) { Type tb = e.type.toBasetype(); if (tb.ty != Tsarray) escapeByRef(e.e1, er, retRefTransition); } else escapeByValue(e.e1, er, retRefTransition); } void visitIndex(IndexExp e) { if (e.e1.type.toBasetype().ty == Tsarray || er.live && e.type.hasPointers()) { escapeByValue(e.e1, er, retRefTransition); } } void visitBin(BinExp e) { Type tb = e.type.toBasetype(); if (tb.ty == Tpointer) { escapeByValue(e.e1, er, retRefTransition); escapeByValue(e.e2, er, retRefTransition); } } void visitBinAssign(BinAssignExp e) { escapeByValue(e.e1, er, retRefTransition); } void visitAssign(AssignExp e) { escapeByValue(e.e1, er, retRefTransition); } void visitComma(CommaExp e) { escapeByValue(e.e2, er, retRefTransition); } void visitCond(CondExp e) { escapeByValue(e.e1, er, retRefTransition); escapeByValue(e.e2, er, retRefTransition); } void visitCall(CallExp e) { //printf("CallExp(): %s\n", e.toChars()); /* Check each argument that is * passed as 'return scope'. */ TypeFunction tf = e.calledFunctionType(); if (!tf || !e.type.hasPointers()) return; if (e.arguments && e.arguments.length) { /* j=1 if _arguments[] is first argument, * skip it because it is not passed by ref */ int j = tf.isDstyleVariadic(); for (size_t i = j; i < e.arguments.length; ++i) { Expression arg = (*e.arguments)[i]; size_t nparams = tf.parameterList.length; if (i - j < nparams && i >= j) { Parameter p = tf.parameterList[i - j]; const stc = tf.parameterStorageClass(null, p); ScopeRef psr = buildScopeRef(stc); if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) { if (tf.isref) { /* ignore `ref` on struct constructor return because * struct S { this(return scope int* q) { this.p = q; } int* p; } * is different from: * ref char* front(return scope char** q) { return *q; } * https://github.com/dlang/dmd/pull/14869 */ if (auto dve = e.e1.isDotVarExp()) if (auto fd = dve.var.isFuncDeclaration()) if (fd.isCtorDeclaration() && tf.next.toBasetype().isTypeStruct()) { escapeByValue(arg, er, retRefTransition); } } else escapeByValue(arg, er, true); } else if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) { if (tf.isref) { /* Treat: * ref P foo(return ref P p) * as: * p; */ escapeByValue(arg, er, retRefTransition); } else escapeByRef(arg, er, retRefTransition); } } } } // If 'this' is returned, check it too Type t1 = e.e1.type.toBasetype(); if (e.e1.op == EXP.dotVariable && t1.ty == Tfunction) { DotVarExp dve = e.e1.isDotVarExp(); FuncDeclaration fd = dve.var.isFuncDeclaration(); if (fd && fd.isThis()) { /* Calling a non-static member function dve.var, which is returning `this`, and with dve.e1 representing `this` */ /***************************** * Concoct storage class for member function's implicit `this` parameter. * Params: * fd = member function * Returns: * storage class for fd's `this` */ StorageClass getThisStorageClass(FuncDeclaration fd) { StorageClass stc; auto tf = fd.type.toBasetype().isTypeFunction(); if (tf.isreturn) stc |= STC.return_; if (tf.isreturnscope) stc |= STC.returnScope | STC.scope_; auto ad = fd.isThis(); if (ad.isClassDeclaration() || tf.isScopeQual) stc |= STC.scope_; if (ad.isStructDeclaration()) stc |= STC.ref_; // `this` for a struct member function is passed by `ref` return stc; } const psr = buildScopeRef(getThisStorageClass(fd)); if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) { if (!tf.isref || tf.isctor) escapeByValue(dve.e1, er, retRefTransition); } else if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) { if (tf.isref) { /* Treat calling: * struct S { ref S foo() return; } * as: * this; */ escapeByValue(dve.e1, er, retRefTransition); } else escapeByRef(dve.e1, er, psr == ScopeRef.ReturnRef_Scope); } } // If it's also a nested function that is 'return scope' if (fd && fd.isNested()) { if (tf.isreturn && tf.isScopeQual) { if (tf.isreturnscope) er.byFunc(fd, true); else er.byExp(e, false); } } } /* If returning the result of a delegate call, the .ptr * field of the delegate must be checked. */ if (t1.isTypeDelegate()) { if (tf.isreturn) escapeByValue(e.e1, er, retRefTransition); } /* If it's a nested function that is 'return scope' */ if (auto ve = e.e1.isVarExp()) { FuncDeclaration fd = ve.var.isFuncDeclaration(); if (fd && fd.isNested()) { if (tf.isreturn && tf.isScopeQual) { if (tf.isreturnscope) er.byFunc(fd, true); else er.byExp(e, false); } } } } switch (e.op) { case EXP.address: return visitAddr(e.isAddrExp()); case EXP.symbolOffset: return visitSymOff(e.isSymOffExp()); case EXP.variable: return visitVar(e.isVarExp()); case EXP.this_: return visitThis(e.isThisExp()); case EXP.star: return visitPtr(e.isPtrExp()); case EXP.dotVariable: return visitDotVar(e.isDotVarExp()); case EXP.delegate_: return visitDelegate(e.isDelegateExp()); case EXP.function_: return visitFunc(e.isFuncExp()); case EXP.tuple: return visitTuple(e.isTupleExp()); case EXP.arrayLiteral: return visitArrayLiteral(e.isArrayLiteralExp()); case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp()); case EXP.new_: return visitNew(e.isNewExp()); case EXP.cast_: return visitCast(e.isCastExp()); case EXP.slice: return visitSlice(e.isSliceExp()); case EXP.index: return visitIndex(e.isIndexExp()); case EXP.blit: return visitAssign(e.isBlitExp()); case EXP.construct: return visitAssign(e.isConstructExp()); case EXP.assign: return visitAssign(e.isAssignExp()); case EXP.comma: return visitComma(e.isCommaExp()); case EXP.question: return visitCond(e.isCondExp()); case EXP.call: return visitCall(e.isCallExp()); default: if (auto b = e.isBinExp()) return visitBin(b); if (auto ba = e.isBinAssignExp()) return visitBinAssign(ba); return visit(e); } } /**************************************** * e is an expression to be returned by 'ref'. * Walk e to determine which variables are possibly being * returned by ref, such as: * ref int function(int i) { return i; } * If e is a form of *p, determine which variables have content * which is being returned as ref, such as: * ref int function(int* p) { return *p; } * Multiple variables can be inserted, because of expressions like this: * ref int function(bool b, int i, int* p) { return b ? i : *p; } * * No side effects. * * Params: * e = expression to be returned by 'ref' * er = where to place collected data * retRefTransition = if `e` is returned through a `return (ref) scope` function call */ private void escapeByRef(Expression e, ref scope EscapeByResults er, bool retRefTransition = false) { //printf("[%s] escapeByRef, e: %s, retRefTransition: %d\n", e.loc.toChars(), e.toChars(), retRefTransition); void visit(Expression e) { } void visitVar(VarExp e) { auto v = e.var.isVarDeclaration(); if (v) { if (v.storage_class & STC.ref_ && v.storage_class & (STC.foreach_ | STC.temp) && v._init) { /* If compiler generated ref temporary * (ref v = ex; ex) * look at the initializer instead */ if (ExpInitializer ez = v._init.isExpInitializer()) { if (auto ce = ez.exp.isConstructExp()) escapeByRef(ce.e2, er, retRefTransition); else escapeByRef(ez.exp, er, retRefTransition); } } else er.byRef(v, retRefTransition); } } void visitThis(ThisExp e) { if (e.var && e.var.toParent2().isFuncDeclaration().hasDualContext()) escapeByValue(e, er, retRefTransition); else if (e.var) er.byRef(e.var, retRefTransition); } void visitPtr(PtrExp e) { escapeByValue(e.e1, er, retRefTransition); } void visitIndex(IndexExp e) { Type tb = e.e1.type.toBasetype(); if (auto ve = e.e1.isVarExp()) { VarDeclaration v = ve.var.isVarDeclaration(); if (v && v.isTypesafeVariadicArray) { er.byRef(v, retRefTransition); return; } } if (tb.ty == Tsarray) { escapeByRef(e.e1, er, retRefTransition); } else if (tb.ty == Tarray) { escapeByValue(e.e1, er, retRefTransition); } } void visitStructLiteral(StructLiteralExp e) { if (e.elements) { foreach (ex; *e.elements) { if (ex) escapeByRef(ex, er, retRefTransition); } } er.byExp(e, retRefTransition); } void visitDotVar(DotVarExp e) { Type t1b = e.e1.type.toBasetype(); if (t1b.ty == Tclass) escapeByValue(e.e1, er, retRefTransition); else escapeByRef(e.e1, er, retRefTransition); } void visitBinAssign(BinAssignExp e) { escapeByRef(e.e1, er, retRefTransition); } void visitAssign(AssignExp e) { escapeByRef(e.e1, er, retRefTransition); } void visitComma(CommaExp e) { escapeByRef(e.e2, er, retRefTransition); } void visitCond(CondExp e) { escapeByRef(e.e1, er, retRefTransition); escapeByRef(e.e2, er, retRefTransition); } void visitCall(CallExp e) { //printf("escapeByRef.CallExp(): %s\n", e.toChars()); /* If the function returns by ref, check each argument that is * passed as 'return ref'. */ TypeFunction tf = e.calledFunctionType(); if (!tf) return; if (tf.isref) { if (e.arguments && e.arguments.length) { /* j=1 if _arguments[] is first argument, * skip it because it is not passed by ref */ int j = tf.isDstyleVariadic(); for (size_t i = j; i < e.arguments.length; ++i) { Expression arg = (*e.arguments)[i]; size_t nparams = tf.parameterList.length; if (i - j < nparams && i >= j) { Parameter p = tf.parameterList[i - j]; const stc = tf.parameterStorageClass(null, p); ScopeRef psr = buildScopeRef(stc); if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) escapeByRef(arg, er, retRefTransition); else if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) { if (auto de = arg.isDelegateExp()) { if (de.func.isNested()) er.byExp(de, false); } else escapeByValue(arg, er, retRefTransition); } } } } // If 'this' is returned by ref, check it too Type t1 = e.e1.type.toBasetype(); if (e.e1.op == EXP.dotVariable && t1.ty == Tfunction) { DotVarExp dve = e.e1.isDotVarExp(); // https://issues.dlang.org/show_bug.cgi?id=20149#c10 if (dve.var.isCtorDeclaration()) { er.byExp(e, false); return; } StorageClass stc = dve.var.storage_class & (STC.return_ | STC.scope_ | STC.ref_); if (tf.isreturn) stc |= STC.return_; if (tf.isref) stc |= STC.ref_; if (tf.isScopeQual) stc |= STC.scope_; if (tf.isreturnscope) stc |= STC.returnScope; const psr = buildScopeRef(stc); if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) escapeByRef(dve.e1, er, psr == ScopeRef.ReturnRef_Scope); else if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) escapeByValue(dve.e1, er, retRefTransition); // If it's also a nested function that is 'return ref' if (FuncDeclaration fd = dve.var.isFuncDeclaration()) { if (fd.isNested() && tf.isreturn) { er.byExp(e, false); } } } // If it's a delegate, check it too if (e.e1.op == EXP.variable && t1.ty == Tdelegate) { escapeByValue(e.e1, er, retRefTransition); } /* If it's a nested function that is 'return ref' */ if (auto ve = e.e1.isVarExp()) { FuncDeclaration fd = ve.var.isFuncDeclaration(); if (fd && fd.isNested()) { if (tf.isreturn) er.byExp(e, false); } } } else er.byExp(e, retRefTransition); } switch (e.op) { case EXP.variable: return visitVar(e.isVarExp()); case EXP.this_: return visitThis(e.isThisExp()); case EXP.star: return visitPtr(e.isPtrExp()); case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp()); case EXP.dotVariable: return visitDotVar(e.isDotVarExp()); case EXP.index: return visitIndex(e.isIndexExp()); case EXP.blit: return visitAssign(e.isBlitExp()); case EXP.construct: return visitAssign(e.isConstructExp()); case EXP.assign: return visitAssign(e.isAssignExp()); case EXP.comma: return visitComma(e.isCommaExp()); case EXP.question: return visitCond(e.isCondExp()); case EXP.call: return visitCall(e.isCallExp()); default: if (auto ba = e.isBinAssignExp()) return visitBinAssign(ba); return visit(e); } } /************************************ * Aggregate the data collected by the escapeBy??() functions. */ public struct EscapeByResults { /* * retRefTransition = Whether the variable / expression went through a `return (ref) scope` function call * * This is needed for the dip1000 by default transition, since the rules for * disambiguating `return scope ref` have changed. Therefore, functions in legacy code * can be mistakenly treated as `return ref` making the compiler believe stack variables * are being escaped, which is an error even in `@system` code. By keeping track of this * information, variables escaped through `return ref` can be treated as a deprecation instead * of error, see test/fail_compilation/dip1000_deprecation.d * * Additionally, return scope can be inferred wrongly instead of scope, in which * case the code could give false positives even without @safe or dip1000: * https://issues.dlang.org/show_bug.cgi?id=23657 */ /// called on variables being returned by ref / address void delegate(VarDeclaration, bool retRefTransition) byRef; /// called on variables with values containing pointers void delegate(VarDeclaration) byValue; /// called on nested functions that are turned into delegates /// When `called` is true, it means the delegate escapes variables /// from the closure through a call to it, while `false` means the /// delegate itself escapes. void delegate(FuncDeclaration, bool called) byFunc; /// called when expression temporaries are being returned by ref / address void delegate(Expression, bool retRefTransition) byExp; /// if @live semantics apply, i.e. expressions `p`, `*p`, `**p`, etc., all return `p`. bool live = false; } /************************* * Find all variables accessed by this delegate that are * in functions enclosing it. * Params: * fd = function * vars = array to append found variables to */ public void findAllOuterAccessedVariables(FuncDeclaration fd, VarDeclarations* vars) { //printf("findAllOuterAccessedVariables(fd: %s)\n", fd.toChars()); for (auto p = fd.parent; p; p = p.parent) { auto fdp = p.isFuncDeclaration(); if (!fdp) continue; foreach (v; fdp.closureVars) { foreach (const fdv; v.nestedrefs) { if (fdv == fd) { //printf("accessed: %s, type %s\n", v.toChars(), v.type.toChars()); vars.push(v); } } } } } /*********************************** * Turn off `maybeScope` for variable `v`. * * This exists in order to find where `maybeScope` is getting turned off. * Params: * v = variable * o = reason for it being turned off: * - `Expression` such as `throw e` or `&e` * - `VarDeclaration` of a non-scope parameter it was assigned to * - `null` for no reason */ private void notMaybeScope(VarDeclaration v, RootObject o) { if (v.maybeScope) { v.maybeScope = false; if (o && v.isParameter()) EscapeState.scopeInferFailure[v.sequenceNumber] = o; } } /*********************************** * Turn off `maybeScope` for variable `v` if it's not a parameter. * * This is for compatibility with the old system with both `STC.maybescope` and `VarDeclaration.doNotInferScope`, * which is now just `VarDeclaration.maybeScope`. * This function should probably be removed in future refactors. * * Params: * v = variable * o = reason for it being turned off */ private void doNotInferScope(VarDeclaration v, RootObject o) { if (!v.isParameter) notMaybeScope(v, o); } /*********************************** * After semantic analysis of the function body, * try to infer `scope` / `return` on the parameters * * Params: * funcdecl = function declaration that was analyzed * f = final function type. `funcdecl.type` started as the 'premature type' before attribute * inference, then its inferred attributes are copied over to final type `f` */ public void finishScopeParamInference(FuncDeclaration funcdecl, ref TypeFunction f) { if (funcdecl.returnInprocess) { funcdecl.returnInprocess = false; if (funcdecl.storage_class & STC.return_) { if (funcdecl.type == f) f = cast(TypeFunction)f.copy(); f.isreturn = true; f.isreturnscope = cast(bool) (funcdecl.storage_class & STC.returnScope); if (funcdecl.storage_class & STC.returninferred) f.isreturninferred = true; } } if (!funcdecl.inferScope) return; funcdecl.inferScope = false; // Eliminate maybescope's { // Create and fill array[] with maybe candidates from the `this` and the parameters VarDeclaration[10] tmp = void; size_t dim = (funcdecl.vthis !is null) + (funcdecl.parameters ? funcdecl.parameters.length : 0); import dmd.common.smallbuffer : SmallBuffer; auto sb = SmallBuffer!VarDeclaration(dim, tmp[]); VarDeclaration[] array = sb[]; size_t n = 0; if (funcdecl.vthis) array[n++] = funcdecl.vthis; if (funcdecl.parameters) { foreach (v; *funcdecl.parameters) { array[n++] = v; } } eliminateMaybeScopes(array[0 .. n]); } // Infer STC.scope_ if (funcdecl.parameters && !funcdecl.errors) { assert(f.parameterList.length == funcdecl.parameters.length); foreach (u, p; f.parameterList) { auto v = (*funcdecl.parameters)[u]; if (!v.isScope() && v.type.hasPointers() && inferScope(v)) { //printf("Inferring scope for %s\n", v.toChars()); p.storageClass |= STC.scope_ | STC.scopeinferred; } } } if (funcdecl.vthis) { inferScope(funcdecl.vthis); f.isScopeQual = funcdecl.vthis.isScope(); f.isscopeinferred = !!(funcdecl.vthis.storage_class & STC.scopeinferred); } } /********************************************** * Have some variables that are maybescopes that were * assigned values from other maybescope variables. * Now that semantic analysis of the function is * complete, we can finalize this by turning off * maybescope for array elements that cannot be scope. * * $(TABLE2 Scope Table, * $(THEAD `va`, `v`, =>, `va` , `v` ) * $(TROW maybe, maybe, =>, scope, scope) * $(TROW scope, scope, =>, scope, scope) * $(TROW scope, maybe, =>, scope, scope) * $(TROW maybe, scope, =>, scope, scope) * $(TROW - , - , =>, - , - ) * $(TROW - , maybe, =>, - , - ) * $(TROW - , scope, =>, error, error) * $(TROW maybe, - , =>, scope, - ) * $(TROW scope, - , =>, scope, - ) * ) * Params: * array = array of variables that were assigned to from maybescope variables */ private void eliminateMaybeScopes(VarDeclaration[] array) { enum log = false; if (log) printf("eliminateMaybeScopes()\n"); bool changes; do { changes = false; foreach (va; array) { if (log) printf(" va = %s\n", va.toChars()); if (!(va.maybeScope || va.isScope())) { if (va.maybes) { foreach (v; *va.maybes) { if (log) printf(" v = %s\n", v.toChars()); if (v.maybeScope) { // v cannot be scope since it is assigned to a non-scope va notMaybeScope(v, va); if (!v.isReference()) v.storage_class &= ~(STC.return_ | STC.returninferred); changes = true; } } } } } } while (changes); } /************************************************ * Is type a reference to a mutable value? * * This is used to determine if an argument that does not have a corresponding * Parameter, i.e. a variadic argument, is a pointer to mutable data. * Params: * t = type of the argument * Returns: * true if it's a pointer (or reference) to mutable data */ private bool isReferenceToMutable(Type t) { t = t.baseElemOf(); if (!t.isMutable() || !t.hasPointers()) return false; switch (t.ty) { case Tpointer: if (t.nextOf().isTypeFunction()) break; goto case; case Tarray: case Taarray: case Tdelegate: if (t.nextOf().isMutable()) return true; break; case Tclass: return true; // even if the class fields are not mutable case Tstruct: // Have to look at each field foreach (VarDeclaration v; t.isTypeStruct().sym.fields) { if (v.storage_class & STC.ref_) { if (v.type.isMutable()) return true; } else if (v.type.isReferenceToMutable()) return true; } break; case Tnull: return false; default: assert(0); } return false; } /**************************************** * Is parameter a reference to a mutable value? * * This is used if an argument has a corresponding Parameter. * The argument type is necessary if the Parameter is inout. * Params: * p = Parameter to check * t = type of corresponding argument * Returns: * true if it's a pointer (or reference) to mutable data */ private bool isReferenceToMutable(Parameter p, Type t) { if (p.isReference()) { if (p.type.isConst() || p.type.isImmutable()) return false; if (p.type.isWild()) { return t.isMutable(); } return p.type.isMutable(); } return isReferenceToMutable(p.type); } /// When checking lifetime for assignment `va=v`, the way `va` encloses `v` private enum EnclosedBy { none = 0, refVar, // `va` is a `ref` variable, which may link to a global variable global, // `va` is a global variable returnScope, // `va` is a scope variable that may be returned longerScope, // `va` is another scope variable declared earlier than `v` } /********************************** * Determine if `va` has a lifetime that lasts past * the destruction of `v` * Params: * va = variable assigned to * v = variable being assigned * Returns: * The way `va` encloses `v` (if any) */ private EnclosedBy enclosesLifetimeOf(VarDeclaration va, VarDeclaration v) { if (!va) return EnclosedBy.none; if (va.isDataseg()) return EnclosedBy.global; if (va.isScope() && va.isReturn() && !v.isReturn()) return EnclosedBy.returnScope; if (va.isReference() && va.isParameter()) return EnclosedBy.refVar; assert(va.sequenceNumber != va.sequenceNumber.init); assert(v.sequenceNumber != v.sequenceNumber.init); if (va.sequenceNumber < v.sequenceNumber) return EnclosedBy.longerScope; return EnclosedBy.none; } /*************************************** * Add variable `v` to maybes[] * * When a maybescope variable `v` is assigned to a maybescope variable `va`, * we cannot determine if `this` is actually scope until the semantic * analysis for the function is completed. Thus, we save the data * until then. * Params: * v = a variable with `maybeScope == true` that was assigned to `this` */ private void addMaybe(VarDeclaration va, VarDeclaration v) { //printf("add %s to %s's list of dependencies\n", v.toChars(), toChars()); if (!va.maybes) va.maybes = new VarDeclarations(); va.maybes.push(v); } // `setUnsafePreview` partially evaluated for dip1000 public bool setUnsafeDIP1000(ref Scope sc, bool gag, Loc loc, const(char)* msg, RootObject arg0 = null, RootObject arg1 = null, RootObject arg2 = null) { return setUnsafePreview(&sc, sc.useDIP1000, gag, loc, msg, arg0, arg1, arg2); } /*************************************** * Check that taking the address of `v` is `@safe` * * It's not possible to take the address of a scope variable, because `scope` only applies * to the top level indirection. * * Params: * v = variable that a reference is created * e = expression that takes the referene * sc = used to obtain function / deprecated status * gag = don't print errors * Returns: * true if taking the address of `v` is problematic because of the lack of transitive `scope` */ private bool checkScopeVarAddr(VarDeclaration v, Expression e, ref Scope sc, bool gag) { if (v.storage_class & STC.temp) return false; if (!v.isScope()) { notMaybeScope(v, e); return false; } if (!e.type) return false; // When the type after dereferencing has no pointers, it's okay. // Comes up when escaping `&someStruct.intMember` of a `scope` struct: // scope does not apply to the `int` Type t = e.type.baseElemOf(); if ((t.ty == Tarray || t.ty == Tpointer) && !t.nextOf().toBasetype().hasPointers()) return false; // take address of `scope` variable not allowed, requires transitive scope return sc.setUnsafeDIP1000(gag, e.loc, "cannot take address of `scope` variable `%s` since `scope` applies to first indirection only", v); } /**************************** * Determine if `v` is a typesafe variadic array, which is implicitly `scope` * Params: * v = variable to check * Returns: * true if `v` is a variadic parameter */ private bool isTypesafeVariadicArray(VarDeclaration v) { if (v.storage_class & STC.variadic) { Type tb = v.type.toBasetype(); if (tb.ty == Tarray || tb.ty == Tsarray) return true; } return false; } ldc-1.40.0-src/dmd/ctorflow.d0000644000000000000000000001421314727557031014405 0ustar rootroot/** * Manage flow analysis for constructors. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/ctorflow.d, _ctorflow.d) * Documentation: https://dlang.org/phobos/dmd_ctorflow.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/ctorflow.d */ module dmd.ctorflow; import core.stdc.stdio; import dmd.root.rmem; import dmd.location; enum CSX : ushort { none = 0, this_ctor = 0x01, /// called this() super_ctor = 0x02, /// called super() label = 0x04, /// seen a label return_ = 0x08, /// seen a return statement any_ctor = 0x10, /// either this() or super() was called halt = 0x20, /// assert(0) } /// Individual field in the Ctor with information about its callees and location. struct FieldInit { CSX csx; /// information about the field's callees Loc loc; /// location of the field initialization } /*********** * Primitive flow analysis for constructors */ struct CtorFlow { CSX callSuper; /// state of calling other constructors FieldInit[] fieldinit; /// state of field initializations void allocFieldinit(size_t dim) { fieldinit = (cast(FieldInit*)mem.xcalloc(FieldInit.sizeof, dim))[0 .. dim]; } void freeFieldinit() { if (fieldinit.ptr) mem.xfree(fieldinit.ptr); fieldinit = null; } /*********************** * Create a deep copy of `this` * Returns: * a copy */ CtorFlow clone() { return CtorFlow(callSuper, fieldinit.arraydup); } /********************************** * Set CSX bits in flow analysis state * Params: * csx = bits to set */ void orCSX(CSX csx) nothrow pure @safe { callSuper |= csx; foreach (ref u; fieldinit) u.csx |= csx; } /****************************** * OR CSX bits to `this` * Params: * ctorflow = bits to OR in */ void OR(const ref CtorFlow ctorflow) pure nothrow @safe { callSuper |= ctorflow.callSuper; if (fieldinit.length && ctorflow.fieldinit.length) { assert(fieldinit.length == ctorflow.fieldinit.length); foreach (i, u; ctorflow.fieldinit) { auto fi = &fieldinit[i]; fi.csx |= u.csx; if (fi.loc is Loc.init) fi.loc = u.loc; } } } } /**************************************** * Merge `b` flow analysis results into `a`. * Params: * a = the path to merge `b` into * b = the other path * Returns: * false means one of the paths skips construction */ bool mergeCallSuper(ref CSX a, const CSX b) pure nothrow @safe { // This does a primitive flow analysis to support the restrictions // regarding when and how constructors can appear. // It merges the results of two paths. // The two paths are `a` and `b`; the result is merged into `a`. if (b == a) return true; // Have ALL branches called a constructor? const aAll = (a & (CSX.this_ctor | CSX.super_ctor)) != 0; const bAll = (b & (CSX.this_ctor | CSX.super_ctor)) != 0; // Have ANY branches called a constructor? const aAny = (a & CSX.any_ctor) != 0; const bAny = (b & CSX.any_ctor) != 0; // Have any branches returned? const aRet = (a & CSX.return_) != 0; const bRet = (b & CSX.return_) != 0; // Have any branches halted? const aHalt = (a & CSX.halt) != 0; const bHalt = (b & CSX.halt) != 0; if (aHalt && bHalt) { a = CSX.halt; } else if ((!bHalt && bRet && !bAny && aAny) || (!aHalt && aRet && !aAny && bAny)) { // If one has returned without a constructor call, there must not // be ctor calls in the other. return false; } else if (bHalt || bRet && bAll) { // If one branch has called a ctor and then exited, anything the // other branch has done is OK (except returning without a // ctor call, but we already checked that). a |= b & (CSX.any_ctor | CSX.label); } else if (aHalt || aRet && aAll) { a = cast(CSX)(b | (a & (CSX.any_ctor | CSX.label))); } else if (aAll != bAll) // both branches must have called ctors, or both not return false; else { // If one returned without a ctor, remember that if (bRet && !bAny) a |= CSX.return_; a |= b & (CSX.any_ctor | CSX.label); } return true; } /**************************************** * Merge `b` flow analysis results into `a`. * Params: * a = the path to merge `b` into * b = the other path * Returns: * false means either `a` or `b` skips initialization */ bool mergeFieldInit(ref CSX a, const CSX b) pure nothrow @safe { if (b == a) return true; // Have any branches returned? const aRet = (a & CSX.return_) != 0; const bRet = (b & CSX.return_) != 0; // Have any branches halted? const aHalt = (a & CSX.halt) != 0; const bHalt = (b & CSX.halt) != 0; if (aHalt && bHalt) { a = CSX.halt; return true; } // The logic here is to prefer the branch that neither halts nor returns. bool ok; if (!bHalt && bRet) { // Branch b returns, no merging required. ok = (b & CSX.this_ctor); } else if (!aHalt && aRet) { // Branch a returns, but b doesn't, b takes precedence. ok = (a & CSX.this_ctor); a = b; } else if (bHalt) { // Branch b halts, no merging required. ok = (a & CSX.this_ctor); } else if (aHalt) { // Branch a halts, but b doesn't, b takes precedence. ok = (b & CSX.this_ctor); a = b; } else { // Neither branch returns nor halts, merge flags. ok = !((a ^ b) & CSX.this_ctor); a |= b; } return ok; } ldc-1.40.0-src/dmd/attrib.d0000644000000000000000000011342114727557031014034 0ustar rootroot/** * Defines declarations of various attributes. * * The term 'attribute' refers to things that can apply to a larger scope than a single declaration. * Among them are: * - Alignment (`align(8)`) * - User defined attributes (`@UDA`) * - Function Attributes (`@safe`) * - Storage classes (`static`, `__gshared`) * - Mixin declarations (`mixin("int x;")`) * - Conditional compilation (`static if`, `static foreach`) * - Linkage (`extern(C)`) * - Anonymous structs / unions * - Protection (`private`, `public`) * - Deprecated declarations (`@deprecated`) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/attrib.d, _attrib.d) * Documentation: https://dlang.org/phobos/dmd_attrib.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/attrib.d */ module dmd.attrib; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; import dmd.cond; import dmd.declaration; import dmd.dmodule; import dmd.dscope; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.errors; import dmd.expression; import dmd.func; import dmd.globals; import dmd.hdrgen : visibilityToBuffer; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.objc; // for objc.addSymbols import dmd.common.outbuffer; import dmd.root.array; // for each import dmd.visitor; /*********************************************************** * Abstract attribute applied to Dsymbol's used as a common * ancestor for storage classes (StorageClassDeclaration), * linkage (LinkageDeclaration) and others. */ extern (C++) abstract class AttribDeclaration : Dsymbol { Dsymbols* decl; /// Dsymbol's affected by this AttribDeclaration extern (D) this(Dsymbols* decl) @safe { this.decl = decl; } extern (D) this(const ref Loc loc, Dsymbols* decl) @safe { super(loc, null); this.decl = decl; } extern (D) this(const ref Loc loc, Identifier ident, Dsymbols* decl) @safe { super(loc, ident); this.decl = decl; } Dsymbols* include(Scope* sc) { if (errors) return null; return decl; } /**************************************** * Create a new scope if one or more given attributes * are different from the sc's. * If the returned scope != sc, the caller should pop * the scope after it used. */ extern (D) static Scope* createNewScope(Scope* sc, StorageClass stc, LINK linkage, CPPMANGLE cppmangle, Visibility visibility, int explicitVisibility, AlignDeclaration aligndecl, PragmaDeclaration inlining) { Scope* sc2 = sc; if (stc != sc.stc || linkage != sc.linkage || cppmangle != sc.cppmangle || explicitVisibility != sc.explicitVisibility || visibility != sc.visibility || aligndecl !is sc.aligndecl || inlining != sc.inlining) { // create new one for changes sc2 = sc.copy(); sc2.stc = stc; sc2.linkage = linkage; sc2.cppmangle = cppmangle; sc2.visibility = visibility; sc2.explicitVisibility = explicitVisibility; sc2.aligndecl = aligndecl; sc2.inlining = inlining; } return sc2; } /**************************************** * A hook point to supply scope for members. * addMember, setScope, importAll, semantic, semantic2 and semantic3 will use this. */ Scope* newScope(Scope* sc) { return sc; } override void addComment(const(char)* comment) { //printf("AttribDeclaration::addComment %s\n", comment); if (comment) { include(null).foreachDsymbol( s => s.addComment(comment) ); } } override const(char)* kind() const { return "attribute"; } override bool oneMember(out Dsymbol ps, Identifier ident) { Dsymbols* d = include(null); return Dsymbol.oneMembers(d, ps, ident); } override final bool hasPointers() { return include(null).foreachDsymbol( (s) { return s.hasPointers(); } ) != 0; } override final bool hasStaticCtorOrDtor() { return include(null).foreachDsymbol( (s) { return s.hasStaticCtorOrDtor(); } ) != 0; } override final void checkCtorConstInit() { include(null).foreachDsymbol( s => s.checkCtorConstInit() ); } /**************************************** */ override final void addObjcSymbols(ClassDeclarations* classes, ClassDeclarations* categories) { objc.addSymbols(this, classes, categories); } override inout(AttribDeclaration) isAttribDeclaration() inout pure @safe { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Storage classes applied to Dsymbols, e.g. `const int i;` * * */ extern (C++) class StorageClassDeclaration : AttribDeclaration { StorageClass stc; extern (D) this(StorageClass stc, Dsymbols* decl) @safe { super(decl); this.stc = stc; } extern (D) this(const ref Loc loc, StorageClass stc, Dsymbols* decl) @safe { super(loc, decl); this.stc = stc; } override StorageClassDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new StorageClassDeclaration(stc, Dsymbol.arraySyntaxCopy(decl)); } override Scope* newScope(Scope* sc) { StorageClass scstc = sc.stc; /* These sets of storage classes are mutually exclusive, * so choose the innermost or most recent one. */ if (stc & (STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.manifest)) scstc &= ~(STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.manifest); if (stc & (STC.auto_ | STC.scope_ | STC.static_ | STC.manifest | STC.gshared)) scstc &= ~(STC.auto_ | STC.scope_ | STC.static_ | STC.manifest | STC.gshared); if (stc & (STC.const_ | STC.immutable_ | STC.manifest)) scstc &= ~(STC.const_ | STC.immutable_ | STC.manifest); if (stc & (STC.gshared | STC.shared_)) scstc &= ~(STC.gshared | STC.shared_); if (stc & (STC.safe | STC.trusted | STC.system)) scstc &= ~(STC.safe | STC.trusted | STC.system); scstc |= stc; //printf("scstc = x%llx\n", scstc); return createNewScope(sc, scstc, sc.linkage, sc.cppmangle, sc.visibility, sc.explicitVisibility, sc.aligndecl, sc.inlining); } override final bool oneMember(out Dsymbol ps, Identifier ident) { bool t = Dsymbol.oneMembers(decl, ps, ident); if (t && ps) { /* This is to deal with the following case: * struct Tick { * template to(T) { const T to() { ... } } * } * For eponymous function templates, the 'const' needs to get attached to 'to' * before the semantic analysis of 'to', so that template overloading based on the * 'this' pointer can be successful. */ FuncDeclaration fd = ps.isFuncDeclaration(); if (fd) { /* Use storage_class2 instead of storage_class otherwise when we do .di generation * we'll wind up with 'const const' rather than 'const'. */ /* Don't think we need to worry about mutually exclusive storage classes here */ fd.storage_class2 |= stc; } } return t; } override inout(StorageClassDeclaration) isStorageClassDeclaration() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Deprecation with an additional message applied to Dsymbols, * e.g. `deprecated("Superseeded by foo") int bar;`. * (Note that `deprecated int bar;` is currently represented as a * StorageClassDeclaration with STC.deprecated_) * * `deprecated() ` */ extern (C++) final class DeprecatedDeclaration : StorageClassDeclaration { Expression msg; /// deprecation message const(char)* msgstr; /// cached string representation of msg extern (D) this(Expression msg, Dsymbols* decl) @safe { super(STC.deprecated_, decl); this.msg = msg; } override DeprecatedDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new DeprecatedDeclaration(msg.syntaxCopy(), Dsymbol.arraySyntaxCopy(decl)); } /** * Provides a new scope with `STC.deprecated_` and `Scope.depdecl` set * * Calls `StorageClassDeclaration.newScope` (as it must be called or copied * in any function overriding `newScope`), then set the `Scope`'s depdecl. * * Returns: * Always a new scope, to use for this `DeprecatedDeclaration`'s members. */ override Scope* newScope(Scope* sc) { auto scx = super.newScope(sc); // The enclosing scope is deprecated as well if (scx == sc) scx = sc.push(); scx.depdecl = this; return scx; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Linkage attribute applied to Dsymbols, e.g. * `extern(C) void foo()`. * * `extern() ` */ extern (C++) final class LinkDeclaration : AttribDeclaration { LINK linkage; /// either explicitly set or `default_` extern (D) this(const ref Loc loc, LINK linkage, Dsymbols* decl) @safe { super(loc, null, decl); //printf("LinkDeclaration(linkage = %d, decl = %p)\n", linkage, decl); this.linkage = linkage; } static LinkDeclaration create(const ref Loc loc, LINK p, Dsymbols* decl) @safe { return new LinkDeclaration(loc, p, decl); } override LinkDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new LinkDeclaration(loc, linkage, Dsymbol.arraySyntaxCopy(decl)); } override Scope* newScope(Scope* sc) { return createNewScope(sc, sc.stc, this.linkage, sc.cppmangle, sc.visibility, sc.explicitVisibility, sc.aligndecl, sc.inlining); } override const(char)* toChars() const { return toString().ptr; } extern(D) override const(char)[] toString() const { return "extern ()"; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Attribute declaring whether an external aggregate should be mangled as * a struct or class in C++, e.g. `extern(C++, struct) class C { ... }`. * This is required for correct name mangling on MSVC targets, * see cppmanglewin.d for details. * * `extern(C++, ) ` */ extern (C++) final class CPPMangleDeclaration : AttribDeclaration { CPPMANGLE cppmangle; extern (D) this(const ref Loc loc, CPPMANGLE cppmangle, Dsymbols* decl) @safe { super(loc, null, decl); //printf("CPPMangleDeclaration(cppmangle = %d, decl = %p)\n", cppmangle, decl); this.cppmangle = cppmangle; } override CPPMangleDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new CPPMangleDeclaration(loc, cppmangle, Dsymbol.arraySyntaxCopy(decl)); } override Scope* newScope(Scope* sc) { return createNewScope(sc, sc.stc, LINK.cpp, cppmangle, sc.visibility, sc.explicitVisibility, sc.aligndecl, sc.inlining); } override const(char)* toChars() const { return toString().ptr; } extern(D) override const(char)[] toString() const { return "extern ()"; } override void accept(Visitor v) { v.visit(this); } } /** * A node to represent an `extern(C++)` namespace attribute * * There are two ways to declarate a symbol as member of a namespace: * `Nspace` and `CPPNamespaceDeclaration`. * The former creates a scope for the symbol, and inject them in the * parent scope at the same time. * The later, this class, has no semantic implications and is only * used for mangling. * Additionally, this class allows one to use reserved identifiers * (D keywords) in the namespace. * * A `CPPNamespaceDeclaration` can be created from an `Identifier` * (already resolved) or from an `Expression`, which is CTFE-ed * and can be either a `TupleExp`, in which can additional * `CPPNamespaceDeclaration` nodes are created, or a `StringExp`. * * Note that this class, like `Nspace`, matches only one identifier * part of a namespace. For the namespace `"foo::bar"`, * the will be a `CPPNamespaceDeclaration` with its `ident` * set to `"bar"`, and its `namespace` field pointing to another * `CPPNamespaceDeclaration` with its `ident` set to `"foo"`. */ extern (C++) final class CPPNamespaceDeclaration : AttribDeclaration { /// CTFE-able expression, resolving to `TupleExp` or `StringExp` Expression exp; extern (D) this(const ref Loc loc, Identifier ident, Dsymbols* decl) @safe { super(loc, ident, decl); } extern (D) this(const ref Loc loc, Expression exp, Dsymbols* decl) @safe { super(loc, null, decl); this.exp = exp; } extern (D) this(const ref Loc loc, Identifier ident, Expression exp, Dsymbols* decl, CPPNamespaceDeclaration parent) @safe { super(loc, ident, decl); this.exp = exp; this.cppnamespace = parent; } override CPPNamespaceDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new CPPNamespaceDeclaration( this.loc, this.ident, this.exp, Dsymbol.arraySyntaxCopy(this.decl), this.cppnamespace); } /** * Returns: * A copy of the parent scope, with `this` as `namespace` and C++ linkage */ override Scope* newScope(Scope* sc) { auto scx = sc.copy(); scx.linkage = LINK.cpp; scx.namespace = this; return scx; } override const(char)* toChars() const { return toString().ptr; } extern(D) override const(char)[] toString() const { return "extern (C++, `namespace`)"; } override void accept(Visitor v) { v.visit(this); } override inout(CPPNamespaceDeclaration) isCPPNamespaceDeclaration() inout { return this; } } /*********************************************************** * Visibility declaration for Dsymbols, e.g. `public int i;` * * ` ` or * `package() ` if `pkg_identifiers !is null` */ extern (C++) final class VisibilityDeclaration : AttribDeclaration { Visibility visibility; /// the visibility Identifier[] pkg_identifiers; /// identifiers for `package(foo.bar)` or null /** * Params: * loc = source location of attribute token * visibility = visibility attribute data * decl = declarations which are affected by this visibility attribute */ extern (D) this(const ref Loc loc, Visibility visibility, Dsymbols* decl) @safe { super(loc, null, decl); this.visibility = visibility; //printf("decl = %p\n", decl); } /** * Params: * loc = source location of attribute token * pkg_identifiers = list of identifiers for a qualified package name * decl = declarations which are affected by this visibility attribute */ extern (D) this(const ref Loc loc, Identifier[] pkg_identifiers, Dsymbols* decl) { super(loc, null, decl); this.visibility.kind = Visibility.Kind.package_; this.pkg_identifiers = pkg_identifiers; if (pkg_identifiers.length > 0) { Dsymbol tmp; Package.resolve(pkg_identifiers, &tmp, null); visibility.pkg = tmp ? tmp.isPackage() : null; } } override VisibilityDeclaration syntaxCopy(Dsymbol s) { assert(!s); if (visibility.kind == Visibility.Kind.package_) return new VisibilityDeclaration(this.loc, pkg_identifiers, Dsymbol.arraySyntaxCopy(decl)); else return new VisibilityDeclaration(this.loc, visibility, Dsymbol.arraySyntaxCopy(decl)); } override Scope* newScope(Scope* sc) { if (pkg_identifiers) dsymbolSemantic(this, sc); return createNewScope(sc, sc.stc, sc.linkage, sc.cppmangle, this.visibility, 1, sc.aligndecl, sc.inlining); } override const(char)* kind() const { return "visibility attribute"; } override const(char)* toPrettyChars(bool) { assert(visibility.kind > Visibility.Kind.undefined); OutBuffer buf; visibilityToBuffer(buf, visibility); return buf.extractChars(); } override inout(VisibilityDeclaration) isVisibilityDeclaration() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Alignment attribute for aggregates, members and variables. * * `align() ` or * `align ` if `ealign` is null */ extern (C++) final class AlignDeclaration : AttribDeclaration { Expressions* exps; /// Expression(s) yielding the desired alignment, /// the largest value wins /// the actual alignment is Unknown until it's either set to the value of `ealign` /// or the default if `ealign` is null ( / an error ocurred) structalign_t salign; extern (D) this(const ref Loc loc, Expression exp, Dsymbols* decl) { super(loc, null, decl); if (exp) { exps = new Expressions(); exps.push(exp); } } extern (D) this(const ref Loc loc, Expressions* exps, Dsymbols* decl) @safe { super(loc, null, decl); this.exps = exps; } extern (D) this(const ref Loc loc, structalign_t salign, Dsymbols* decl) @safe { super(loc, null, decl); this.salign = salign; } override AlignDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new AlignDeclaration(loc, Expression.arraySyntaxCopy(exps), Dsymbol.arraySyntaxCopy(decl)); } override Scope* newScope(Scope* sc) { return createNewScope(sc, sc.stc, sc.linkage, sc.cppmangle, sc.visibility, sc.explicitVisibility, this, sc.inlining); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * An anonymous struct/union (defined by `isunion`). */ extern (C++) final class AnonDeclaration : AttribDeclaration { bool isunion; /// whether it's a union int sem; /// 1 if successful semantic() uint anonoffset; /// offset of anonymous struct uint anonstructsize; /// size of anonymous struct uint anonalignsize; /// size of anonymous struct for alignment purposes extern (D) this(const ref Loc loc, bool isunion, Dsymbols* decl) @safe { super(loc, null, decl); this.isunion = isunion; } override AnonDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new AnonDeclaration(loc, isunion, Dsymbol.arraySyntaxCopy(decl)); } override const(char)* kind() const { return (isunion ? "anonymous union" : "anonymous struct"); } override inout(AnonDeclaration) isAnonDeclaration() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Pragma applied to Dsymbols, e.g. `pragma(inline, true) void foo`, * but not PragmaStatement's like `pragma(msg, "hello");`. * * pragma(, ) */ extern (C++) final class PragmaDeclaration : AttribDeclaration { Expressions* args; /// parameters of this pragma extern (D) this(const ref Loc loc, Identifier ident, Expressions* args, Dsymbols* decl) @safe { super(loc, ident, decl); this.args = args; } override PragmaDeclaration syntaxCopy(Dsymbol s) { //printf("PragmaDeclaration::syntaxCopy(%s)\n", toChars()); assert(!s); return new PragmaDeclaration(loc, ident, Expression.arraySyntaxCopy(args), Dsymbol.arraySyntaxCopy(decl)); } override Scope* newScope(Scope* sc) { if (ident == Id.Pinline) { // We keep track of this pragma inside scopes, // then it's evaluated on demand in function semantic return createNewScope(sc, sc.stc, sc.linkage, sc.cppmangle, sc.visibility, sc.explicitVisibility, sc.aligndecl, this); } if (IN_LLVM && ident == Id.LDC_profile_instr) { import gen.dpragma : DtoCheckProfileInstrPragma; bool emitInstr = true; if (!args || args.length != 1 || !DtoCheckProfileInstrPragma((*args)[0], emitInstr)) { error(loc, "pragma(LDC_profile_instr, true or false) expected"); (*args)[0] = ErrorExp.get(); } else { // Only create a new scope if the emitInstrumentation flag is changed if (sc.emitInstrumentation != emitInstr) { auto newscope = sc.copy(); newscope.emitInstrumentation = emitInstr; return newscope; } } } return sc; } override const(char)* kind() const { return "pragma"; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * A conditional compilation declaration, used for `version` * / `debug` and specialized for `static if`. * * { } else { } */ extern (C++) class ConditionalDeclaration : AttribDeclaration { Condition condition; /// condition deciding whether decl or elsedecl applies Dsymbols* elsedecl; /// array of Dsymbol's for else block extern (D) this(const ref Loc loc, Condition condition, Dsymbols* decl, Dsymbols* elsedecl) @safe { super(loc, null, decl); //printf("ConditionalDeclaration::ConditionalDeclaration()\n"); this.condition = condition; this.elsedecl = elsedecl; } override ConditionalDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new ConditionalDeclaration(loc, condition.syntaxCopy(), Dsymbol.arraySyntaxCopy(decl), Dsymbol.arraySyntaxCopy(elsedecl)); } override final bool oneMember(out Dsymbol ps, Identifier ident) { //printf("ConditionalDeclaration::oneMember(), inc = %d\n", condition.inc); if (condition.inc != Include.notComputed) { Dsymbols* d = condition.include(null) ? decl : elsedecl; return Dsymbol.oneMembers(d, ps, ident); } else { bool res = (Dsymbol.oneMembers(decl, ps, ident) && ps is null && Dsymbol.oneMembers(elsedecl, ps, ident) && ps is null); ps = null; return res; } } // Decide if 'then' or 'else' code should be included override Dsymbols* include(Scope* sc) { //printf("ConditionalDeclaration::include(sc = %p) scope = %p\n", sc, _scope); if (errors) return null; assert(condition); return condition.include(_scope ? _scope : sc) ? decl : elsedecl; } override final void addComment(const(char)* comment) { /* Because addComment is called by the parser, if we called * include() it would define a version before it was used. * But it's no problem to drill down to both decl and elsedecl, * so that's the workaround. */ if (comment) { decl .foreachDsymbol( s => s.addComment(comment) ); elsedecl.foreachDsymbol( s => s.addComment(comment) ); } } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * ` { * static if () { } else { } * }` */ extern (C++) final class StaticIfDeclaration : ConditionalDeclaration { ScopeDsymbol scopesym; /// enclosing symbol (e.g. module) where symbols will be inserted private bool addisdone = false; /// true if members have been added to scope private bool onStack = false; /// true if a call to `include` is currently active extern (D) this(const ref Loc loc, Condition condition, Dsymbols* decl, Dsymbols* elsedecl) @safe { super(loc, condition, decl, elsedecl); //printf("StaticIfDeclaration::StaticIfDeclaration()\n"); } override StaticIfDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new StaticIfDeclaration(loc, condition.syntaxCopy(), Dsymbol.arraySyntaxCopy(decl), Dsymbol.arraySyntaxCopy(elsedecl)); } /**************************************** * Different from other AttribDeclaration subclasses, include() call requires * the completion of addMember and setScope phases. */ override Dsymbols* include(Scope* sc) { //printf("StaticIfDeclaration::include(sc = %p) scope = %p\n", sc, _scope); if (errors || onStack) return null; onStack = true; scope(exit) onStack = false; if (sc && condition.inc == Include.notComputed) { assert(scopesym); // addMember is already done assert(_scope); // setScope is already done Dsymbols* d = ConditionalDeclaration.include(_scope); if (d && !addisdone) { // Add members lazily. d.foreachDsymbol( s => s.addMember(_scope, scopesym) ); // Set the member scopes lazily. d.foreachDsymbol( s => s.setScope(_scope) ); addisdone = true; } return d; } else { return ConditionalDeclaration.include(sc); } } override const(char)* kind() const { return "static if"; } override inout(StaticIfDeclaration) isStaticIfDeclaration() inout pure @safe { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Static foreach at declaration scope, like: * static foreach (i; [0, 1, 2]){ } */ extern (C++) final class StaticForeachDeclaration : AttribDeclaration { StaticForeach sfe; /// contains `static foreach` expansion logic ScopeDsymbol scopesym; /// cached enclosing scope (mimics `static if` declaration) /++ `include` can be called multiple times, but a `static foreach` should be expanded at most once. Achieved by caching the result of the first call. We need both `cached` and `cache`, because `null` is a valid value for `cache`. +/ bool onStack = false; bool cached = false; Dsymbols* cache = null; extern (D) this(StaticForeach sfe, Dsymbols* decl) @safe { super(sfe.loc, null, decl); this.sfe = sfe; } override StaticForeachDeclaration syntaxCopy(Dsymbol s) { assert(!s); return new StaticForeachDeclaration( sfe.syntaxCopy(), Dsymbol.arraySyntaxCopy(decl)); } override bool oneMember(out Dsymbol ps, Identifier ident) { // Required to support IFTI on a template that contains a // `static foreach` declaration. `super.oneMember` calls // include with a `null` scope. As `static foreach` requires // the scope for expansion, `oneMember` can only return a // precise result once `static foreach` has been expanded. if (cached) { return super.oneMember(ps, ident); } ps = null; // a `static foreach` declaration may in general expand to multiple symbols return false; } override Dsymbols* include(Scope* sc) { if (errors || onStack) return null; if (cached) { assert(!onStack); return cache; } onStack = true; scope(exit) onStack = false; if (_scope) { sfe.prepare(_scope); // lower static foreach aggregate } if (!sfe.ready()) { return null; // TODO: ok? } // expand static foreach import dmd.statementsem: makeTupleForeach; Dsymbols* d = makeTupleForeach(_scope, true, true, sfe.aggrfe, decl, sfe.needExpansion).decl; if (d) // process generated declarations { // Add members lazily. d.foreachDsymbol( s => s.addMember(_scope, scopesym) ); // Set the member scopes lazily. d.foreachDsymbol( s => s.setScope(_scope) ); } cached = true; cache = d; return d; } override void addComment(const(char)* comment) { // do nothing // change this to give semantics to documentation comments on static foreach declarations } override const(char)* kind() const { return "static foreach"; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Collection of declarations that stores foreach index variables in a * local symbol table. Other symbols declared within are forwarded to * another scope, like: * * static foreach (i; 0 .. 10) // loop variables for different indices do not conflict. * { // this body is expanded into 10 ForwardingAttribDeclarations, where `i` has storage class STC.local * mixin("enum x" ~ to!string(i) ~ " = i"); // ok, can access current loop variable * } * * static foreach (i; 0.. 10) * { * pragma(msg, mixin("x" ~ to!string(i))); // ok, all 10 symbols are visible as they were forwarded to the global scope * } * * static assert (!is(typeof(i))); // loop index variable is not visible outside of the static foreach loop * * A StaticForeachDeclaration generates one * ForwardingAttribDeclaration for each expansion of its body. The * AST of the ForwardingAttribDeclaration contains both the `static * foreach` variables and the respective copy of the `static foreach` * body. The functionality is achieved by using a * ForwardingScopeDsymbol as the parent symbol for the generated * declarations. */ extern(C++) final class ForwardingAttribDeclaration : AttribDeclaration { ForwardingScopeDsymbol sym = null; this(Dsymbols* decl) @safe { super(decl); sym = new ForwardingScopeDsymbol(); sym.symtab = new DsymbolTable(); } /************************************** * Use the ForwardingScopeDsymbol as the parent symbol for members. */ override Scope* newScope(Scope* sc) { return sc.push(sym); } override inout(ForwardingAttribDeclaration) isForwardingAttribDeclaration() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Mixin declarations, like: * mixin("int x"); * https://dlang.org/spec/module.html#mixin-declaration */ // Note: was CompileDeclaration extern (C++) final class MixinDeclaration : AttribDeclaration { Expressions* exps; ScopeDsymbol scopesym; bool compiled; extern (D) this(const ref Loc loc, Expressions* exps) @safe { super(loc, null, null); //printf("MixinDeclaration(loc = %d)\n", loc.linnum); this.exps = exps; } override MixinDeclaration syntaxCopy(Dsymbol s) { //printf("MixinDeclaration::syntaxCopy('%s')\n", toChars()); return new MixinDeclaration(loc, Expression.arraySyntaxCopy(exps)); } override const(char)* kind() const { return "mixin"; } override inout(MixinDeclaration) isMixinDeclaration() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * User defined attributes look like: * @foo(args, ...) * @(args, ...) */ extern (C++) final class UserAttributeDeclaration : AttribDeclaration { Expressions* atts; extern (D) this(Expressions* atts, Dsymbols* decl) @safe { super(decl); this.atts = atts; } override UserAttributeDeclaration syntaxCopy(Dsymbol s) { //printf("UserAttributeDeclaration::syntaxCopy('%s')\n", toChars()); assert(!s); return new UserAttributeDeclaration(Expression.arraySyntaxCopy(this.atts), Dsymbol.arraySyntaxCopy(decl)); } override Scope* newScope(Scope* sc) { Scope* sc2 = sc; if (atts && atts.length) { // create new one for changes sc2 = sc.copy(); sc2.userAttribDecl = this; } return sc2; } extern (D) static Expressions* concat(Expressions* udas1, Expressions* udas2) { Expressions* udas; if (!udas1 || udas1.length == 0) udas = udas2; else if (!udas2 || udas2.length == 0) udas = udas1; else { /* Create a new tuple that combines them * (do not append to left operand, as this is a copy-on-write operation) */ udas = new Expressions(2); (*udas)[0] = new TupleExp(Loc.initial, udas1); (*udas)[1] = new TupleExp(Loc.initial, udas2); } return udas; } override const(char)* kind() const { return "UserAttribute"; } override void accept(Visitor v) { v.visit(this); } /** * Check if the provided expression references `core.attribute.gnuAbiTag` * * This should be called after semantic has been run on the expression. * Semantic on UDA happens in semantic2 (see `dmd.semantic2`). * * Params: * e = Expression to check (usually from `UserAttributeDeclaration.atts`) * * Returns: * `true` if the expression references the compiler-recognized `gnuAbiTag` */ static bool isGNUABITag(Expression e) { if (global.params.cplusplus < CppStdRevision.cpp11) return false; auto ts = e.type ? e.type.isTypeStruct() : null; if (!ts) return false; if (ts.sym.ident != Id.udaGNUAbiTag || !ts.sym.parent) return false; // Can only be defined in druntime Module m = ts.sym.parent.isModule(); if (!m || !m.isCoreModule(Id.attribute)) return false; return true; } /** * Called from a symbol's semantic to check if `gnuAbiTag` UDA * can be applied to them * * Directly emits an error if the UDA doesn't work with this symbol * * Params: * sym = symbol to check for `gnuAbiTag` * linkage = Linkage of the symbol (Declaration.link or sc.link) */ static void checkGNUABITag(Dsymbol sym, LINK linkage) { if (global.params.cplusplus < CppStdRevision.cpp11) return; foreachUdaNoSemantic(sym, (exp) { if (isGNUABITag(exp)) { if (sym.isCPPNamespaceDeclaration() || sym.isNspace()) { .error(exp.loc, "`@%s` cannot be applied to namespaces", Id.udaGNUAbiTag.toChars()); sym.errors = true; } else if (linkage != LINK.cpp) { .error(exp.loc, "`@%s` can only apply to C++ symbols", Id.udaGNUAbiTag.toChars()); sym.errors = true; } // Only one `@gnuAbiTag` is allowed by semantic2 return 1; // break } return 0; // continue }); } } /** * Returns `true` if the given symbol is a symbol declared in * `core.attribute` and has the given identifier. * * This is used to determine if a symbol is a UDA declared in * `core.attribute`. * * Params: * sym = the symbol to check * ident = the name of the expected UDA */ bool isCoreUda(Dsymbol sym, Identifier ident) { if (sym.ident != ident || !sym.parent) return false; auto _module = sym.parent.isModule(); return _module && _module.isCoreModule(Id.attribute); } /** * Iterates the UDAs attached to the given symbol, without performing semantic * analysis. * * Use this function instead of `foreachUda` if semantic analysis of `sym` is * still in progress. * * Params: * sym = the symbol to get the UDAs from * dg = called once for each UDA * * Returns: * If `dg` returns `!= 0`, stops the iteration and returns that value. * Otherwise, returns 0. */ int foreachUdaNoSemantic(Dsymbol sym, int delegate(Expression) dg) { if (sym.userAttribDecl is null || sym.userAttribDecl.atts is null) return 0; foreach (exp; *sym.userAttribDecl.atts) { if (auto result = dg(exp)) return result; } return 0; } /** * Returns: true if the given expression is an enum from `core.attribute` named `id` */ bool isEnumAttribute(Expression e, Identifier id) { import dmd.attrib : isCoreUda; import dmd.id : Id; // Logic based on dmd.objc.Supported.declaredAsOptionalCount auto typeExp = e.isTypeExp; if (!typeExp) return false; auto typeEnum = typeExp.type.isTypeEnum(); if (!typeEnum) return false; if (isCoreUda(typeEnum.sym, id)) return true; return false; } ldc-1.40.0-src/dmd/initsem.d0000644000000000000000000016530214727557031014224 0ustar rootroot/** * Semantic analysis of initializers. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/initsem.d, _initsem.d) * Documentation: https://dlang.org/phobos/dmd_initsem.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/initsem.d */ module dmd.initsem; import core.stdc.stdio; import core.checkedint; import dmd.aggregate; import dmd.aliasthis; import dmd.arraytypes; import dmd.astenums; import dmd.dcast; import dmd.declaration; import dmd.dinterpret; import dmd.dscope; import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.importc; import dmd.init; import dmd.location; import dmd.mtype; import dmd.opover; import dmd.optimize; import dmd.statement; import dmd.target; import dmd.tokens; import dmd.typesem; /******************************** * If possible, convert array initializer to associative array initializer. * * Params: * ai = array initializer to be converted * * Returns: * The converted associative array initializer or ErrorExp if `ai` * is not an associative array initializer. */ Expression toAssocArrayLiteral(ArrayInitializer ai) { //printf("ArrayInitializer::toAssocArrayInitializer(%s)\n", ai.toChars()); //static int i; if (++i == 2) assert(0); auto no(const char* format, Initializer i) { error(i.loc, format, toChars(i)); return ErrorExp.get(); } const dim = ai.value.length; if (!dim) return no("invalid associative array initializer `%s`, use `null` instead", ai); auto keys = new Expressions(dim); auto values = new Expressions(dim); foreach (i, iz; ai.value[]) { assert(iz); auto ev = iz.initializerToExpression(); if (!ev) return no("invalid value `%s` in initializer", iz); (*values)[i] = ev; auto ei = ai.index[i]; if (!ei) return no("missing key for value `%s` in initializer", iz); (*keys)[i] = ei; } return new AssocArrayLiteralExp(ai.loc, keys, values); } /****************************************** * Perform semantic analysis on init. * Params: * init = Initializer AST node * sc = context * tx = type that the initializer needs to become. If tx is an incomplete * type and the initializer completes it, it is updated to be the * complete type. ImportC has incomplete types * needInterpret = if CTFE needs to be run on this, * such as if it is the initializer for a const declaration * Returns: * `Initializer` with completed semantic analysis, `ErrorInitializer` if errors * were encountered */ Initializer initializerSemantic(Initializer init, Scope* sc, ref Type tx, NeedInterpret needInterpret) { //printf("initializerSemantic() tx: %p %s\n", tx, tx.toChars()); Type t = tx; static Initializer err() { return new ErrorInitializer(); } Initializer visitVoid(VoidInitializer i) { i.type = t; return i; } Initializer visitDefault(DefaultInitializer i) { i.type = t; return i; } Initializer visitError(ErrorInitializer i) { return i; } Initializer visitStruct(StructInitializer i) { //printf("StructInitializer::semantic(t = %s) %s\n", t.toChars(), i.toChars()); /* This works by replacing the StructInitializer with an ExpInitializer. */ t = t.toBasetype(); if (auto tsa = t.isTypeSArray()) { auto ts = tsa.nextOf().toBasetype().isTypeStruct(); if (ts) t = ts; } if (auto ts = t.isTypeStruct()) { StructDeclaration sd = ts.sym; // check if the sd has a regular ctor (user defined non-copy ctor) // that is not disabled. if (sd.hasRegularCtor(true)) { error(i.loc, "%s `%s` has constructors, cannot use `{ initializers }`, use `%s( initializers )` instead", sd.kind(), sd.toChars(), sd.toChars()); return err(); } sd.size(i.loc); if (sd.sizeok != Sizeok.done) return err(); Expression getExp(size_t j, Type fieldType) { // Convert initializer to Expression `ex` auto tm = fieldType.addMod(t.mod); auto iz = i.value[j].initializerSemantic(sc, tm, needInterpret); auto ex = iz.initializerToExpression(null, (sc.flags & SCOPE.Cfile) != 0); if (ex.op != EXP.error) i.value[j] = iz; return ex; } auto elements = resolveStructLiteralNamedArgs(sd, t, sc, i.loc, i.field[], &getExp, (size_t j) => i.value[j].loc); if (!elements) return err(); // Make a StructLiteralExp out of elements[] auto sle = new StructLiteralExp(i.loc, sd, elements, t); if (!sd.fill(i.loc, *elements, false)) return err(); sle.type = t; auto ie = new ExpInitializer(i.loc, sle); return ie.initializerSemantic(sc, t, needInterpret); } else if ((t.ty == Tdelegate || t.isPtrToFunction()) && i.value.length == 0) { const tok = (t.ty == Tdelegate) ? TOK.delegate_ : TOK.function_; /* Rewrite as empty delegate literal { } */ Type tf = new TypeFunction(ParameterList(), null, LINK.d); auto fd = new FuncLiteralDeclaration(i.loc, Loc.initial, tf, tok, null); fd.fbody = new CompoundStatement(i.loc, new Statements()); fd.endloc = i.loc; Expression e = new FuncExp(i.loc, fd); auto ie = new ExpInitializer(i.loc, e); return ie.initializerSemantic(sc, t, needInterpret); } if (t.ty != Terror) error(i.loc, "a struct is not a valid initializer for a `%s`", t.toChars()); return err(); } Initializer visitArray(ArrayInitializer i) { uint length; const(uint) amax = 0x80000000; bool errors = false; //printf("ArrayInitializer::semantic(%s), ai: %s\n", t.toChars(), toChars(i)); if (i.sem) // if semantic() already run { return i; } i.sem = true; t = t.toBasetype(); switch (t.ty) { case Tsarray: case Tarray: break; case Tvector: t = t.isTypeVector().basetype; break; case Taarray: case Tstruct: // consider implicit constructor call { Expression e; // note: MyStruct foo = [1:2, 3:4] is correct code if MyStruct has a this(int[int]) if (t.ty == Taarray || i.isAssociativeArray()) e = i.toAssocArrayLiteral(); else e = i.initializerToExpression(); // Bugzilla 13987 if (!e) { error(i.loc, "cannot use array to initialize `%s`", t.toChars()); return err(); } auto ei = new ExpInitializer(e.loc, e); return ei.initializerSemantic(sc, t, needInterpret); } case Tpointer: if (t.nextOf().isTypeFunction()) goto default; break; default: error(i.loc, "cannot use array to initialize `%s`", t.toChars()); return err(); } i.type = t; length = 0; for (size_t j = 0; j < i.index.length; j++) // don't replace with foreach; j is modified { Expression idx = i.index[j]; if (idx) { sc = sc.startCTFE(); idx = idx.expressionSemantic(sc); sc = sc.endCTFE(); idx = idx.ctfeInterpret(); i.index[j] = idx; const uinteger_t idxvalue = idx.toInteger(); if (idxvalue >= amax) { error(i.loc, "array index %llu overflow", idxvalue); errors = true; } length = cast(uint)idxvalue; if (idx.op == EXP.error) errors = true; } Initializer val = i.value[j]; ExpInitializer ei = val.isExpInitializer(); if (ei && !idx) ei.expandTuples = true; auto tn = t.nextOf(); val = val.initializerSemantic(sc, tn, needInterpret); if (val.isErrorInitializer()) errors = true; ei = val.isExpInitializer(); // found a tuple, expand it if (ei && ei.exp.op == EXP.tuple) { TupleExp te = ei.exp.isTupleExp(); i.index.remove(j); i.value.remove(j); foreach (k, e; (*te.exps)[]) { i.index.insert(j + k, cast(Expression)null); i.value.insert(j + k, new ExpInitializer(e.loc, e)); } j--; continue; } else { i.value[j] = val; } ++length; if (length == 0) { error(i.loc, "array dimension overflow"); return err(); } if (length > i.dim) i.dim = length; } if (auto tsa = t.isTypeSArray()) { if (sc.flags & SCOPE.Cfile && tsa.isIncomplete()) { // Change to array of known length auto tn = tsa.next.toBasetype(); tsa = new TypeSArray(tn, new IntegerExp(Loc.initial, i.dim, Type.tsize_t)); tx = tsa; // rewrite caller's type i.type = tsa; // remember for later passes } else { ulong edim = tsa.dim.toInteger(); if (i.dim > edim) { error(i.loc, "array initializer has %u elements, but array length is %llu", i.dim, edim); return err(); } } } if (errors) return err(); const sz = t.nextOf().size(); if (sz == SIZE_INVALID) return err(); bool overflow; const max = mulu(i.dim, sz, overflow); if (overflow || max >= amax) { error(i.loc, "array dimension %llu exceeds max of %llu", ulong(i.dim), ulong(amax / sz)); return err(); } //printf("returns ai: %s\n", i.toChars()); return i; } Initializer visitExp(ExpInitializer i) { //printf("ExpInitializer::semantic(%s), type = %s\n", i.exp.toChars(), t.toChars()); if (needInterpret) sc = sc.startCTFE(); i.exp = i.exp.expressionSemantic(sc); i.exp = resolveProperties(sc, i.exp); if (needInterpret) sc = sc.endCTFE(); if (i.exp.op == EXP.error) return err(); const olderrors = global.errors; /* ImportC: convert arrays to pointers, functions to pointers to functions */ Type tb = t.toBasetype(); if (tb.isTypePointer()) i.exp = i.exp.arrayFuncConv(sc); /* Save the expression before ctfe * Otherwise the error message would contain for example "&[0][0]" instead of "new int" * Regression: https://issues.dlang.org/show_bug.cgi?id=21687 */ Expression currExp = i.exp; if (needInterpret) { // If the result will be implicitly cast, move the cast into CTFE // to avoid premature truncation of polysemous types. // eg real [] x = [1.1, 2.2]; should use real precision. if (i.exp.implicitConvTo(t) && !(sc.flags & SCOPE.Cfile)) { i.exp = i.exp.implicitCastTo(sc, t); } if (!global.gag && olderrors != global.errors) { return i; } if (sc.flags & SCOPE.Cfile) { /* the interpreter turns (char*)"string" into &"string"[0] which then * it cannot interpret. Resolve that case by doing optimize() first */ i.exp = i.exp.optimize(WANTvalue); if (i.exp.isSymOffExp()) { /* `static variable cannot be read at compile time` * https://issues.dlang.org/show_bug.cgi?id=22513 * Maybe this would be better addressed in ctfeInterpret()? */ needInterpret = NeedInterpret.INITnointerpret; } } if (needInterpret) i.exp = i.exp.ctfeInterpret(); if (i.exp.op == EXP.voidExpression) error(i.loc, "variables cannot be initialized with an expression of type `void`. Use `void` initialization instead."); } else { i.exp = i.exp.optimize(WANTvalue); } if (!global.gag && olderrors != global.errors) { return i; // Failed, suppress duplicate error messages } if (i.exp.type.isTypeTuple() && i.exp.type.isTypeTuple().arguments.length == 0) { Type et = i.exp.type; i.exp = new TupleExp(i.exp.loc, new Expressions()); i.exp.type = et; } if (i.exp.op == EXP.type) { error(i.exp.loc, "initializer must be an expression, not `%s`", i.exp.toChars()); return err(); } // Make sure all pointers are constants if (needInterpret && hasNonConstPointers(i.exp)) { error(i.exp.loc, "cannot use non-constant CTFE pointer in an initializer `%s`", currExp.toChars()); return err(); } Type ti = i.exp.type.toBasetype(); if (i.exp.op == EXP.tuple && i.expandTuples && !i.exp.implicitConvTo(t)) { return new ExpInitializer(i.loc, i.exp); } /* Look for case of initializing a static array with a too-short * string literal, such as: * char[5] foo = "abc"; * Allow this by doing an explicit cast, which will lengthen the string * literal. */ if (i.exp.op == EXP.string_ && tb.ty == Tsarray) { StringExp se = i.exp.isStringExp(); Type typeb = se.type.toBasetype(); TY tynto = tb.nextOf().ty; if (!se.committed && (typeb.ty == Tarray || typeb.ty == Tsarray) && tynto.isSomeChar && se.numberOfCodeUnits(tynto) < tb.isTypeSArray().dim.toInteger()) { i.exp = se.castTo(sc, t); goto L1; } /* Lop off terminating 0 of initializer for: * static char s[5] = "hello"; */ if (sc.flags & SCOPE.Cfile && typeb.ty == Tsarray && tynto.isSomeChar && tb.isTypeSArray().dim.toInteger() + 1 == typeb.isTypeSArray().dim.toInteger()) { i.exp = se.castTo(sc, t); goto L1; } } /* C11 6.7.9-14..15 * Initialize an array of unknown size with a string. * Change to static array of known size */ if (sc.flags & SCOPE.Cfile && i.exp.isStringExp() && tb.isTypeSArray() && tb.isTypeSArray().isIncomplete()) { StringExp se = i.exp.isStringExp(); auto ts = new TypeSArray(tb.nextOf(), new IntegerExp(Loc.initial, se.len + 1, Type.tsize_t)); t = typeSemantic(ts, Loc.initial, sc); i.exp.type = t; tx = t; } // Look for implicit constructor call if (tb.ty == Tstruct && !(ti.ty == Tstruct && tb.toDsymbol(sc) == ti.toDsymbol(sc)) && !i.exp.implicitConvTo(t)) { StructDeclaration sd = tb.isTypeStruct().sym; if (sd.ctor) { // Rewrite as S().ctor(exp) Expression e; e = new StructLiteralExp(i.loc, sd, null); e = new DotIdExp(i.loc, e, Id.ctor); e = new CallExp(i.loc, e, i.exp); e = e.expressionSemantic(sc); if (needInterpret) i.exp = e.ctfeInterpret(); else i.exp = e.optimize(WANTvalue); } else if (search_function(sd, Id.call)) { /* https://issues.dlang.org/show_bug.cgi?id=1547 * * Look for static opCall * * Rewrite as: * i.exp = typeof(sd).opCall(arguments) */ Expression e = typeDotIdExp(i.loc, sd.type, Id.call); e = new CallExp(i.loc, e, i.exp); e = e.expressionSemantic(sc); e = resolveProperties(sc, e); if (needInterpret) i.exp = e.ctfeInterpret(); else i.exp = e.optimize(WANTvalue); } } // Look for the case of statically initializing an array with a single member. // Recursively strip static array / enum layers until a compatible element is found, // and return an `ArrayLiteralExp` repeating the initializer, or `null` if no match found // int[2][3] = 7 => [[7, 7], [7, 7], [7, 7]] // int[2] = new Object => null Expression sarrayRepeat(Type tb) { auto tsa = tb.isTypeSArray(); if (!tsa) return null; // printf("i.exp = %s, tsa = %s\n", i.exp.toChars(), tsa.toChars()); Expression elem = null; if (i.exp.implicitConvTo(tb.nextOf())) elem = i.exp.implicitCastTo(sc, tb.nextOf()); else if (auto ae = sarrayRepeat(tb.nextOf().toBasetype())) elem = ae; else return null; auto arrayElements = new Expressions(cast(size_t) tsa.dim.toInteger()); foreach (ref e; *arrayElements) e = elem; return new ArrayLiteralExp(i.exp.loc, tb, elem, arrayElements); } if (auto sa = sarrayRepeat(tb)) { // printf("sa = %s\n", sa.toChars()); i.exp = sa; } { auto tta = t.isTypeSArray(); if (i.exp.implicitConvTo(t)) { i.exp = i.exp.implicitCastTo(sc, t); } else if (sc.flags & SCOPE.Cfile && i.exp.isStringExp() && tta && (tta.next.ty == Tint8 || tta.next.ty == Tuns8) && ti.ty == Tsarray && ti.nextOf().ty == Tchar) { /* unsigned char bbb[1] = ""; * signed char ccc[1] = ""; */ i.exp = i.exp.castTo(sc, t); } else { auto tba = tb.isTypeSArray(); // Look for mismatch of compile-time known length to emit // better diagnostic message, as same as AssignExp::semantic. if (tba && i.exp.implicitConvTo(tba.next.arrayOf()) > MATCH.nomatch) { uinteger_t dim1 = tba.dim.toInteger(); uinteger_t dim2 = dim1; if (auto ale = i.exp.isArrayLiteralExp()) { dim2 = ale.elements ? ale.elements.length : 0; } else if (auto se = i.exp.isSliceExp()) { if (Type tx = toStaticArrayType(se)) dim2 = tx.isTypeSArray().dim.toInteger(); } if (dim1 != dim2) { error(i.exp.loc, "mismatched array lengths, %d and %d", cast(int)dim1, cast(int)dim2); i.exp = ErrorExp.get(); } } Type et = i.exp.type; const errors = global.startGagging(); i.exp = i.exp.implicitCastTo(sc, t); if (global.endGagging(errors)) error(currExp.loc, "cannot implicitly convert expression `%s` of type `%s` to `%s`", currExp.toChars(), et.toChars(), t.toChars()); } } L1: if (i.exp.op == EXP.error) { return i; } if (needInterpret) i.exp = i.exp.ctfeInterpret(); else i.exp = i.exp.optimize(WANTvalue); //printf("-ExpInitializer::semantic(): "); i.exp.print(); return i; } Initializer visitC(CInitializer ci) { //printf("CInitializer::semantic() tx: %s t: %s ci: %s\n", (tx ? tx.toChars() : "".ptr), t.toChars(), toChars(ci)); static if (0) if (auto ts = tx.isTypeStruct()) { import dmd.common.outbuffer; OutBuffer buf; HdrGenState hgs; toCBuffer(ts.sym, buf, hgs); printf("%s\n", buf.peekChars()); } /* Rewrite CInitializer into ExpInitializer, ArrayInitializer, or StructInitializer */ t = t.toBasetype(); if (auto tv = t.isTypeVector()) t = tv.basetype; /* If `{ expression }` return the expression initializer */ ExpInitializer isBraceExpression() { auto dil = ci.initializerList[]; return (dil.length == 1 && !dil[0].designatorList) ? dil[0].initializer.isExpInitializer() : null; } /******************************** */ bool overlaps(VarDeclaration field, VarDeclaration[] fields, StructInitializer si) { foreach (fld; fields) { if (field.isOverlappedWith(fld)) { // look for initializer corresponding with fld foreach (i, ident; si.field[]) { if (ident == fld.ident && si.value[i]) return true; // already an initializer for `field` } } } return false; } /* Run semantic on ExpInitializer, see if it represents entire struct ts */ bool representsStruct(ExpInitializer ei, TypeStruct ts) { if (needInterpret) sc = sc.startCTFE(); ei.exp = ei.exp.expressionSemantic(sc); ei.exp = resolveProperties(sc, ei.exp); if (needInterpret) sc = sc.endCTFE(); return ei.exp.implicitConvTo(ts) != MATCH.nomatch; // initializer represents the entire struct } /* If { } are omitted from substructs, use recursion to reconstruct where * brackets go * Params: * ts = substruct to initialize * index = index into ci.initializer, updated * Returns: struct initializer for this substruct */ Initializer subStruct()(TypeStruct ts, ref size_t index) { //printf("subStruct(ts: %s, index %d)\n", ts.toChars(), cast(int)index); auto si = new StructInitializer(ci.loc); StructDeclaration sd = ts.sym; sd.size(ci.loc); if (sd.sizeok != Sizeok.done) { index = ci.initializerList.length; return err(); } const nfields = sd.fields.length; foreach (fieldi; 0 .. nfields) { if (index >= ci.initializerList.length) break; // ran out of initializers auto di = ci.initializerList[index]; if (di.designatorList && fieldi != 0) break; // back to top level else { VarDeclaration field; while (1) // skip field if it overlaps with previously seen fields { field = sd.fields[fieldi]; ++fieldi; if (!overlaps(field, sd.fields[], si)) break; if (fieldi == nfields) break; } auto tn = field.type.toBasetype(); auto tnsa = tn.isTypeSArray(); auto tns = tn.isTypeStruct(); auto ix = di.initializer; if (tnsa && ix.isExpInitializer()) { ExpInitializer ei = ix.isExpInitializer(); if (ei.exp.isStringExp() && tnsa.nextOf().isintegral()) { si.addInit(field.ident, ei); ++index; } else si.addInit(field.ident, subArray(tnsa, index)); // fwd ref of subArray is why subStruct is a template } else if (tns && ix.isExpInitializer()) { /* Disambiguate between an exp representing the entire * struct, and an exp representing the first field of the struct */ if (representsStruct(ix.isExpInitializer(), tns)) // initializer represents the entire struct { si.addInit(field.ident, initializerSemantic(ix, sc, tn, needInterpret)); ++index; } else // field initializers for struct si.addInit(field.ident, subStruct(tns, index)); // the first field } else { si.addInit(field.ident, ix); ++index; } } } //printf("subStruct() returns ai: %s, index: %d\n", si.toChars(), cast(int)index); return si; } /* If { } are omitted from subarrays, use recursion to reconstruct where * brackets go * Params: * tsa = subarray to initialize * index = index into ci.initializer, updated * Returns: array initializer for this subarray */ Initializer subArray(TypeSArray tsa, ref size_t index) { //printf("array(tsa: %s, index %d)\n", tsa.toChars(), cast(int)index); if (tsa.isIncomplete()) { // C11 6.2.5-20 "element type shall be complete whenever the array type is specified" assert(0); // should have been detected by parser } auto tnsa = tsa.nextOf().toBasetype().isTypeSArray(); auto ai = new ArrayInitializer(ci.loc); ai.isCarray = true; foreach (n; 0 .. cast(size_t)tsa.dim.toInteger()) { if (index >= ci.initializerList.length) break; // ran out of initializers auto di = ci.initializerList[index]; if (di.designatorList) break; // back to top level else if (tnsa && di.initializer.isExpInitializer()) { ExpInitializer ei = di.initializer.isExpInitializer(); if (ei.exp.isStringExp() && tnsa.nextOf().isintegral()) { ai.addInit(null, ei); ++index; } else ai.addInit(null, subArray(tnsa, index)); } else { ai.addInit(null, di.initializer); ++index; } } //printf("array() returns ai: %s, index: %d\n", ai.toChars(), cast(int)index); return ai; } if (auto ts = t.isTypeStruct()) { auto si = new StructInitializer(ci.loc); StructDeclaration sd = ts.sym; sd.size(ci.loc); // run semantic() on sd to get fields if (sd.sizeok != Sizeok.done) { return err(); } const nfields = sd.fields.length; size_t fieldi = 0; Loop1: for (size_t index = 0; index < ci.initializerList.length; ) { DesigInit di = ci.initializerList[index]; Designators* dlist = di.designatorList; if (dlist) { const length = (*dlist).length; if (length == 0 || !(*dlist)[0].ident) { error(ci.loc, "`.identifier` expected for C struct field initializer `%s`", toChars(ci)); return err(); } if (length > 1) { error(ci.loc, "only 1 designator currently allowed for C struct field initializer `%s`", toChars(ci)); return err(); } auto id = (*dlist)[0].ident; foreach (k, f; sd.fields[]) // linear search for now { if (f.ident == id) { fieldi = k; si.addInit(id, di.initializer); ++fieldi; ++index; continue Loop1; } } error(ci.loc, "`.%s` is not a field of `%s`\n", id.toChars(), sd.toChars()); return err(); } else { if (fieldi == nfields) break; auto ix = di.initializer; /* If a C initializer is wrapped in a C initializer, with no designators, * peel off the outer one */ if (ix.isCInitializer()) { CInitializer cix = ix.isCInitializer(); if (cix.initializerList.length == 1) { DesigInit dix = cix.initializerList[0]; if (!dix.designatorList) { Initializer inix = dix.initializer; if (inix.isCInitializer()) ix = inix; } } } if (auto cix = ix.isCInitializer()) { /* ImportC loses the structure from anonymous structs, but this is retained * by the initializer syntax. if a CInitializer has a Designator, it is probably * a nested anonymous struct */ int found; foreach (dix; cix.initializerList) { Designators* dlistx = dix.designatorList; if (!dlistx) continue; if ((*dlistx).length == 1 && (*dlistx)[0].ident) { auto id = (*dlistx)[0].ident; foreach (k, f; sd.fields[]) // linear search for now { if (f.ident == id) { fieldi = k; si.addInit(id, dix.initializer); ++fieldi; ++index; ++found; break; } } } else { error(ci.loc, "only 1 designator currently allowed for C struct field initializer `%s`", toChars(ci)); } } if (found == cix.initializerList.length) continue Loop1; } VarDeclaration field; while (1) // skip field if it overlaps with previously seen fields { field = sd.fields[fieldi]; ++fieldi; if (!overlaps(field, sd.fields[], si)) break; if (fieldi == nfields) break; } auto tn = field.type.toBasetype(); auto tnsa = tn.isTypeSArray(); auto tns = tn.isTypeStruct(); if (tnsa && ix.isExpInitializer()) { ExpInitializer ei = ix.isExpInitializer(); if (ei.exp.isStringExp() && tnsa.nextOf().isintegral()) { si.addInit(field.ident, ei); ++index; } else si.addInit(field.ident, subArray(tnsa, index)); } else if (tns && ix.isExpInitializer()) { /* Disambiguate between an exp representing the entire * struct, and an exp representing the first field of the struct */ if (representsStruct(ix.isExpInitializer(), tns)) // initializer represents the entire struct { si.addInit(field.ident, initializerSemantic(ix, sc, tn, needInterpret)); ++index; } else // field initializers for struct si.addInit(field.ident, subStruct(tns, index)); // the first field } else { si.addInit(field.ident, di.initializer); ++index; } } } return initializerSemantic(si, sc, t, needInterpret); } else if (auto ta = t.isTypeSArray()) { auto tn = t.nextOf().toBasetype(); // element type of array /* If it's an array of integral being initialized by `{ string }` * replace with `string` */ if (tn.isintegral()) { if (ExpInitializer ei = isBraceExpression()) { if (ei.exp.isStringExp()) return ei.initializerSemantic(sc, t, needInterpret); } } auto tnsa = tn.isTypeSArray(); // array of array auto tns = tn.isTypeStruct(); // array of struct auto ai = new ArrayInitializer(ci.loc); ai.isCarray = true; for (size_t index = 0; index < ci.initializerList.length; ) { auto di = ci.initializerList[index]; if (auto dlist = di.designatorList) { const length = (*dlist).length; if (length == 0 || !(*dlist)[0].exp) { error(ci.loc, "`[ constant-expression ]` expected for C array element initializer `%s`", toChars(ci)); return err(); } if (length > 1) { error(ci.loc, "only 1 designator currently allowed for C array element initializer `%s`", toChars(ci)); return err(); } //printf("tn: %s, di.initializer: %s\n", tn.toChars(), di.initializer.toChars()); auto ix = di.initializer; if (tnsa && ix.isExpInitializer()) { // Wrap initializer in [ ] auto ain = new ArrayInitializer(ci.loc); ain.addInit(null, di.initializer); ix = ain; ai.addInit((*dlist)[0].exp, initializerSemantic(ix, sc, tn, needInterpret)); ++index; } else if (tns && ix.isExpInitializer()) { /* Disambiguate between an exp representing the entire * struct, and an exp representing the first field of the struct */ if (representsStruct(ix.isExpInitializer(), tns)) // initializer represents the entire struct { ai.addInit((*dlist)[0].exp, initializerSemantic(ix, sc, tn, needInterpret)); ++index; } else // field initializers for struct ai.addInit((*dlist)[0].exp, subStruct(tns, index)); // the first field } else { ai.addInit((*dlist)[0].exp, initializerSemantic(ix, sc, tn, needInterpret)); ++index; } } else if (tnsa && di.initializer.isExpInitializer()) { ExpInitializer ei = di.initializer.isExpInitializer(); if (ei.exp.isStringExp() && tnsa.nextOf().isintegral()) { ai.addInit(null, ei); ++index; } else ai.addInit(null, subArray(tnsa, index)); } else if (tns && di.initializer.isExpInitializer()) { /* Disambiguate between an exp representing the entire * struct, and an exp representing the first field of the struct */ if (representsStruct(di.initializer.isExpInitializer(), tns)) // initializer represents the entire struct { ai.addInit(null, initializerSemantic(di.initializer, sc, tn, needInterpret)); ++index; } else // field initializers for struct ai.addInit(null, subStruct(tns, index)); // the first field } else { ai.addInit(null, initializerSemantic(di.initializer, sc, tn, needInterpret)); ++index; } } return initializerSemantic(ai, sc, tx, needInterpret); } else if (ExpInitializer ei = isBraceExpression()) { return visitExp(ei); } else { error(ci.loc, "unrecognized C initializer `%s` for type `%s`", toChars(ci), t.toChars()); return err(); } } mixin VisitInitializer!Initializer visit; auto result = visit.VisitInitializer(init); return (result !is null) ? result : new ErrorInitializer(); } /*********************** * Translate init to an `Expression` in order to infer the type. * Params: * init = `Initializer` AST node * sc = context * Returns: * an equivalent `ExpInitializer` if successful, or `ErrorInitializer` if it cannot be translated */ Initializer inferType(Initializer init, Scope* sc) { Initializer visitVoid(VoidInitializer i) { error(i.loc, "cannot infer type from void initializer"); return new ErrorInitializer(); } Initializer visitDefault(DefaultInitializer i) { error(i.loc, "cannot infer type from default initializer"); return new ErrorInitializer(); } Initializer visitError(ErrorInitializer i) { return i; } Initializer visitStruct(StructInitializer i) { error(i.loc, "cannot infer type from struct initializer"); return new ErrorInitializer(); } Initializer visitArray(ArrayInitializer init) { //printf("ArrayInitializer::inferType() %s\n", toChars()); Expressions* keys = null; Expressions* values; if (init.isAssociativeArray()) { keys = new Expressions(init.value.length); values = new Expressions(init.value.length); for (size_t i = 0; i < init.value.length; i++) { Expression e = init.index[i]; if (!e) goto Lno; (*keys)[i] = e; Initializer iz = init.value[i]; if (!iz) goto Lno; iz = iz.inferType(sc); if (iz.isErrorInitializer()) { return iz; } (*values)[i] = iz.isExpInitializer().exp; assert(!(*values)[i].isErrorExp()); } Expression e = new AssocArrayLiteralExp(init.loc, keys, values); auto ei = new ExpInitializer(init.loc, e); return ei.inferType(sc); } else { auto elements = new Expressions(init.value.length); elements.zero(); for (size_t i = 0; i < init.value.length; i++) { assert(!init.index[i]); // already asserted by isAssociativeArray() Initializer iz = init.value[i]; if (!iz) goto Lno; iz = iz.inferType(sc); if (iz.isErrorInitializer()) { return iz; } (*elements)[i] = iz.isExpInitializer().exp; assert(!(*elements)[i].isErrorExp()); } Expression e = new ArrayLiteralExp(init.loc, null, elements); auto ei = new ExpInitializer(init.loc, e); return ei.inferType(sc); } Lno: if (keys) { error(init.loc, "not an associative array initializer"); } else { error(init.loc, "cannot infer type from array initializer"); } return new ErrorInitializer(); } Initializer visitExp(ExpInitializer init) { //printf("ExpInitializer::inferType() %s\n", init.toChars()); init.exp = init.exp.expressionSemantic(sc); // for static alias this: https://issues.dlang.org/show_bug.cgi?id=17684 if (init.exp.op == EXP.type) init.exp = resolveAliasThis(sc, init.exp); init.exp = resolveProperties(sc, init.exp); if (auto se = init.exp.isScopeExp()) { TemplateInstance ti = se.sds.isTemplateInstance(); if (ti && ti.semanticRun == PASS.semantic && !ti.aliasdecl) error(se.loc, "cannot infer type from %s `%s`, possible circular dependency", se.sds.kind(), se.toChars()); else error(se.loc, "cannot infer type from %s `%s`", se.sds.kind(), se.toChars()); return new ErrorInitializer(); } // Give error for overloaded function addresses bool hasOverloads; if (auto f = isFuncAddress(init.exp, &hasOverloads)) { if (checkForwardRef(f, init.loc)) { return new ErrorInitializer(); } if (hasOverloads && !f.isUnique()) { error(init.exp.loc, "cannot infer type from overloaded function symbol `%s`", init.exp.toChars()); return new ErrorInitializer(); } } if (auto ae = init.exp.isAddrExp()) { if (ae.e1.op == EXP.overloadSet) { error(init.exp.loc, "cannot infer type from overloaded function symbol `%s`", init.exp.toChars()); return new ErrorInitializer(); } } if (init.exp.isErrorExp()) { return new ErrorInitializer(); } if (!init.exp.type) { return new ErrorInitializer(); } return init; } Initializer visitC(CInitializer i) { //printf("CInitializer.inferType()\n"); error(i.loc, "TODO C inferType initializers not supported yet"); return new ErrorInitializer(); } mixin VisitInitializer!Initializer visit; auto result = visit.VisitInitializer(init); return (result !is null) ? result : new ErrorInitializer(); } /*********************** * Translate init to an `Expression`. * Params: * init = `Initializer` AST node * itype = if not `null`, type to coerce expression to * isCfile = default initializers are different with C * Returns: * `Expression` created, `null` if cannot, `ErrorExp` for other errors */ Expression initializerToExpression(Initializer init, Type itype = null, const bool isCfile = false) { //printf("initializerToExpression() isCfile: %d\n", isCfile); Expression visitVoid(VoidInitializer) { return null; } Expression visitDefault(DefaultInitializer di) { return di.type ? di.type.defaultInit(Loc.initial, isCfile) : null; } Expression visitError(ErrorInitializer) { return ErrorExp.get(); } /*************************************** * This works by transforming a struct initializer into * a struct literal. In the future, the two should be the * same thing. */ Expression visitStruct(StructInitializer) { // cannot convert to an expression without target 'ad' return null; } /******************************** * If possible, convert array initializer to array literal. * Otherwise return NULL. */ Expression visitArray(ArrayInitializer init) { //printf("ArrayInitializer::toExpression(), dim = %d\n", dim); //static int i; if (++i == 2) assert(0); uint edim; // the length of the resulting array literal const(uint) amax = 0x80000000; Type t = null; // type of the array literal being initialized if (init.type) { if (init.type == Type.terror) { return ErrorExp.get(); } t = init.type.toBasetype(); switch (t.ty) { case Tvector: t = t.isTypeVector().basetype; goto case Tsarray; case Tsarray: uinteger_t adim = t.isTypeSArray().dim.toInteger(); if (adim >= amax) return null; edim = cast(uint)adim; break; case Tpointer: case Tarray: edim = init.dim; break; default: assert(0); } } else { /* Calculate the length of the array literal */ edim = cast(uint)init.value.length; size_t j = 0; foreach (i; 0 .. init.value.length) { if (auto e = init.index[i]) { if (e.op == EXP.int64) { const uinteger_t idxval = e.toInteger(); if (idxval >= amax) return null; j = cast(size_t)idxval; } else return null; } ++j; if (j > edim) edim = cast(uint)j; } } auto elements = new Expressions(edim); elements.zero(); size_t j = 0; foreach (i; 0 .. init.value.length) { if (auto e = init.index[i]) j = cast(size_t)e.toInteger(); assert(j < edim); if (Initializer iz = init.value[i]) { if (Expression ex = iz.initializerToExpression(null, isCfile)) { (*elements)[j] = ex; ++j; } else return null; } else return null; } /* Fill in any missing elements with the default initializer */ Expression defaultInit = null; // lazily create it foreach (ref element; (*elements)[0 .. edim]) { if (!element) { if (!init.type) // don't know what type to use return null; if (!defaultInit) defaultInit = (cast(TypeNext)t).next.defaultInit(Loc.initial, isCfile); element = defaultInit; } } /* Expand any static array initializers that are a single expression * into an array of them * e => [e, e, ..., e, e] */ if (t) { Type tn = t.nextOf().toBasetype(); if (tn.ty == Tsarray) { const dim = cast(size_t)(cast(TypeSArray)tn).dim.toInteger(); Type te = tn.nextOf().toBasetype(); foreach (ref e; *elements) { if (te.equals(e.type)) { auto elements2 = new Expressions(dim); foreach (ref e2; *elements2) e2 = e; e = new ArrayLiteralExp(e.loc, tn, elements2); } } } } /* If any elements are errors, then the whole thing is an error */ foreach (e; (*elements)[0 .. edim]) { if (e.op == EXP.error) { return e; } } Expression e = new ArrayLiteralExp(init.loc, init.type, elements); return e; } Expression visitExp(ExpInitializer i) { if (itype) { //printf("ExpInitializer::toExpression(t = %s) exp = %s\n", itype.toChars(), i.exp.toChars()); Type tb = itype.toBasetype(); Expression e = (i.exp.op == EXP.construct || i.exp.op == EXP.blit) ? (cast(AssignExp)i.exp).e2 : i.exp; if (tb.ty == Tsarray && e.implicitConvTo(tb.nextOf())) { TypeSArray tsa = cast(TypeSArray)tb; size_t d = cast(size_t)tsa.dim.toInteger(); auto elements = new Expressions(d); for (size_t j = 0; j < d; j++) (*elements)[j] = e; auto ae = new ArrayLiteralExp(e.loc, itype, elements); return ae; } } return i.exp; } Expression visitC(CInitializer i) { //printf("CInitializer.initializerToExpression(null, true)\n"); return null; } mixin VisitInitializer!Expression visit; return visit.VisitInitializer(init); } /************************************** * Determine if expression has non-constant pointers, or more precisely, * a pointer that CTFE cannot handle. * Params: * e = expression to check * Returns: * true if it has non-constant pointers */ private bool hasNonConstPointers(Expression e) { static bool checkArray(Expressions* elems) { foreach (e; *elems) { if (e && hasNonConstPointers(e)) return true; } return false; } if (e.type.ty == Terror) return false; if (e.op == EXP.null_) return false; if (auto se = e.isStructLiteralExp()) { return checkArray(se.elements); } if (auto ae = e.isArrayLiteralExp()) { if (!ae.type.nextOf().hasPointers()) return false; return checkArray(ae.elements); } if (auto ae = e.isAssocArrayLiteralExp()) { if (ae.type.nextOf().hasPointers() && checkArray(ae.values)) return true; if (ae.type.isTypeAArray().index.hasPointers()) return checkArray(ae.keys); return false; } if (auto ae = e.isAddrExp()) { if (ae.type.nextOf().isImmutable() || ae.type.nextOf().isConst()) { return false; } if (auto se = ae.e1.isStructLiteralExp()) { if (!(se.stageflags & stageSearchPointers)) { const old = se.stageflags; se.stageflags |= stageSearchPointers; bool ret = checkArray(se.elements); se.stageflags = old; return ret; } else { return false; } } return true; } if (e.type.ty == Tpointer && !e.type.isPtrToFunction()) { if (e.op == EXP.symbolOffset) // address of a global is OK return false; if (e.op == EXP.int64) // cast(void *)int is OK return false; if (e.op == EXP.string_) // "abc".ptr is OK return false; return true; } return false; } /** Given the names and values of a `StructInitializer` or `CallExp`, resolve it to a list of expressions to construct a `StructLiteralExp`. Params: sd = struct t = type of struct (potentially including qualifiers such as `const` or `immutable`) sc = scope of the expression initializing the struct iloc = location of expression initializing the struct names = identifiers passed in argument list, `null` entries for positional arguments getExp = function that, given an index into `names` and destination type, returns the initializing expression getLoc = function that, given an index into `names`, returns a location for error messages Returns: list of expressions ordered to the struct's fields, or `null` on error */ Expressions* resolveStructLiteralNamedArgs(StructDeclaration sd, Type t, Scope* sc, Loc iloc, Identifier[] names, scope Expression delegate(size_t i, Type fieldType) getExp, scope Loc delegate(size_t i) getLoc ) { //expandTuples for non-identity arguments? const nfields = sd.nonHiddenFields(); auto elements = new Expressions(nfields); auto elems = (*elements)[]; foreach (ref elem; elems) elem = null; // Run semantic for explicitly given initializers // TODO: this part is slightly different from StructLiteralExp::semantic. bool errors = false; size_t fieldi = 0; foreach (j, id; names) { const argLoc = getLoc(j); if (id) { // Determine `fieldi` that `id` matches Dsymbol s = sd.search(iloc, id); if (!s) { s = sd.search_correct(id); if (s) error(argLoc, "`%s` is not a member of `%s`, did you mean %s `%s`?", id.toChars(), sd.toChars(), s.kind(), s.toChars()); else error(argLoc, "`%s` is not a member of `%s`", id.toChars(), sd.toChars()); return null; } s.checkDeprecated(iloc, sc); s = s.toAlias(); // Find out which field index `s` is for (fieldi = 0; 1; fieldi++) { if (fieldi >= nfields) { error(iloc, "`%s.%s` is not a per-instance initializable field", sd.toChars(), s.toChars()); return null; } if (s == sd.fields[fieldi]) break; } } if (nfields == 0) { error(argLoc, "initializer provided for struct `%s` with no fields", sd.toChars()); return null; } if (j >= nfields) { error(argLoc, "too many initializers for `%s` with %d field%s", sd.toChars(), cast(int) nfields, nfields != 1 ? "s".ptr : "".ptr); return null; } if (fieldi >= nfields) { error(argLoc, "trying to initialize past the last field `%s` of `%s`", sd.fields[nfields - 1].toChars(), sd.toChars()); return null; } VarDeclaration vd = sd.fields[fieldi]; if (elems[fieldi]) { error(argLoc, "duplicate initializer for field `%s`", vd.toChars()); errors = true; elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors ++fieldi; continue; } // Check for @safe violations if (vd.type.hasPointers) { if ((!t.alignment.isDefault() && t.alignment.get() < target.ptrsize || (vd.offset & (target.ptrsize - 1)))) { if (sc.setUnsafe(false, argLoc, "field `%s.%s` cannot assign to misaligned pointers in `@safe` code", sd, vd)) { errors = true; elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors ++fieldi; continue; } } } // Check for overlapping initializations (can happen with unions) foreach (k, v2; sd.fields[0 .. nfields]) { if (vd.isOverlappedWith(v2) && elems[k]) { error(elems[k].loc, "overlapping initialization for field `%s` and `%s`", v2.toChars(), vd.toChars()); enum errorMsg = "`struct` initializers that contain anonymous unions" ~ " must initialize only the first member of a `union`. All subsequent" ~ " non-overlapping fields are default initialized"; if (!sd.isUnionDeclaration()) .errorSupplemental(elems[k].loc, errorMsg); errors = true; continue; } } assert(sc); auto ex = getExp(j, vd.type); if (ex.op == EXP.error) { errors = true; elems[fieldi] = ErrorExp.get(); // for better diagnostics on multiple errors ++fieldi; continue; } elems[fieldi] = doCopyOrMove(sc, ex); ++fieldi; } if (errors) return null; return elements; } ldc-1.40.0-src/dmd/enumsem.d0000644000000000000000000005571514727557031014233 0ustar rootroot/** * Does the semantic passes on enums. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/enumsem.d, _enumsem.d) * Documentation: https://dlang.org/phobos/dmd_enumsem.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/enumsem.d */ module dmd.enumsem; import core.stdc.stdio; import core.stdc.string; import dmd.aggregate; import dmd.aliasthis; import dmd.arraytypes; import dmd.astcodegen; import dmd.astenums; import dmd.attrib; import dmd.blockexit; import dmd.clone; import dmd.cond; import dmd.compiler; import dmd.dcast; import dmd.dclass; import dmd.declaration; import dmd.denum; import dmd.dimport; import dmd.dinterpret; import dmd.dmangle; import dmd.dmodule; import dmd.dscope; import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.dversion; import dmd.errors; import dmd.escape; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.importc; import dmd.init; import dmd.initsem; import dmd.intrange; import dmd.hdrgen; import dmd.location; import dmd.mtype; import dmd.mustuse; import dmd.nogc; import dmd.nspace; import dmd.objc; import dmd.opover; import dmd.optimize; import dmd.parse; import dmd.root.array; import dmd.root.filename; import dmd.common.outbuffer; import dmd.root.rmem; import dmd.rootobject; import dmd.root.utf; import dmd.semantic2; import dmd.semantic3; import dmd.sideeffect; import dmd.statementsem; import dmd.staticassert; import dmd.tokens; import dmd.utils; import dmd.statement; import dmd.target; import dmd.templateparamsem; import dmd.typesem; import dmd.visitor; /********************************* * Perform semantic analysis on enum declaration `em` */ void enumSemantic(Scope* sc, EnumDeclaration ed) { //printf("EnumDeclaration::semantic(sd = %p, '%s') %s\n", sc.scopesym, sc.scopesym.toChars(), ed.toChars()); //printf("EnumDeclaration::semantic() %p %s\n", ed, ed.toChars()); if (ed.semanticRun >= PASS.semanticdone) return; // semantic() already completed if (ed.semanticRun == PASS.semantic) { assert(ed.memtype); error(ed.loc, "circular reference to enum base type `%s`", ed.memtype.toChars()); ed.errors = true; ed.semanticRun = PASS.semanticdone; return; } Scope* scx = null; if (ed._scope) { sc = ed._scope; scx = ed._scope; // save so we don't make redundant copies ed._scope = null; } if (!sc) return; ed.parent = sc.parent; ed.type = ed.type.typeSemantic(ed.loc, sc); ed.visibility = sc.visibility; if (sc.stc & STC.deprecated_) ed.isdeprecated = true; ed.userAttribDecl = sc.userAttribDecl; ed.cppnamespace = sc.namespace; ed.semanticRun = PASS.semantic; UserAttributeDeclaration.checkGNUABITag(ed, sc.linkage); checkMustUseReserved(ed); if (!ed.members && !ed.memtype) // enum ident; { ed.semanticRun = PASS.semanticdone; return; } if (!ed.symtab) ed.symtab = new DsymbolTable(); /* The separate, and distinct, cases are: * 1. enum { ... } * 2. enum : memtype { ... } * 3. enum ident { ... } * 4. enum ident : memtype { ... } * 5. enum ident : memtype; * 6. enum ident; */ if (ed.memtype) { ed.memtype = ed.memtype.typeSemantic(ed.loc, sc); /* Check to see if memtype is forward referenced */ if (auto te = ed.memtype.isTypeEnum()) { auto sym = te.toDsymbol(sc).isEnumDeclaration(); // Special enums like __c_[u]long[long] are fine to forward reference // see https://issues.dlang.org/show_bug.cgi?id=20599 if (!sym.isSpecial() && (!sym.memtype || !sym.members || !sym.symtab || sym._scope)) { // memtype is forward referenced, so try again later deferDsymbolSemantic(sc, ed, scx); //printf("\tdeferring %s\n", toChars()); ed.semanticRun = PASS.initial; return; } else // Ensure that semantic is run to detect. e.g. invalid forward references sym.dsymbolSemantic(sc); } if (ed.memtype.ty == Tvoid) { .error(ed.loc, "%s `%s` base type must not be `void`", ed.kind, ed.toPrettyChars); ed.memtype = Type.terror; } if (ed.memtype.ty == Terror) { ed.errors = true; // poison all the members ed.members.foreachDsymbol( (s) { s.errors = true; } ); ed.semanticRun = PASS.semanticdone; return; } } if (!ed.members) // enum ident : memtype; { ed.semanticRun = PASS.semanticdone; return; } if (ed.members.length == 0) { .error(ed.loc, "%s `%s` enum `%s` must have at least one member", ed.kind, ed.toPrettyChars, ed.toChars()); ed.errors = true; ed.semanticRun = PASS.semanticdone; return; } if (!(sc.flags & SCOPE.Cfile)) // C enum remains incomplete until members are done ed.semanticRun = PASS.semanticdone; version (none) { // @@@DEPRECATED_2.110@@@ https://dlang.org/deprecate.html#scope%20as%20a%20type%20constraint // Deprecated in 2.100 // Make an error in 2.110 if (sc.stc & STC.scope_) deprecation(ed.loc, "`scope` as a type constraint is deprecated. Use `scope` at the usage site."); } Scope* sce; if (ed.isAnonymous()) sce = sc; else { sce = sc.push(ed); sce.parent = ed; } sce = sce.startCTFE(); sce.setNoFree(); // needed for getMaxMinValue() /* Each enum member gets the sce scope */ ed.members.foreachDsymbol( (s) { EnumMember em = s.isEnumMember(); if (em) em._scope = sce; }); /* addMember() is not called when the EnumDeclaration appears as a function statement, * so we have to do what addMember() does and install the enum members in the right symbol * table */ addEnumMembersToSymtab(ed, sc, sc.getScopesym()); if (sc.flags & SCOPE.Cfile) { /* C11 6.7.2.2 */ Type commonType = ed.memtype; if (!commonType) commonType = Type.tint32; ulong nextValue = 0; // C11 6.7.2.2-3 first member value defaults to 0 // C11 6.7.2.2-2 value must be representable as an int. // The sizemask represents all values that int will fit into, // from 0..uint.max. We want to cover int.min..uint.max. IntRange ir = IntRange.fromType(commonType); void emSemantic(EnumMember em, ref ulong nextValue) { static void errorReturn(EnumMember em) { em.value = ErrorExp.get(); em.errors = true; em.semanticRun = PASS.semanticdone; } em.semanticRun = PASS.semantic; em.type = commonType; em._linkage = LINK.c; em.storage_class |= STC.manifest; if (em.value) { Expression e = em.value; assert(e.dyncast() == DYNCAST.expression); /* To merge the type of e with commonType, add 0 of type commonType */ if (!ed.memtype) e = new AddExp(em.loc, e, new IntegerExp(em.loc, 0, commonType)); e = e.expressionSemantic(sc); e = resolveProperties(sc, e); e = e.integralPromotions(sc); e = e.ctfeInterpret(); if (e.op == EXP.error) return errorReturn(em); auto ie = e.isIntegerExp(); if (!ie) { // C11 6.7.2.2-2 .error(em.loc, "%s `%s` enum member must be an integral constant expression, not `%s` of type `%s`", em.kind, em.toPrettyChars, e.toChars(), e.type.toChars()); return errorReturn(em); } if (ed.memtype && !ir.contains(getIntRange(ie))) { // C11 6.7.2.2-2 .error(em.loc, "%s `%s` enum member value `%s` does not fit in `%s`", em.kind, em.toPrettyChars, e.toChars(), commonType.toChars()); return errorReturn(em); } nextValue = ie.toInteger(); if (!ed.memtype) commonType = e.type; em.value = new IntegerExp(em.loc, nextValue, commonType); } else { // C11 6.7.2.2-3 add 1 to value of previous enumeration constant bool first = (em == (*em.ed.members)[0]); if (!first) { Expression max = getProperty(commonType, null, em.loc, Id.max, 0); if (nextValue == max.toInteger()) { .error(em.loc, "%s `%s` initialization with `%s+1` causes overflow for type `%s`", em.kind, em.toPrettyChars, max.toChars(), commonType.toChars()); return errorReturn(em); } nextValue += 1; } em.value = new IntegerExp(em.loc, nextValue, commonType); } em.type = commonType; em.semanticRun = PASS.semanticdone; } ed.members.foreachDsymbol( (s) { if (EnumMember em = s.isEnumMember()) emSemantic(em, nextValue); }); if (!ed.memtype) { // cast all members to commonType ed.members.foreachDsymbol( (s) { if (EnumMember em = s.isEnumMember()) { em.type = commonType; // optimize out the cast so that other parts of the compiler can // assume that an integral enum's members are `IntegerExp`s. // https://issues.dlang.org/show_bug.cgi?id=24504 em.value = em.value.castTo(sc, commonType).optimize(WANTvalue); } }); } ed.memtype = commonType; ed.semanticRun = PASS.semanticdone; return; } ed.members.foreachDsymbol( (s) { if (EnumMember em = s.isEnumMember()) em.dsymbolSemantic(em._scope); }); //printf("ed.defaultval = %lld\n", ed.defaultval); //if (ed.defaultval) printf("ed.defaultval: %s %s\n", ed.defaultval.toChars(), ed.defaultval.type.toChars()); //printf("members = %s\n", members.toChars()); } Expression getDefaultValue(EnumDeclaration ed, const ref Loc loc) { Expression handleErrors(){ ed.defaultval = ErrorExp.get(); return ed.defaultval; } //printf("EnumDeclaration::getDefaultValue() %p %s\n", this, toChars()); // https://issues.dlang.org/show_bug.cgi?id=23904 // Return ed.defaultval only if it is not ErrorExp. // A speculative context may set ed.defaultval to ErrorExp; // subsequent non-speculative contexts need to be able // to print the error. if (ed.defaultval && !ed.defaultval.isErrorExp()) return ed.defaultval; if (ed.isCsymbol()) return ed.memtype.defaultInit(loc, true); if (ed._scope) dsymbolSemantic(ed, ed._scope); if (ed.errors) return handleErrors(); if (!ed.members) { if (ed.isSpecial()) { /* Allow these special enums to not need a member list */ return ed.defaultval = ed.memtype.defaultInit(loc); } error(loc, "%s `%s` is opaque and has no default initializer", ed.kind, ed.toPrettyChars); return handleErrors(); } foreach (const i; 0 .. ed.members.length) { EnumMember em = (*ed.members)[i].isEnumMember(); if (em) { if (em.semanticRun < PASS.semanticdone) { error(loc, "%s `%s` forward reference of `%s.init`", ed.kind, ed.toPrettyChars, ed.toChars()); return handleErrors(); } ed.defaultval = em.value; return ed.defaultval; } } return handleErrors(); } Type getMemtype(EnumDeclaration ed, const ref Loc loc) { if (ed._scope) { /* Enum is forward referenced. We don't need to resolve the whole thing, * just the base type */ if (ed.memtype) { Loc locx = loc.isValid() ? loc : ed.loc; ed.memtype = ed.memtype.typeSemantic(locx, ed._scope); } else { // Run semantic to get the type from a possible first member value dsymbolSemantic(ed, ed._scope); } } if (!ed.memtype) { if (!ed.isAnonymous() && (ed.members || ed.semanticRun >= PASS.semanticdone)) ed.memtype = Type.tint32; else { Loc locx = loc.isValid() ? loc : ed.loc; error(locx, "is forward referenced looking for base type"); return Type.terror; } } return ed.memtype; } /********************************* * Perform semantic analysis on enum member `em` */ void enumMemberSemantic(Scope* sc, EnumMember em) { //printf("EnumMember::semantic() %s\n", em.toChars()); void errorReturn() { em.errors = true; em.semanticRun = PASS.semanticdone; } if (em.errors || em.semanticRun >= PASS.semanticdone) return; if (em.semanticRun == PASS.semantic) { .error(em.loc, "%s `%s` circular reference to `enum` member", em.kind, em.toPrettyChars); return errorReturn(); } assert(em.ed); em.ed.dsymbolSemantic(sc); if (em.ed.errors) return errorReturn(); if (em.errors || em.semanticRun >= PASS.semanticdone) return; if (em._scope) sc = em._scope; if (!sc) return; em.semanticRun = PASS.semantic; em.visibility = em.ed.isAnonymous() ? em.ed.visibility : Visibility(Visibility.Kind.public_); em._linkage = LINK.d; em.storage_class |= STC.manifest; // https://issues.dlang.org/show_bug.cgi?id=9701 if (em.ed.isAnonymous()) { if (em.userAttribDecl) em.userAttribDecl.userAttribDecl = em.ed.userAttribDecl; else em.userAttribDecl = em.ed.userAttribDecl; } // Eval UDA in this same scope. Issues 19344, 20835, 21122 if (em.userAttribDecl) { // Set scope but avoid extra sc.uda attachment inside setScope() auto inneruda = em.userAttribDecl.userAttribDecl; em.userAttribDecl.setScope(sc); em.userAttribDecl.userAttribDecl = inneruda; em.userAttribDecl.dsymbolSemantic(sc); } // The first enum member is special bool first = (em == (*em.ed.members)[0]); if (em.origType) { em.origType = em.origType.typeSemantic(em.loc, sc); em.type = em.origType; assert(em.value); // "type id;" is not a valid enum member declaration } if (em.value) { Expression e = em.value; assert(e.dyncast() == DYNCAST.expression); if (em.ed.memtype) e = inferType(e, em.ed.memtype); e = e.expressionSemantic(sc); e = resolveProperties(sc, e); e = e.ctfeInterpret(); if (e.op == EXP.error) return errorReturn(); if (first && !em.ed.memtype && !em.ed.isAnonymous()) { em.ed.memtype = e.type; if (em.ed.memtype.ty == Terror) { em.ed.errors = true; return errorReturn(); } if (em.ed.memtype.ty != Terror) { /* https://issues.dlang.org/show_bug.cgi?id=11746 * All of named enum members should have same type * with the first member. If the following members were referenced * during the first member semantic, their types should be unified. */ em.ed.members.foreachDsymbol( (s) { EnumMember enm = s.isEnumMember(); if (!enm || enm == em || enm.semanticRun < PASS.semanticdone || enm.origType) return; //printf("[%d] em = %s, em.semanticRun = %d\n", i, toChars(), em.semanticRun); Expression ev = enm.value; ev = ev.implicitCastTo(sc, em.ed.memtype); ev = ev.ctfeInterpret(); ev = ev.castTo(sc, em.ed.type); if (ev.op == EXP.error) em.ed.errors = true; enm.value = ev; }); if (em.ed.errors) { em.ed.memtype = Type.terror; return errorReturn(); } } } if (em.ed.memtype && !em.origType) { e = e.implicitCastTo(sc, em.ed.memtype); e = e.ctfeInterpret(); // save origValue for better json output em.origValue = e; if (!em.ed.isAnonymous()) { e = e.castTo(sc, em.ed.type.addMod(e.type.mod)); // https://issues.dlang.org/show_bug.cgi?id=12385 e = e.ctfeInterpret(); } } else if (em.origType) { e = e.implicitCastTo(sc, em.origType); e = e.ctfeInterpret(); assert(em.ed.isAnonymous()); // save origValue for better json output em.origValue = e; } em.value = e; // https://issues.dlang.org/show_bug.cgi?id=24311 // First enum member is .init value, which gets put into static segment if (first) lowerStaticAAs(e, sc); } else if (first) { Type t; if (em.ed.memtype) t = em.ed.memtype; else { t = Type.tint32; if (!em.ed.isAnonymous()) em.ed.memtype = t; } const errors = global.startGagging(); Expression e = new IntegerExp(em.loc, 0, t); e = e.ctfeInterpret(); if (global.endGagging(errors)) { error(em.loc, "cannot generate 0 value of type `%s` for `%s`", t.toChars(), em.toChars()); } // save origValue for better json output em.origValue = e; if (!em.ed.isAnonymous()) { e = e.castTo(sc, em.ed.type); e = e.ctfeInterpret(); } em.value = e; } else { /* Find the previous enum member, * and set this to be the previous value + 1 */ EnumMember emprev = null; em.ed.members.foreachDsymbol( (s) { if (auto enm = s.isEnumMember()) { if (enm == em) return 1; // found emprev = enm; } return 0; // continue }); assert(emprev); if (emprev.semanticRun < PASS.semanticdone) // if forward reference emprev.dsymbolSemantic(emprev._scope); // resolve it if (emprev.errors) return errorReturn(); auto errors = global.startGagging(); Expression eprev = emprev.value; assert(eprev); // .toHeadMutable() due to https://issues.dlang.org/show_bug.cgi?id=18645 Type tprev = eprev.type.toHeadMutable().equals(em.ed.type.toHeadMutable()) ? em.ed.memtype : eprev.type; /* https://issues.dlang.org/show_bug.cgi?id=20777 Previously this used getProperty, which doesn't consider anything user defined, this construct does do that and thus fixes the bug. */ Expression emax = DotIdExp.create(em.ed.loc, new TypeExp(em.ed.loc, tprev), Id.max); emax = emax.expressionSemantic(sc); emax = emax.ctfeInterpret(); // check that (eprev != emax) Expression e = new EqualExp(EXP.equal, em.loc, eprev, emax); e = e.expressionSemantic(sc); e = e.ctfeInterpret(); if (global.endGagging(errors)) { // display an introductory error before showing what actually failed error(em.loc, "cannot check `%s` value for overflow", em.toPrettyChars()); // rerun to show errors Expression e2 = DotIdExp.create(em.ed.loc, new TypeExp(em.ed.loc, tprev), Id.max); e2 = e2.expressionSemantic(sc); e2 = e2.ctfeInterpret(); e2 = new EqualExp(EXP.equal, em.loc, eprev, e2); e2 = e2.expressionSemantic(sc); e2 = e2.ctfeInterpret(); } // now any errors are for generating a value if (e.toInteger()) { auto mt = em.ed.memtype; if (!mt) mt = eprev.type; .error(em.loc, "%s `%s` initialization with `%s.%s+1` causes overflow for type `%s`", em.kind, em.toPrettyChars, emprev.ed.toChars(), emprev.toChars(), mt.toChars()); return errorReturn(); } errors = global.startGagging(); // Now set e to (eprev + 1) e = new AddExp(em.loc, eprev, IntegerExp.literal!1); e = e.expressionSemantic(sc); e = e.castTo(sc, eprev.type); e = e.ctfeInterpret(); if (global.endGagging(errors)) { error(em.loc, "cannot generate value for `%s`", em.toPrettyChars()); // rerun to show errors Expression e2 = new AddExp(em.loc, eprev, IntegerExp.literal!1); e2 = e2.expressionSemantic(sc); e2 = e2.castTo(sc, eprev.type); e2 = e2.ctfeInterpret(); } // save origValue (without cast) for better json output if (e.op != EXP.error) // avoid duplicate diagnostics { assert(emprev.origValue); em.origValue = new AddExp(em.loc, emprev.origValue, IntegerExp.literal!1); em.origValue = em.origValue.expressionSemantic(sc); em.origValue = em.origValue.ctfeInterpret(); } if (e.op == EXP.error) return errorReturn(); if (e.type.isfloating()) { // Check that e != eprev (not always true for floats) Expression etest = new EqualExp(EXP.equal, em.loc, e, eprev); etest = etest.expressionSemantic(sc); etest = etest.ctfeInterpret(); if (etest.toInteger()) { .error(em.loc, "%s `%s` has inexact value due to loss of precision", em.kind, em.toPrettyChars); return errorReturn(); } } em.value = e; } if (!em.origType) em.type = em.value.type; assert(em.origValue); em.semanticRun = PASS.semanticdone; } ldc-1.40.0-src/dmd/dtemplate.d0000644000000000000000000066544014727557031014543 0ustar rootroot/** * Defines `TemplateDeclaration`, `TemplateInstance` and a few utilities * * This modules holds the two main template types: * `TemplateDeclaration`, which is the user-provided declaration of a template, * and `TemplateInstance`, which is an instance of a `TemplateDeclaration` * with specific arguments. * * Template_Parameter: * Additionally, the classes for template parameters are defined in this module. * The base class, `TemplateParameter`, is inherited by: * - `TemplateTypeParameter` * - `TemplateThisParameter` * - `TemplateValueParameter` * - `TemplateAliasParameter` * - `TemplateTupleParameter` * * Templates_semantic: * The start of the template instantiation process looks like this: * - A `TypeInstance` or `TypeIdentifier` is encountered. * `TypeInstance` have a bang (e.g. `Foo!(arg)`) while `TypeIdentifier` don't. * - A `TemplateInstance` is instantiated * - Semantic is run on the `TemplateInstance` (see `dmd.dsymbolsem`) * - The `TemplateInstance` search for its `TemplateDeclaration`, * runs semantic on the template arguments and deduce the best match * among the possible overloads. * - The `TemplateInstance` search for existing instances with the same * arguments, and uses it if found. * - Otherwise, the rest of semantic is run on the `TemplateInstance`. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dtemplate.d, _dtemplate.d) * Documentation: https://dlang.org/phobos/dmd_dtemplate.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dtemplate.d */ module dmd.dtemplate; import core.stdc.stdio; import core.stdc.string; import dmd.aggregate; import dmd.aliasthis; import dmd.arraytypes; import dmd.astenums; import dmd.ast_node; import dmd.attrib; import dmd.dcast; import dmd.dclass; import dmd.declaration; import dmd.dinterpret; import dmd.dmangle; import dmd.dmodule; import dmd.dscope; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.errors; import dmd.errorsink; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.impcnvtab; import dmd.init; import dmd.initsem; import dmd.location; import dmd.mtype; import dmd.opover; import dmd.optimize; import dmd.root.array; import dmd.common.outbuffer; import dmd.rootobject; import dmd.semantic2; import dmd.semantic3; import dmd.templatesem; import dmd.tokens; import dmd.typesem; import dmd.visitor; import dmd.templateparamsem; //debug = FindExistingInstance; // print debug stats of findExistingInstance private enum LOG = false; enum IDX_NOTFOUND = 0x12345678; pure nothrow @nogc @trusted { /******************************************** * These functions substitute for dynamic_cast. dynamic_cast does not work * on earlier versions of gcc. */ inout(Expression) isExpression(inout RootObject o) { //return dynamic_cast(o); if (!o || o.dyncast() != DYNCAST.expression) return null; return cast(inout(Expression))o; } inout(Dsymbol) isDsymbol(inout RootObject o) { //return dynamic_cast(o); if (!o || o.dyncast() != DYNCAST.dsymbol) return null; return cast(inout(Dsymbol))o; } inout(Type) isType(inout RootObject o) { //return dynamic_cast(o); if (!o || o.dyncast() != DYNCAST.type) return null; return cast(inout(Type))o; } inout(Tuple) isTuple(inout RootObject o) { //return dynamic_cast(o); if (!o || o.dyncast() != DYNCAST.tuple) return null; return cast(inout(Tuple))o; } inout(Parameter) isParameter(inout RootObject o) { //return dynamic_cast(o); if (!o || o.dyncast() != DYNCAST.parameter) return null; return cast(inout(Parameter))o; } inout(TemplateParameter) isTemplateParameter(inout RootObject o) { if (!o || o.dyncast() != DYNCAST.templateparameter) return null; return cast(inout(TemplateParameter))o; } } // end @trusted casts pure nothrow @nogc @safe { /************************************** * Is this Object an error? */ bool isError(const RootObject o) { if (const t = isType(o)) return (t.ty == Terror); if (const e = isExpression(o)) return (e.op == EXP.error || !e.type || e.type.ty == Terror); if (const v = isTuple(o)) return arrayObjectIsError(v.objects); const s = isDsymbol(o); assert(s); if (s.errors) return true; return s.parent ? isError(s.parent) : false; } /************************************** * Are any of the Objects an error? */ bool arrayObjectIsError(const ref Objects args) { foreach (const o; args) { if (isError(o)) return true; } return false; } /*********************** * Try to get arg as a type. */ inout(Type) getType(inout RootObject o) { inout t = isType(o); if (!t) { if (inout e = isExpression(o)) return e.type; } return t; } } /*********************************** * If oarg represents a Dsymbol, return that Dsymbol * Params: * oarg = argument to check * Returns: * Dsymbol if a symbol, null if not */ Dsymbol getDsymbol(RootObject oarg) { //printf("getDsymbol()\n"); //printf("e %p s %p t %p v %p\n", isExpression(oarg), isDsymbol(oarg), isType(oarg), isTuple(oarg)); if (auto ea = isExpression(oarg)) { // Try to convert Expression to symbol if (auto ve = ea.isVarExp()) return ve.var; else if (auto fe = ea.isFuncExp()) return fe.td ? fe.td : fe.fd; else if (auto te = ea.isTemplateExp()) return te.td; else if (auto te = ea.isScopeExp()) return te.sds; else return null; } else { // Try to convert Type to symbol if (auto ta = isType(oarg)) return ta.toDsymbol(null); else return isDsymbol(oarg); // if already a symbol } } private Expression getValue(ref Dsymbol s) { if (s) { if (VarDeclaration v = s.isVarDeclaration()) { if (v.storage_class & STC.manifest) return v.getConstInitializer(); } } return null; } /*********************** * Try to get value from manifest constant */ private Expression getValue(Expression e) { if (!e) return null; if (auto ve = e.isVarExp()) { if (auto v = ve.var.isVarDeclaration()) { if (v.storage_class & STC.manifest) { e = v.getConstInitializer(); } } } return e; } private Expression getExpression(RootObject o) { auto s = isDsymbol(o); return s ? .getValue(s) : .getValue(isExpression(o)); } /****************************** * See if two objects match * Params: * o1 = first object * o2 = second object * Returns: true if they match */ private bool match(RootObject o1, RootObject o2) { enum log = false; static if (log) { printf("match() o1 = %p %s (%d), o2 = %p %s (%d)\n", o1, o1.toChars(), o1.dyncast(), o2, o2.toChars(), o2.dyncast()); } bool yes() { static if (log) printf("\t. match\n"); return true; } bool no() { static if (log) printf("\t. nomatch\n"); return false; } /* A proper implementation of the various equals() overrides * should make it possible to just do o1.equals(o2), but * we'll do that another day. */ /* Manifest constants should be compared by their values, * at least in template arguments. */ if (auto t1 = isType(o1)) { auto t2 = isType(o2); if (!t2) return no(); static if (log) { printf("\tt1 = %s\n", t1.toChars()); printf("\tt2 = %s\n", t2.toChars()); } if (!t1.equals(t2)) return no(); return yes(); } if (auto e1 = getExpression(o1)) { auto e2 = getExpression(o2); if (!e2) return no(); static if (log) { printf("\te1 = %s '%s' %s\n", e1.type ? e1.type.toChars() : "null", EXPtoString(e1.op).ptr, e1.toChars()); printf("\te2 = %s '%s' %s\n", e2.type ? e2.type.toChars() : "null", EXPtoString(e2.op).ptr, e2.toChars()); } // two expressions can be equal although they do not have the same // type; that happens when they have the same value. So check type // as well as expression equality to ensure templates are properly // matched. if (!(e1.type && e2.type && e1.type.equals(e2.type)) || !e1.equals(e2)) return no(); return yes(); } if (auto s1 = isDsymbol(o1)) { auto s2 = isDsymbol(o2); if (!s2) return no(); static if (log) { printf("\ts1 = %s \n", s1.kind(), s1.toChars()); printf("\ts2 = %s \n", s2.kind(), s2.toChars()); } if (!s1.equals(s2)) return no(); if (s1.parent != s2.parent && !s1.isFuncDeclaration() && !s2.isFuncDeclaration()) return no(); return yes(); } if (auto u1 = isTuple(o1)) { auto u2 = isTuple(o2); if (!u2) return no(); static if (log) { printf("\tu1 = %s\n", u1.toChars()); printf("\tu2 = %s\n", u2.toChars()); } if (!arrayObjectMatch(u1.objects, u2.objects)) return no(); return yes(); } return yes(); } /************************************ * Match an array of them. */ bool arrayObjectMatch(ref Objects oa1, ref Objects oa2) { if (&oa1 == &oa2) return true; if (oa1.length != oa2.length) return false; immutable oa1dim = oa1.length; auto oa1d = oa1[].ptr; auto oa2d = oa2[].ptr; foreach (j; 0 .. oa1dim) { RootObject o1 = oa1d[j]; RootObject o2 = oa2d[j]; if (!match(o1, o2)) { return false; } } return true; } /************************************ * Return hash of Objects. */ private size_t arrayObjectHash(ref Objects oa1) { import dmd.root.hash : mixHash; size_t hash = 0; foreach (o1; oa1) { /* Must follow the logic of match() */ if (auto t1 = isType(o1)) hash = mixHash(hash, cast(size_t)t1.deco); else if (auto e1 = getExpression(o1)) hash = mixHash(hash, expressionHash(e1)); else if (auto s1 = isDsymbol(o1)) { auto fa1 = s1.isFuncAliasDeclaration(); if (fa1) s1 = fa1.toAliasFunc(); hash = mixHash(hash, mixHash(cast(size_t)cast(void*)s1.getIdent(), cast(size_t)cast(void*)s1.parent)); } else if (auto u1 = isTuple(o1)) hash = mixHash(hash, arrayObjectHash(u1.objects)); } return hash; } /************************************ * Computes hash of expression. * Handles all Expression classes and MUST match their equals method, * i.e. e1.equals(e2) implies expressionHash(e1) == expressionHash(e2). */ private size_t expressionHash(Expression e) { import dmd.root.ctfloat : CTFloat; import dmd.root.hash : calcHash, mixHash; switch (e.op) { case EXP.int64: return cast(size_t) e.isIntegerExp().getInteger(); case EXP.float64: return CTFloat.hash(e.isRealExp().value); case EXP.complex80: auto ce = e.isComplexExp(); return mixHash(CTFloat.hash(ce.toReal), CTFloat.hash(ce.toImaginary)); case EXP.identifier: return cast(size_t)cast(void*) e.isIdentifierExp().ident; case EXP.null_: return cast(size_t)cast(void*) e.isNullExp().type; case EXP.string_: return calcHash(e.isStringExp.peekData()); case EXP.tuple: { auto te = e.isTupleExp(); size_t hash = 0; hash += te.e0 ? expressionHash(te.e0) : 0; foreach (elem; *te.exps) hash = mixHash(hash, expressionHash(elem)); return hash; } case EXP.arrayLiteral: { auto ae = e.isArrayLiteralExp(); size_t hash; foreach (i; 0 .. ae.elements.length) hash = mixHash(hash, expressionHash(ae[i])); return hash; } case EXP.assocArrayLiteral: { auto ae = e.isAssocArrayLiteralExp(); size_t hash; foreach (i; 0 .. ae.keys.length) // reduction needs associative op as keys are unsorted (use XOR) hash ^= mixHash(expressionHash((*ae.keys)[i]), expressionHash((*ae.values)[i])); return hash; } case EXP.structLiteral: { auto se = e.isStructLiteralExp(); size_t hash; foreach (elem; *se.elements) hash = mixHash(hash, elem ? expressionHash(elem) : 0); return hash; } case EXP.variable: return cast(size_t)cast(void*) e.isVarExp().var; case EXP.function_: return cast(size_t)cast(void*) e.isFuncExp().fd; default: // no custom equals for this expression assert((&e.equals).funcptr is &RootObject.equals); // equals based on identity return cast(size_t)cast(void*) e; } } RootObject objectSyntaxCopy(RootObject o) { if (!o) return null; if (Type t = isType(o)) return t.syntaxCopy(); if (Expression e = isExpression(o)) return e.syntaxCopy(); return o; } extern (C++) final class Tuple : RootObject { Objects objects; extern (D) this() {} /** Params: numObjects = The initial number of objects. */ extern (D) this(size_t numObjects) { objects.setDim(numObjects); } // kludge for template.isType() override DYNCAST dyncast() const { return DYNCAST.tuple; } override const(char)* toChars() const { return objects.toChars(); } } struct TemplatePrevious { TemplatePrevious* prev; Scope* sc; Objects* dedargs; } /*********************************************************** * [mixin] template Identifier (parameters) [Constraint] * https://dlang.org/spec/template.html * https://dlang.org/spec/template-mixin.html */ extern (C++) final class TemplateDeclaration : ScopeDsymbol { import dmd.root.array : Array; TemplateParameters* parameters; // array of TemplateParameter's TemplateParameters* origParameters; // originals for Ddoc Expression constraint; // Hash table to look up TemplateInstance's of this TemplateDeclaration TemplateInstance[TemplateInstanceBox] instances; TemplateDeclaration overnext; // next overloaded TemplateDeclaration TemplateDeclaration overroot; // first in overnext list FuncDeclaration funcroot; // first function in unified overload list Dsymbol onemember; // if !=null then one member of this template bool literal; // this template declaration is a literal bool ismixin; // this is a mixin template declaration bool isstatic; // this is static template declaration bool isTrivialAliasSeq; /// matches pattern `template AliasSeq(T...) { alias AliasSeq = T; }` bool isTrivialAlias; /// matches pattern `template Alias(T) { alias Alias = qualifiers(T); }` bool deprecated_; /// this template declaration is deprecated Visibility visibility; // threaded list of previous instantiation attempts on stack TemplatePrevious* previous; version (IN_LLVM) { const(char)* intrinsicName; } Expression lastConstraint; /// the constraint after the last failed evaluation Array!Expression lastConstraintNegs; /// its negative parts Objects* lastConstraintTiargs; /// template instance arguments for `lastConstraint` extern (D) this(const ref Loc loc, Identifier ident, TemplateParameters* parameters, Expression constraint, Dsymbols* decldefs, bool ismixin = false, bool literal = false) { super(loc, ident); static if (LOG) { printf("TemplateDeclaration(this = %p, id = '%s')\n", this, ident.toChars()); } version (none) { if (parameters) for (int i = 0; i < parameters.length; i++) { TemplateParameter tp = (*parameters)[i]; //printf("\tparameter[%d] = %p\n", i, tp); TemplateTypeParameter ttp = tp.isTemplateTypeParameter(); if (ttp) { printf("\tparameter[%d] = %s : %s\n", i, tp.ident.toChars(), ttp.specType ? ttp.specType.toChars() : ""); } } } this.parameters = parameters; this.origParameters = parameters; this.constraint = constraint; this.members = decldefs; this.literal = literal; this.ismixin = ismixin; this.isstatic = true; this.visibility = Visibility(Visibility.Kind.undefined); // Compute in advance for Ddoc's use // https://issues.dlang.org/show_bug.cgi?id=11153: ident could be NULL if parsing fails. if (!members || !ident) return; Dsymbol s; if (!Dsymbol.oneMembers(members, s, ident) || !s) return; onemember = s; s.parent = this; /* Set isTrivialAliasSeq if this fits the pattern: * template AliasSeq(T...) { alias AliasSeq = T; } * or set isTrivialAlias if this fits the pattern: * template Alias(T) { alias Alias = qualifiers(T); } */ if (!(parameters && parameters.length == 1)) return; auto ad = s.isAliasDeclaration(); if (!ad || !ad.type) return; auto ti = ad.type.isTypeIdentifier(); if (!ti || ti.idents.length != 0) return; if (auto ttp = (*parameters)[0].isTemplateTupleParameter()) { if (ti.ident is ttp.ident && ti.mod == 0) { //printf("found isTrivialAliasSeq %s %s\n", s.toChars(), ad.type.toChars()); isTrivialAliasSeq = true; } } else if (auto ttp = (*parameters)[0].isTemplateTypeParameter()) { if (ti.ident is ttp.ident) { //printf("found isTrivialAlias %s %s\n", s.toChars(), ad.type.toChars()); isTrivialAlias = true; } } } override TemplateDeclaration syntaxCopy(Dsymbol) { //printf("TemplateDeclaration.syntaxCopy()\n"); TemplateParameters* p = null; if (parameters) { p = new TemplateParameters(parameters.length); foreach (i, ref param; *p) param = (*parameters)[i].syntaxCopy(); } version (IN_LLVM) { auto td = new TemplateDeclaration(loc, ident, p, constraint ? constraint.syntaxCopy() : null, Dsymbol.arraySyntaxCopy(members), ismixin, literal); td.intrinsicName = intrinsicName; return td; } else { return new TemplateDeclaration(loc, ident, p, constraint ? constraint.syntaxCopy() : null, Dsymbol.arraySyntaxCopy(members), ismixin, literal); } } /********************************** * Overload existing TemplateDeclaration 'this' with the new one 's'. * Params: * s = symbol to be inserted * Return: true if successful; i.e. no conflict. */ override bool overloadInsert(Dsymbol s) { static if (LOG) { printf("TemplateDeclaration.overloadInsert('%s')\n", s.toChars()); } FuncDeclaration fd = s.isFuncDeclaration(); if (fd) { if (funcroot) return funcroot.overloadInsert(fd); funcroot = fd; return funcroot.overloadInsert(this); } // https://issues.dlang.org/show_bug.cgi?id=15795 // if candidate is an alias and its sema is not run then // insertion can fail because the thing it alias is not known if (AliasDeclaration ad = s.isAliasDeclaration()) { if (s._scope) aliasSemantic(ad, s._scope); if (ad.aliassym && ad.aliassym is this) return false; } TemplateDeclaration td = s.toAlias().isTemplateDeclaration(); if (!td) return false; TemplateDeclaration pthis = this; TemplateDeclaration* ptd; for (ptd = &pthis; *ptd; ptd = &(*ptd).overnext) { } td.overroot = this; *ptd = td; static if (LOG) { printf("\ttrue: no conflict\n"); } return true; } override bool hasStaticCtorOrDtor() { return false; // don't scan uninstantiated templates } override const(char)* kind() const { return (onemember && onemember.isAggregateDeclaration()) ? onemember.kind() : "template"; } override const(char)* toChars() const { HdrGenState hgs; OutBuffer buf; toCharsMaybeConstraints(this, buf, hgs); return buf.extractChars(); } /**************************** * Similar to `toChars`, but does not print the template constraints */ const(char)* toCharsNoConstraints() const { HdrGenState hgs = { skipConstraints: true }; OutBuffer buf; toCharsMaybeConstraints(this, buf, hgs); return buf.extractChars(); } override Visibility visible() pure nothrow @nogc @safe { return visibility; } /**************************** * Destructively get the error message from the last constraint evaluation * Params: * tip = tip to show after printing all overloads */ const(char)* getConstraintEvalError(ref const(char)* tip) { import dmd.staticcond; // there will be a full tree view in verbose mode, and more compact list in the usual const full = global.params.v.verbose; uint count; const msg = visualizeStaticCondition(constraint, lastConstraint, lastConstraintNegs[], full, count); scope (exit) { lastConstraint = null; lastConstraintTiargs = null; lastConstraintNegs.setDim(0); } if (!msg) return null; OutBuffer buf; assert(parameters && lastConstraintTiargs); if (parameters.length > 0) { formatParamsWithTiargs(*parameters, *lastConstraintTiargs, isVariadic() !is null, buf); buf.writenl(); } if (!full) { // choosing singular/plural const s = (count == 1) ? " must satisfy the following constraint:" : " must satisfy one of the following constraints:"; buf.writestring(s); buf.writenl(); // the constraints buf.writeByte('`'); buf.writestring(msg); buf.writeByte('`'); } else { buf.writestring(" whose parameters have the following constraints:"); buf.writenl(); const sep = " `~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`"; buf.writestring(sep); buf.writenl(); // the constraints buf.writeByte('`'); buf.writestring(msg); buf.writeByte('`'); buf.writestring(sep); tip = "not satisfied constraints are marked with `>`"; } return buf.extractChars(); } debug (FindExistingInstance) { __gshared uint nFound, nNotFound, nAdded, nRemoved; shared static ~this() { printf("debug (FindExistingInstance) nFound %u, nNotFound: %u, nAdded: %u, nRemoved: %u\n", nFound, nNotFound, nAdded, nRemoved); } } /**************************************************** * Given a new instance `tithis` of this TemplateDeclaration, * see if there already exists an instance. * * Params: * tithis = template instance to check * argumentList = For function templates, needed because different * `auto ref` resolutions create different instances, * even when template parameters are identical * * Returns: that existing instance, or `null` when it doesn't exist */ extern (D) TemplateInstance findExistingInstance(TemplateInstance tithis, ArgumentList argumentList) { //printf("findExistingInstance() %s\n", tithis.toChars()); tithis.fargs = argumentList.arguments; tithis.fnames = argumentList.names; auto tibox = TemplateInstanceBox(tithis); auto p = tibox in this.instances; debug (FindExistingInstance) ++(p ? nFound : nNotFound); //if (p) printf("\tfound %p\n", *p); else printf("\tnot found\n"); return p ? *p : null; } /******************************************** * Add instance ti to TemplateDeclaration's table of instances. * Return a handle we can use to later remove it if it fails instantiation. */ extern (D) TemplateInstance addInstance(TemplateInstance ti) { //printf("addInstance() %p %s\n", instances, ti.toChars()); auto tibox = TemplateInstanceBox(ti); instances[tibox] = ti; debug (FindExistingInstance) ++nAdded; return ti; } /******************************************* * Remove TemplateInstance from table of instances. * Input: * handle returned by addInstance() */ extern (D) void removeInstance(TemplateInstance ti) { //printf("removeInstance() %s\n", ti.toChars()); auto tibox = TemplateInstanceBox(ti); debug (FindExistingInstance) ++nRemoved; instances.remove(tibox); } override inout(TemplateDeclaration) isTemplateDeclaration() inout { return this; } /** * Check if the last template parameter is a tuple one, * and returns it if so, else returns `null`. * * Returns: * The last template parameter if it's a `TemplateTupleParameter` */ extern (D) TemplateTupleParameter isVariadic() { size_t dim = parameters.length; if (dim == 0) return null; return (*parameters)[dim - 1].isTemplateTupleParameter(); } extern(C++) override bool isDeprecated() const { return this.deprecated_; } /*********************************** * We can overload templates. */ override bool isOverloadable() const { return true; } override void accept(Visitor v) { v.visit(this); } } extern (C++) final class TypeDeduced : Type { Type tded; Expressions argexps; // corresponding expressions Types tparams; // tparams[i].mod extern (D) this(Type tt, Expression e, Type tparam) { super(Tnone); tded = tt; argexps.push(e); tparams.push(tparam); } void update(Expression e, Type tparam) { argexps.push(e); tparams.push(tparam); } void update(Type tt, Expression e, Type tparam) { tded = tt; argexps.push(e); tparams.push(tparam); } MATCH matchAll(Type tt) { MATCH match = MATCH.exact; foreach (j, e; argexps) { assert(e); if (e == emptyArrayElement) continue; Type t = tt.addMod(tparams[j].mod).substWildTo(MODFlags.const_); MATCH m = e.implicitConvTo(t); if (match > m) match = m; if (match == MATCH.nomatch) break; } return match; } } /* ======================== Type ============================================ */ /**** * Given an identifier, figure out which TemplateParameter it is. * Return IDX_NOTFOUND if not found. */ private size_t templateIdentifierLookup(Identifier id, TemplateParameters* parameters) { for (size_t i = 0; i < parameters.length; i++) { TemplateParameter tp = (*parameters)[i]; if (tp.ident.equals(id)) return i; } return IDX_NOTFOUND; } size_t templateParameterLookup(Type tparam, TemplateParameters* parameters) { if (TypeIdentifier tident = tparam.isTypeIdentifier()) { //printf("\ttident = '%s'\n", tident.toChars()); return templateIdentifierLookup(tident.ident, parameters); } return IDX_NOTFOUND; } ubyte deduceWildHelper(Type t, Type* at, Type tparam) { if ((tparam.mod & MODFlags.wild) == 0) return 0; *at = null; auto X(T, U)(T U, U T) { return (U << 4) | T; } switch (X(tparam.mod, t.mod)) { case X(MODFlags.wild, 0): case X(MODFlags.wild, MODFlags.const_): case X(MODFlags.wild, MODFlags.shared_): case X(MODFlags.wild, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.wild, MODFlags.immutable_): case X(MODFlags.wildconst, 0): case X(MODFlags.wildconst, MODFlags.const_): case X(MODFlags.wildconst, MODFlags.shared_): case X(MODFlags.wildconst, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.wildconst, MODFlags.immutable_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.immutable_): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.immutable_): { ubyte wm = (t.mod & ~MODFlags.shared_); if (wm == 0) wm = MODFlags.mutable; ubyte m = (t.mod & (MODFlags.const_ | MODFlags.immutable_)) | (tparam.mod & t.mod & MODFlags.shared_); *at = t.unqualify(m); return wm; } case X(MODFlags.wild, MODFlags.wild): case X(MODFlags.wild, MODFlags.wildconst): case X(MODFlags.wild, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.wild, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.wildconst, MODFlags.wild): case X(MODFlags.wildconst, MODFlags.wildconst): case X(MODFlags.wildconst, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.wildconst, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_ | MODFlags.wildconst): { *at = t.unqualify(tparam.mod & t.mod); return MODFlags.wild; } default: return 0; } } /** * Returns the common type of the 2 types. */ private Type rawTypeMerge(Type t1, Type t2) { if (t1.equals(t2)) return t1; if (t1.equivalent(t2)) return t1.castMod(MODmerge(t1.mod, t2.mod)); auto t1b = t1.toBasetype(); auto t2b = t2.toBasetype(); if (t1b.equals(t2b)) return t1b; if (t1b.equivalent(t2b)) return t1b.castMod(MODmerge(t1b.mod, t2b.mod)); auto ty = implicitConvCommonTy(t1b.ty, t2b.ty); if (ty != Terror) return Type.basic[ty]; return null; } MATCH deduceTypeHelper(Type t, out Type at, Type tparam) { // 9*9 == 81 cases auto X(T, U)(T U, U T) { return (U << 4) | T; } switch (X(tparam.mod, t.mod)) { case X(0, 0): case X(0, MODFlags.const_): case X(0, MODFlags.wild): case X(0, MODFlags.wildconst): case X(0, MODFlags.shared_): case X(0, MODFlags.shared_ | MODFlags.const_): case X(0, MODFlags.shared_ | MODFlags.wild): case X(0, MODFlags.shared_ | MODFlags.wildconst): case X(0, MODFlags.immutable_): // foo(U) T => T // foo(U) const(T) => const(T) // foo(U) inout(T) => inout(T) // foo(U) inout(const(T)) => inout(const(T)) // foo(U) shared(T) => shared(T) // foo(U) shared(const(T)) => shared(const(T)) // foo(U) shared(inout(T)) => shared(inout(T)) // foo(U) shared(inout(const(T))) => shared(inout(const(T))) // foo(U) immutable(T) => immutable(T) { at = t; return MATCH.exact; } case X(MODFlags.const_, MODFlags.const_): case X(MODFlags.wild, MODFlags.wild): case X(MODFlags.wildconst, MODFlags.wildconst): case X(MODFlags.shared_, MODFlags.shared_): case X(MODFlags.shared_ | MODFlags.const_, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.immutable_, MODFlags.immutable_): // foo(const(U)) const(T) => T // foo(inout(U)) inout(T) => T // foo(inout(const(U))) inout(const(T)) => T // foo(shared(U)) shared(T) => T // foo(shared(const(U))) shared(const(T)) => T // foo(shared(inout(U))) shared(inout(T)) => T // foo(shared(inout(const(U)))) shared(inout(const(T))) => T // foo(immutable(U)) immutable(T) => T { at = t.mutableOf().unSharedOf(); return MATCH.exact; } case X(MODFlags.const_, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.wild, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.wildconst, MODFlags.shared_ | MODFlags.wildconst): // foo(const(U)) shared(const(T)) => shared(T) // foo(inout(U)) shared(inout(T)) => shared(T) // foo(inout(const(U))) shared(inout(const(T))) => shared(T) { at = t.mutableOf(); return MATCH.exact; } case X(MODFlags.const_, 0): case X(MODFlags.const_, MODFlags.wild): case X(MODFlags.const_, MODFlags.wildconst): case X(MODFlags.const_, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.const_, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.const_, MODFlags.immutable_): case X(MODFlags.shared_ | MODFlags.const_, MODFlags.immutable_): // foo(const(U)) T => T // foo(const(U)) inout(T) => T // foo(const(U)) inout(const(T)) => T // foo(const(U)) shared(inout(T)) => shared(T) // foo(const(U)) shared(inout(const(T))) => shared(T) // foo(const(U)) immutable(T) => T // foo(shared(const(U))) immutable(T) => T { at = t.mutableOf(); return MATCH.constant; } case X(MODFlags.const_, MODFlags.shared_): // foo(const(U)) shared(T) => shared(T) { at = t; return MATCH.constant; } case X(MODFlags.shared_, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.shared_, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.shared_, MODFlags.shared_ | MODFlags.wildconst): // foo(shared(U)) shared(const(T)) => const(T) // foo(shared(U)) shared(inout(T)) => inout(T) // foo(shared(U)) shared(inout(const(T))) => inout(const(T)) { at = t.unSharedOf(); return MATCH.exact; } case X(MODFlags.shared_ | MODFlags.const_, MODFlags.shared_): // foo(shared(const(U))) shared(T) => T { at = t.unSharedOf(); return MATCH.constant; } case X(MODFlags.wildconst, MODFlags.immutable_): case X(MODFlags.shared_ | MODFlags.const_, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.immutable_): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_ | MODFlags.wild): // foo(inout(const(U))) immutable(T) => T // foo(shared(const(U))) shared(inout(const(T))) => T // foo(shared(inout(const(U)))) immutable(T) => T // foo(shared(inout(const(U)))) shared(inout(T)) => T { at = t.unSharedOf().mutableOf(); return MATCH.constant; } case X(MODFlags.shared_ | MODFlags.const_, MODFlags.shared_ | MODFlags.wild): // foo(shared(const(U))) shared(inout(T)) => T { at = t.unSharedOf().mutableOf(); return MATCH.constant; } case X(MODFlags.wild, 0): case X(MODFlags.wild, MODFlags.const_): case X(MODFlags.wild, MODFlags.wildconst): case X(MODFlags.wild, MODFlags.immutable_): case X(MODFlags.wild, MODFlags.shared_): case X(MODFlags.wild, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.wild, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.wildconst, 0): case X(MODFlags.wildconst, MODFlags.const_): case X(MODFlags.wildconst, MODFlags.wild): case X(MODFlags.wildconst, MODFlags.shared_): case X(MODFlags.wildconst, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.wildconst, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.shared_, 0): case X(MODFlags.shared_, MODFlags.const_): case X(MODFlags.shared_, MODFlags.wild): case X(MODFlags.shared_, MODFlags.wildconst): case X(MODFlags.shared_, MODFlags.immutable_): case X(MODFlags.shared_ | MODFlags.const_, 0): case X(MODFlags.shared_ | MODFlags.const_, MODFlags.const_): case X(MODFlags.shared_ | MODFlags.const_, MODFlags.wild): case X(MODFlags.shared_ | MODFlags.const_, MODFlags.wildconst): case X(MODFlags.shared_ | MODFlags.wild, 0): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.const_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.wild): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.wildconst): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.immutable_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.shared_ | MODFlags.wild, MODFlags.shared_ | MODFlags.wildconst): case X(MODFlags.shared_ | MODFlags.wildconst, 0): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.const_): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.wild): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.wildconst): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_): case X(MODFlags.shared_ | MODFlags.wildconst, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.immutable_, 0): case X(MODFlags.immutable_, MODFlags.const_): case X(MODFlags.immutable_, MODFlags.wild): case X(MODFlags.immutable_, MODFlags.wildconst): case X(MODFlags.immutable_, MODFlags.shared_): case X(MODFlags.immutable_, MODFlags.shared_ | MODFlags.const_): case X(MODFlags.immutable_, MODFlags.shared_ | MODFlags.wild): case X(MODFlags.immutable_, MODFlags.shared_ | MODFlags.wildconst): // foo(inout(U)) T => nomatch // foo(inout(U)) const(T) => nomatch // foo(inout(U)) inout(const(T)) => nomatch // foo(inout(U)) immutable(T) => nomatch // foo(inout(U)) shared(T) => nomatch // foo(inout(U)) shared(const(T)) => nomatch // foo(inout(U)) shared(inout(const(T))) => nomatch // foo(inout(const(U))) T => nomatch // foo(inout(const(U))) const(T) => nomatch // foo(inout(const(U))) inout(T) => nomatch // foo(inout(const(U))) shared(T) => nomatch // foo(inout(const(U))) shared(const(T)) => nomatch // foo(inout(const(U))) shared(inout(T)) => nomatch // foo(shared(U)) T => nomatch // foo(shared(U)) const(T) => nomatch // foo(shared(U)) inout(T) => nomatch // foo(shared(U)) inout(const(T)) => nomatch // foo(shared(U)) immutable(T) => nomatch // foo(shared(const(U))) T => nomatch // foo(shared(const(U))) const(T) => nomatch // foo(shared(const(U))) inout(T) => nomatch // foo(shared(const(U))) inout(const(T)) => nomatch // foo(shared(inout(U))) T => nomatch // foo(shared(inout(U))) const(T) => nomatch // foo(shared(inout(U))) inout(T) => nomatch // foo(shared(inout(U))) inout(const(T)) => nomatch // foo(shared(inout(U))) immutable(T) => nomatch // foo(shared(inout(U))) shared(T) => nomatch // foo(shared(inout(U))) shared(const(T)) => nomatch // foo(shared(inout(U))) shared(inout(const(T))) => nomatch // foo(shared(inout(const(U)))) T => nomatch // foo(shared(inout(const(U)))) const(T) => nomatch // foo(shared(inout(const(U)))) inout(T) => nomatch // foo(shared(inout(const(U)))) inout(const(T)) => nomatch // foo(shared(inout(const(U)))) shared(T) => nomatch // foo(shared(inout(const(U)))) shared(const(T)) => nomatch // foo(immutable(U)) T => nomatch // foo(immutable(U)) const(T) => nomatch // foo(immutable(U)) inout(T) => nomatch // foo(immutable(U)) inout(const(T)) => nomatch // foo(immutable(U)) shared(T) => nomatch // foo(immutable(U)) shared(const(T)) => nomatch // foo(immutable(U)) shared(inout(T)) => nomatch // foo(immutable(U)) shared(inout(const(T))) => nomatch return MATCH.nomatch; default: assert(0); } } __gshared Expression emptyArrayElement = null; /* These form the heart of template argument deduction. * Given 'this' being the type argument to the template instance, * it is matched against the template declaration parameter specialization * 'tparam' to determine the type to be used for the parameter. * Example: * template Foo(T:T*) // template declaration * Foo!(int*) // template instantiation * Input: * this = int* * tparam = T* * parameters = [ T:T* ] // Array of TemplateParameter's * Output: * dedtypes = [ int ] // Array of Expression/Type's */ MATCH deduceType(RootObject o, Scope* sc, Type tparam, ref TemplateParameters parameters, ref Objects dedtypes, uint* wm = null, size_t inferStart = 0, bool ignoreAliasThis = false) { extern (C++) final class DeduceType : Visitor { alias visit = Visitor.visit; public: MATCH result; extern (D) this() @safe { result = MATCH.nomatch; } override void visit(Type t) { if (!tparam) goto Lnomatch; if (t == tparam) goto Lexact; if (tparam.ty == Tident) { // Determine which parameter tparam is size_t i = templateParameterLookup(tparam, ¶meters); if (i == IDX_NOTFOUND) { if (!sc) goto Lnomatch; /* Need a loc to go with the semantic routine. */ Loc loc; if (parameters.length) { TemplateParameter tp = parameters[0]; loc = tp.loc; } /* BUG: what if tparam is a template instance, that * has as an argument another Tident? */ tparam = tparam.typeSemantic(loc, sc); assert(tparam.ty != Tident); result = deduceType(t, sc, tparam, parameters, dedtypes, wm); return; } TemplateParameter tp = parameters[i]; TypeIdentifier tident = tparam.isTypeIdentifier(); if (tident.idents.length > 0) { //printf("matching %s to %s\n", tparam.toChars(), t.toChars()); Dsymbol s = t.toDsymbol(sc); for (size_t j = tident.idents.length; j-- > 0;) { RootObject id = tident.idents[j]; if (id.dyncast() == DYNCAST.identifier) { if (!s || !s.parent) goto Lnomatch; Dsymbol s2 = s.parent.search(Loc.initial, cast(Identifier)id); if (!s2) goto Lnomatch; s2 = s2.toAlias(); //printf("[%d] s = %s %s, s2 = %s %s\n", j, s.kind(), s.toChars(), s2.kind(), s2.toChars()); if (s != s2) { if (Type tx = s2.getType()) { if (s != tx.toDsymbol(sc)) goto Lnomatch; } else goto Lnomatch; } s = s.parent; } else goto Lnomatch; } //printf("[e] s = %s\n", s?s.toChars():"(null)"); if (tp.isTemplateTypeParameter()) { Type tt = s.getType(); if (!tt) goto Lnomatch; Type at = cast(Type)dedtypes[i]; if (at && at.ty == Tnone) at = (cast(TypeDeduced)at).tded; if (!at || tt.equals(at)) { dedtypes[i] = tt; goto Lexact; } } if (tp.isTemplateAliasParameter()) { Dsymbol s2 = cast(Dsymbol)dedtypes[i]; if (!s2 || s == s2) { dedtypes[i] = s; goto Lexact; } } goto Lnomatch; } // Found the corresponding parameter tp /+ https://issues.dlang.org/show_bug.cgi?id=23578 To pattern match: static if (is(S!int == S!av, alias av)) We eventually need to deduce `int` (Tint32 [0]) and `av` (Tident). Previously this would not get pattern matched at all, but now we check if the template parameter `av` came from. This note has been left to serve as a hint for further explorers into how IsExp matching works. +/ if (auto ta = tp.isTemplateAliasParameter()) { dedtypes[i] = t; goto Lexact; } // (23578) - ensure previous behaviour for non-alias template params if (!tp.isTemplateTypeParameter()) { goto Lnomatch; } Type at = cast(Type)dedtypes[i]; Type tt; if (ubyte wx = wm ? deduceWildHelper(t, &tt, tparam) : 0) { // type vs (none) if (!at) { dedtypes[i] = tt; *wm |= wx; result = MATCH.constant; return; } // type vs expressions if (at.ty == Tnone) { auto xt = cast(TypeDeduced)at; result = xt.matchAll(tt); if (result > MATCH.nomatch) { dedtypes[i] = tt; if (result > MATCH.constant) result = MATCH.constant; // limit level for inout matches } return; } // type vs type if (tt.equals(at)) { dedtypes[i] = tt; // Prefer current type match goto Lconst; } if (tt.implicitConvTo(at.constOf())) { dedtypes[i] = at.constOf().mutableOf(); *wm |= MODFlags.const_; goto Lconst; } if (at.implicitConvTo(tt.constOf())) { dedtypes[i] = tt.constOf().mutableOf(); *wm |= MODFlags.const_; goto Lconst; } goto Lnomatch; } else if (MATCH m = deduceTypeHelper(t, tt, tparam)) { // type vs (none) if (!at) { dedtypes[i] = tt; result = m; return; } // type vs expressions if (at.ty == Tnone) { auto xt = cast(TypeDeduced)at; result = xt.matchAll(tt); if (result > MATCH.nomatch) { dedtypes[i] = tt; } return; } // type vs type if (tt.equals(at)) { goto Lexact; } if (tt.ty == Tclass && at.ty == Tclass) { result = tt.implicitConvTo(at); return; } if (tt.ty == Tsarray && at.ty == Tarray && tt.nextOf().implicitConvTo(at.nextOf()) >= MATCH.constant) { goto Lexact; } } goto Lnomatch; } if (tparam.ty == Ttypeof) { /* Need a loc to go with the semantic routine. */ Loc loc; if (parameters.length) { TemplateParameter tp = parameters[0]; loc = tp.loc; } tparam = tparam.typeSemantic(loc, sc); } if (t.ty != tparam.ty) { if (Dsymbol sym = t.toDsymbol(sc)) { if (sym.isforwardRef() && !tparam.deco) goto Lnomatch; } MATCH m = t.implicitConvTo(tparam); if (m == MATCH.nomatch && !ignoreAliasThis) { if (auto tc = t.isTypeClass()) { if (tc.sym.aliasthis && !(tc.att & AliasThisRec.tracingDT)) { if (auto ato = t.aliasthisOf()) { tc.att = cast(AliasThisRec)(tc.att | AliasThisRec.tracingDT); m = deduceType(ato, sc, tparam, parameters, dedtypes, wm); tc.att = cast(AliasThisRec)(tc.att & ~AliasThisRec.tracingDT); } } } else if (auto ts = t.isTypeStruct()) { if (ts.sym.aliasthis && !(ts.att & AliasThisRec.tracingDT)) { if (auto ato = t.aliasthisOf()) { ts.att = cast(AliasThisRec)(ts.att | AliasThisRec.tracingDT); m = deduceType(ato, sc, tparam, parameters, dedtypes, wm); ts.att = cast(AliasThisRec)(ts.att & ~AliasThisRec.tracingDT); } } } } result = m; return; } if (t.nextOf()) { if (tparam.deco && !tparam.hasWild()) { result = t.implicitConvTo(tparam); return; } Type tpn = tparam.nextOf(); if (wm && t.ty == Taarray && tparam.isWild()) { // https://issues.dlang.org/show_bug.cgi?id=12403 // In IFTI, stop inout matching on transitive part of AA types. tpn = tpn.substWildTo(MODFlags.mutable); } result = deduceType(t.nextOf(), sc, tpn, parameters, dedtypes, wm); return; } Lexact: result = MATCH.exact; return; Lnomatch: result = MATCH.nomatch; return; Lconst: result = MATCH.constant; } override void visit(TypeVector t) { if (auto tp = tparam.isTypeVector()) { result = deduceType(t.basetype, sc, tp.basetype, parameters, dedtypes, wm); return; } visit(cast(Type)t); } override void visit(TypeDArray t) { visit(cast(Type)t); } override void visit(TypeSArray t) { // Extra check that array dimensions must match if (tparam) { if (tparam.ty == Tarray) { MATCH m = deduceType(t.next, sc, tparam.nextOf(), parameters, dedtypes, wm); result = (m >= MATCH.constant) ? MATCH.convert : MATCH.nomatch; return; } TemplateParameter tp = null; Expression edim = null; size_t i; if (auto tsa = tparam.isTypeSArray()) { if (tsa.dim.isVarExp() && tsa.dim.isVarExp().var.storage_class & STC.templateparameter) { Identifier id = tsa.dim.isVarExp().var.ident; i = templateIdentifierLookup(id, ¶meters); assert(i != IDX_NOTFOUND); tp = parameters[i]; } else edim = tsa.dim; } else if (auto taa = tparam.isTypeAArray()) { i = templateParameterLookup(taa.index, ¶meters); if (i != IDX_NOTFOUND) tp = parameters[i]; else { Loc loc; // The "type" (it hasn't been resolved yet) of the function parameter // does not have a location but the parameter it is related to does, // so we use that for the resolution (better error message). if (inferStart < parameters.length) { TemplateParameter loctp = parameters[inferStart]; loc = loctp.loc; } Expression e; Type tx; Dsymbol s; taa.index.resolve(loc, sc, e, tx, s); edim = s ? getValue(s) : getValue(e); } } if ((tp && tp.matchArg(sc, t.dim, i, ¶meters, dedtypes, null)) || (edim && edim.isIntegerExp() && edim.toInteger() == t.dim.toInteger()) ) { result = deduceType(t.next, sc, tparam.nextOf(), parameters, dedtypes, wm); return; } } visit(cast(Type)t); } override void visit(TypeAArray t) { // Extra check that index type must match if (tparam && tparam.ty == Taarray) { TypeAArray tp = tparam.isTypeAArray(); if (!deduceType(t.index, sc, tp.index, parameters, dedtypes)) { result = MATCH.nomatch; return; } } visit(cast(Type)t); } override void visit(TypeFunction t) { // Extra check that function characteristics must match if (!tparam) return visit(cast(Type)t); if (auto tp = tparam.isTypeFunction()) { if (t.parameterList.varargs != tp.parameterList.varargs || t.linkage != tp.linkage) { result = MATCH.nomatch; return; } foreach (fparam; *tp.parameterList.parameters) { // https://issues.dlang.org/show_bug.cgi?id=2579 // Apply function parameter storage classes to parameter types fparam.type = fparam.type.addStorageClass(fparam.storageClass); fparam.storageClass &= ~STC.TYPECTOR; // https://issues.dlang.org/show_bug.cgi?id=15243 // Resolve parameter type if it's not related with template parameters if (!reliesOnTemplateParameters(fparam.type, parameters[inferStart .. parameters.length])) { auto tx = fparam.type.typeSemantic(Loc.initial, sc); if (tx.ty == Terror) { result = MATCH.nomatch; return; } fparam.type = tx; } } const size_t nfargs = t.parameterList.length; size_t nfparams = tp.parameterList.length; /* See if tuple match */ if (nfparams > 0 && nfargs >= nfparams - 1) { /* See if 'A' of the template parameter matches 'A' * of the type of the last function parameter. */ Parameter fparam = tp.parameterList[nfparams - 1]; assert(fparam); assert(fparam.type); if (fparam.type.ty != Tident) goto L1; TypeIdentifier tid = fparam.type.isTypeIdentifier(); if (tid.idents.length) goto L1; /* Look through parameters to find tuple matching tid.ident */ size_t tupi = 0; for (; 1; tupi++) { if (tupi == parameters.length) goto L1; TemplateParameter tx = parameters[tupi]; TemplateTupleParameter tup = tx.isTemplateTupleParameter(); if (tup && tup.ident.equals(tid.ident)) break; } /* The types of the function arguments [nfparams - 1 .. nfargs] * now form the tuple argument. */ size_t tuple_dim = nfargs - (nfparams - 1); /* See if existing tuple, and whether it matches or not */ RootObject o = dedtypes[tupi]; if (o) { // Existing deduced argument must be a tuple, and must match Tuple tup = isTuple(o); if (!tup || tup.objects.length != tuple_dim) { result = MATCH.nomatch; return; } for (size_t i = 0; i < tuple_dim; i++) { Parameter arg = t.parameterList[nfparams - 1 + i]; if (!arg.type.equals(tup.objects[i])) { result = MATCH.nomatch; return; } } } else { // Create new tuple auto tup = new Tuple(tuple_dim); for (size_t i = 0; i < tuple_dim; i++) { Parameter arg = t.parameterList[nfparams - 1 + i]; tup.objects[i] = arg.type; } dedtypes[tupi] = tup; } nfparams--; // don't consider the last parameter for type deduction goto L2; } L1: if (nfargs != nfparams) { result = MATCH.nomatch; return; } L2: assert(nfparams <= tp.parameterList.length); foreach (i, ap; tp.parameterList) { if (i == nfparams) break; Parameter a = t.parameterList[i]; if (!a.isCovariant(t.isref, ap) || !deduceType(a.type, sc, ap.type, parameters, dedtypes)) { result = MATCH.nomatch; return; } } } visit(cast(Type)t); } override void visit(TypeIdentifier t) { // Extra check if (tparam && tparam.ty == Tident) { TypeIdentifier tp = tparam.isTypeIdentifier(); for (size_t i = 0; i < t.idents.length; i++) { RootObject id1 = t.idents[i]; RootObject id2 = tp.idents[i]; if (!id1.equals(id2)) { result = MATCH.nomatch; return; } } } visit(cast(Type)t); } override void visit(TypeInstance t) { // Extra check if (tparam && tparam.ty == Tinstance && t.tempinst.tempdecl) { TemplateDeclaration tempdecl = t.tempinst.tempdecl.isTemplateDeclaration(); assert(tempdecl); TypeInstance tp = tparam.isTypeInstance(); //printf("tempinst.tempdecl = %p\n", tempdecl); //printf("tp.tempinst.tempdecl = %p\n", tp.tempinst.tempdecl); if (!tp.tempinst.tempdecl) { //printf("tp.tempinst.name = '%s'\n", tp.tempinst.name.toChars()); /* Handle case of: * template Foo(T : sa!(T), alias sa) */ size_t i = templateIdentifierLookup(tp.tempinst.name, ¶meters); if (i == IDX_NOTFOUND) { /* Didn't find it as a parameter identifier. Try looking * it up and seeing if is an alias. * https://issues.dlang.org/show_bug.cgi?id=1454 */ auto tid = new TypeIdentifier(tp.loc, tp.tempinst.name); Type tx; Expression e; Dsymbol s; tid.resolve(tp.loc, sc, e, tx, s); if (tx) { s = tx.toDsymbol(sc); if (TemplateInstance ti = s ? s.parent.isTemplateInstance() : null) { // https://issues.dlang.org/show_bug.cgi?id=14290 // Try to match with ti.tempecl, // only when ti is an enclosing instance. Dsymbol p = sc.parent; while (p && p != ti) p = p.parent; if (p) s = ti.tempdecl; } } if (s) { s = s.toAlias(); TemplateDeclaration td = s.isTemplateDeclaration(); if (td) { if (td.overroot) td = td.overroot; for (; td; td = td.overnext) { if (td == tempdecl) goto L2; } } } goto Lnomatch; } TemplateParameter tpx = parameters[i]; if (!tpx.matchArg(sc, tempdecl, i, ¶meters, dedtypes, null)) goto Lnomatch; } else if (tempdecl != tp.tempinst.tempdecl) goto Lnomatch; L2: if (!resolveTemplateInstantiation(sc, ¶meters, t.tempinst.tiargs, &t.tempinst.tdtypes, tempdecl, tp, &dedtypes)) goto Lnomatch; } visit(cast(Type)t); return; Lnomatch: //printf("no match\n"); result = MATCH.nomatch; } override void visit(TypeStruct t) { /* If this struct is a template struct, and we're matching * it against a template instance, convert the struct type * to a template instance, too, and try again. */ TemplateInstance ti = t.sym.parent.isTemplateInstance(); if (tparam && tparam.ty == Tinstance) { if (ti && ti.toAlias() == t.sym) { auto tx = new TypeInstance(Loc.initial, ti); auto m = deduceType(tx, sc, tparam, parameters, dedtypes, wm); // if we have a no match we still need to check alias this if (m != MATCH.nomatch) { result = m; return; } } /* Match things like: * S!(T).foo */ TypeInstance tpi = tparam.isTypeInstance(); if (tpi.idents.length) { RootObject id = tpi.idents[tpi.idents.length - 1]; if (id.dyncast() == DYNCAST.identifier && t.sym.ident.equals(cast(Identifier)id)) { Type tparent = t.sym.parent.getType(); if (tparent) { /* Slice off the .foo in S!(T).foo */ tpi.idents.length--; result = deduceType(tparent, sc, tpi, parameters, dedtypes, wm); tpi.idents.length++; return; } } } } // Extra check if (tparam && tparam.ty == Tstruct) { TypeStruct tp = tparam.isTypeStruct(); //printf("\t%d\n", cast(MATCH) t.implicitConvTo(tp)); if (wm && t.deduceWild(tparam, false)) { result = MATCH.constant; return; } result = t.implicitConvTo(tp); return; } visit(cast(Type)t); } override void visit(TypeEnum t) { // Extra check if (tparam && tparam.ty == Tenum) { TypeEnum tp = tparam.isTypeEnum(); if (t.sym == tp.sym) visit(cast(Type)t); else result = MATCH.nomatch; return; } Type tb = t.toBasetype(); if (tb.ty == tparam.ty || tb.ty == Tsarray && tparam.ty == Taarray) { result = deduceType(tb, sc, tparam, parameters, dedtypes, wm); if (result == MATCH.exact) result = MATCH.convert; return; } visit(cast(Type)t); } override void visit(TypeClass t) { //printf("TypeClass.deduceType(this = %s)\n", t.toChars()); /* If this class is a template class, and we're matching * it against a template instance, convert the class type * to a template instance, too, and try again. */ TemplateInstance ti = t.sym.parent.isTemplateInstance(); if (tparam && tparam.ty == Tinstance) { if (ti && ti.toAlias() == t.sym) { auto tx = new TypeInstance(Loc.initial, ti); MATCH m = deduceType(tx, sc, tparam, parameters, dedtypes, wm); // Even if the match fails, there is still a chance it could match // a base class. if (m != MATCH.nomatch) { result = m; return; } } /* Match things like: * S!(T).foo */ TypeInstance tpi = tparam.isTypeInstance(); if (tpi.idents.length) { RootObject id = tpi.idents[tpi.idents.length - 1]; if (id.dyncast() == DYNCAST.identifier && t.sym.ident.equals(cast(Identifier)id)) { Type tparent = t.sym.parent.getType(); if (tparent) { /* Slice off the .foo in S!(T).foo */ tpi.idents.length--; result = deduceType(tparent, sc, tpi, parameters, dedtypes, wm); tpi.idents.length++; return; } } } // If it matches exactly or via implicit conversion, we're done visit(cast(Type)t); if (result != MATCH.nomatch) return; /* There is still a chance to match via implicit conversion to * a base class or interface. Because there could be more than one such * match, we need to check them all. */ int numBaseClassMatches = 0; // Have we found an interface match? // Our best guess at dedtypes auto best = new Objects(dedtypes.length); ClassDeclaration s = t.sym; while (s && s.baseclasses.length > 0) { // Test the base class deduceBaseClassParameters(*(*s.baseclasses)[0], sc, tparam, parameters, dedtypes, *best, numBaseClassMatches); // Test the interfaces inherited by the base class foreach (b; s.interfaces) { deduceBaseClassParameters(*b, sc, tparam, parameters, dedtypes, *best, numBaseClassMatches); } s = (*s.baseclasses)[0].sym; } if (numBaseClassMatches == 0) { result = MATCH.nomatch; return; } // If we got at least one match, copy the known types into dedtypes memcpy(dedtypes.tdata(), best.tdata(), best.length * (void*).sizeof); result = MATCH.convert; return; } // Extra check if (tparam && tparam.ty == Tclass) { TypeClass tp = tparam.isTypeClass(); //printf("\t%d\n", cast(MATCH) t.implicitConvTo(tp)); if (wm && t.deduceWild(tparam, false)) { result = MATCH.constant; return; } result = t.implicitConvTo(tp); return; } visit(cast(Type)t); } override void visit(Expression e) { //printf("Expression.deduceType(e = %s)\n", e.toChars()); size_t i = templateParameterLookup(tparam, ¶meters); if (i == IDX_NOTFOUND || tparam.isTypeIdentifier().idents.length > 0) { if (e == emptyArrayElement && tparam.ty == Tarray) { Type tn = (cast(TypeNext)tparam).next; result = deduceType(emptyArrayElement, sc, tn, parameters, dedtypes, wm); return; } e.type.accept(this); return; } TemplateTypeParameter tp = parameters[i].isTemplateTypeParameter(); if (!tp) return; // nomatch if (e == emptyArrayElement) { if (dedtypes[i]) { result = MATCH.exact; return; } if (tp.defaultType) { tp.defaultType.accept(this); return; } } /* Returns `true` if `t` is a reference type, or an array of reference types */ bool isTopRef(Type t) { auto tb = t.baseElemOf(); return tb.ty == Tclass || tb.ty == Taarray || tb.ty == Tstruct && tb.hasPointers(); } Type at = cast(Type)dedtypes[i]; Type tt; if (ubyte wx = deduceWildHelper(e.type, &tt, tparam)) { *wm |= wx; result = MATCH.constant; } else if (MATCH m = deduceTypeHelper(e.type, tt, tparam)) { result = m; } else if (!isTopRef(e.type)) { /* https://issues.dlang.org/show_bug.cgi?id=15653 * In IFTI, recognize top-qualifier conversions * through the value copy, e.g. * int --> immutable(int) * immutable(string[]) --> immutable(string)[] */ tt = e.type.mutableOf(); result = MATCH.convert; } else return; // nomatch // expression vs (none) if (!at) { dedtypes[i] = new TypeDeduced(tt, e, tparam); return; } TypeDeduced xt = null; if (at.ty == Tnone) { xt = cast(TypeDeduced)at; at = xt.tded; } // From previous matched expressions to current deduced type MATCH match1 = xt ? xt.matchAll(tt) : MATCH.nomatch; // From current expressions to previous deduced type Type pt = at.addMod(tparam.mod); if (*wm) pt = pt.substWildTo(*wm); MATCH match2 = e.implicitConvTo(pt); if (match1 > MATCH.nomatch && match2 > MATCH.nomatch) { if (at.implicitConvTo(tt) == MATCH.nomatch) match1 = MATCH.nomatch; // Prefer at else if (tt.implicitConvTo(at) == MATCH.nomatch) match2 = MATCH.nomatch; // Prefer tt else if (tt.isTypeBasic() && tt.ty == at.ty && tt.mod != at.mod) { if (!tt.isMutable() && !at.isMutable()) tt = tt.mutableOf().addMod(MODmerge(tt.mod, at.mod)); else if (tt.isMutable()) { if (at.mod == 0) // Prefer unshared match1 = MATCH.nomatch; else match2 = MATCH.nomatch; } else if (at.isMutable()) { if (tt.mod == 0) // Prefer unshared match2 = MATCH.nomatch; else match1 = MATCH.nomatch; } //printf("tt = %s, at = %s\n", tt.toChars(), at.toChars()); } else { match1 = MATCH.nomatch; match2 = MATCH.nomatch; } } if (match1 > MATCH.nomatch) { // Prefer current match: tt if (xt) xt.update(tt, e, tparam); else dedtypes[i] = tt; result = match1; return; } if (match2 > MATCH.nomatch) { // Prefer previous match: (*dedtypes)[i] if (xt) xt.update(e, tparam); result = match2; return; } /* Deduce common type */ if (Type t = rawTypeMerge(at, tt)) { if (xt) xt.update(t, e, tparam); else dedtypes[i] = t; pt = tt.addMod(tparam.mod); if (*wm) pt = pt.substWildTo(*wm); result = e.implicitConvTo(pt); return; } result = MATCH.nomatch; } MATCH deduceEmptyArrayElement() { if (!emptyArrayElement) { emptyArrayElement = new IdentifierExp(Loc.initial, Id.p); // dummy emptyArrayElement.type = Type.tvoid; } assert(tparam.ty == Tarray); Type tn = (cast(TypeNext)tparam).next; return deduceType(emptyArrayElement, sc, tn, parameters, dedtypes, wm); } override void visit(NullExp e) { if (tparam.ty == Tarray && e.type.ty == Tnull) { // tparam:T[] <- e:null (void[]) result = deduceEmptyArrayElement(); return; } visit(cast(Expression)e); } override void visit(StringExp e) { Type taai; if (e.type.ty == Tarray && (tparam.ty == Tsarray || tparam.ty == Taarray && (taai = (cast(TypeAArray)tparam).index).ty == Tident && (cast(TypeIdentifier)taai).idents.length == 0)) { // Consider compile-time known boundaries e.type.nextOf().sarrayOf(e.len).accept(this); return; } visit(cast(Expression)e); } override void visit(ArrayLiteralExp e) { // https://issues.dlang.org/show_bug.cgi?id=20092 if (e.elements && e.elements.length && e.type.toBasetype().nextOf().ty == Tvoid) { result = deduceEmptyArrayElement(); return; } if ((!e.elements || !e.elements.length) && e.type.toBasetype().nextOf().ty == Tvoid && tparam.ty == Tarray) { // tparam:T[] <- e:[] (void[]) result = deduceEmptyArrayElement(); return; } if (tparam.ty == Tarray && e.elements && e.elements.length) { Type tn = (cast(TypeDArray)tparam).next; result = MATCH.exact; if (e.basis) { MATCH m = deduceType(e.basis, sc, tn, parameters, dedtypes, wm); if (m < result) result = m; } foreach (el; *e.elements) { if (result == MATCH.nomatch) break; if (!el) continue; MATCH m = deduceType(el, sc, tn, parameters, dedtypes, wm); if (m < result) result = m; } return; } Type taai; if (e.type.ty == Tarray && (tparam.ty == Tsarray || tparam.ty == Taarray && (taai = (cast(TypeAArray)tparam).index).ty == Tident && (cast(TypeIdentifier)taai).idents.length == 0)) { // Consider compile-time known boundaries e.type.nextOf().sarrayOf(e.elements.length).accept(this); return; } visit(cast(Expression)e); } override void visit(AssocArrayLiteralExp e) { if (tparam.ty == Taarray && e.keys && e.keys.length) { TypeAArray taa = cast(TypeAArray)tparam; result = MATCH.exact; foreach (i, key; *e.keys) { MATCH m1 = deduceType(key, sc, taa.index, parameters, dedtypes, wm); if (m1 < result) result = m1; if (result == MATCH.nomatch) break; MATCH m2 = deduceType((*e.values)[i], sc, taa.next, parameters, dedtypes, wm); if (m2 < result) result = m2; if (result == MATCH.nomatch) break; } return; } visit(cast(Expression)e); } override void visit(FuncExp e) { //printf("e.type = %s, tparam = %s\n", e.type.toChars(), tparam.toChars()); if (e.td) { Type to = tparam; if (!to.nextOf()) return; auto tof = to.nextOf().isTypeFunction(); if (!tof) return; // Parameter types inference from 'tof' assert(e.td._scope); TypeFunction tf = e.fd.type.isTypeFunction(); //printf("\ttof = %s\n", tof.toChars()); //printf("\ttf = %s\n", tf.toChars()); const dim = tf.parameterList.length; if (tof.parameterList.length != dim || tof.parameterList.varargs != tf.parameterList.varargs) return; auto tiargs = new Objects(); tiargs.reserve(e.td.parameters.length); foreach (tp; *e.td.parameters) { size_t u = 0; foreach (i, p; tf.parameterList) { if (p.type.ty == Tident && (cast(TypeIdentifier)p.type).ident == tp.ident) break; ++u; } assert(u < dim); Parameter pto = tof.parameterList[u]; if (!pto) break; Type t = pto.type.syntaxCopy(); // https://issues.dlang.org/show_bug.cgi?id=11774 if (reliesOnTemplateParameters(t, parameters[inferStart .. parameters.length])) return; t = t.typeSemantic(e.loc, sc); if (t.ty == Terror) return; tiargs.push(t); } // Set target of return type inference if (!tf.next && tof.next) e.fd.treq = tparam; auto ti = new TemplateInstance(e.loc, e.td, tiargs); Expression ex = (new ScopeExp(e.loc, ti)).expressionSemantic(e.td._scope); // Reset inference target for the later re-semantic e.fd.treq = null; if (ex.op == EXP.error) return; if (ex.op != EXP.function_) return; visit(ex.type); return; } Type t = e.type; if (t.ty == Tdelegate && tparam.ty == Tpointer) return; // Allow conversion from implicit function pointer to delegate if (e.tok == TOK.reserved && t.ty == Tpointer && tparam.ty == Tdelegate) { TypeFunction tf = t.nextOf().isTypeFunction(); t = (new TypeDelegate(tf)).merge(); } //printf("tparam = %s <= e.type = %s, t = %s\n", tparam.toChars(), e.type.toChars(), t.toChars()); visit(t); } override void visit(SliceExp e) { Type taai; if (e.type.ty == Tarray && (tparam.ty == Tsarray || tparam.ty == Taarray && (taai = (cast(TypeAArray)tparam).index).ty == Tident && (cast(TypeIdentifier)taai).idents.length == 0)) { // Consider compile-time known boundaries if (Type tsa = toStaticArrayType(e)) { tsa.accept(this); if (result > MATCH.convert) result = MATCH.convert; // match with implicit conversion at most return; } } visit(cast(Expression)e); } override void visit(CommaExp e) { e.e2.accept(this); } } scope DeduceType v = new DeduceType(); if (Type t = isType(o)) t.accept(v); else if (Expression e = isExpression(o)) { assert(wm); e.accept(v); } else assert(0); return v.result; } /* Helper for TypeClass.deduceType(). * Classes can match with implicit conversion to a base class or interface. * This is complicated, because there may be more than one base class which * matches. In such cases, one or more parameters remain ambiguous. * For example, * * interface I(X, Y) {} * class C : I(uint, double), I(char, double) {} * C x; * foo(T, U)( I!(T, U) x) * * deduces that U is double, but T remains ambiguous (could be char or uint). * * Given a baseclass b, and initial deduced types 'dedtypes', this function * tries to match tparam with b, and also tries all base interfaces of b. * If a match occurs, numBaseClassMatches is incremented, and the new deduced * types are ANDed with the current 'best' estimate for dedtypes. */ private void deduceBaseClassParameters(ref BaseClass b, Scope* sc, Type tparam, ref TemplateParameters parameters, ref Objects dedtypes, ref Objects best, ref int numBaseClassMatches) { TemplateInstance parti = b.sym ? b.sym.parent.isTemplateInstance() : null; if (parti) { // Make a temporary copy of dedtypes so we don't destroy it auto tmpdedtypes = new Objects(dedtypes.length); memcpy(tmpdedtypes.tdata(), dedtypes.tdata(), dedtypes.length * (void*).sizeof); auto t = new TypeInstance(Loc.initial, parti); MATCH m = deduceType(t, sc, tparam, parameters, *tmpdedtypes); if (m > MATCH.nomatch) { // If this is the first ever match, it becomes our best estimate if (numBaseClassMatches == 0) memcpy(best.tdata(), tmpdedtypes.tdata(), tmpdedtypes.length * (void*).sizeof); else for (size_t k = 0; k < tmpdedtypes.length; ++k) { // If we've found more than one possible type for a parameter, // mark it as unknown. if ((*tmpdedtypes)[k] != best[k]) best[k] = dedtypes[k]; } ++numBaseClassMatches; } } // Now recursively test the inherited interfaces foreach (ref bi; b.baseInterfaces) { deduceBaseClassParameters(bi, sc, tparam, parameters, dedtypes, best, numBaseClassMatches); } } /******************** * Match template `parameters` to the target template instance. * Example: * struct Temp(U, int Z) {} * void foo(T)(Temp!(T, 3)); * foo(Temp!(int, 3)()); * Input: * sc = context * parameters = template params of foo -> [T] * tiargs = .tiargs -> [int, 3] * tdtypes = .tdtypes -> [int, 3] * tempdecl = -> [T, Z] * tp = * Output: * dedtypes = deduced params of `foo(Temp!(int, 3)())` -> [int] */ private bool resolveTemplateInstantiation(Scope* sc, TemplateParameters* parameters, Objects* tiargs, Objects* tdtypes, TemplateDeclaration tempdecl, TypeInstance tp, Objects* dedtypes) { for (size_t i = 0; 1; i++) { //printf("\ttest: tempinst.tiargs[%zu]\n", i); RootObject o1 = null; if (i < tiargs.length) o1 = (*tiargs)[i]; else if (i < tdtypes.length && i < tp.tempinst.tiargs.length) { // Pick up default arg o1 = (*tdtypes)[i]; } else if (i >= tp.tempinst.tiargs.length) break; //printf("\ttest: o1 = %s\n", o1.toChars()); if (i >= tp.tempinst.tiargs.length) { size_t dim = tempdecl.parameters.length - (tempdecl.isVariadic() ? 1 : 0); while (i < dim && ((*tempdecl.parameters)[i].dependent || (*tempdecl.parameters)[i].hasDefaultArg())) { i++; } if (i >= dim) break; // match if all remained parameters are dependent return false; } RootObject o2 = (*tp.tempinst.tiargs)[i]; Type t2 = isType(o2); //printf("\ttest: o2 = %s\n", o2.toChars()); size_t j = (t2 && t2.ty == Tident && i == tp.tempinst.tiargs.length - 1) ? templateParameterLookup(t2, parameters) : IDX_NOTFOUND; if (j != IDX_NOTFOUND && j == parameters.length - 1 && (*parameters)[j].isTemplateTupleParameter()) { /* Given: * struct A(B...) {} * alias A!(int, float) X; * static if (is(X Y == A!(Z), Z...)) {} * deduce that Z is a tuple(int, float) */ /* Create tuple from remaining args */ size_t vtdim = (tempdecl.isVariadic() ? tiargs.length : tdtypes.length) - i; auto vt = new Tuple(vtdim); for (size_t k = 0; k < vtdim; k++) { RootObject o; if (k < tiargs.length) o = (*tiargs)[i + k]; else // Pick up default arg o = (*tdtypes)[i + k]; vt.objects[k] = o; } Tuple v = cast(Tuple)(*dedtypes)[j]; if (v) { if (!match(v, vt)) return false; } else (*dedtypes)[j] = vt; break; } else if (!o1) break; Type t1 = isType(o1); Dsymbol s1 = isDsymbol(o1); Dsymbol s2 = isDsymbol(o2); Expression e1 = s1 ? getValue(s1) : getValue(isExpression(o1)); Expression e2 = isExpression(o2); version (none) { Tuple v1 = isTuple(o1); Tuple v2 = isTuple(o2); if (t1) printf("t1 = %s\n", t1.toChars()); if (t2) printf("t2 = %s\n", t2.toChars()); if (e1) printf("e1 = %s\n", e1.toChars()); if (e2) printf("e2 = %s\n", e2.toChars()); if (s1) printf("s1 = %s\n", s1.toChars()); if (s2) printf("s2 = %s\n", s2.toChars()); if (v1) printf("v1 = %s\n", v1.toChars()); if (v2) printf("v2 = %s\n", v2.toChars()); } if (t1 && t2) { if (!deduceType(t1, sc, t2, *parameters, *dedtypes)) return false; } else if (e1 && e2) { Le: e1 = e1.ctfeInterpret(); /* If it is one of the template parameters for this template, * we should not attempt to interpret it. It already has a value. */ if (e2.op == EXP.variable && (e2.isVarExp().var.storage_class & STC.templateparameter)) { /* * (T:Number!(e2), int e2) */ j = templateIdentifierLookup(e2.isVarExp().var.ident, parameters); if (j != IDX_NOTFOUND) goto L1; // The template parameter was not from this template // (it may be from a parent template, for example) } e2 = e2.expressionSemantic(sc); // https://issues.dlang.org/show_bug.cgi?id=13417 e2 = e2.ctfeInterpret(); //printf("e1 = %s, type = %s %d\n", e1.toChars(), e1.type.toChars(), e1.type.ty); //printf("e2 = %s, type = %s %d\n", e2.toChars(), e2.type.toChars(), e2.type.ty); if (!e1.equals(e2)) { if (!e2.implicitConvTo(e1.type)) return false; e2 = e2.implicitCastTo(sc, e1.type); e2 = e2.ctfeInterpret(); if (!e1.equals(e2)) return false; } } else if (e1 && t2 && t2.ty == Tident) { j = templateParameterLookup(t2, parameters); L1: if (j == IDX_NOTFOUND) { t2.resolve((cast(TypeIdentifier)t2).loc, sc, e2, t2, s2); if (e2) goto Le; return false; } if (!(*parameters)[j].matchArg(sc, e1, j, parameters, *dedtypes, null)) return false; } else if (s1 && s2) { Ls: if (!s1.equals(s2)) return false; } else if (s1 && t2 && t2.ty == Tident) { j = templateParameterLookup(t2, parameters); if (j == IDX_NOTFOUND) { t2.resolve((cast(TypeIdentifier)t2).loc, sc, e2, t2, s2); if (s2) goto Ls; return false; } if (!(*parameters)[j].matchArg(sc, s1, j, parameters, *dedtypes, null)) return false; } else return false; } return true; } /*********************************************************** * Check whether the type t representation relies on one or more the template parameters. * Params: * t = Tested type, if null, returns false. * tparams = Template parameters. * iStart = Start index of tparams to limit the tested parameters. If it's * nonzero, tparams[0..iStart] will be excluded from the test target. */ bool reliesOnTident(Type t, TemplateParameters* tparams, size_t iStart = 0) { return reliesOnTemplateParameters(t, (*tparams)[0 .. tparams.length]); } /*********************************************************** * Check whether the type t representation relies on one or more the template parameters. * Params: * t = Tested type, if null, returns false. * tparams = Template parameters. */ bool reliesOnTemplateParameters(Type t, TemplateParameter[] tparams) { bool visitVector(TypeVector t) { return t.basetype.reliesOnTemplateParameters(tparams); } bool visitAArray(TypeAArray t) { return t.next.reliesOnTemplateParameters(tparams) || t.index.reliesOnTemplateParameters(tparams); } bool visitFunction(TypeFunction t) { foreach (i, fparam; t.parameterList) { if (fparam.type.reliesOnTemplateParameters(tparams)) return true; } return t.next.reliesOnTemplateParameters(tparams); } bool visitIdentifier(TypeIdentifier t) { foreach (tp; tparams) { if (tp.ident.equals(t.ident)) return true; } return false; } bool visitInstance(TypeInstance t) { foreach (tp; tparams) { if (t.tempinst.name == tp.ident) return true; } if (t.tempinst.tiargs) foreach (arg; *t.tempinst.tiargs) { if (Type ta = isType(arg)) { if (ta.reliesOnTemplateParameters(tparams)) return true; } } return false; } bool visitTypeof(TypeTypeof t) { //printf("TypeTypeof.reliesOnTemplateParameters('%s')\n", t.toChars()); return t.exp.reliesOnTemplateParameters(tparams); } bool visitTuple(TypeTuple t) { if (t.arguments) foreach (arg; *t.arguments) { if (arg.type.reliesOnTemplateParameters(tparams)) return true; } return false; } if (!t) return false; Type tb = t.toBasetype(); switch (tb.ty) { case Tvector: return visitVector(tb.isTypeVector()); case Taarray: return visitAArray(tb.isTypeAArray()); case Tfunction: return visitFunction(tb.isTypeFunction()); case Tident: return visitIdentifier(tb.isTypeIdentifier()); case Tinstance: return visitInstance(tb.isTypeInstance()); case Ttypeof: return visitTypeof(tb.isTypeTypeof()); case Ttuple: return visitTuple(tb.isTypeTuple()); case Tenum: return false; default: return tb.nextOf().reliesOnTemplateParameters(tparams); } } /*********************************************************** * Check whether the expression representation relies on one or more the template parameters. * Params: * e = expression to test * tparams = Template parameters. * Returns: * true if it does */ private bool reliesOnTemplateParameters(Expression e, TemplateParameter[] tparams) { extern (C++) final class ReliesOnTemplateParameters : Visitor { alias visit = Visitor.visit; public: TemplateParameter[] tparams; bool result; extern (D) this(TemplateParameter[] tparams) @safe { this.tparams = tparams; } override void visit(Expression e) { //printf("Expression.reliesOnTemplateParameters('%s')\n", e.toChars()); } override void visit(IdentifierExp e) { //printf("IdentifierExp.reliesOnTemplateParameters('%s')\n", e.toChars()); foreach (tp; tparams) { if (e.ident == tp.ident) { result = true; return; } } } override void visit(TupleExp e) { //printf("TupleExp.reliesOnTemplateParameters('%s')\n", e.toChars()); if (e.exps) { foreach (ea; *e.exps) { ea.accept(this); if (result) return; } } } override void visit(ArrayLiteralExp e) { //printf("ArrayLiteralExp.reliesOnTemplateParameters('%s')\n", e.toChars()); if (e.elements) { foreach (el; *e.elements) { el.accept(this); if (result) return; } } } override void visit(AssocArrayLiteralExp e) { //printf("AssocArrayLiteralExp.reliesOnTemplateParameters('%s')\n", e.toChars()); foreach (ek; *e.keys) { ek.accept(this); if (result) return; } foreach (ev; *e.values) { ev.accept(this); if (result) return; } } override void visit(StructLiteralExp e) { //printf("StructLiteralExp.reliesOnTemplateParameters('%s')\n", e.toChars()); if (e.elements) { foreach (ea; *e.elements) { ea.accept(this); if (result) return; } } } override void visit(TypeExp e) { //printf("TypeExp.reliesOnTemplateParameters('%s')\n", e.toChars()); result = e.type.reliesOnTemplateParameters(tparams); } override void visit(NewExp e) { //printf("NewExp.reliesOnTemplateParameters('%s')\n", e.toChars()); if (e.thisexp) e.thisexp.accept(this); result = e.newtype.reliesOnTemplateParameters(tparams); if (!result && e.arguments) { foreach (ea; *e.arguments) { ea.accept(this); if (result) return; } } } override void visit(NewAnonClassExp e) { //printf("NewAnonClassExp.reliesOnTemplateParameters('%s')\n", e.toChars()); result = true; } override void visit(FuncExp e) { //printf("FuncExp.reliesOnTemplateParameters('%s')\n", e.toChars()); result = true; } override void visit(TypeidExp e) { //printf("TypeidExp.reliesOnTemplateParameters('%s')\n", e.toChars()); if (auto ea = isExpression(e.obj)) ea.accept(this); else if (auto ta = isType(e.obj)) result = ta.reliesOnTemplateParameters(tparams); } override void visit(TraitsExp e) { //printf("TraitsExp.reliesOnTemplateParameters('%s')\n", e.toChars()); if (e.args) { foreach (oa; *e.args) { if (auto ea = isExpression(oa)) ea.accept(this); else if (auto ta = isType(oa)) result = ta.reliesOnTemplateParameters(tparams); if (result) return; } } } override void visit(IsExp e) { //printf("IsExp.reliesOnTemplateParameters('%s')\n", e.toChars()); result = e.targ.reliesOnTemplateParameters(tparams); } override void visit(UnaExp e) { //printf("UnaExp.reliesOnTemplateParameters('%s')\n", e.toChars()); e.e1.accept(this); } override void visit(DotTemplateInstanceExp e) { //printf("DotTemplateInstanceExp.reliesOnTemplateParameters('%s')\n", e.toChars()); visit(e.isUnaExp()); if (!result && e.ti.tiargs) { foreach (oa; *e.ti.tiargs) { if (auto ea = isExpression(oa)) ea.accept(this); else if (auto ta = isType(oa)) result = ta.reliesOnTemplateParameters(tparams); if (result) return; } } } override void visit(CallExp e) { //printf("CallExp.reliesOnTemplateParameters('%s')\n", e.toChars()); visit(e.isUnaExp()); if (!result && e.arguments) { foreach (ea; *e.arguments) { ea.accept(this); if (result) return; } } } override void visit(CastExp e) { //printf("CallExp.reliesOnTemplateParameters('%s')\n", e.toChars()); visit(e.isUnaExp()); // e.to can be null for cast() with no type if (!result && e.to) result = e.to.reliesOnTemplateParameters(tparams); } override void visit(SliceExp e) { //printf("SliceExp.reliesOnTemplateParameters('%s')\n", e.toChars()); visit(e.isUnaExp()); if (!result && e.lwr) e.lwr.accept(this); if (!result && e.upr) e.upr.accept(this); } override void visit(IntervalExp e) { //printf("IntervalExp.reliesOnTemplateParameters('%s')\n", e.toChars()); e.lwr.accept(this); if (!result) e.upr.accept(this); } override void visit(ArrayExp e) { //printf("ArrayExp.reliesOnTemplateParameters('%s')\n", e.toChars()); visit(e.isUnaExp()); if (!result && e.arguments) { foreach (ea; *e.arguments) ea.accept(this); } } override void visit(BinExp e) { //printf("BinExp.reliesOnTemplateParameters('%s')\n", e.toChars()); e.e1.accept(this); if (!result) e.e2.accept(this); } override void visit(CondExp e) { //printf("BinExp.reliesOnTemplateParameters('%s')\n", e.toChars()); e.econd.accept(this); if (!result) visit(e.isBinExp()); } } scope ReliesOnTemplateParameters v = new ReliesOnTemplateParameters(tparams); e.accept(v); return v.result; } /*********************************************************** * https://dlang.org/spec/template.html#TemplateParameter */ extern (C++) class TemplateParameter : ASTNode { Loc loc; Identifier ident; /* True if this is a part of precedent parameter specialization pattern. * * template A(T : X!TL, alias X, TL...) {} * // X and TL are dependent template parameter * * A dependent template parameter should return MATCH.exact in matchArg() * to respect the match level of the corresponding precedent parameter. */ bool dependent; /* ======================== TemplateParameter =============================== */ extern (D) this(const ref Loc loc, Identifier ident) @safe { this.loc = loc; this.ident = ident; } TemplateTypeParameter isTemplateTypeParameter() { return null; } TemplateValueParameter isTemplateValueParameter() { return null; } TemplateAliasParameter isTemplateAliasParameter() { return null; } TemplateThisParameter isTemplateThisParameter() { return null; } TemplateTupleParameter isTemplateTupleParameter() { return null; } abstract TemplateParameter syntaxCopy(); abstract bool declareParameter(Scope* sc); abstract void print(RootObject oarg, RootObject oded); abstract RootObject specialization(); abstract RootObject defaultArg(const ref Loc instLoc, Scope* sc); abstract bool hasDefaultArg(); override const(char)* toChars() const { return this.ident.toChars(); } override DYNCAST dyncast() const { return DYNCAST.templateparameter; } /* Create dummy argument based on parameter. */ abstract RootObject dummyArg(); override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/template.html#TemplateTypeParameter * Syntax: * ident : specType = defaultType */ extern (C++) class TemplateTypeParameter : TemplateParameter { Type specType; // if !=null, this is the type specialization Type defaultType; extern (D) __gshared Type tdummy = null; extern (D) this(const ref Loc loc, Identifier ident, Type specType, Type defaultType) @safe { super(loc, ident); this.specType = specType; this.defaultType = defaultType; } override final TemplateTypeParameter isTemplateTypeParameter() { return this; } override TemplateTypeParameter syntaxCopy() { return new TemplateTypeParameter(loc, ident, specType ? specType.syntaxCopy() : null, defaultType ? defaultType.syntaxCopy() : null); } override final bool declareParameter(Scope* sc) { //printf("TemplateTypeParameter.declareParameter('%s')\n", ident.toChars()); auto ti = new TypeIdentifier(loc, ident); Declaration ad = new AliasDeclaration(loc, ident, ti); return sc.insert(ad) !is null; } override final void print(RootObject oarg, RootObject oded) { printf(" %s\n", ident.toChars()); Type t = isType(oarg); Type ta = isType(oded); assert(ta); if (specType) printf("\tSpecialization: %s\n", specType.toChars()); if (defaultType) printf("\tDefault: %s\n", defaultType.toChars()); printf("\tParameter: %s\n", t ? t.toChars() : "NULL"); printf("\tDeduced Type: %s\n", ta.toChars()); } override final RootObject specialization() { return specType; } override final RootObject defaultArg(const ref Loc instLoc, Scope* sc) { Type t = defaultType; if (t) { t = t.syntaxCopy(); t = t.typeSemantic(loc, sc); // use the parameter loc } return t; } override final bool hasDefaultArg() { return defaultType !is null; } override final RootObject dummyArg() { Type t = specType; if (!t) { // Use this for alias-parameter's too (?) if (!tdummy) tdummy = new TypeIdentifier(loc, ident); t = tdummy; } return t; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/template.html#TemplateThisParameter * Syntax: * this ident : specType = defaultType */ extern (C++) final class TemplateThisParameter : TemplateTypeParameter { extern (D) this(const ref Loc loc, Identifier ident, Type specType, Type defaultType) @safe { super(loc, ident, specType, defaultType); } override TemplateThisParameter isTemplateThisParameter() { return this; } override TemplateThisParameter syntaxCopy() { return new TemplateThisParameter(loc, ident, specType ? specType.syntaxCopy() : null, defaultType ? defaultType.syntaxCopy() : null); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/template.html#TemplateValueParameter * Syntax: * valType ident : specValue = defaultValue */ extern (C++) final class TemplateValueParameter : TemplateParameter { Type valType; Expression specValue; Expression defaultValue; extern (D) __gshared Expression[void*] edummies; extern (D) this(const ref Loc loc, Identifier ident, Type valType, Expression specValue, Expression defaultValue) @safe { super(loc, ident); this.valType = valType; this.specValue = specValue; this.defaultValue = defaultValue; } override TemplateValueParameter isTemplateValueParameter() { return this; } override TemplateValueParameter syntaxCopy() { return new TemplateValueParameter(loc, ident, valType.syntaxCopy(), specValue ? specValue.syntaxCopy() : null, defaultValue ? defaultValue.syntaxCopy() : null); } override bool declareParameter(Scope* sc) { /* Do type semantic earlier. This means for certain erroneous value parameters their "type" can be known earlier and thus a better error message given. For example: `template test(x* x) {}` now yields "undefined identifier" rather than the opaque "variable `x` is used as a type". */ if (valType) valType = valType.typeSemantic(loc, sc); auto v = new VarDeclaration(loc, valType, ident, null); v.storage_class = STC.templateparameter; return sc.insert(v) !is null; } override void print(RootObject oarg, RootObject oded) { printf(" %s\n", ident.toChars()); Expression ea = isExpression(oded); if (specValue) printf("\tSpecialization: %s\n", specValue.toChars()); printf("\tParameter Value: %s\n", ea ? ea.toChars() : "NULL"); } override RootObject specialization() { return specValue; } override RootObject defaultArg(const ref Loc instLoc, Scope* sc) { Expression e = defaultValue; if (e) { e = e.syntaxCopy(); Scope* sc2 = sc.push(); sc2.inDefaultArg = true; e = e.expressionSemantic(sc2); sc2.pop(); if (e is null) return null; if (auto te = e.isTemplateExp()) { assert(sc && sc.tinst); if (te.td == sc.tinst.tempdecl) { // defaultValue is a reference to its template declaration // i.e: `template T(int arg = T)` // Raise error now before calling resolveProperties otherwise we'll // start looping on the expansion of the template instance. auto td = sc.tinst.tempdecl; .error(td.loc, "%s `%s` recursive template expansion", td.kind, td.toPrettyChars); return ErrorExp.get(); } } if ((e = resolveProperties(sc, e)) is null) return null; e = e.resolveLoc(instLoc, sc); // use the instantiated loc e = e.optimize(WANTvalue); } return e; } override bool hasDefaultArg() { return defaultValue !is null; } override RootObject dummyArg() { Expression e = specValue; if (!e) { // Create a dummy value auto pe = cast(void*)valType in edummies; if (!pe) { e = valType.defaultInit(Loc.initial); edummies[cast(void*)valType] = e; } else e = *pe; } return e; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/template.html#TemplateAliasParameter * Syntax: * specType ident : specAlias = defaultAlias */ extern (C++) final class TemplateAliasParameter : TemplateParameter { Type specType; RootObject specAlias; RootObject defaultAlias; extern (D) __gshared Dsymbol sdummy = null; extern (D) this(const ref Loc loc, Identifier ident, Type specType, RootObject specAlias, RootObject defaultAlias) @safe { super(loc, ident); this.specType = specType; this.specAlias = specAlias; this.defaultAlias = defaultAlias; } override TemplateAliasParameter isTemplateAliasParameter() { return this; } override TemplateAliasParameter syntaxCopy() { return new TemplateAliasParameter(loc, ident, specType ? specType.syntaxCopy() : null, objectSyntaxCopy(specAlias), objectSyntaxCopy(defaultAlias)); } override bool declareParameter(Scope* sc) { auto ti = new TypeIdentifier(loc, ident); Declaration ad = new AliasDeclaration(loc, ident, ti); return sc.insert(ad) !is null; } override void print(RootObject oarg, RootObject oded) { printf(" %s\n", ident.toChars()); Dsymbol sa = isDsymbol(oded); assert(sa); printf("\tParameter alias: %s\n", sa.toChars()); } override RootObject specialization() { return specAlias; } override RootObject defaultArg(const ref Loc instLoc, Scope* sc) { RootObject da = defaultAlias; if (auto ta = isType(defaultAlias)) { switch (ta.ty) { // If the default arg is a template, instantiate for each type case Tinstance : // same if the default arg is a mixin, traits, typeof // since the content might rely on a previous parameter // (https://issues.dlang.org/show_bug.cgi?id=23686) case Tmixin, Ttypeof, Ttraits : da = ta.syntaxCopy(); break; default: } } RootObject o = aliasParameterSemantic(loc, sc, da, null); // use the parameter loc return o; } override bool hasDefaultArg() { return defaultAlias !is null; } override RootObject dummyArg() { RootObject s = specAlias; if (!s) { if (!sdummy) sdummy = new Dsymbol(); s = sdummy; } return s; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/template.html#TemplateSequenceParameter * Syntax: * ident ... */ extern (C++) final class TemplateTupleParameter : TemplateParameter { extern (D) this(const ref Loc loc, Identifier ident) @safe { super(loc, ident); } override TemplateTupleParameter isTemplateTupleParameter() { return this; } override TemplateTupleParameter syntaxCopy() { return new TemplateTupleParameter(loc, ident); } override bool declareParameter(Scope* sc) { auto ti = new TypeIdentifier(loc, ident); Declaration ad = new AliasDeclaration(loc, ident, ti); return sc.insert(ad) !is null; } override void print(RootObject oarg, RootObject oded) { printf(" %s... [", ident.toChars()); Tuple v = isTuple(oded); assert(v); //printf("|%d| ", v.objects.length); foreach (i, o; v.objects) { if (i) printf(", "); Dsymbol sa = isDsymbol(o); if (sa) printf("alias: %s", sa.toChars()); Type ta = isType(o); if (ta) printf("type: %s", ta.toChars()); Expression ea = isExpression(o); if (ea) printf("exp: %s", ea.toChars()); assert(!isTuple(o)); // no nested Tuple arguments } printf("]\n"); } override RootObject specialization() { return null; } override RootObject defaultArg(const ref Loc instLoc, Scope* sc) { return null; } override bool hasDefaultArg() { return false; } override RootObject dummyArg() { return null; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/template.html#explicit_tmp_instantiation * Given: * foo!(args) => * name = foo * tiargs = args */ extern (C++) class TemplateInstance : ScopeDsymbol { Identifier name; // Array of Types/Expressions of template // instance arguments [int*, char, 10*10] Objects* tiargs; // Array of Types/Expressions corresponding // to TemplateDeclaration.parameters // [int, char, 100] Objects tdtypes; // Modules imported by this template instance Modules importedModules; Dsymbol tempdecl; // referenced by foo.bar.abc Dsymbol enclosing; // if referencing local symbols, this is the context Dsymbol aliasdecl; // !=null if instance is an alias for its sole member /** If this is not null and it has a value that is not the current object, then this field points to an existing template instance and that object has been duplicated into us. If this object is a duplicate, the ``memberOf`` field will be set to a root module (passed on CLI). This information is useful to deduplicate analysis that may occur after semantic 3 has completed. See_Also: memberOf */ TemplateInstance inst; ScopeDsymbol argsym; // argument symbol table size_t hash; // cached result of toHash() /// For function template, these are the function names and arguments /// Relevant because different resolutions of `auto ref` parameters /// create different template instances even with the same template arguments Expressions* fargs; Identifiers* fnames; TemplateInstances* deferred; /** If this is not null then this template instance appears in a root module's members. Note: This is not useful for determining duplication status of this template instance. Use the field ``inst`` for determining if a template instance has been duplicated into this object. See_Also: inst */ Module memberOf; // Used to determine the instance needs code generation. // Note that these are inaccurate until semantic analysis phase completed. TemplateInstance tinst; // enclosing template instance TemplateInstance tnext; // non-first instantiated instances Module minst; // the top module that instantiated this instance private ushort _nest; // for recursive pretty printing detection, 3 MSBs reserved for flags (below) ubyte inuse; // for recursive expansion detection private enum Flag : uint { semantictiargsdone = 1u << (_nest.sizeof * 8 - 1), // MSB of _nest havetempdecl = semantictiargsdone >> 1, gagged = semantictiargsdone >> 2, available = gagged - 1 // always last flag minus one, 1s for all available bits } extern(D) final @safe @property pure nothrow @nogc { ushort nest() const { return _nest & Flag.available; } void nestUp() { assert(nest() < Flag.available); ++_nest; } void nestDown() { assert(nest() > 0); --_nest; } /// has semanticTiargs() been done? bool semantictiargsdone() const { return (_nest & Flag.semantictiargsdone) != 0; } void semantictiargsdone(bool x) { if (x) _nest |= Flag.semantictiargsdone; else _nest &= ~Flag.semantictiargsdone; } /// if used second constructor bool havetempdecl() const { return (_nest & Flag.havetempdecl) != 0; } void havetempdecl(bool x) { if (x) _nest |= Flag.havetempdecl; else _nest &= ~Flag.havetempdecl; } /// if the instantiation is done with error gagging bool gagged() const { return (_nest & Flag.gagged) != 0; } void gagged(bool x) { if (x) _nest |= Flag.gagged; else _nest &= ~Flag.gagged; } } extern (D) this(const ref Loc loc, Identifier ident, Objects* tiargs) scope { super(loc, null); static if (LOG) { printf("TemplateInstance(this = %p, ident = '%s')\n", this, ident ? ident.toChars() : "null"); } this.name = ident; this.tiargs = tiargs; } /***************** * This constructor is only called when we figured out which function * template to instantiate. */ extern (D) this(const ref Loc loc, TemplateDeclaration td, Objects* tiargs) scope { super(loc, null); static if (LOG) { printf("TemplateInstance(this = %p, tempdecl = '%s')\n", this, td.toChars()); } this.name = td.ident; this.tiargs = tiargs; this.tempdecl = td; this.semantictiargsdone = true; this.havetempdecl = true; assert(tempdecl._scope); } extern (D) static Objects* arraySyntaxCopy(Objects* objs) { Objects* a = null; if (objs) { a = new Objects(objs.length); foreach (i, o; *objs) (*a)[i] = objectSyntaxCopy(o); } return a; } override TemplateInstance syntaxCopy(Dsymbol s) { TemplateInstance ti = s ? cast(TemplateInstance)s : new TemplateInstance(loc, name, null); ti.tiargs = arraySyntaxCopy(tiargs); TemplateDeclaration td; if (inst && tempdecl && (td = tempdecl.isTemplateDeclaration()) !is null) td.ScopeDsymbol.syntaxCopy(ti); else ScopeDsymbol.syntaxCopy(ti); return ti; } // resolve real symbol override final Dsymbol toAlias() { static if (LOG) { printf("TemplateInstance.toAlias()\n"); } if (!inst) { // Maybe we can resolve it if (_scope) { dsymbolSemantic(this, _scope); } if (!inst) { .error(loc, "%s `%s` cannot resolve forward reference", kind, toPrettyChars); errors = true; return this; } } if (inst != this) return inst.toAlias(); if (aliasdecl) { return aliasdecl.toAlias(); } return inst; } override const(char)* kind() const { return "template instance"; } override bool oneMember(out Dsymbol ps, Identifier ident) { ps = null; return true; } override const(char)* toChars() const { OutBuffer buf; toCBufferInstance(this, buf); return buf.extractChars(); } override final const(char)* toPrettyCharsHelper() { OutBuffer buf; toCBufferInstance(this, buf, true); return buf.extractChars(); } /************************************** * Given an error instantiating the TemplateInstance, * give the nested TemplateInstance instantiations that got * us here. Those are a list threaded into the nested scopes. * Params: * cl = classification of this trace as printing either errors or deprecations * max_shown = maximum number of trace elements printed (controlled with -v/-verror-limit) */ extern(D) final void printInstantiationTrace(Classification cl = Classification.error, const(uint) max_shown = global.params.v.errorSupplementCount()) { if (global.gag) return; // Print full trace for verbose mode, otherwise only short traces const(char)* format = "instantiated from here: `%s`"; // This returns a function pointer scope printFn = () { final switch (cl) { case Classification.error: return &errorSupplemental; case Classification.deprecation: return &deprecationSupplemental; case Classification.gagged, Classification.tip, Classification.warning: assert(0); } }(); // determine instantiation depth and number of recursive instantiations int n_instantiations = 1; int n_totalrecursions = 0; for (TemplateInstance cur = this; cur; cur = cur.tinst) { ++n_instantiations; // Set error here as we don't want it to depend on the number of // entries that are being printed. if (cl == Classification.error || (cl == Classification.warning && global.params.warnings == DiagnosticReporting.error) || (cl == Classification.deprecation && global.params.useDeprecated == DiagnosticReporting.error)) cur.errors = true; // If two instantiations use the same declaration, they are recursive. // (this works even if they are instantiated from different places in the // same template). // In principle, we could also check for multiple-template recursion, but it's // probably not worthwhile. if (cur.tinst && cur.tempdecl && cur.tinst.tempdecl && cur.tempdecl.loc.equals(cur.tinst.tempdecl.loc)) ++n_totalrecursions; } if (n_instantiations <= max_shown) { for (TemplateInstance cur = this; cur; cur = cur.tinst) printFn(cur.loc, format, cur.toChars()); } else if (n_instantiations - n_totalrecursions <= max_shown) { // By collapsing recursive instantiations into a single line, // we can stay under the limit. int recursionDepth = 0; for (TemplateInstance cur = this; cur; cur = cur.tinst) { if (cur.tinst && cur.tempdecl && cur.tinst.tempdecl && cur.tempdecl.loc.equals(cur.tinst.tempdecl.loc)) { ++recursionDepth; } else { if (recursionDepth) printFn(cur.loc, "%d recursive instantiations from here: `%s`", recursionDepth + 2, cur.toChars()); else printFn(cur.loc, format, cur.toChars()); recursionDepth = 0; } } } else { // Even after collapsing the recursions, the depth is too deep. // Just display the first few and last few instantiations. uint i = 0; for (TemplateInstance cur = this; cur; cur = cur.tinst) { if (i == max_shown / 2) printFn(cur.loc, "... (%d instantiations, -v to show) ...", n_instantiations - max_shown); if (i < max_shown / 2 || i >= n_instantiations - max_shown + max_shown / 2) printFn(cur.loc, format, cur.toChars()); ++i; } } } /************************************* * Lazily generate identifier for template instance. * This is because 75% of the ident's are never needed. */ override final Identifier getIdent() { if (!ident && inst && !errors) ident = genIdent(tiargs); // need an identifier for name mangling purposes. return ident; } /************************************* * Compare proposed template instantiation with existing template instantiation. * Note that this is not commutative because of the auto ref check. * Params: * ti = existing template instantiation * Returns: * true for match */ final bool equalsx(TemplateInstance ti) { //printf("this = %p, ti = %p\n", this, ti); assert(tdtypes.length == ti.tdtypes.length); // Nesting must match if (enclosing != ti.enclosing) { //printf("test2 enclosing %s ti.enclosing %s\n", enclosing ? enclosing.toChars() : "", ti.enclosing ? ti.enclosing.toChars() : ""); goto Lnotequals; } //printf("parent = %s, ti.parent = %s\n", parent.toPrettyChars(), ti.parent.toPrettyChars()); if (!arrayObjectMatch(tdtypes, ti.tdtypes)) goto Lnotequals; /* Template functions may have different instantiations based on * "auto ref" parameters. */ if (auto fd = ti.toAlias().isFuncDeclaration()) { if (!fd.errors) { auto resolvedArgs = fd.type.isTypeFunction().resolveNamedArgs( ArgumentList(this.fargs, this.fnames), null); // resolvedArgs can be null when there's an error: fail_compilation/fail14669.d // In that case, equalsx returns true to prevent endless template instantiations // However, it can also mean the function was explicitly instantiated // without function arguments: fail_compilation/fail14669 // Hence the following check: if (this.fargs && !resolvedArgs) return true; Expression[] args = resolvedArgs ? (*resolvedArgs)[] : []; auto fparameters = fd.getParameterList(); size_t nfparams = fparameters.length; // Num function parameters for (size_t j = 0; j < nfparams; j++) { Parameter fparam = fparameters[j]; if (fparam.storageClass & STC.autoref) // if "auto ref" { Expression farg = (j < args.length) ? args[j] : fparam.defaultArg; // resolveNamedArgs strips trailing nulls / default params // when it doesn't anymore, the ternary can be replaced with: // assert(j < resolvedArgs.length); if (!farg) farg = fparam.defaultArg; if (!farg) goto Lnotequals; if (farg.isLvalue()) { if (!(fparam.storageClass & STC.ref_)) goto Lnotequals; // auto ref's don't match } else { if (fparam.storageClass & STC.ref_) goto Lnotequals; // auto ref's don't match } } } } } return true; Lnotequals: return false; } extern (D) final size_t toHash() { if (!hash) { hash = cast(size_t)cast(void*)enclosing; hash += arrayObjectHash(tdtypes); hash += hash == 0; } return hash; } /** Returns: true if the instances' innards are discardable. The idea of this function is to see if the template instantiation can be 100% replaced with its eponymous member. All other members can be discarded, even in the compiler to free memory (for example, the template could be expanded in a region allocator, deemed trivial, the end result copied back out independently and the entire region freed), and can be elided entirely from the binary. The current implementation affects code that generally looks like: --- template foo(args...) { some_basic_type_or_string helper() { .... } enum foo = helper(); } --- since it was the easiest starting point of implementation but it can and should be expanded more later. */ final bool isDiscardable() { if (aliasdecl is null) return false; auto v = aliasdecl.isVarDeclaration(); if (v is null) return false; if (!(v.storage_class & STC.manifest)) return false; // Currently only doing basic types here because it is the easiest proof-of-concept // implementation with minimal risk of side effects, but it could likely be // expanded to any type that already exists outside this particular instance. if (!(v.type.equals(Type.tstring) || (v.type.isTypeBasic() !is null))) return false; // Static ctors and dtors, even in an eponymous enum template, are still run, // so if any of them are in here, we'd better not assume it is trivial lest // we break useful code foreach(member; *members) { if(member.hasStaticCtorOrDtor()) return false; if(member.isStaticDtorDeclaration()) return false; if(member.isStaticCtorDeclaration()) return false; } // but if it passes through this gauntlet... it should be fine. D code will // see only the eponymous member, outside stuff can never access it, even through // reflection; the outside world ought to be none the wiser. Even dmd should be // able to simply free the memory of everything except the final result. return true; } /*********************************************** * Returns true if this is not instantiated in non-root module, and * is a part of non-speculative instantiatiation. * * Note: minst does not stabilize until semantic analysis is completed, * so don't call this function during semantic analysis to return precise result. */ final bool needsCodegen() { version (IN_LLVM) { assert(global.params.linkonceTemplates != LinkonceTemplates.aggressive); } //printf("needsCodegen() %s\n", toChars()); // minst is finalized after the 1st invocation. // tnext is only needed for the 1st invocation and // cleared for further invocations. TemplateInstance tnext = this.tnext; TemplateInstance tinst = this.tinst; this.tnext = null; // Don't do codegen if the instance has errors, // is a dummy instance (see evaluateConstraint), // or is determined to be discardable. if (errors || inst is null || inst.isDiscardable()) { minst = null; // mark as speculative return false; } // This should only be called on the primary instantiation. assert(this is inst); if (global.params.allInst) { // Do codegen if there is an instantiation from a root module, to maximize link-ability. static ThreeState needsCodegenAllInst(TemplateInstance tithis, TemplateInstance tinst) { // Do codegen if `this` is instantiated from a root module. if (tithis.minst && tithis.minst.isRoot()) return ThreeState.yes; // Do codegen if the ancestor needs it. if (tinst && tinst.inst && tinst.inst.needsCodegen()) { tithis.minst = tinst.inst.minst; // cache result assert(tithis.minst); assert(tithis.minst.isRoot()); return ThreeState.yes; } return ThreeState.none; } if (const needsCodegen = needsCodegenAllInst(this, tinst)) return needsCodegen == ThreeState.yes ? true : false; // Do codegen if a sibling needs it. for (; tnext; tnext = tnext.tnext) { const needsCodegen = needsCodegenAllInst(tnext, tnext.tinst); if (needsCodegen == ThreeState.yes) { minst = tnext.minst; // cache result assert(minst); assert(minst.isRoot()); return true; } else if (!minst && tnext.minst) { minst = tnext.minst; // cache result from non-speculative sibling // continue searching } else if (needsCodegen != ThreeState.none) break; } // Elide codegen because there's no instantiation from any root modules. return false; } else { // Prefer instantiations from non-root modules, to minimize object code size. /* If a TemplateInstance is ever instantiated from a non-root module, * we do not have to generate code for it, * because it will be generated when the non-root module is compiled. * * But, if the non-root 'minst' imports any root modules, it might still need codegen. * * The problem is if A imports B, and B imports A, and both A * and B instantiate the same template, does the compilation of A * or the compilation of B do the actual instantiation? * * See https://issues.dlang.org/show_bug.cgi?id=2500. * * => Elide codegen if there is at least one instantiation from a non-root module * which doesn't import any root modules. */ static ThreeState needsCodegenRootOnly(TemplateInstance tithis, TemplateInstance tinst) { // If the ancestor isn't speculative, // 1. do codegen if the ancestor needs it // 2. elide codegen if the ancestor doesn't need it (non-root instantiation of ancestor incl. subtree) if (tinst && tinst.inst) { tinst = tinst.inst; const needsCodegen = tinst.needsCodegen(); // sets tinst.minst if (tinst.minst) // not speculative { tithis.minst = tinst.minst; // cache result return needsCodegen ? ThreeState.yes : ThreeState.no; } } // Elide codegen if `this` doesn't need it. if (tithis.minst && !tithis.minst.isRoot() && !tithis.minst.rootImports()) return ThreeState.no; return ThreeState.none; } if (const needsCodegen = needsCodegenRootOnly(this, tinst)) return needsCodegen == ThreeState.yes ? true : false; // Elide codegen if a (non-speculative) sibling doesn't need it. for (; tnext; tnext = tnext.tnext) { const needsCodegen = needsCodegenRootOnly(tnext, tnext.tinst); // sets tnext.minst if (tnext.minst) // not speculative { if (needsCodegen == ThreeState.no) { minst = tnext.minst; // cache result assert(!minst.isRoot() && !minst.rootImports()); return false; } else if (!minst) { minst = tnext.minst; // cache result from non-speculative sibling // continue searching } else if (needsCodegen != ThreeState.none) break; } } // Unless `this` is still speculative (=> all further siblings speculative too), // do codegen because we found no guaranteed-codegen'd non-root instantiation. return minst !is null; } } /********************************************** * Find template declaration corresponding to template instance. * * Returns: * false if finding fails. * Note: * This function is reentrant against error occurrence. If returns false, * any members of this object won't be modified, and repetition call will * reproduce same error. */ extern (D) final bool findTempDecl(Scope* sc, WithScopeSymbol* pwithsym) { if (pwithsym) *pwithsym = null; if (havetempdecl) return true; //printf("TemplateInstance.findTempDecl() %s\n", toChars()); if (!tempdecl) { /* Given: * foo!( ... ) * figure out which TemplateDeclaration foo refers to. */ Identifier id = name; Dsymbol scopesym; Dsymbol s = sc.search(loc, id, scopesym); if (!s) { s = sc.search_correct(id); if (s) .error(loc, "%s `%s` template `%s` is not defined, did you mean %s?", kind, toPrettyChars, id.toChars(), s.toChars()); else .error(loc, "%s `%s` template `%s` is not defined", kind, toPrettyChars, id.toChars()); return false; } static if (LOG) { printf("It's an instance of '%s' kind '%s'\n", s.toChars(), s.kind()); if (s.parent) printf("s.parent = '%s'\n", s.parent.toChars()); } if (pwithsym) *pwithsym = scopesym.isWithScopeSymbol(); /* We might have found an alias within a template when * we really want the template. */ TemplateInstance ti; if (s.parent && (ti = s.parent.isTemplateInstance()) !is null) { if (ti.tempdecl && ti.tempdecl.ident == id) { /* This is so that one can refer to the enclosing * template, even if it has the same name as a member * of the template, if it has a !(arguments) */ TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration(); assert(td); if (td.overroot) // if not start of overloaded list of TemplateDeclaration's td = td.overroot; // then get the start s = td; } } // The template might originate from a selective import which implies that // s is a lowered AliasDeclaration of the actual TemplateDeclaration. // This is the last place where we see the deprecated alias because it is // stripped below, so check if the selective import was deprecated. // See https://issues.dlang.org/show_bug.cgi?id=20840. if (s.isAliasDeclaration()) s.checkDeprecated(this.loc, sc); if (!updateTempDecl(sc, s)) { return false; } } assert(tempdecl); // Look for forward references auto tovers = tempdecl.isOverloadSet(); foreach (size_t oi; 0 .. tovers ? tovers.a.length : 1) { Dsymbol dstart = tovers ? tovers.a[oi] : tempdecl; int r = overloadApply(dstart, (Dsymbol s) { auto td = s.isTemplateDeclaration(); if (!td) return 0; if (td.semanticRun == PASS.initial) { if (td._scope) { // Try to fix forward reference. Ungag errors while doing so. Ungag ungag = td.ungagSpeculative(); td.dsymbolSemantic(td._scope); } if (td.semanticRun == PASS.initial) { .error(loc, "%s `%s` `%s` forward references template declaration `%s`", kind, toPrettyChars, toChars(), td.toChars()); return 1; } } return 0; }); if (r) return false; } return true; } /********************************************** * Confirm s is a valid template, then store it. * Input: * sc * s candidate symbol of template. It may be: * TemplateDeclaration * FuncDeclaration with findTemplateDeclRoot() != NULL * OverloadSet which contains candidates * Returns: * true if updating succeeds. */ extern (D) final bool updateTempDecl(Scope* sc, Dsymbol s) { if (!s) return tempdecl !is null; Identifier id = name; s = s.toAlias(); /* If an OverloadSet, look for a unique member that is a template declaration */ if (OverloadSet os = s.isOverloadSet()) { s = null; foreach (s2; os.a) { if (FuncDeclaration f = s2.isFuncDeclaration()) s2 = f.findTemplateDeclRoot(); else s2 = s2.isTemplateDeclaration(); if (s2) { if (s) { tempdecl = os; return true; } s = s2; } } if (!s) { .error(loc, "%s `%s` template `%s` is not defined", kind, toPrettyChars, id.toChars()); return false; } } if (OverDeclaration od = s.isOverDeclaration()) { tempdecl = od; // TODO: more strict check return true; } /* It should be a TemplateDeclaration, not some other symbol */ if (FuncDeclaration f = s.isFuncDeclaration()) tempdecl = f.findTemplateDeclRoot(); else tempdecl = s.isTemplateDeclaration(); // We're done if (tempdecl) return true; // Error already issued, just return `false` if (!s.parent && global.errors) return false; if (!s.parent && s.getType()) { Dsymbol s2 = s.getType().toDsymbol(sc); if (!s2) { .error(loc, "`%s` is not a valid template instance, because `%s` is not a template declaration but a type (`%s == %s`)", toChars(), id.toChars(), id.toChars(), s.getType.kind()); return false; } // because s can be the alias created for a TemplateParameter const AliasDeclaration ad = s.isAliasDeclaration(); version (none) { if (ad && ad.isAliasedTemplateParameter()) printf("`%s` is an alias created from a template parameter\n", s.toChars()); } if (!ad || !ad.isAliasedTemplateParameter()) s = s2; } TemplateInstance ti = s.parent ? s.parent.isTemplateInstance() : null; /* This avoids the VarDeclaration.toAlias() which runs semantic() too soon */ static bool matchId(TemplateInstance ti, Identifier id) { if (ti.aliasdecl && ti.aliasdecl.isVarDeclaration()) return ti.aliasdecl.isVarDeclaration().ident == id; return ti.toAlias().ident == id; } if (ti && (ti.name == s.ident || matchId(ti, s.ident)) && ti.tempdecl) { /* This is so that one can refer to the enclosing * template, even if it has the same name as a member * of the template, if it has a !(arguments) */ TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration(); assert(td); if (td.overroot) // if not start of overloaded list of TemplateDeclaration's td = td.overroot; // then get the start tempdecl = td; return true; } else { .error(loc, "%s `%s` `%s` is not a template declaration, it is a %s", kind, toPrettyChars, id.toChars(), s.kind()); return false; } } /********************************** * Run semantic of tiargs as arguments of template. * Input: * loc * sc * tiargs array of template arguments * flags 1: replace const variables with their initializers * 2: don't devolve Parameter to Type * atd tuple being optimized. If found, it's not expanded here * but in AliasAssign semantic. * Returns: * false if one or more arguments have errors. */ extern (D) static bool semanticTiargs(const ref Loc loc, Scope* sc, Objects* tiargs, int flags, TupleDeclaration atd = null) { // Run semantic on each argument, place results in tiargs[] //printf("+TemplateInstance.semanticTiargs()\n"); if (!tiargs) return true; bool err = false; // The arguments are not treated as part of a default argument, // because they are evaluated at compile time. const inCondition = !!(sc.flags & SCOPE.condition); // For master branch: const inCondition = sc.condition; sc = sc.push(); sc.inDefaultArg = false; // https://issues.dlang.org/show_bug.cgi?id=24699 sc.flags |= SCOPE.condition * inCondition; // For master branch: sc.condition = inCondition; for (size_t j = 0; j < tiargs.length; j++) { RootObject o = (*tiargs)[j]; Type ta = isType(o); Expression ea = isExpression(o); Dsymbol sa = isDsymbol(o); //printf("1: (*tiargs)[%d] = %p, s=%p, v=%p, ea=%p, ta=%p\n", j, o, isDsymbol(o), isTuple(o), ea, ta); if (ta) { //printf("type %s\n", ta.toChars()); // It might really be an Expression or an Alias ta.resolve(loc, sc, ea, ta, sa, (flags & 1) != 0); if (ea) goto Lexpr; if (sa) goto Ldsym; if (ta is null) { assert(global.errors); ta = Type.terror; } Ltype: if (TypeTuple tt = ta.isTypeTuple()) { // Expand tuple size_t dim = tt.arguments.length; tiargs.remove(j); if (dim) { tiargs.reserve(dim); foreach (i, arg; *tt.arguments) { if (flags & 2 && (arg.storageClass & STC.parameter)) tiargs.insert(j + i, arg); else tiargs.insert(j + i, arg.type); } } j--; continue; } if (ta.ty == Terror) { err = true; continue; } (*tiargs)[j] = ta.merge2(); } else if (ea) { Lexpr: //printf("+[%d] ea = %s %s\n", j, EXPtoString(ea.op).ptr, ea.toChars()); if (flags & 1) // only used by __traits { ea = ea.expressionSemantic(sc); // must not interpret the args, excepting template parameters if (!ea.isVarExp() || (ea.isVarExp().var.storage_class & STC.templateparameter)) { ea = ea.optimize(WANTvalue); } } else { sc = sc.startCTFE(); ea = ea.expressionSemantic(sc); sc = sc.endCTFE(); if (auto varExp = ea.isVarExp()) { /* If the parameter is a function that is not called * explicitly, i.e. `foo!func` as opposed to `foo!func()`, * then it is a dsymbol, not the return value of `func()` */ Declaration vd = varExp.var; if (auto fd = vd.isFuncDeclaration()) { sa = fd; goto Ldsym; } /* Otherwise skip substituting a const var with * its initializer. The problem is the initializer won't * match with an 'alias' parameter. Instead, do the * const substitution in TemplateValueParameter.matchArg(). */ } else if (definitelyValueParameter(ea)) { if (ea.checkValue()) // check void expression ea = ErrorExp.get(); uint olderrs = global.errors; ea = ea.ctfeInterpret(); if (global.errors != olderrs) ea = ErrorExp.get(); } } //printf("-[%d] ea = %s %s\n", j, EXPtoString(ea.op).ptr, ea.toChars()); if (TupleExp te = ea.isTupleExp()) { // Expand tuple size_t dim = te.exps.length; tiargs.remove(j); if (dim) { tiargs.reserve(dim); foreach (i, exp; *te.exps) tiargs.insert(j + i, exp); } j--; continue; } if (ea.op == EXP.error) { err = true; continue; } (*tiargs)[j] = ea; if (ea.op == EXP.type) { ta = ea.type; goto Ltype; } if (ea.op == EXP.scope_) { sa = ea.isScopeExp().sds; goto Ldsym; } if (FuncExp fe = ea.isFuncExp()) { /* A function literal, that is passed to template and * already semanticed as function pointer, never requires * outer frame. So convert it to global function is valid. */ if (fe.fd.tok == TOK.reserved && fe.type.ty == Tpointer) { // change to non-nested fe.fd.tok = TOK.function_; fe.fd.vthis = null; } else if (fe.td) { /* If template argument is a template lambda, * get template declaration itself. */ //sa = fe.td; //goto Ldsym; } } if (ea.op == EXP.dotVariable && !(flags & 1)) { // translate expression to dsymbol. sa = ea.isDotVarExp().var; goto Ldsym; } if (auto te = ea.isTemplateExp()) { sa = te.td; goto Ldsym; } if (ea.op == EXP.dotTemplateDeclaration && !(flags & 1)) { // translate expression to dsymbol. sa = ea.isDotTemplateExp().td; goto Ldsym; } if (auto de = ea.isDotExp()) { if (auto se = de.e2.isScopeExp()) { sa = se.sds; goto Ldsym; } } } else if (sa) { Ldsym: //printf("dsym %s %s\n", sa.kind(), sa.toChars()); if (sa.errors) { err = true; continue; } TupleDeclaration d = sa.toAlias().isTupleDeclaration(); if (d) { if (d is atd) { (*tiargs)[j] = d; continue; } // Expand tuple tiargs.remove(j); tiargs.insert(j, d.objects); j--; continue; } if (FuncAliasDeclaration fa = sa.isFuncAliasDeclaration()) { FuncDeclaration f = fa.toAliasFunc(); if (!fa.hasOverloads && f.isUnique()) { // Strip FuncAlias only when the aliased function // does not have any overloads. sa = f; } } (*tiargs)[j] = sa; TemplateDeclaration td = sa.isTemplateDeclaration(); if (td && td.semanticRun == PASS.initial && td.literal) { td.dsymbolSemantic(sc); } FuncDeclaration fd = sa.isFuncDeclaration(); if (fd) functionSemantic(fd); } else if (isParameter(o)) { } else { assert(0); } //printf("1: (*tiargs)[%d] = %p\n", j, (*tiargs)[j]); } sc.pop(); version (none) { printf("-TemplateInstance.semanticTiargs()\n"); for (size_t j = 0; j < tiargs.length; j++) { RootObject o = (*tiargs)[j]; Type ta = isType(o); Expression ea = isExpression(o); Dsymbol sa = isDsymbol(o); Tuple va = isTuple(o); printf("\ttiargs[%d] = ta %p, ea %p, sa %p, va %p\n", j, ta, ea, sa, va); } } return !err; } /********************************** * Run semantic on the elements of tiargs. * Input: * sc * Returns: * false if one or more arguments have errors. * Note: * This function is reentrant against error occurrence. If returns false, * all elements of tiargs won't be modified. */ extern (D) final bool semanticTiargs(Scope* sc) { //printf("+TemplateInstance.semanticTiargs() %s\n", toChars()); if (semantictiargsdone) return true; if (semanticTiargs(loc, sc, tiargs, 0)) { // cache the result iff semantic analysis succeeded entirely semantictiargsdone = 1; return true; } return false; } /********************************** * Find the TemplateDeclaration that matches this TemplateInstance best. * * Params: * sc = the scope this TemplateInstance resides in * argumentList = function arguments in case of a template function * * Returns: * `true` if a match was found, `false` otherwise */ extern (D) final bool findBestMatch(Scope* sc, ArgumentList argumentList) { if (havetempdecl) { TemplateDeclaration tempdecl = this.tempdecl.isTemplateDeclaration(); assert(tempdecl); assert(tempdecl._scope); // Deduce tdtypes tdtypes.setDim(tempdecl.parameters.length); if (!matchWithInstance(sc, tempdecl, this, tdtypes, argumentList, 2)) { .error(loc, "%s `%s` incompatible arguments for template instantiation", kind, toPrettyChars); return false; } // TODO: Normalizing tiargs for https://issues.dlang.org/show_bug.cgi?id=7469 is necessary? return true; } static if (LOG) { printf("TemplateInstance.findBestMatch()\n"); } uint errs = global.errors; TemplateDeclaration td_last = null; Objects dedtypes; /* Since there can be multiple TemplateDeclaration's with the same * name, look for the best match. */ auto tovers = tempdecl.isOverloadSet(); foreach (size_t oi; 0 .. tovers ? tovers.a.length : 1) { TemplateDeclaration td_best; TemplateDeclaration td_ambig; MATCH m_best = MATCH.nomatch; Dsymbol dstart = tovers ? tovers.a[oi] : tempdecl; overloadApply(dstart, (Dsymbol s) { auto td = s.isTemplateDeclaration(); if (!td) return 0; if (td == td_best) // skip duplicates return 0; //printf("td = %s\n", td.toPrettyChars()); // If more arguments than parameters, // then this is no match. if (td.parameters.length < tiargs.length) { if (!td.isVariadic()) return 0; } dedtypes.setDim(td.parameters.length); dedtypes.zero(); assert(td.semanticRun != PASS.initial); MATCH m = matchWithInstance(sc, td, this, dedtypes, argumentList, 0); //printf("matchWithInstance = %d\n", m); if (m == MATCH.nomatch) // no match at all return 0; if (m < m_best) goto Ltd_best; if (m > m_best) goto Ltd; // Disambiguate by picking the most specialized TemplateDeclaration { MATCH c1 = leastAsSpecialized(sc, td, td_best, argumentList); MATCH c2 = leastAsSpecialized(sc, td_best, td, argumentList); //printf("c1 = %d, c2 = %d\n", c1, c2); if (c1 > c2) goto Ltd; if (c1 < c2) goto Ltd_best; } td_ambig = td; return 0; Ltd_best: // td_best is the best match so far td_ambig = null; return 0; Ltd: // td is the new best match td_ambig = null; td_best = td; m_best = m; tdtypes.setDim(dedtypes.length); memcpy(tdtypes.tdata(), dedtypes.tdata(), tdtypes.length * (void*).sizeof); return 0; }); if (td_ambig) { .error(loc, "%s `%s.%s` matches more than one template declaration:", td_best.kind(), td_best.parent.toPrettyChars(), td_best.ident.toChars()); .errorSupplemental(td_best.loc, "`%s`\nand:", td_best.toChars()); .errorSupplemental(td_ambig.loc, "`%s`", td_ambig.toChars()); return false; } if (td_best) { if (!td_last) td_last = td_best; else if (td_last != td_best) { ScopeDsymbol.multiplyDefined(loc, td_last, td_best); return false; } } } if (td_last) { /* https://issues.dlang.org/show_bug.cgi?id=7469 * Normalize tiargs by using corresponding deduced * template value parameters and tuples for the correct mangling. * * By doing this before hasNestedArgs, CTFEable local variable will be * accepted as a value parameter. For example: * * void foo() { * struct S(int n) {} // non-global template * const int num = 1; // CTFEable local variable * S!num s; // S!1 is instantiated, not S!num * } */ size_t dim = td_last.parameters.length - (td_last.isVariadic() ? 1 : 0); for (size_t i = 0; i < dim; i++) { if (tiargs.length <= i) tiargs.push(tdtypes[i]); assert(i < tiargs.length); auto tvp = (*td_last.parameters)[i].isTemplateValueParameter(); if (!tvp) continue; assert(tdtypes[i]); // tdtypes[i] is already normalized to the required type in matchArg (*tiargs)[i] = tdtypes[i]; } if (td_last.isVariadic() && tiargs.length == dim && tdtypes[dim]) { Tuple va = isTuple(tdtypes[dim]); assert(va); tiargs.pushSlice(va.objects[]); } } else if (errors && inst) { // instantiation was failed with error reporting assert(global.errors); return false; } else { auto tdecl = tempdecl.isTemplateDeclaration(); if (errs != global.errors) errorSupplemental(loc, "while looking for match for `%s`", toChars()); else if (tdecl && !tdecl.overnext) { // Only one template, so we can give better error message const(char)* msg = "does not match template declaration"; const(char)* tip; OutBuffer buf; HdrGenState hgs; hgs.skipConstraints = true; toCharsMaybeConstraints(tdecl, buf, hgs); const tmsg = buf.peekChars(); const cmsg = tdecl.getConstraintEvalError(tip); if (cmsg) { .error(loc, "%s `%s` %s `%s`\n%s", kind, toPrettyChars, msg, tmsg, cmsg); if (tip) .tip(tip); } else { .error(loc, "%s `%s` %s `%s`", kind, toPrettyChars, msg, tmsg); if (tdecl.parameters.length == tiargs.length) { // https://issues.dlang.org/show_bug.cgi?id=7352 // print additional information, e.g. `foo` is not a type foreach (i, param; *tdecl.parameters) { MATCH match = param.matchArg(loc, sc, tiargs, i, tdecl.parameters, dedtypes, null); auto arg = (*tiargs)[i]; auto sym = arg.isDsymbol; auto exp = arg.isExpression; if (exp) exp = exp.optimize(WANTvalue); if (match == MATCH.nomatch && ((sym && sym.isFuncDeclaration) || (exp && exp.isVarExp))) { if (param.isTemplateTypeParameter) errorSupplemental(loc, "`%s` is not a type", arg.toChars); else if (auto tvp = param.isTemplateValueParameter) errorSupplemental(loc, "`%s` is not of a value of type `%s`", arg.toChars, tvp.valType.toChars); } } } } } else { .error(loc, "%s `%s` does not match any template declaration", kind(), toPrettyChars()); bool found; overloadApply(tempdecl, (s){ if (!found) errorSupplemental(loc, "Candidates are:"); found = true; errorSupplemental(s.loc, "%s", s.toChars()); return 0; }); } return false; } /* The best match is td_last */ tempdecl = td_last; static if (LOG) { printf("\tIt's a match with template declaration '%s'\n", tempdecl.toChars()); } return (errs == global.errors); } /***************************************************** * Determine if template instance is really a template function, * and that template function needs to infer types from the function * arguments. * * Like findBestMatch, iterate possible template candidates, * but just looks only the necessity of type inference. */ extern (D) final bool needsTypeInference(Scope* sc, int flag = 0) { //printf("TemplateInstance.needsTypeInference() %s\n", toChars()); if (semanticRun != PASS.initial) return false; uint olderrs = global.errors; Objects dedtypes; size_t count = 0; auto tovers = tempdecl.isOverloadSet(); foreach (size_t oi; 0 .. tovers ? tovers.a.length : 1) { Dsymbol dstart = tovers ? tovers.a[oi] : tempdecl; int r = overloadApply(dstart, (Dsymbol s) { auto td = s.isTemplateDeclaration(); if (!td) return 0; /* If any of the overloaded template declarations need inference, * then return true */ if (!td.onemember) return 0; if (auto td2 = td.onemember.isTemplateDeclaration()) { if (!td2.onemember || !td2.onemember.isFuncDeclaration()) return 0; if (tiargs.length >= td.parameters.length - (td.isVariadic() ? 1 : 0)) return 0; return 1; } auto fd = td.onemember.isFuncDeclaration(); if (!fd || fd.type.ty != Tfunction) return 0; foreach (tp; *td.parameters) { if (tp.isTemplateThisParameter()) return 1; } /* Determine if the instance arguments, tiargs, are all that is necessary * to instantiate the template. */ //printf("tp = %p, td.parameters.length = %d, tiargs.length = %d\n", tp, td.parameters.length, tiargs.length); auto tf = fd.type.isTypeFunction(); if (tf.parameterList.length) { auto tp = td.isVariadic(); if (tp && td.parameters.length > 1) return 1; if (!tp && tiargs.length < td.parameters.length) { // Can remain tiargs be filled by default arguments? foreach (size_t i; tiargs.length .. td.parameters.length) { if (!(*td.parameters)[i].hasDefaultArg()) return 1; } } foreach (i, fparam; tf.parameterList) { // 'auto ref' needs inference. if (fparam.storageClass & STC.auto_) return 1; } } if (!flag) { /* Calculate the need for overload resolution. * When only one template can match with tiargs, inference is not necessary. */ dedtypes.setDim(td.parameters.length); dedtypes.zero(); if (td.semanticRun == PASS.initial) { if (td._scope) { // Try to fix forward reference. Ungag errors while doing so. Ungag ungag = td.ungagSpeculative(); td.dsymbolSemantic(td._scope); } if (td.semanticRun == PASS.initial) { .error(loc, "%s `%s` `%s` forward references template declaration `%s`", kind, toPrettyChars, toChars(), td.toChars()); return 1; } } MATCH m = matchWithInstance(sc, td, this, dedtypes, ArgumentList(), 0); if (m == MATCH.nomatch) return 0; } /* If there is more than one function template which matches, we may * need type inference (see https://issues.dlang.org/show_bug.cgi?id=4430) */ return ++count > 1 ? 1 : 0; }); if (r) return true; } if (olderrs != global.errors) { if (!global.gag) { errorSupplemental(loc, "while looking for match for `%s`", toChars()); semanticRun = PASS.semanticdone; inst = this; } errors = true; } //printf("false\n"); return false; } /***************************************** * Determines if a TemplateInstance will need a nested * generation of the TemplateDeclaration. * Sets enclosing property if so, and returns != 0; */ extern (D) final bool hasNestedArgs(Objects* args, bool isstatic) { int nested = 0; //printf("TemplateInstance.hasNestedArgs('%s')\n", tempdecl.ident.toChars()); // arguments from parent instances are also accessible if (!enclosing) { if (TemplateInstance ti = tempdecl.toParent().isTemplateInstance()) enclosing = ti.enclosing; } /* A nested instance happens when an argument references a local * symbol that is on the stack. */ foreach (o; *args) { Expression ea = isExpression(o); Dsymbol sa = isDsymbol(o); Tuple va = isTuple(o); if (ea) { if (auto ve = ea.isVarExp()) { sa = ve.var; goto Lsa; } if (auto te = ea.isThisExp()) { sa = te.var; goto Lsa; } if (auto fe = ea.isFuncExp()) { if (fe.td) sa = fe.td; else sa = fe.fd; goto Lsa; } // Emulate Expression.toMangleBuffer call that had exist in TemplateInstance.genIdent. if (ea.op != EXP.int64 && ea.op != EXP.float64 && ea.op != EXP.complex80 && ea.op != EXP.null_ && ea.op != EXP.string_ && ea.op != EXP.arrayLiteral && ea.op != EXP.assocArrayLiteral && ea.op != EXP.structLiteral) { if (!ea.type.isTypeError()) .error(ea.loc, "%s `%s` expression `%s` is not a valid template value argument", kind, toPrettyChars, ea.toChars()); errors = true; } } else if (sa) { Lsa: sa = sa.toAlias(); TemplateDeclaration td = sa.isTemplateDeclaration(); if (td) { TemplateInstance ti = sa.toParent().isTemplateInstance(); if (ti && ti.enclosing) sa = ti; } TemplateInstance ti = sa.isTemplateInstance(); Declaration d = sa.isDeclaration(); if ((td && td.literal) || (ti && ti.enclosing) || (d && !d.isDataseg() && !(d.storage_class & STC.manifest) && (!d.isFuncDeclaration() || d.isFuncDeclaration().isNested()) && !isTemplateMixin())) { Dsymbol dparent = sa.toParent2(); if (!dparent || dparent.isModule) goto L1; else if (!enclosing) enclosing = dparent; else if (enclosing != dparent) { /* Select the more deeply nested of the two. * Error if one is not nested inside the other. */ for (Dsymbol p = enclosing; p; p = p.parent) { if (p == dparent) goto L1; // enclosing is most nested } for (Dsymbol p = dparent; p; p = p.parent) { if (p == enclosing) { enclosing = dparent; goto L1; // dparent is most nested } } //https://issues.dlang.org/show_bug.cgi?id=17870 if (dparent.isClassDeclaration() && enclosing.isClassDeclaration()) { auto pc = dparent.isClassDeclaration(); auto ec = enclosing.isClassDeclaration(); if (pc.isBaseOf(ec, null)) goto L1; else if (ec.isBaseOf(pc, null)) { enclosing = dparent; goto L1; } } .error(loc, "%s `%s` `%s` is nested in both `%s` and `%s`", kind, toPrettyChars, toChars(), enclosing.toChars(), dparent.toChars()); errors = true; } L1: //printf("\tnested inside %s as it references %s\n", enclosing.toChars(), sa.toChars()); nested |= 1; } } else if (va) { nested |= cast(int)hasNestedArgs(&va.objects, isstatic); } } //printf("-TemplateInstance.hasNestedArgs('%s') = %d\n", tempdecl.ident.toChars(), nested); return nested != 0; } /***************************************** * Append 'this' to the specific module members[] */ extern (D) final Dsymbols* appendToModuleMember() { Module mi = minst; // instantiated . inserted module version (IN_LLVM) { if (global.params.linkonceTemplates == LinkonceTemplates.aggressive) { // Skip if it's not a root module. if (!mi || !mi.isRoot()) return null; // Skip if the primary instance has already been assigned to a root // module. if (inst.memberOf) return null; // Okay, this is the primary instance to be assigned to a root // module and getting semantic3. assert(this is inst); } } //printf("%s.appendToModuleMember() enclosing = %s mi = %s\n", // toPrettyChars(), // enclosing ? enclosing.toPrettyChars() : null, // mi ? mi.toPrettyChars() : null); if (global.params.allInst || !mi || mi.isRoot()) { /* If the instantiated module is speculative or root, insert to the * member of a root module. Then: * - semantic3 pass will get called on the instance members. * - codegen pass will get a selection chance to do/skip it (needsCodegen()). */ static Dsymbol getStrictEnclosing(TemplateInstance ti) { do { if (ti.enclosing) return ti.enclosing; ti = ti.tempdecl.isInstantiated(); } while (ti); return null; } Dsymbol enc = getStrictEnclosing(this); // insert target is made stable by using the module // where tempdecl is declared. mi = (enc ? enc : tempdecl).getModule(); if (!mi.isRoot()) { if (mi.importedFrom) { mi = mi.importedFrom; assert(mi.isRoot()); } else { // This can happen when using the frontend as a library. // Append it to the non-root module. } } } else { /* If the instantiated module is non-root, insert to the member of the * non-root module. Then: * - semantic3 pass won't be called on the instance. * - codegen pass won't reach to the instance. * Unless it is re-appended to a root module later (with changed minst). */ } //printf("\t-. mi = %s\n", mi.toPrettyChars()); if (memberOf) // already appended to some module { assert(mi.isRoot(), "can only re-append to a root module"); if (memberOf.isRoot()) return null; // no need to move to another root module } Dsymbols* a = mi.members; a.push(this); memberOf = mi; if (mi.semanticRun >= PASS.semantic2done && mi.isRoot()) Module.addDeferredSemantic2(this); if (mi.semanticRun >= PASS.semantic3done && mi.isRoot()) Module.addDeferredSemantic3(this); return a; } /**************************************************** * Declare parameters of template instance, initialize them with the * template instance arguments. */ extern (D) final void declareParameters(Scope* sc) { TemplateDeclaration tempdecl = this.tempdecl.isTemplateDeclaration(); assert(tempdecl); //printf("TemplateInstance.declareParameters()\n"); foreach (i, o; tdtypes) // initializer for tp { TemplateParameter tp = (*tempdecl.parameters)[i]; //printf("\ttdtypes[%d] = %p\n", i, o); declareParameter(tempdecl, sc, tp, o); } } /**************************************** * This instance needs an identifier for name mangling purposes. * Create one by taking the template declaration name and adding * the type signature for it. */ extern (D) final Identifier genIdent(Objects* args) { //printf("TemplateInstance.genIdent('%s')\n", tempdecl.ident.toChars()); assert(args is tiargs); OutBuffer buf; mangleToBuffer(this, buf); //printf("\tgenIdent = %s\n", buf.peekChars()); return Identifier.idPool(buf[]); } extern (D) final void expandMembers(Scope* sc2) { members.foreachDsymbol( (s) { s.setScope (sc2); } ); members.foreachDsymbol( (s) { s.importAll(sc2); } ); if (!aliasdecl) { /* static if's are crucial to evaluating aliasdecl correctly. But * evaluating the if/else bodies may require aliasdecl. * So, evaluate the condition for static if's, but not their if/else bodies. * Then try to set aliasdecl. * Later do the if/else bodies. * https://issues.dlang.org/show_bug.cgi?id=23598 * It might be better to do this by attaching a lambda to the StaticIfDeclaration * to do the oneMembers call after the sid.include(sc2) is run as part of dsymbolSemantic(). */ bool done; void staticIfDg(Dsymbol s) { if (done || aliasdecl) return; //printf("\t staticIfDg on '%s %s' in '%s'\n", s.kind(), s.toChars(), this.toChars()); if (!s.isStaticIfDeclaration()) { //s.dsymbolSemantic(sc2); done = true; return; } auto sid = s.isStaticIfDeclaration(); sid.include(sc2); if (members.length) { Dsymbol sa; if (Dsymbol.oneMembers(members, sa, tempdecl.ident) && sa) aliasdecl = sa; } done = true; } members.foreachDsymbol(&staticIfDg); } void symbolDg(Dsymbol s) { //printf("\t semantic on '%s' %p kind %s in '%s'\n", s.toChars(), s, s.kind(), this.toChars()); //printf("test: enclosing = %d, sc2.parent = %s\n", enclosing, sc2.parent.toChars()); //if (enclosing) // s.parent = sc.parent; //printf("test3: enclosing = %d, s.parent = %s\n", enclosing, s.parent.toChars()); s.dsymbolSemantic(sc2); //printf("test4: enclosing = %d, s.parent = %s\n", enclosing, s.parent.toChars()); Module.runDeferredSemantic(); } members.foreachDsymbol(&symbolDg); } extern (D) final void tryExpandMembers(Scope* sc2) { __gshared int nest; // extracted to a function to allow windows SEH to work without destructors in the same function //printf("%d\n", nest); if (++nest > global.recursionLimit) { global.gag = 0; // ensure error message gets printed .error(loc, "%s `%s` recursive expansion exceeded allowed nesting limit", kind, toPrettyChars); fatal(); } expandMembers(sc2); nest--; } extern (D) final void trySemantic3(Scope* sc2) { // extracted to a function to allow windows SEH to work without destructors in the same function __gshared int nest; //printf("%d\n", nest); if (++nest > global.recursionLimit) { global.gag = 0; // ensure error message gets printed .error(loc, "%s `%s` recursive expansion exceeded allowed nesting limit", kind, toPrettyChars); fatal(); } semantic3(this, sc2); --nest; } override final inout(TemplateInstance) isTemplateInstance() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /************************************** * IsExpression can evaluate the specified type speculatively, and even if * it instantiates any symbols, they are normally unnecessary for the * final executable. * However, if those symbols leak to the actual code, compiler should remark * them as non-speculative to generate their code and link to the final executable. */ void unSpeculative(Scope* sc, RootObject o) { if (!o) return; if (Tuple tup = isTuple(o)) { foreach (obj; tup.objects) { unSpeculative(sc, obj); } return; } Dsymbol s = getDsymbol(o); if (!s) return; if (Declaration d = s.isDeclaration()) { if (VarDeclaration vd = d.isVarDeclaration()) o = vd.type; else if (AliasDeclaration ad = d.isAliasDeclaration()) { o = ad.getType(); if (!o) o = ad.toAlias(); } else o = d.toAlias(); s = getDsymbol(o); if (!s) return; } if (TemplateInstance ti = s.isTemplateInstance()) { // If the instance is already non-speculative, // or it is leaked to the speculative scope. if (ti.minst !is null || sc.minst is null) return; // Remark as non-speculative instance. ti.minst = sc.minst; if (!ti.tinst) ti.tinst = sc.tinst; unSpeculative(sc, ti.tempdecl); } if (TemplateInstance ti = s.isInstantiated()) unSpeculative(sc, ti); } /********************************** * Return true if e could be valid only as a template value parameter. * Return false if it might be an alias or tuple. * (Note that even in this case, it could still turn out to be a value). */ bool definitelyValueParameter(Expression e) @safe { // None of these can be value parameters if (e.op == EXP.tuple || e.op == EXP.scope_ || e.op == EXP.type || e.op == EXP.dotType || e.op == EXP.template_ || e.op == EXP.dotTemplateDeclaration || e.op == EXP.function_ || e.op == EXP.error || e.op == EXP.this_ || e.op == EXP.super_ || e.op == EXP.dot) return false; if (e.op != EXP.dotVariable) return true; /* Template instantiations involving a DotVar expression are difficult. * In most cases, they should be treated as a value parameter, and interpreted. * But they might also just be a fully qualified name, which should be treated * as an alias. */ // x.y.f cannot be a value FuncDeclaration f = e.isDotVarExp().var.isFuncDeclaration(); if (f) return false; while (e.op == EXP.dotVariable) { e = e.isDotVarExp().e1; } // this.x.y and super.x.y couldn't possibly be valid values. if (e.op == EXP.this_ || e.op == EXP.super_) return false; // e.type.x could be an alias if (e.op == EXP.dotType) return false; // var.x.y is the only other possible form of alias if (e.op != EXP.variable) return true; VarDeclaration v = e.isVarExp().var.isVarDeclaration(); // func.x.y is not an alias if (!v) return true; // https://issues.dlang.org/show_bug.cgi?id=16685 // var.x.y where var is a constant available at compile time if (v.storage_class & STC.manifest) return true; // TODO: Should we force CTFE if it is a global constant? return false; } /*********************************************************** * https://dlang.org/spec/template-mixin.html * Syntax: * mixin MixinTemplateName [TemplateArguments] [Identifier]; */ extern (C++) final class TemplateMixin : TemplateInstance { TypeQualified tqual; extern (D) this(const ref Loc loc, Identifier ident, TypeQualified tqual, Objects* tiargs) { super(loc, tqual.idents.length ? cast(Identifier)tqual.idents[tqual.idents.length - 1] : (cast(TypeIdentifier)tqual).ident, tiargs ? tiargs : new Objects()); //printf("TemplateMixin(ident = '%s')\n", ident ? ident.toChars() : ""); this.ident = ident; this.tqual = tqual; } override TemplateInstance syntaxCopy(Dsymbol s) { auto tm = new TemplateMixin(loc, ident, tqual.syntaxCopy(), tiargs); return TemplateInstance.syntaxCopy(tm); } override const(char)* kind() const { return "mixin"; } override bool oneMember(out Dsymbol ps, Identifier ident) { return Dsymbol.oneMember(ps, ident); } override bool hasPointers() { //printf("TemplateMixin.hasPointers() %s\n", toChars()); return members.foreachDsymbol( (s) { return s.hasPointers(); } ) != 0; } override const(char)* toChars() const { OutBuffer buf; toCBufferInstance(this, buf); return buf.extractChars(); } extern (D) bool findTempDecl(Scope* sc) { // Follow qualifications to find the TemplateDeclaration if (!tempdecl) { Expression e; Type t; Dsymbol s; tqual.resolve(loc, sc, e, t, s); if (!s) { .error(loc, "%s `%s` is not defined", kind, toPrettyChars); return false; } s = s.toAlias(); tempdecl = s.isTemplateDeclaration(); OverloadSet os = s.isOverloadSet(); /* If an OverloadSet, look for a unique member that is a template declaration */ if (os) { Dsymbol ds = null; foreach (i, sym; os.a) { Dsymbol s2 = sym.isTemplateDeclaration(); if (s2) { if (ds) { tempdecl = os; break; } ds = s2; } } } if (!tempdecl) { .error(loc, "%s `%s` - `%s` is a %s, not a template", kind, toPrettyChars, s.toChars(), s.kind()); return false; } } assert(tempdecl); // Look for forward references auto tovers = tempdecl.isOverloadSet(); foreach (size_t oi; 0 .. tovers ? tovers.a.length : 1) { Dsymbol dstart = tovers ? tovers.a[oi] : tempdecl; int r = overloadApply(dstart, (Dsymbol s) { auto td = s.isTemplateDeclaration(); if (!td) return 0; if (td.semanticRun == PASS.initial) { if (td._scope) td.dsymbolSemantic(td._scope); else { semanticRun = PASS.initial; return 1; } } return 0; }); if (r) return false; } return true; } override inout(TemplateMixin) isTemplateMixin() inout { return this; } override void accept(Visitor v) { v.visit(this); } } /************************************ * This struct is needed for TemplateInstance to be the key in an associative array. * Fixing https://issues.dlang.org/show_bug.cgi?id=15813 would make it unnecessary. */ struct TemplateInstanceBox { TemplateInstance ti; this(TemplateInstance ti) { this.ti = ti; this.ti.toHash(); assert(this.ti.hash); } size_t toHash() const @safe pure nothrow { assert(ti.hash); return ti.hash; } bool opEquals(ref const TemplateInstanceBox s) @trusted const { bool res = void; if (ti.inst && s.ti.inst) { /* This clause is only used when an instance with errors * is replaced with a correct instance. */ res = ti is s.ti; } else { /* Used when a proposed instance is used to see if there's * an existing instance. */ static if (__VERSION__ < 2099) // https://issues.dlang.org/show_bug.cgi?id=22717 res = (cast()s.ti).equalsx(cast()ti); else res = (cast()ti).equalsx(cast()s.ti); } debug (FindExistingInstance) ++(res ? nHits : nCollisions); return res; } debug (FindExistingInstance) { __gshared uint nHits, nCollisions; shared static ~this() { printf("debug (FindExistingInstance) TemplateInstanceBox.equals hits: %u collisions: %u\n", nHits, nCollisions); } } } /******************************************* * Match to a particular TemplateParameter. * Input: * instLoc location that the template is instantiated. * tiargs[] actual arguments to template instance * i i'th argument * parameters[] template parameters * dedtypes[] deduced arguments to template instance * *psparam set to symbol declared and initialized to dedtypes[i] */ MATCH matchArg(TemplateParameter tp, Loc instLoc, Scope* sc, Objects* tiargs, size_t i, TemplateParameters* parameters, ref Objects dedtypes, Declaration* psparam) { MATCH matchArgNoMatch() { if (psparam) *psparam = null; return MATCH.nomatch; } MATCH matchArgParameter() { RootObject oarg; if (i < tiargs.length) oarg = (*tiargs)[i]; else { // Get default argument instead oarg = tp.defaultArg(instLoc, sc); if (!oarg) { assert(i < dedtypes.length); // It might have already been deduced oarg = dedtypes[i]; if (!oarg) return matchArgNoMatch(); } } return tp.matchArg(sc, oarg, i, parameters, dedtypes, psparam); } MATCH matchArgTuple(TemplateTupleParameter ttp) { /* The rest of the actual arguments (tiargs[]) form the match * for the variadic parameter. */ assert(i + 1 == dedtypes.length); // must be the last one Tuple ovar; if (Tuple u = isTuple(dedtypes[i])) { // It has already been deduced ovar = u; } else if (i + 1 == tiargs.length && isTuple((*tiargs)[i])) ovar = isTuple((*tiargs)[i]); else { ovar = new Tuple(); //printf("ovar = %p\n", ovar); if (i < tiargs.length) { //printf("i = %d, tiargs.length = %d\n", i, tiargs.length); ovar.objects.setDim(tiargs.length - i); foreach (j, ref obj; ovar.objects) obj = (*tiargs)[i + j]; } } return ttp.matchArg(sc, ovar, i, parameters, dedtypes, psparam); } if (auto ttp = tp.isTemplateTupleParameter()) return matchArgTuple(ttp); else return matchArgParameter(); } MATCH matchArg(TemplateParameter tp, Scope* sc, RootObject oarg, size_t i, TemplateParameters* parameters, ref Objects dedtypes, Declaration* psparam) { MATCH matchArgNoMatch() { //printf("\tm = %d\n", MATCH.nomatch); if (psparam) *psparam = null; return MATCH.nomatch; } MATCH matchArgType(TemplateTypeParameter ttp) { //printf("TemplateTypeParameter.matchArg('%s')\n", ttp.ident.toChars()); MATCH m = MATCH.exact; Type ta = isType(oarg); if (!ta) { //printf("%s %p %p %p\n", oarg.toChars(), isExpression(oarg), isDsymbol(oarg), isTuple(oarg)); return matchArgNoMatch(); } //printf("ta is %s\n", ta.toChars()); if (ttp.specType) { if (!ta || ta == TemplateTypeParameter.tdummy) return matchArgNoMatch(); //printf("\tcalling deduceType(): ta is %s, specType is %s\n", ta.toChars(), ttp.specType.toChars()); MATCH m2 = deduceType(ta, sc, ttp.specType, *parameters, dedtypes); if (m2 == MATCH.nomatch) { //printf("\tfailed deduceType\n"); return matchArgNoMatch(); } if (m2 < m) m = m2; if (dedtypes[i]) { Type t = cast(Type)dedtypes[i]; if (ttp.dependent && !t.equals(ta)) // https://issues.dlang.org/show_bug.cgi?id=14357 return matchArgNoMatch(); /* This is a self-dependent parameter. For example: * template X(T : T*) {} * template X(T : S!T, alias S) {} */ //printf("t = %s ta = %s\n", t.toChars(), ta.toChars()); ta = t; } } else { if (dedtypes[i]) { // Must match already deduced type Type t = cast(Type)dedtypes[i]; if (!t.equals(ta)) { //printf("t = %s ta = %s\n", t.toChars(), ta.toChars()); return matchArgNoMatch(); } } else { // So that matches with specializations are better m = MATCH.convert; } } dedtypes[i] = ta; if (psparam) *psparam = new AliasDeclaration(ttp.loc, ttp.ident, ta); //printf("\tm = %d\n", m); return ttp.dependent ? MATCH.exact : m; } MATCH matchArgValue(TemplateValueParameter tvp) { //printf("TemplateValueParameter.matchArg('%s')\n", tvp.ident.toChars()); MATCH m = MATCH.exact; Expression ei = isExpression(oarg); Type vt; if (!ei && oarg) { Dsymbol si = isDsymbol(oarg); FuncDeclaration f = si ? si.isFuncDeclaration() : null; if (!f || !f.fbody || f.needThis()) return matchArgNoMatch(); ei = new VarExp(tvp.loc, f); ei = ei.expressionSemantic(sc); /* If a function is really property-like, and then * it's CTFEable, ei will be a literal expression. */ uint olderrors = global.startGagging(); ei = resolveProperties(sc, ei); ei = ei.ctfeInterpret(); if (global.endGagging(olderrors) || ei.op == EXP.error) return matchArgNoMatch(); /* https://issues.dlang.org/show_bug.cgi?id=14520 * A property-like function can match to both * TemplateAlias and ValueParameter. But for template overloads, * it should always prefer alias parameter to be consistent * template match result. * * template X(alias f) { enum X = 1; } * template X(int val) { enum X = 2; } * int f1() { return 0; } // CTFEable * int f2(); // body-less function is not CTFEable * enum x1 = X!f1; // should be 1 * enum x2 = X!f2; // should be 1 * * e.g. The x1 value must be same even if the f1 definition will be moved * into di while stripping body code. */ m = MATCH.convert; } if (ei && ei.op == EXP.variable) { // Resolve const variables that we had skipped earlier ei = ei.ctfeInterpret(); } //printf("\tvalType: %s, ty = %d\n", tvp.valType.toChars(), tvp.valType.ty); vt = tvp.valType.typeSemantic(tvp.loc, sc); //printf("ei: %s, ei.type: %s\n", ei.toChars(), ei.type.toChars()); //printf("vt = %s\n", vt.toChars()); if (ei.type) { MATCH m2 = ei.implicitConvTo(vt); //printf("m: %d\n", m); if (m2 < m) m = m2; if (m == MATCH.nomatch) return matchArgNoMatch(); ei = ei.implicitCastTo(sc, vt); ei = ei.ctfeInterpret(); } if (tvp.specValue) { if (ei is null || (cast(void*)ei.type in TemplateValueParameter.edummies && TemplateValueParameter.edummies[cast(void*)ei.type] == ei)) return matchArgNoMatch(); Expression e = tvp.specValue; sc = sc.startCTFE(); e = e.expressionSemantic(sc); e = resolveProperties(sc, e); sc = sc.endCTFE(); e = e.implicitCastTo(sc, vt); e = e.ctfeInterpret(); ei = ei.syntaxCopy(); sc = sc.startCTFE(); ei = ei.expressionSemantic(sc); sc = sc.endCTFE(); ei = ei.implicitCastTo(sc, vt); ei = ei.ctfeInterpret(); //printf("\tei: %s, %s\n", ei.toChars(), ei.type.toChars()); //printf("\te : %s, %s\n", e.toChars(), e.type.toChars()); if (!ei.equals(e)) return matchArgNoMatch(); } else { if (dedtypes[i]) { // Must match already deduced value Expression e = cast(Expression)dedtypes[i]; if (!ei || !ei.equals(e)) return matchArgNoMatch(); } } dedtypes[i] = ei; if (psparam) { Initializer _init = new ExpInitializer(tvp.loc, ei); Declaration sparam = new VarDeclaration(tvp.loc, vt, tvp.ident, _init); sparam.storage_class = STC.manifest; *psparam = sparam; } return tvp.dependent ? MATCH.exact : m; } MATCH matchArgAlias(TemplateAliasParameter tap) { //printf("TemplateAliasParameter.matchArg('%s')\n", tap.ident.toChars()); MATCH m = MATCH.exact; Type ta = isType(oarg); RootObject sa = ta && !ta.deco ? null : getDsymbol(oarg); Expression ea = isExpression(oarg); if (ea) { if (auto te = ea.isThisExp()) sa = te.var; else if (auto se = ea.isSuperExp()) sa = se.var; else if (auto se = ea.isScopeExp()) sa = se.sds; } if (sa) { if ((cast(Dsymbol)sa).isAggregateDeclaration()) m = MATCH.convert; /* specType means the alias must be a declaration with a type * that matches specType. */ if (tap.specType) { tap.specType = typeSemantic(tap.specType, tap.loc, sc); Declaration d = (cast(Dsymbol)sa).isDeclaration(); if (!d) return matchArgNoMatch(); if (!d.type.equals(tap.specType)) return matchArgNoMatch(); } } else { sa = oarg; if (ea) { if (tap.specType) { if (!ea.type.equals(tap.specType)) return matchArgNoMatch(); } } else if (ta && ta.ty == Tinstance && !tap.specAlias) { /* Specialized parameter should be preferred * match to the template type parameter. * template X(alias a) {} // a == this * template X(alias a : B!A, alias B, A...) {} // B!A => ta */ } else if (sa && sa == TemplateTypeParameter.tdummy) { /* https://issues.dlang.org/show_bug.cgi?id=2025 * Aggregate Types should preferentially * match to the template type parameter. * template X(alias a) {} // a == this * template X(T) {} // T => sa */ } else if (ta && ta.ty != Tident) { /* Match any type that's not a TypeIdentifier to alias parameters, * but prefer type parameter. * template X(alias a) { } // a == ta * * TypeIdentifiers are excluded because they might be not yet resolved aliases. */ m = MATCH.convert; } else return matchArgNoMatch(); } if (tap.specAlias) { if (sa == TemplateAliasParameter.sdummy) return matchArgNoMatch(); // check specialization if template arg is a symbol Dsymbol sx = isDsymbol(sa); if (sa != tap.specAlias && sx) { Type talias = isType(tap.specAlias); if (!talias) return matchArgNoMatch(); TemplateInstance ti = sx.isTemplateInstance(); if (!ti && sx.parent) { ti = sx.parent.isTemplateInstance(); if (ti && ti.name != sx.ident) return matchArgNoMatch(); } if (!ti) return matchArgNoMatch(); Type t = new TypeInstance(Loc.initial, ti); MATCH m2 = deduceType(t, sc, talias, *parameters, dedtypes); if (m2 == MATCH.nomatch) return matchArgNoMatch(); } // check specialization if template arg is a type else if (ta) { if (Type tspec = isType(tap.specAlias)) { MATCH m2 = ta.implicitConvTo(tspec); if (m2 == MATCH.nomatch) return matchArgNoMatch(); } else { error(tap.loc, "template parameter specialization for a type must be a type and not `%s`", tap.specAlias.toChars()); return matchArgNoMatch(); } } } else if (dedtypes[i]) { // Must match already deduced symbol RootObject si = dedtypes[i]; if (!sa || si != sa) return matchArgNoMatch(); } dedtypes[i] = sa; if (psparam) { if (Dsymbol s = isDsymbol(sa)) { *psparam = new AliasDeclaration(tap.loc, tap.ident, s); } else if (Type t = isType(sa)) { *psparam = new AliasDeclaration(tap.loc, tap.ident, t); } else { assert(ea); // Declare manifest constant Initializer _init = new ExpInitializer(tap.loc, ea); auto v = new VarDeclaration(tap.loc, null, tap.ident, _init); v.storage_class = STC.manifest; v.dsymbolSemantic(sc); *psparam = v; } } return tap.dependent ? MATCH.exact : m; } MATCH matchArgTuple(TemplateTupleParameter ttp) { //printf("TemplateTupleParameter.matchArg('%s')\n", ttp.ident.toChars()); Tuple ovar = isTuple(oarg); if (!ovar) return MATCH.nomatch; if (dedtypes[i]) { Tuple tup = isTuple(dedtypes[i]); if (!tup) return MATCH.nomatch; if (!match(tup, ovar)) return MATCH.nomatch; } dedtypes[i] = ovar; if (psparam) *psparam = new TupleDeclaration(ttp.loc, ttp.ident, &ovar.objects); return ttp.dependent ? MATCH.exact : MATCH.convert; } if (auto ttp = tp.isTemplateTypeParameter()) return matchArgType(ttp); else if (auto tvp = tp.isTemplateValueParameter()) return matchArgValue(tvp); else if (auto tap = tp.isTemplateAliasParameter()) return matchArgAlias(tap); else if (auto ttp = tp.isTemplateTupleParameter()) return matchArgTuple(ttp); else assert(0); } /*********************************************** * Collect and print statistics on template instantiations. */ struct TemplateStats { __gshared TemplateStats[const void*] stats; uint numInstantiations; // number of instantiations of the template uint uniqueInstantiations; // number of unique instantiations of the template TemplateInstances* allInstances; /******************************* * Add this instance * Params: * td = template declaration * ti = instance of td * listInstances = keep track of instances of templates */ static void incInstance(const TemplateDeclaration td, const TemplateInstance ti, bool listInstances) { void log(ref TemplateStats ts) { if (ts.allInstances is null) ts.allInstances = new TemplateInstances(); if (listInstances) ts.allInstances.push(cast() ti); } // message(ti.loc, "incInstance %p %p", td, ti); if (!td) return; assert(ti); if (auto ts = cast(const void*) td in stats) { log(*ts); ++ts.numInstantiations; } else { stats[cast(const void*) td] = TemplateStats(1, 0); log(stats[cast(const void*) td]); } } /******************************* * Add this unique instance */ static void incUnique(const TemplateDeclaration td, const TemplateInstance ti) { // message(ti.loc, "incUnique %p %p", td, ti); if (!td) return; assert(ti); if (auto ts = cast(const void*) td in stats) ++ts.uniqueInstantiations; else stats[cast(const void*) td] = TemplateStats(0, 1); } } /******************************** * Print informational statistics on template instantiations. * Params: * listInstances = list instances of templates * eSink = where the print is sent */ void printTemplateStats(bool listInstances, ErrorSink eSink) { static struct TemplateDeclarationStats { TemplateDeclaration td; TemplateStats ts; static int compare(scope const TemplateDeclarationStats* a, scope const TemplateDeclarationStats* b) @safe nothrow @nogc pure { auto diff = b.ts.uniqueInstantiations - a.ts.uniqueInstantiations; if (diff) return diff; else return b.ts.numInstantiations - a.ts.numInstantiations; } } const stats_length = TemplateStats.stats.length; if (!stats_length) return; // nothing to report Array!(TemplateDeclarationStats) sortedStats; sortedStats.reserve(stats_length); foreach (td_, ref ts; TemplateStats.stats) { sortedStats.push(TemplateDeclarationStats(cast(TemplateDeclaration) td_, ts)); } sortedStats.sort!(TemplateDeclarationStats.compare); OutBuffer buf; foreach (const ref ss; sortedStats[]) { buf.reset(); HdrGenState hgs; hgs.skipConstraints = true; toCharsMaybeConstraints(ss.td, buf, hgs); const tchars = buf.peekChars(); if (listInstances && ss.ts.allInstances) { eSink.message(ss.td.loc, "vtemplate: %u (%u distinct) instantiation(s) of template `%s` found, they are:", ss.ts.numInstantiations, ss.ts.uniqueInstantiations, tchars); foreach (const ti; (*ss.ts.allInstances)[]) { if (ti.tinst) // if has enclosing instance eSink.message(ti.loc, "vtemplate: implicit instance `%s`", ti.toChars()); else eSink.message(ti.loc, "vtemplate: explicit instance `%s`", ti.toChars()); } } else { eSink.message(ss.td.loc, "vtemplate: %u (%u distinct) instantiation(s) of template `%s` found", ss.ts.numInstantiations, ss.ts.uniqueInstantiations, tchars); } } } /// Pair of MATCHes struct MATCHpair { MATCH mta; /// match template parameters by initial template arguments MATCH mfa; /// match template parameters by inferred template arguments debug this(MATCH mta, MATCH mfa) { assert(MATCH.min <= mta && mta <= MATCH.max); assert(MATCH.min <= mfa && mfa <= MATCH.max); this.mta = mta; this.mfa = mfa; } } void write(ref OutBuffer buf, RootObject obj) { if (obj) { buf.writestring(obj.toChars()); } } ldc-1.40.0-src/dmd/expression.h0000644000000000000000000010422414727557031014753 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/expression.h */ #pragma once #include "ast_node.h" #include "globals.h" #include "arraytypes.h" #include "visitor.h" #include "tokens.h" #include "root/complex_t.h" #include "root/dcompat.h" #include "root/optional.h" class Type; class TypeVector; struct Scope; class TupleDeclaration; class VarDeclaration; class FuncDeclaration; class FuncLiteralDeclaration; class CtorDeclaration; class Dsymbol; class ScopeDsymbol; class Expression; class Declaration; class StructDeclaration; class TemplateInstance; class TemplateDeclaration; class ClassDeclaration; class OverloadSet; class StringExp; class InterpExp; class LoweredAssignExp; class EnumDeclaration; #ifdef IN_GCC typedef union tree_node Symbol; #elif IN_LLVM namespace llvm { class Value; } #else struct Symbol; // back end symbol #endif namespace dmd { // in expressionsem.d Expression *expressionSemantic(Expression *e, Scope *sc); // in typesem.d Expression *defaultInit(Type *mt, const Loc &loc, const bool isCfile = false); // in enumsem.d Expression *getDefaultValue(EnumDeclaration *ed, const Loc &loc); // Entry point for CTFE. // A compile-time result is required. Give an error if not possible Expression *ctfeInterpret(Expression *e); void expandTuples(Expressions *exps, Identifiers *names = nullptr); Expression *optimize(Expression *exp, int result, bool keepLvalue = false); } typedef unsigned char OwnedBy; enum { OWNEDcode, // normal code expression in AST OWNEDctfe, // value expression for CTFE OWNEDcache // constant value cached for CTFE }; #define WANTvalue 0 // default #define WANTexpand 1 // expand const/immutable variables if possible /** * Specifies how the checkModify deals with certain situations */ enum class ModifyFlags { /// Issue error messages on invalid modifications of the variable none, /// No errors are emitted for invalid modifications noError = 0x1, /// The modification occurs for a subfield of the current variable fieldAssign = 0x2, }; class Expression : public ASTNode { public: Type *type; // !=NULL means that semantic() has been run Loc loc; // file location EXP op; // to minimize use of dynamic_cast d_bool parens; // if this is a parenthesized expression size_t size() const; static void _init(); virtual Expression *syntaxCopy(); // kludge for template.isExpression() DYNCAST dyncast() const override final { return DYNCAST_EXPRESSION; } const char *toChars() const override; virtual dinteger_t toInteger(); virtual uinteger_t toUInteger(); virtual real_t toReal(); virtual real_t toImaginary(); virtual complex_t toComplex(); virtual StringExp *toStringExp(); virtual bool isLvalue(); virtual bool checkType(); virtual bool checkValue(); Expression *addressOf(); Expression *deref(); int isConst(); virtual bool isIdentical(const Expression *e) const; virtual Optional toBool(); virtual bool hasCode() { return true; } IntegerExp* isIntegerExp(); ErrorExp* isErrorExp(); VoidInitExp* isVoidInitExp(); RealExp* isRealExp(); ComplexExp* isComplexExp(); IdentifierExp* isIdentifierExp(); DollarExp* isDollarExp(); DsymbolExp* isDsymbolExp(); ThisExp* isThisExp(); SuperExp* isSuperExp(); NullExp* isNullExp(); StringExp* isStringExp(); InterpExp* isInterpExp(); TupleExp* isTupleExp(); ArrayLiteralExp* isArrayLiteralExp(); AssocArrayLiteralExp* isAssocArrayLiteralExp(); StructLiteralExp* isStructLiteralExp(); TypeExp* isTypeExp(); ScopeExp* isScopeExp(); TemplateExp* isTemplateExp(); NewExp* isNewExp(); NewAnonClassExp* isNewAnonClassExp(); SymOffExp* isSymOffExp(); VarExp* isVarExp(); OverExp* isOverExp(); FuncExp* isFuncExp(); DeclarationExp* isDeclarationExp(); TypeidExp* isTypeidExp(); TraitsExp* isTraitsExp(); HaltExp* isHaltExp(); IsExp* isExp(); MixinExp* isMixinExp(); ImportExp* isImportExp(); AssertExp* isAssertExp(); DotIdExp* isDotIdExp(); DotTemplateExp* isDotTemplateExp(); DotVarExp* isDotVarExp(); DotTemplateInstanceExp* isDotTemplateInstanceExp(); DelegateExp* isDelegateExp(); DotTypeExp* isDotTypeExp(); CallExp* isCallExp(); AddrExp* isAddrExp(); PtrExp* isPtrExp(); NegExp* isNegExp(); UAddExp* isUAddExp(); ComExp* isComExp(); NotExp* isNotExp(); DeleteExp* isDeleteExp(); CastExp* isCastExp(); VectorExp* isVectorExp(); VectorArrayExp* isVectorArrayExp(); SliceExp* isSliceExp(); ArrayLengthExp* isArrayLengthExp(); ArrayExp* isArrayExp(); DotExp* isDotExp(); CommaExp* isCommaExp(); IntervalExp* isIntervalExp(); DelegatePtrExp* isDelegatePtrExp(); DelegateFuncptrExp* isDelegateFuncptrExp(); IndexExp* isIndexExp(); PostExp* isPostExp(); PreExp* isPreExp(); AssignExp* isAssignExp(); ConstructExp* isConstructExp(); BlitExp* isBlitExp(); AddAssignExp* isAddAssignExp(); MinAssignExp* isMinAssignExp(); MulAssignExp* isMulAssignExp(); DivAssignExp* isDivAssignExp(); ModAssignExp* isModAssignExp(); AndAssignExp* isAndAssignExp(); OrAssignExp* isOrAssignExp(); XorAssignExp* isXorAssignExp(); PowAssignExp* isPowAssignExp(); ShlAssignExp* isShlAssignExp(); ShrAssignExp* isShrAssignExp(); UshrAssignExp* isUshrAssignExp(); CatAssignExp* isCatAssignExp(); CatElemAssignExp* isCatElemAssignExp(); CatDcharAssignExp* isCatDcharAssignExp(); AddExp* isAddExp(); MinExp* isMinExp(); CatExp* isCatExp(); MulExp* isMulExp(); DivExp* isDivExp(); ModExp* isModExp(); PowExp* isPowExp(); ShlExp* isShlExp(); ShrExp* isShrExp(); UshrExp* isUshrExp(); AndExp* isAndExp(); OrExp* isOrExp(); XorExp* isXorExp(); LogicalExp* isLogicalExp(); InExp* isInExp(); RemoveExp* isRemoveExp(); EqualExp* isEqualExp(); IdentityExp* isIdentityExp(); CondExp* isCondExp(); GenericExp* isGenericExp(); DefaultInitExp* isDefaultInitExp(); FileInitExp* isFileInitExp(); LineInitExp* isLineInitExp(); ModuleInitExp* isModuleInitExp(); FuncInitExp* isFuncInitExp(); PrettyFuncInitExp* isPrettyFuncInitExp(); ClassReferenceExp* isClassReferenceExp(); ThrownExceptionExp* isThrownExceptionExp(); UnaExp* isUnaExp(); BinExp* isBinExp(); BinAssignExp* isBinAssignExp(); LoweredAssignExp* isLoweredAssignExp(); void accept(Visitor *v) override { v->visit(this); } }; class IntegerExp final : public Expression { public: dinteger_t value; static IntegerExp *create(const Loc &loc, dinteger_t value, Type *type); bool equals(const RootObject * const o) const override; dinteger_t toInteger() override; real_t toReal() override; real_t toImaginary() override; complex_t toComplex() override; Optional toBool() override; void accept(Visitor *v) override { v->visit(this); } dinteger_t getInteger() { return value; } template static IntegerExp literal(); }; class ErrorExp final : public Expression { public: void accept(Visitor *v) override { v->visit(this); } static ErrorExp *errorexp; // handy shared value }; class RealExp final : public Expression { public: real_t value; static RealExp *create(const Loc &loc, real_t value, Type *type); bool equals(const RootObject * const o) const override; bool isIdentical(const Expression *e) const override; dinteger_t toInteger() override; uinteger_t toUInteger() override; real_t toReal() override; real_t toImaginary() override; complex_t toComplex() override; Optional toBool() override; void accept(Visitor *v) override { v->visit(this); } }; class ComplexExp final : public Expression { public: complex_t value; static ComplexExp *create(const Loc &loc, complex_t value, Type *type); bool equals(const RootObject * const o) const override; bool isIdentical(const Expression *e) const override; dinteger_t toInteger() override; uinteger_t toUInteger() override; real_t toReal() override; real_t toImaginary() override; complex_t toComplex() override; Optional toBool() override; void accept(Visitor *v) override { v->visit(this); } }; class IdentifierExp : public Expression { public: Identifier *ident; static IdentifierExp *create(const Loc &loc, Identifier *ident); bool isLvalue() override final; void accept(Visitor *v) override { v->visit(this); } }; class DollarExp final : public IdentifierExp { public: void accept(Visitor *v) override { v->visit(this); } }; class DsymbolExp final : public Expression { public: Dsymbol *s; d_bool hasOverloads; DsymbolExp *syntaxCopy() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class ThisExp : public Expression { public: VarDeclaration *var; ThisExp *syntaxCopy() override; Optional toBool() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class SuperExp final : public ThisExp { public: void accept(Visitor *v) override { v->visit(this); } }; class NullExp final : public Expression { public: bool equals(const RootObject * const o) const override; Optional toBool() override; StringExp *toStringExp() override; void accept(Visitor *v) override { v->visit(this); } }; class StringExp final : public Expression { public: utf8_t postfix; // 'c', 'w', 'd' OwnedBy ownedByCtfe; void *string; // char, wchar, dchar, or long data size_t len; // number of chars, wchars, or dchars unsigned char sz; // 1: char, 2: wchar, 4: dchar d_bool committed; // if type is committed d_bool hexString; // if string is parsed from a hex string literal static StringExp *create(const Loc &loc, const char *s); static StringExp *create(const Loc &loc, const void *s, d_size_t len); bool equals(const RootObject * const o) const override; char32_t getCodeUnit(d_size_t i) const; dinteger_t getIndex(d_size_t i) const; StringExp *toStringExp() override; Optional toBool() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } #if IN_LLVM // The D version returns a slice. DString peekString() const { assert(sz == 1); return {len, static_cast(string)}; } // ditto DArray peekData() const { return {len * sz, static_cast(string)}; } #endif size_t numberOfCodeUnits(int tynto = 0) const; void writeTo(void* dest, bool zero, int tyto = 0) const; }; class InterpExp final : public Expression { public: utf8_t postfix; // 'c', 'w', 'd' OwnedBy ownedByCtfe; void* interpolatedSet; void accept(Visitor* v) override { v->visit(this); } }; // Tuple class TupleExp final : public Expression { public: Expression *e0; // side-effect part /* Tuple-field access may need to take out its side effect part. * For example: * foo().tupleof * is rewritten as: * (ref __tup = foo(); tuple(__tup.field0, __tup.field1, ...)) * The declaration of temporary variable __tup will be stored in TupleExp::e0. */ Expressions *exps; static TupleExp *create(const Loc &loc, Expressions *exps); TupleExp *syntaxCopy() override; bool equals(const RootObject * const o) const override; void accept(Visitor *v) override { v->visit(this); } }; class ArrayLiteralExp final : public Expression { public: OwnedBy ownedByCtfe; d_bool onstack; Expression *basis; Expressions *elements; static ArrayLiteralExp *create(const Loc &loc, Expressions *elements); ArrayLiteralExp *syntaxCopy() override; bool equals(const RootObject * const o) const override; Expression *getElement(d_size_t i); Optional toBool() override; StringExp *toStringExp() override; void accept(Visitor *v) override { v->visit(this); } }; class AssocArrayLiteralExp final : public Expression { public: OwnedBy ownedByCtfe; Expressions *keys; Expressions *values; Expression* lowering; bool equals(const RootObject * const o) const override; AssocArrayLiteralExp *syntaxCopy() override; Optional toBool() override; void accept(Visitor *v) override { v->visit(this); } }; class StructLiteralExp final : public Expression { public: StructDeclaration *sd; // which aggregate this is for Expressions *elements; // parallels sd->fields[] with NULL entries for fields to skip Type *stype; // final type of result (can be different from sd's type) union { #if IN_LLVM // With the introduction of pointers returned from CTFE, struct literals can // now contain pointers to themselves. While in toElem, contains a pointer // to the memory used to build the literal for resolving such references. llvm::Value *inProgressMemory; #else Symbol *sym; // back end symbol to initialize with literal (used as a Symbol*) #endif // those fields need to prevent a infinite recursion when one field of struct initialized with 'this' pointer. StructLiteralExp *inlinecopy; }; /** pointer to the origin instance of the expression. * once a new expression is created, origin is set to 'this'. * anytime when an expression copy is created, 'origin' pointer is set to * 'origin' pointer value of the original expression. */ StructLiteralExp *origin; /** anytime when recursive function is calling, 'stageflags' marks with bit flag of * current stage and unmarks before return from this function. * 'inlinecopy' uses similar 'stageflags' and from multiple evaluation 'doInline' * (with infinite recursion) of this expression. */ uint8_t stageflags; d_bool useStaticInit; // if this is true, use the StructDeclaration's init symbol d_bool isOriginal; // used when moving instances to indicate `this is this.origin` OwnedBy ownedByCtfe; static StructLiteralExp *create(const Loc &loc, StructDeclaration *sd, void *elements, Type *stype = nullptr); bool equals(const RootObject * const o) const override; StructLiteralExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeExp final : public Expression { public: TypeExp *syntaxCopy() override; bool checkType() override; bool checkValue() override; void accept(Visitor *v) override { v->visit(this); } }; class ScopeExp final : public Expression { public: ScopeDsymbol *sds; ScopeExp *syntaxCopy() override; bool checkType() override; bool checkValue() override; void accept(Visitor *v) override { v->visit(this); } }; class TemplateExp final : public Expression { public: TemplateDeclaration *td; FuncDeclaration *fd; bool isLvalue() override; bool checkType() override; bool checkValue() override; void accept(Visitor *v) override { v->visit(this); } }; class NewExp final : public Expression { public: /* newtype(arguments) */ Expression *thisexp; // if !NULL, 'this' for class being allocated Type *newtype; Expressions *arguments; // Array of Expression's Identifiers *names; // Array of names corresponding to expressions Expression *argprefix; // expression to be evaluated just before arguments[] CtorDeclaration *member; // constructor function d_bool onstack; // allocate on stack d_bool thrownew; // this NewExp is the expression of a ThrowStatement Expression *lowering; // lowered druntime hook: `_d_newclass` static NewExp *create(const Loc &loc, Expression *thisexp, Type *newtype, Expressions *arguments); NewExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class NewAnonClassExp final : public Expression { public: /* class baseclasses { } (arguments) */ Expression *thisexp; // if !NULL, 'this' for class being allocated ClassDeclaration *cd; // class being instantiated Expressions *arguments; // Array of Expression's to call class constructor NewAnonClassExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class SymbolExp : public Expression { public: Declaration *var; Dsymbol *originalScope; d_bool hasOverloads; void accept(Visitor *v) override { v->visit(this); } }; // Offset from symbol class SymOffExp final : public SymbolExp { public: dinteger_t offset; Optional toBool() override; void accept(Visitor *v) override { v->visit(this); } }; // Variable class VarExp final : public SymbolExp { public: d_bool delegateWasExtracted; static VarExp *create(const Loc &loc, Declaration *var, bool hasOverloads = true); bool equals(const RootObject * const o) const override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; // Overload Set class OverExp final : public Expression { public: OverloadSet *vars; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; // Function/Delegate literal class FuncExp final : public Expression { public: FuncLiteralDeclaration *fd; TemplateDeclaration *td; TOK tok; bool equals(const RootObject * const o) const override; FuncExp *syntaxCopy() override; const char *toChars() const override; bool checkType() override; bool checkValue() override; void accept(Visitor *v) override { v->visit(this); } }; // Declaration of a symbol // D grammar allows declarations only as statements. However in AST representation // it can be part of any expression. This is used, for example, during internal // syntax re-writes to inject hidden symbols. class DeclarationExp final : public Expression { public: Dsymbol *declaration; DeclarationExp *syntaxCopy() override; bool hasCode() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeidExp final : public Expression { public: RootObject *obj; TypeidExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TraitsExp final : public Expression { public: Identifier *ident; Objects *args; TraitsExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class HaltExp final : public Expression { public: void accept(Visitor *v) override { v->visit(this); } }; class IsExp final : public Expression { public: /* is(targ id tok tspec) * is(targ id == tok2) */ Type *targ; Identifier *id; // can be NULL Type *tspec; // can be NULL TemplateParameters *parameters; TOK tok; // ':' or '==' TOK tok2; // 'struct', 'union', etc. IsExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; /****************************************************************/ class UnaExp : public Expression { public: Expression *e1; UnaExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class BinExp : public Expression { public: Expression *e1; Expression *e2; Type *att1; // Save alias this type to detect recursion Type *att2; // Save alias this type to detect recursion BinExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class BinAssignExp : public BinExp { public: bool isLvalue() override final; void accept(Visitor *v) override { v->visit(this); } }; /****************************************************************/ class MixinExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ImportExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class AssertExp final : public UnaExp { public: Expression *msg; AssertExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class ThrowExp final : public UnaExp { public: ThrowExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class DotIdExp final : public UnaExp { public: Identifier *ident; d_bool noderef; // true if the result of the expression will never be dereferenced d_bool wantsym; // do not replace Symbol with its initializer during semantic() d_bool arrow; // ImportC: if -> instead of . static DotIdExp *create(const Loc &loc, Expression *e, Identifier *ident); void accept(Visitor *v) override { v->visit(this); } }; class DotTemplateExp final : public UnaExp { public: TemplateDeclaration *td; bool checkType() override; bool checkValue() override; void accept(Visitor *v) override { v->visit(this); } }; class DotVarExp final : public UnaExp { public: Declaration *var; d_bool hasOverloads; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class DotTemplateInstanceExp final : public UnaExp { public: TemplateInstance *ti; DotTemplateInstanceExp *syntaxCopy() override; bool checkType() override; bool checkValue() override; void accept(Visitor *v) override { v->visit(this); } }; class DelegateExp final : public UnaExp { public: FuncDeclaration *func; d_bool hasOverloads; VarDeclaration *vthis2; // container for multi-context void accept(Visitor *v) override { v->visit(this); } }; class DotTypeExp final : public UnaExp { public: Dsymbol *sym; // symbol that represents a type void accept(Visitor *v) override { v->visit(this); } }; struct ArgumentList final { Expressions* arguments; Identifiers* names; ArgumentList() : arguments(), names() { } ArgumentList(Expressions* arguments, Identifiers* names = nullptr) : arguments(arguments), names(names) {} }; class CallExp final : public UnaExp { public: Expressions *arguments; // function arguments Identifiers *names; FuncDeclaration *f; // symbol to call d_bool directcall; // true if a virtual call is devirtualized d_bool inDebugStatement; // true if this was in a debug statement d_bool ignoreAttributes; // don't enforce attributes (e.g. call @gc function in @nogc code) d_bool isUfcsRewrite; // the first argument was pushed in here by a UFCS rewrite VarDeclaration *vthis2; // container for multi-context static CallExp *create(const Loc &loc, Expression *e, Expressions *exps); static CallExp *create(const Loc &loc, Expression *e); static CallExp *create(const Loc &loc, Expression *e, Expression *earg1); static CallExp *create(const Loc &loc, FuncDeclaration *fd, Expression *earg1); CallExp *syntaxCopy() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class AddrExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class PtrExp final : public UnaExp { public: bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class NegExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class UAddExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ComExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class NotExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class DeleteExp final : public UnaExp { public: d_bool isRAII; void accept(Visitor *v) override { v->visit(this); } }; class CastExp final : public UnaExp { public: // Possible to cast to one type while painting to another type Type *to; // type to cast to unsigned char mod; // MODxxxxx d_bool trusted; // assume cast is safe CastExp *syntaxCopy() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class VectorExp final : public UnaExp { public: TypeVector *to; // the target vector type before semantic() unsigned dim; // number of elements in the vector OwnedBy ownedByCtfe; static VectorExp *create(const Loc &loc, Expression *e, Type *t); VectorExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class VectorArrayExp final : public UnaExp { public: bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class SliceExp final : public UnaExp { public: Expression *upr; // NULL if implicit 0 Expression *lwr; // NULL if implicit [length - 1] VarDeclaration *lengthVar; bool upperIsInBounds() const; // true if upr <= e1.length bool upperIsInBounds(bool v); bool lowerIsLessThanUpper() const; // true if lwr <= upr bool lowerIsLessThanUpper(bool v); bool arrayop() const; // an array operation, rather than a slice bool arrayop(bool v); private: uint8_t bitFields; public: SliceExp *syntaxCopy() override; bool isLvalue() override; Optional toBool() override; void accept(Visitor *v) override { v->visit(this); } }; class ArrayLengthExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; class IntervalExp final : public Expression { public: Expression *lwr; Expression *upr; IntervalExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class DelegatePtrExp final : public UnaExp { public: bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class DelegateFuncptrExp final : public UnaExp { public: bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; // e1[a0,a1,a2,a3,...] class ArrayExp final : public UnaExp { public: Expressions *arguments; // Array of Expression's size_t currentDimension; // for opDollar VarDeclaration *lengthVar; ArrayExp *syntaxCopy() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; /****************************************************************/ class DotExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class CommaExp final : public BinExp { public: d_bool isGenerated; d_bool allowCommaExp; bool isLvalue() override; Optional toBool() override; void accept(Visitor *v) override { v->visit(this); } #if IN_LLVM // Returns the head of this CommaExp, descending recursively. // `(a, b), c` => a Expression *getHead() { auto l = e1; while (auto ce = l->isCommaExp()) l = ce->e1; return l; } // Returns the tail of this CommaExp, descending recursively. // `a, (b, c)` => c Expression *getTail() { auto r = e2; while (auto ce = r->isCommaExp()) r = ce->e2; return r; } #endif }; class IndexExp final : public BinExp { public: VarDeclaration *lengthVar; d_bool modifiable; d_bool indexIsInBounds; // true if 0 <= e2 && e2 <= e1.length - 1 IndexExp *syntaxCopy() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; /* For both i++ and i-- */ class PostExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; /* For both ++i and --i */ class PreExp final : public UnaExp { public: void accept(Visitor *v) override { v->visit(this); } }; enum class MemorySet { none = 0, // simple assignment blockAssign = 1, // setting the contents of an array referenceInit = 2 // setting the reference of STCref variable }; class AssignExp : public BinExp { public: MemorySet memset; bool isLvalue() override final; void accept(Visitor *v) override { v->visit(this); } }; class ConstructExp final : public AssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class LoweredAssignExp final : public AssignExp { public: Expression *lowering; const char *toChars() const override; void accept(Visitor *v) override { v->visit(this); } }; class BlitExp final : public AssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class AddAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class MinAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class MulAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class DivAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ModAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class AndAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class OrAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class XorAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class PowAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ShlAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ShrAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class UshrAssignExp final : public BinAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class CatAssignExp : public BinAssignExp { public: Expression *lowering; // lowered druntime hook `_d_arrayappend{cTX,T}` void accept(Visitor *v) override { v->visit(this); } }; class CatElemAssignExp final : public CatAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class CatDcharAssignExp final : public CatAssignExp { public: void accept(Visitor *v) override { v->visit(this); } }; class AddExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class MinExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class CatExp final : public BinExp { public: Expression *lowering; // call to druntime hook `_d_arraycatnTX` void accept(Visitor *v) override { v->visit(this); } }; class MulExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class DivExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ModExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class PowExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ShlExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ShrExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class UshrExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class AndExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class OrExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class XorExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class LogicalExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class CmpExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class InExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; class RemoveExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; // == and != class EqualExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; // is and !is class IdentityExp final : public BinExp { public: void accept(Visitor *v) override { v->visit(this); } }; /****************************************************************/ class CondExp final : public BinExp { public: Expression *econd; CondExp *syntaxCopy() override; bool isLvalue() override; void accept(Visitor *v) override { v->visit(this); } }; class GenericExp final : Expression { Expression *cntlExp; Types *types; Expressions *exps; GenericExp *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; /****************************************************************/ class DefaultInitExp : public Expression { public: void accept(Visitor *v) override { v->visit(this); } }; class FileInitExp final : public DefaultInitExp { public: void accept(Visitor *v) override { v->visit(this); } }; class LineInitExp final : public DefaultInitExp { public: void accept(Visitor *v) override { v->visit(this); } }; class ModuleInitExp final : public DefaultInitExp { public: void accept(Visitor *v) override { v->visit(this); } }; class FuncInitExp final : public DefaultInitExp { public: void accept(Visitor *v) override { v->visit(this); } }; class PrettyFuncInitExp final : public DefaultInitExp { public: void accept(Visitor *v) override { v->visit(this); } }; /****************************************************************/ class ObjcClassReferenceExp final : public Expression { public: ClassDeclaration* classDeclaration; void accept(Visitor *v) override { v->visit(this); } }; ldc-1.40.0-src/dmd/constfold.d0000644000000000000000000014306514727557031014551 0ustar rootroot/** * Perform constant folding of arithmetic expressions. * * The routines in this module are called from `optimize.d`. * * Specification: $(LINK2 https://dlang.org/spec/float.html#fp_const_folding, Floating Point Constant Folding) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/constfold.d, _constfold.d) * Documentation: https://dlang.org/phobos/dmd_constfold.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/constfold.d */ module dmd.constfold; import core.stdc.string; import core.stdc.stdio; import dmd.arraytypes; import dmd.astenums; import dmd.ctfeexpr; import dmd.dcast; import dmd.declaration; import dmd.dstruct; import dmd.errors; import dmd.expression; import dmd.globals; import dmd.location; import dmd.mtype; import dmd.root.complex; import dmd.root.ctfloat; import dmd.root.port; import dmd.root.rmem; import dmd.root.utf; import dmd.sideeffect; import dmd.target; import dmd.tokens; import dmd.typesem : toDsymbol, equivalent, sarrayOf, size; private enum LOG = false; private Expression expType(Type type, Expression e) { if (type != e.type) { e = e.copy(); e.type = type; } return e; } /********************************** * Initialize a EXP.cantExpression Expression. * Params: * ue = where to write it */ void cantExp(out UnionExp ue) { emplaceExp!(CTFEExp)(&ue, EXP.cantExpression); } /* =============================== constFold() ============================== */ /* The constFold() functions were redundant with the optimize() ones, * and so have been folded in with them. */ /* ========================================================================== */ UnionExp Neg(Type type, Expression e1) { UnionExp ue = void; Loc loc = e1.loc; if (e1.type.isreal()) { emplaceExp!(RealExp)(&ue, loc, -e1.toReal(), type); } else if (e1.type.isimaginary()) { emplaceExp!(RealExp)(&ue, loc, -e1.toImaginary(), type); } else if (e1.type.iscomplex()) { emplaceExp!(ComplexExp)(&ue, loc, -e1.toComplex(), type); } else { emplaceExp!(IntegerExp)(&ue, loc, -e1.toInteger(), type); } return ue; } UnionExp Com(Type type, Expression e1) { UnionExp ue = void; Loc loc = e1.loc; emplaceExp!(IntegerExp)(&ue, loc, ~e1.toInteger(), type); return ue; } UnionExp Not(Type type, Expression e1) { UnionExp ue = void; Loc loc = e1.loc; // BUG: Should be replaced with e1.toBool().get(), but this is apparently // executed for some expressions that cannot be const-folded // To be fixed in another PR emplaceExp!(IntegerExp)(&ue, loc, e1.toBool().hasValue(false) ? 1 : 0, type); return ue; } UnionExp Add(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; static if (LOG) { printf("Add(e1 = %s, e2 = %s)\n", e1.toChars(), e2.toChars()); } if (type.isreal()) { emplaceExp!(RealExp)(&ue, loc, e1.toReal() + e2.toReal(), type); } else if (type.isimaginary()) { emplaceExp!(RealExp)(&ue, loc, e1.toImaginary() + e2.toImaginary(), type); } else if (type.iscomplex()) { // This rigamarole is necessary so that -0.0 doesn't get // converted to +0.0 by doing an extraneous add with +0.0 auto c1 = complex_t(CTFloat.zero); real_t r1 = CTFloat.zero; real_t i1 = CTFloat.zero; auto c2 = complex_t(CTFloat.zero); real_t r2 = CTFloat.zero; real_t i2 = CTFloat.zero; auto v = complex_t(CTFloat.zero); int x; if (e1.type.isreal()) { r1 = e1.toReal(); x = 0; } else if (e1.type.isimaginary()) { i1 = e1.toImaginary(); x = 3; } else { c1 = e1.toComplex(); x = 6; } if (e2.type.isreal()) { r2 = e2.toReal(); } else if (e2.type.isimaginary()) { i2 = e2.toImaginary(); x += 1; } else { c2 = e2.toComplex(); x += 2; } switch (x) { case 0 + 0: v = complex_t(r1 + r2); break; case 0 + 1: v = complex_t(r1, i2); break; case 0 + 2: v = complex_t(r1 + creall(c2), cimagl(c2)); break; case 3 + 0: v = complex_t(r2, i1); break; case 3 + 1: v = complex_t(CTFloat.zero, i1 + i2); break; case 3 + 2: v = complex_t(creall(c2), i1 + cimagl(c2)); break; case 6 + 0: v = complex_t(creall(c1) + r2, cimagl(c2)); break; case 6 + 1: v = complex_t(creall(c1), cimagl(c1) + i2); break; case 6 + 2: v = c1 + c2; break; default: assert(0); } emplaceExp!(ComplexExp)(&ue, loc, v, type); } else if (SymOffExp soe = e1.isSymOffExp()) { emplaceExp!(SymOffExp)(&ue, loc, soe.var, soe.offset + e2.toInteger()); ue.exp().type = type; } else if (SymOffExp soe = e2.isSymOffExp()) { emplaceExp!(SymOffExp)(&ue, loc, soe.var, soe.offset + e1.toInteger()); ue.exp().type = type; } else emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger() + e2.toInteger(), type); return ue; } UnionExp Min(const ref Loc loc, Type type, Expression e1, Expression e2) { // Compute e1-e2 as e1+(-e2) UnionExp neg = Neg(e2.type, e2); UnionExp ue = Add(loc, type, e1, neg.exp()); return ue; } UnionExp Mul(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; if (type.isfloating()) { auto c = complex_t(CTFloat.zero); real_t r = CTFloat.zero; if (e1.type.isreal()) { r = e1.toReal(); c = e2.toComplex(); c = complex_t(r * creall(c), r * cimagl(c)); } else if (e1.type.isimaginary()) { r = e1.toImaginary(); c = e2.toComplex(); c = complex_t(-r * cimagl(c), r * creall(c)); } else if (e2.type.isreal()) { r = e2.toReal(); c = e1.toComplex(); c = complex_t(r * creall(c), r * cimagl(c)); } else if (e2.type.isimaginary()) { r = e2.toImaginary(); c = e1.toComplex(); c = complex_t(-r * cimagl(c), r * creall(c)); } else c = e1.toComplex() * e2.toComplex(); if (type.isreal()) emplaceExp!(RealExp)(&ue, loc, creall(c), type); else if (type.isimaginary()) emplaceExp!(RealExp)(&ue, loc, cimagl(c), type); else if (type.iscomplex()) emplaceExp!(ComplexExp)(&ue, loc, c, type); else assert(0); } else { emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger() * e2.toInteger(), type); } return ue; } UnionExp Div(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; if (type.isfloating()) { auto c = complex_t(CTFloat.zero); if (e2.type.isreal()) { if (e1.type.isreal()) { emplaceExp!(RealExp)(&ue, loc, e1.toReal() / e2.toReal(), type); return ue; } const r = e2.toReal(); c = e1.toComplex(); c = complex_t(creall(c) / r, cimagl(c) / r); } else if (e2.type.isimaginary()) { const r = e2.toImaginary(); c = e1.toComplex(); c = complex_t(cimagl(c) / r, -creall(c) / r); } else { c = e1.toComplex() / e2.toComplex(); } if (type.isreal()) emplaceExp!(RealExp)(&ue, loc, creall(c), type); else if (type.isimaginary()) emplaceExp!(RealExp)(&ue, loc, cimagl(c), type); else if (type.iscomplex()) emplaceExp!(ComplexExp)(&ue, loc, c, type); else assert(0); } else { sinteger_t n1; sinteger_t n2; sinteger_t n; n1 = e1.toInteger(); n2 = e2.toInteger(); if (n2 == 0) { error(e2.loc, "divide by 0"); emplaceExp!(ErrorExp)(&ue); return ue; } if (n2 == -1 && !type.isunsigned()) { // Check for int.min / -1 if (n1 == 0xFFFFFFFF80000000UL && type.toBasetype().ty != Tint64) { error(e2.loc, "integer overflow: `int.min / -1`"); emplaceExp!(ErrorExp)(&ue); return ue; } else if (n1 == 0x8000000000000000L) // long.min / -1 { error(e2.loc, "integer overflow: `long.min / -1L`"); emplaceExp!(ErrorExp)(&ue); return ue; } } if (e1.type.isunsigned() || e2.type.isunsigned()) n = (cast(dinteger_t)n1) / (cast(dinteger_t)n2); else n = n1 / n2; emplaceExp!(IntegerExp)(&ue, loc, n, type); } return ue; } UnionExp Mod(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; if (type.isfloating()) { auto c = complex_t(CTFloat.zero); if (e2.type.isreal()) { const r2 = e2.toReal(); c = complex_t(e1.toReal() % r2, e1.toImaginary() % r2); } else if (e2.type.isimaginary()) { const i2 = e2.toImaginary(); c = complex_t(e1.toReal() % i2, e1.toImaginary() % i2); } else assert(0); if (type.isreal()) emplaceExp!(RealExp)(&ue, loc, creall(c), type); else if (type.isimaginary()) emplaceExp!(RealExp)(&ue, loc, cimagl(c), type); else if (type.iscomplex()) emplaceExp!(ComplexExp)(&ue, loc, c, type); else assert(0); } else { sinteger_t n1; sinteger_t n2; sinteger_t n; n1 = e1.toInteger(); n2 = e2.toInteger(); if (n2 == 0) { error(e2.loc, "divide by 0"); emplaceExp!(ErrorExp)(&ue); return ue; } if (n2 == -1 && !type.isunsigned()) { // Check for int.min % -1 if (n1 == 0xFFFFFFFF80000000UL && type.toBasetype().ty != Tint64) { error(e2.loc, "integer overflow: `int.min %% -1`"); emplaceExp!(ErrorExp)(&ue); return ue; } else if (n1 == 0x8000000000000000L) // long.min % -1 { error(e2.loc, "integer overflow: `long.min %% -1L`"); emplaceExp!(ErrorExp)(&ue); return ue; } } if (e1.type.isunsigned() || e2.type.isunsigned()) n = (cast(dinteger_t)n1) % (cast(dinteger_t)n2); else n = n1 % n2; emplaceExp!(IntegerExp)(&ue, loc, n, type); } return ue; } UnionExp Pow(const ref Loc loc, Type type, Expression e1, Expression e2) { //printf("Pow()\n"); UnionExp ue; // Handle integer power operations. if (e2.type.isintegral()) { dinteger_t n = e2.toInteger(); bool neg; if (!e2.type.isunsigned() && cast(sinteger_t)n < 0) { if (e1.type.isintegral()) { cantExp(ue); return ue; } // Don't worry about overflow, from now on n is unsigned. neg = true; n = -n; } else neg = false; UnionExp ur, uv; if (e1.type.iscomplex()) { emplaceExp!(ComplexExp)(&ur, loc, e1.toComplex(), e1.type); emplaceExp!(ComplexExp)(&uv, loc, complex_t(CTFloat.one), e1.type); } else if (e1.type.isfloating()) { emplaceExp!(RealExp)(&ur, loc, e1.toReal(), e1.type); emplaceExp!(RealExp)(&uv, loc, CTFloat.one, e1.type); } else { emplaceExp!(IntegerExp)(&ur, loc, e1.toInteger(), e1.type); emplaceExp!(IntegerExp)(&uv, loc, 1, e1.type); } Expression r = ur.exp(); Expression v = uv.exp(); while (n != 0) { if (n & 1) { // v = v * r; uv = Mul(loc, v.type, v, r); } n >>= 1; // r = r * r ur = Mul(loc, r.type, r, r); } if (neg) { // ue = 1.0 / v UnionExp one; emplaceExp!(RealExp)(&one, loc, CTFloat.one, v.type); uv = Div(loc, v.type, one.exp(), v); } if (type.iscomplex()) emplaceExp!(ComplexExp)(&ue, loc, v.toComplex(), type); else if (type.isintegral()) emplaceExp!(IntegerExp)(&ue, loc, v.toInteger(), type); else emplaceExp!(RealExp)(&ue, loc, v.toReal(), type); } else if (e2.type.isfloating()) { // x ^^ y for x < 0 and y not an integer is not defined; so set result as NaN if (e1.toReal() < CTFloat.zero) { emplaceExp!(RealExp)(&ue, loc, target.RealProperties.nan, type); } else cantExp(ue); } else cantExp(ue); return ue; } UnionExp Shl(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger() << e2.toInteger(), type); return ue; } UnionExp Shr(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; dinteger_t value = e1.toInteger(); dinteger_t dcount = e2.toInteger(); assert(dcount <= 0xFFFFFFFF); uint count = cast(uint)dcount; switch (e1.type.toBasetype().ty) { case Tint8: value = cast(byte)value >> count; break; case Tuns8: case Tchar: value = cast(ubyte)value >> count; break; case Tint16: value = cast(short)value >> count; break; case Tuns16: case Twchar: value = cast(ushort)value >> count; break; case Tint32: value = cast(int)value >> count; break; case Tuns32: case Tdchar: value = cast(uint)value >> count; break; case Tint64: value = cast(long)value >> count; break; case Tuns64: value = cast(ulong)value >> count; break; case Terror: emplaceExp!(ErrorExp)(&ue); return ue; default: assert(0); } emplaceExp!(IntegerExp)(&ue, loc, value, type); return ue; } UnionExp Ushr(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; dinteger_t value = e1.toInteger(); dinteger_t dcount = e2.toInteger(); assert(dcount <= 0xFFFFFFFF); uint count = cast(uint)dcount; switch (e1.type.toBasetype().ty) { case Tint8: case Tuns8: case Tchar: // Possible only with >>>=. >>> always gets promoted to int. value = (value & 0xFF) >>> count; break; case Tint16: case Tuns16: case Twchar: // Possible only with >>>=. >>> always gets promoted to int. value = (value & 0xFFFF) >>> count; break; case Tint32: case Tuns32: case Tdchar: value = (value & 0xFFFFFFFF) >>> count; break; case Tint64: case Tuns64: value = value >>> count; break; case Terror: emplaceExp!(ErrorExp)(&ue); return ue; default: assert(0); } emplaceExp!(IntegerExp)(&ue, loc, value, type); return ue; } UnionExp And(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger() & e2.toInteger(), type); return ue; } UnionExp Or(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger() | e2.toInteger(), type); return ue; } UnionExp Xor(const ref Loc loc, Type type, Expression e1, Expression e2) { //printf("Xor(linnum = %d, e1 = %s, e2 = %s)\n", loc.linnum, e1.toChars(), e2.toChars()); UnionExp ue = void; emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger() ^ e2.toInteger(), type); return ue; } /* Also returns EXP.cantExpression if cannot be computed. */ UnionExp Equal(EXP op, const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; int cmp = 0; real_t r1 = CTFloat.zero; real_t r2 = CTFloat.zero; //printf("Equal(e1 = %s, e2 = %s)\n", e1.toChars(), e2.toChars()); assert(op == EXP.equal || op == EXP.notEqual); if (e1.op == EXP.null_) { if (e2.op == EXP.null_) cmp = 1; else if (StringExp es2 = e2.isStringExp()) { cmp = (0 == es2.len); } else if (ArrayLiteralExp es2 = e2.isArrayLiteralExp()) { cmp = !es2.elements || (0 == es2.elements.length); } else { cantExp(ue); return ue; } } else if (e2.op == EXP.null_) { if (StringExp es1 = e1.isStringExp()) { cmp = (0 == es1.len); } else if (ArrayLiteralExp es1 = e1.isArrayLiteralExp()) { cmp = !es1.elements || (0 == es1.elements.length); } else { cantExp(ue); return ue; } } else if (e1.op == EXP.string_ && e2.op == EXP.string_) { StringExp es1 = e1.isStringExp(); StringExp es2 = e2.isStringExp(); if (es1.sz != es2.sz) { assert(global.errors); cantExp(ue); return ue; } const data1 = es1.peekData(); const data2 = es2.peekData(); if (es1.len == es2.len && memcmp(data1.ptr, data2.ptr, es1.sz * es1.len) == 0) cmp = 1; else cmp = 0; } else if (e1.op == EXP.arrayLiteral && e2.op == EXP.arrayLiteral) { ArrayLiteralExp es1 = e1.isArrayLiteralExp(); ArrayLiteralExp es2 = e2.isArrayLiteralExp(); if ((!es1.elements || !es1.elements.length) && (!es2.elements || !es2.elements.length)) cmp = 1; // both arrays are empty else if (!es1.elements || !es2.elements) cmp = 0; else if (es1.elements.length != es2.elements.length) cmp = 0; else { for (size_t i = 0; i < es1.elements.length; i++) { auto ee1 = es1[i]; auto ee2 = es2[i]; ue = Equal(EXP.equal, loc, Type.tint32, ee1, ee2); if (CTFEExp.isCantExp(ue.exp())) return ue; cmp = cast(int)ue.exp().toInteger(); if (cmp == 0) break; } } } else if (e1.op == EXP.arrayLiteral && e2.op == EXP.string_) { // Swap operands and use common code Expression etmp = e1; e1 = e2; e2 = etmp; goto Lsa; } else if (e1.op == EXP.string_ && e2.op == EXP.arrayLiteral) { Lsa: StringExp es1 = e1.isStringExp(); ArrayLiteralExp es2 = e2.isArrayLiteralExp(); size_t dim1 = es1.len; size_t dim2 = es2.elements ? es2.elements.length : 0; if (dim1 != dim2) cmp = 0; else { cmp = 1; // if dim1 winds up being 0 foreach (i; 0 .. dim1) { uinteger_t c = es1.getIndex(i); auto ee2 = es2[i]; if (ee2.isConst() != 1) { cantExp(ue); return ue; } cmp = (c == ee2.toInteger()); if (cmp == 0) break; } } } else if (e1.op == EXP.structLiteral && e2.op == EXP.structLiteral) { StructLiteralExp es1 = e1.isStructLiteralExp(); StructLiteralExp es2 = e2.isStructLiteralExp(); if (es1.sd != es2.sd) cmp = 0; else if ((!es1.elements || !es1.elements.length) && (!es2.elements || !es2.elements.length)) cmp = 1; // both arrays are empty else if (!es1.elements || !es2.elements) cmp = 0; else if (es1.elements.length != es2.elements.length) cmp = 0; else { cmp = 1; for (size_t i = 0; i < es1.elements.length; i++) { Expression ee1 = (*es1.elements)[i]; Expression ee2 = (*es2.elements)[i]; if (ee1 == ee2) continue; if (!ee1 || !ee2) { cmp = 0; break; } ue = Equal(EXP.equal, loc, Type.tint32, ee1, ee2); if (ue.exp().op == EXP.cantExpression) return ue; cmp = cast(int)ue.exp().toInteger(); if (cmp == 0) break; } } } else if (e1.isConst() != 1 || e2.isConst() != 1) { cantExp(ue); return ue; } else if (e1.type.isreal()) { r1 = e1.toReal(); r2 = e2.toReal(); goto L1; } else if (e1.type.isimaginary()) { r1 = e1.toImaginary(); r2 = e2.toImaginary(); L1: if (CTFloat.isNaN(r1) || CTFloat.isNaN(r2)) // if unordered { cmp = 0; } else { cmp = (r1 == r2); } } else if (e1.type.iscomplex()) { cmp = e1.toComplex() == e2.toComplex(); } else if (e1.type.isintegral() || e1.type.toBasetype().ty == Tpointer) { cmp = (e1.toInteger() == e2.toInteger()); } else { cantExp(ue); return ue; } if (op == EXP.notEqual) cmp ^= 1; emplaceExp!(IntegerExp)(&ue, loc, cmp, type); return ue; } UnionExp Identity(EXP op, const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; int cmp; if (e1.op == EXP.null_) { cmp = (e2.op == EXP.null_); } else if (e2.op == EXP.null_) { cmp = 0; } else if (e1.op == EXP.symbolOffset && e2.op == EXP.symbolOffset) { SymOffExp es1 = e1.isSymOffExp(); SymOffExp es2 = e2.isSymOffExp(); cmp = (es1.var == es2.var && es1.offset == es2.offset); } else { if (e1.type.isfloating()) cmp = e1.isIdentical(e2); else { ue = Equal((op == EXP.identity) ? EXP.equal : EXP.notEqual, loc, type, e1, e2); return ue; } } if (op == EXP.notIdentity) cmp ^= 1; emplaceExp!(IntegerExp)(&ue, loc, cmp, type); return ue; } UnionExp Cmp(EXP op, const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; dinteger_t n; real_t r1 = CTFloat.zero; real_t r2 = CTFloat.zero; //printf("Cmp(e1 = %s, e2 = %s)\n", e1.toChars(), e2.toChars()); if (e1.op == EXP.string_ && e2.op == EXP.string_) { StringExp es1 = e1.isStringExp(); StringExp es2 = e2.isStringExp(); size_t sz = es1.sz; assert(sz == es2.sz); size_t len = es1.len; if (es2.len < len) len = es2.len; const data1 = es1.peekData(); const data2 = es1.peekData(); int rawCmp = memcmp(data1.ptr, data2.ptr, sz * len); if (rawCmp == 0) rawCmp = cast(int)(es1.len - es2.len); n = specificCmp(op, rawCmp); } else if (e1.isConst() != 1 || e2.isConst() != 1) { cantExp(ue); return ue; } else if (e1.type.isreal()) { r1 = e1.toReal(); r2 = e2.toReal(); goto L1; } else if (e1.type.isimaginary()) { r1 = e1.toImaginary(); r2 = e2.toImaginary(); L1: n = realCmp(op, r1, r2); } else if (e1.type.iscomplex()) { assert(0); } else { sinteger_t n1; sinteger_t n2; n1 = e1.toInteger(); n2 = e2.toInteger(); if (e1.type.isunsigned() || e2.type.isunsigned()) n = intUnsignedCmp(op, n1, n2); else n = intSignedCmp(op, n1, n2); } emplaceExp!(IntegerExp)(&ue, loc, n, type); return ue; } /* Also returns EXP.cantExpression if cannot be computed. * to: type to cast to * type: type to paint the result */ UnionExp Cast(const ref Loc loc, Type type, Type to, Expression e1) { UnionExp ue = void; Type tb = to.toBasetype(); Type typeb = type.toBasetype(); //printf("Cast(type = %s, to = %s, e1 = %s)\n", type.toChars(), to.toChars(), e1.toChars()); //printf("\te1.type = %s\n", e1.type.toChars()); if (e1.type.equals(type) && type.equals(to)) { emplaceExp!(UnionExp)(&ue, e1); return ue; } if (e1.op == EXP.vector && (cast(TypeVector)e1.type).basetype.equals(type) && type.equals(to)) { Expression ex = e1.isVectorExp().e1; emplaceExp!(UnionExp)(&ue, ex); return ue; } if (e1.type.toBasetype.equals(type) && type.equals(to)) { emplaceExp!(UnionExp)(&ue, e1); ue.exp().type = type; return ue; } if (e1.type.implicitConvTo(to) >= MATCH.constant || to.implicitConvTo(e1.type) >= MATCH.constant) { goto L1; } // Allow covariant converions of delegates // (Perhaps implicit conversion from pure to impure should be a MATCH.constant, // then we wouldn't need this extra check.) if (e1.type.toBasetype().ty == Tdelegate && e1.type.implicitConvTo(to) == MATCH.convert) { goto L1; } /* Allow casting from one string type to another */ if (e1.op == EXP.string_) { if (tb.ty == Tarray && typeb.ty == Tarray && tb.nextOf().size() == typeb.nextOf().size()) { goto L1; } } if (e1.op == EXP.arrayLiteral && typeb == tb) { L1: Expression ex = expType(to, e1); emplaceExp!(UnionExp)(&ue, ex); return ue; } if (e1.isConst() != 1) { cantExp(ue); } else if (tb.ty == Tbool) { const opt = e1.toBool(); if (opt.isEmpty()) { cantExp(ue); return ue; } emplaceExp!(IntegerExp)(&ue, loc, opt.get(), type); } else if (type.isintegral()) { if (e1.type.isfloating()) { dinteger_t result; real_t r = e1.toReal(); switch (typeb.ty) { case Tint8: result = cast(byte)cast(sinteger_t)r; break; case Tchar: case Tuns8: result = cast(ubyte)cast(dinteger_t)r; break; case Tint16: result = cast(short)cast(sinteger_t)r; break; case Twchar: case Tuns16: result = cast(ushort)cast(dinteger_t)r; break; case Tint32: result = cast(int)r; break; case Tdchar: case Tuns32: result = cast(uint)r; break; case Tint64: result = cast(long)r; break; case Tuns64: result = cast(ulong)r; break; default: assert(0); } emplaceExp!(IntegerExp)(&ue, loc, result, type); } else if (type.isunsigned()) emplaceExp!(IntegerExp)(&ue, loc, e1.toUInteger(), type); else emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger(), type); } else if (tb.isreal()) { real_t value = e1.toReal(); emplaceExp!(RealExp)(&ue, loc, value, type); } else if (tb.isimaginary()) { real_t value = e1.toImaginary(); emplaceExp!(RealExp)(&ue, loc, value, type); } else if (tb.iscomplex()) { complex_t value = e1.toComplex(); emplaceExp!(ComplexExp)(&ue, loc, value, type); } else if (tb.isscalar()) { emplaceExp!(IntegerExp)(&ue, loc, e1.toInteger(), type); } else if (tb.ty == Tvoid) { cantExp(ue); } else if (tb.ty == Tstruct && e1.op == EXP.int64) { // Struct = 0; StructDeclaration sd = tb.toDsymbol(null).isStructDeclaration(); assert(sd); auto elements = new Expressions(); for (size_t i = 0; i < sd.fields.length; i++) { VarDeclaration v = sd.fields[i]; UnionExp zero; emplaceExp!(IntegerExp)(&zero, 0); ue = Cast(loc, v.type, v.type, zero.exp()); if (ue.exp().op == EXP.cantExpression) return ue; elements.push(ue.exp().copy()); } emplaceExp!(StructLiteralExp)(&ue, loc, sd, elements); ue.exp().type = type; } else { if (type != Type.terror) { // have to change to internal compiler error // all invalid casts should be handled already in Expression::castTo(). error(loc, "cannot cast `%s` to `%s`", e1.type.toChars(), type.toChars()); } emplaceExp!(ErrorExp)(&ue); } return ue; } UnionExp ArrayLength(Type type, Expression e1) { UnionExp ue = void; Loc loc = e1.loc; if (StringExp es1 = e1.isStringExp()) { emplaceExp!(IntegerExp)(&ue, loc, es1.len, type); } else if (ArrayLiteralExp ale = e1.isArrayLiteralExp()) { size_t dim = ale.elements ? ale.elements.length : 0; emplaceExp!(IntegerExp)(&ue, loc, dim, type); } else if (AssocArrayLiteralExp ale = e1.isAssocArrayLiteralExp) { size_t dim = ale.keys.length; emplaceExp!(IntegerExp)(&ue, loc, dim, type); } else if (e1.type.toBasetype().ty == Tsarray) { Expression e = (cast(TypeSArray)e1.type.toBasetype()).dim; emplaceExp!(UnionExp)(&ue, e); } else if (e1.isNullExp()) { emplaceExp!(IntegerExp)(&ue, loc, 0, type); } else cantExp(ue); return ue; } /* Also return EXP.cantExpression if this fails */ UnionExp Index(Type type, Expression e1, Expression e2, bool indexIsInBounds) { UnionExp ue = void; Loc loc = e1.loc; //printf("Index(e1 = %s, e2 = %s)\n", e1.toChars(), e2.toChars()); assert(e1.type); if (e1.op == EXP.string_ && e2.op == EXP.int64) { StringExp es1 = e1.isStringExp(); uinteger_t i = e2.toInteger(); if (i >= es1.len) { error(e1.loc, "string index %llu is out of bounds `[0 .. %llu]`", i, cast(ulong)es1.len); emplaceExp!(ErrorExp)(&ue); } else { emplaceExp!(IntegerExp)(&ue, loc, es1.getIndex(cast(size_t) i), type); } } else if (e1.type.toBasetype().ty == Tsarray && e2.op == EXP.int64) { TypeSArray tsa = cast(TypeSArray)e1.type.toBasetype(); uinteger_t length = tsa.dim.toInteger(); uinteger_t i = e2.toInteger(); if (i >= length && (e1.op == EXP.arrayLiteral || !indexIsInBounds)) { // C code only checks bounds if an ArrayLiteralExp error(e1.loc, "array index %llu is out of bounds `%s[0 .. %llu]`", i, e1.toChars(), length); emplaceExp!(ErrorExp)(&ue); } else if (ArrayLiteralExp ale = e1.isArrayLiteralExp()) { auto e = ale[cast(size_t)i]; e.type = type; e.loc = loc; if (hasSideEffect(e)) cantExp(ue); else emplaceExp!(UnionExp)(&ue, e); } else cantExp(ue); } else if (e1.type.toBasetype().ty == Tarray && e2.op == EXP.int64) { uinteger_t i = e2.toInteger(); if (ArrayLiteralExp ale = e1.isArrayLiteralExp()) { if (i >= ale.elements.length) { error(e1.loc, "array index %llu is out of bounds `%s[0 .. %llu]`", i, e1.toChars(), cast(ulong) ale.elements.length); emplaceExp!(ErrorExp)(&ue); } else { auto e = ale[cast(size_t)i]; e.type = type; e.loc = loc; if (hasSideEffect(e)) cantExp(ue); else emplaceExp!(UnionExp)(&ue, e); } } else cantExp(ue); } else if (AssocArrayLiteralExp ae = e1.isAssocArrayLiteralExp()) { /* Search the keys backwards, in case there are duplicate keys */ for (size_t i = ae.keys.length; i;) { i--; Expression ekey = (*ae.keys)[i]; ue = Equal(EXP.equal, loc, Type.tbool, ekey, e2); if (CTFEExp.isCantExp(ue.exp())) return ue; if (ue.exp().toBool().hasValue(true)) { Expression e = (*ae.values)[i]; e.type = type; e.loc = loc; if (hasSideEffect(e)) cantExp(ue); else emplaceExp!(UnionExp)(&ue, e); return ue; } } cantExp(ue); } else cantExp(ue); return ue; } /* Also return EXP.cantExpression if this fails */ UnionExp Slice(Type type, Expression e1, Expression lwr, Expression upr) { UnionExp ue = void; Loc loc = e1.loc; static if (LOG) { printf("Slice()\n"); if (lwr) { printf("\te1 = %s\n", e1.toChars()); printf("\tlwr = %s\n", lwr.toChars()); printf("\tupr = %s\n", upr.toChars()); } } if (!lwr) { if (e1.op == EXP.string_) emplaceExp(&ue, e1); else cantExp(ue); } else if (e1.op == EXP.string_ && lwr.op == EXP.int64 && upr.op == EXP.int64) { StringExp es1 = e1.isStringExp(); const uinteger_t ilwr = lwr.toInteger(); const uinteger_t iupr = upr.toInteger(); if (sliceBoundsCheck(0, es1.len, ilwr, iupr)) cantExp(ue); // https://issues.dlang.org/show_bug.cgi?id=18115 else { const len = cast(size_t)(iupr - ilwr); const sz = es1.sz; void* s = mem.xmalloc(len * sz); const data1 = es1.peekData(); memcpy(s, data1.ptr + ilwr * sz, len * sz); emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz, es1.postfix); StringExp es = ue.exp().isStringExp(); es.committed = es1.committed; es.type = type; } } else if (e1.op == EXP.arrayLiteral && lwr.op == EXP.int64 && upr.op == EXP.int64 && !hasSideEffect(e1)) { ArrayLiteralExp es1 = e1.isArrayLiteralExp(); const uinteger_t ilwr = lwr.toInteger(); const uinteger_t iupr = upr.toInteger(); if (sliceBoundsCheck(0, es1.elements.length, ilwr, iupr)) cantExp(ue); else { auto elements = new Expressions(cast(size_t)(iupr - ilwr)); memcpy(elements.tdata(), es1.elements.tdata() + ilwr, cast(size_t)(iupr - ilwr) * ((*es1.elements)[0]).sizeof); emplaceExp!(ArrayLiteralExp)(&ue, e1.loc, type, elements); } } else cantExp(ue); return ue; } /* Check whether slice `[newlwr .. newupr]` is in the range `[lwr .. upr]` */ bool sliceBoundsCheck(uinteger_t lwr, uinteger_t upr, uinteger_t newlwr, uinteger_t newupr) pure @safe { assert(lwr <= upr); return !(newlwr <= newupr && lwr <= newlwr && newupr <= upr); } /* Set a slice of char/integer array literal 'existingAE' from a string 'newval'. * existingAE[firstIndex..firstIndex+newval.length] = newval. */ void sliceAssignArrayLiteralFromString(ArrayLiteralExp existingAE, const StringExp newval, size_t firstIndex) { const len = newval.len; Type elemType = existingAE.type.nextOf(); foreach (j; 0 .. len) { const val = newval.getIndex(j); (*existingAE.elements)[j + firstIndex] = new IntegerExp(newval.loc, val, elemType); } } /* Set a slice of string 'existingSE' from a char array literal 'newae'. * existingSE[firstIndex..firstIndex+newae.length] = newae. */ void sliceAssignStringFromArrayLiteral(StringExp existingSE, ArrayLiteralExp newae, size_t firstIndex) { assert(existingSE.ownedByCtfe != OwnedBy.code); foreach (j; 0 .. newae.elements.length) { existingSE.setCodeUnit(firstIndex + j, cast(dchar)newae[j].toInteger()); } } /* Set a slice of string 'existingSE' from a string 'newstr'. * existingSE[firstIndex..firstIndex+newstr.length] = newstr. */ void sliceAssignStringFromString(StringExp existingSE, const StringExp newstr, size_t firstIndex) { assert(existingSE.ownedByCtfe != OwnedBy.code); size_t sz = existingSE.sz; assert(sz == newstr.sz); auto data1 = existingSE.borrowData(); const data2 = newstr.peekData(); memcpy(data1.ptr + firstIndex * sz, data2.ptr, data2.length); } /* Compare a string slice with another string slice. * Conceptually equivalent to memcmp( se1[lo1..lo1+len], se2[lo2..lo2+len]) */ int sliceCmpStringWithString(const StringExp se1, const StringExp se2, size_t lo1, size_t lo2, size_t len) { size_t sz = se1.sz; assert(sz == se2.sz); const data1 = se1.peekData(); const data2 = se2.peekData(); return memcmp(data1.ptr + sz * lo1, data2.ptr + sz * lo2, sz * len); } /* Compare a string slice with an array literal slice * Conceptually equivalent to memcmp( se1[lo1..lo1+len], ae2[lo2..lo2+len]) */ int sliceCmpStringWithArray(const StringExp se1, ArrayLiteralExp ae2, size_t lo1, size_t lo2, size_t len) { foreach (j; 0 .. len) { const val2 = ae2[j + lo2].toInteger(); const val1 = se1.getIndex(j + lo1); const int c = (val1 > val2) - (val1 < val2); if (c) return c; } return 0; } /** Copy element `Expressions` in the parameters when they're `ArrayLiteralExp`s. * Params: * e1 = If it's ArrayLiteralExp, its `elements` will be copied. * Otherwise, `e1` itself will be pushed into the new `Expressions`. * e2 = If it's not `null`, it will be pushed/appended to the new * `Expressions` by the same way with `e1`. * Returns: * Newly allocated `Expressions`. Note that it points to the original * `Expression` values in e1 and e2. */ private Expressions* copyElements(Expression e1, Expression e2 = null) { auto elems = new Expressions(); void append(ArrayLiteralExp ale) { if (!ale.elements) return; auto d = elems.length; elems.append(ale.elements); foreach (ref el; (*elems)[d .. elems.length]) { if (!el) el = ale.basis; } } if (auto ale = e1.isArrayLiteralExp()) append(ale); else elems.push(e1); if (e2) { if (auto ale = e2.isArrayLiteralExp()) append(ale); else elems.push(e2); } return elems; } /* Also return EXP.cantExpression if this fails */ UnionExp Cat(const ref Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; Expression e = CTFEExp.cantexp; Type t; Type t1 = e1.type.toBasetype(); Type t2 = e2.type.toBasetype(); //printf("Cat(e1 = %s, e2 = %s)\n", e1.toChars(), e2.toChars()); //printf("\tt1 = %s, t2 = %s, type = %s\n", t1.toChars(), t2.toChars(), type.toChars()); /* e is the non-null operand, t is the type of the null operand */ UnionExp catNull(Expression e, Type t) { Type tn = e.type.toBasetype(); if (tn.ty.isSomeChar) { // Create a StringExp if (t.nextOf()) t = t.nextOf().toBasetype(); const sz = cast(ubyte)t.size(); dinteger_t v = e.toInteger(); const len = (t.ty == tn.ty) ? 1 : utf_codeLength(sz, cast(dchar)v); void* s = mem.xmalloc(len * sz); if (t.ty == tn.ty) Port.valcpy(s, v, sz); else utf_encode(sz, s, cast(dchar)v); emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz); StringExp es = ue.exp().isStringExp(); es.type = type; es.committed = true; } else { // Create an ArrayLiteralExp auto elements = new Expressions(); elements.push(e); emplaceExp!(ArrayLiteralExp)(&ue, e.loc, type, elements); } assert(ue.exp().type); return ue; } if (e1.op == EXP.null_ && (e2.op == EXP.int64 || e2.op == EXP.structLiteral)) { return catNull(e2, t1); } else if ((e1.op == EXP.int64 || e1.op == EXP.structLiteral) && e2.op == EXP.null_) { return catNull(e1, t2); } else if (e1.op == EXP.null_ && e2.op == EXP.null_) { if (type == e1.type) { // Handle null ~= null if (t1.ty == Tarray && t2 == t1.nextOf()) { emplaceExp!(ArrayLiteralExp)(&ue, e1.loc, type, e2); assert(ue.exp().type); return ue; } else { emplaceExp!(UnionExp)(&ue, e1); assert(ue.exp().type); return ue; } } if (type == e2.type) { emplaceExp!(UnionExp)(&ue, e2); assert(ue.exp().type); return ue; } emplaceExp!(NullExp)(&ue, e1.loc, type); assert(ue.exp().type); return ue; } else if (e1.op == EXP.string_ && e2.op == EXP.string_) { // Concatenate the strings StringExp es1 = e1.isStringExp(); StringExp es2 = e2.isStringExp(); size_t len = es1.len + es2.len; ubyte sz = es1.sz; if (sz != es2.sz) { /* Can happen with: * auto s = "foo"d ~ "bar"c; */ assert(global.errors); cantExp(ue); assert(ue.exp().type); return ue; } void* s = mem.xmalloc(len * sz); const data1 = es1.peekData(); const data2 = es2.peekData(); memcpy(cast(char*)s, data1.ptr, es1.len * sz); memcpy(cast(char*)s + es1.len * sz, data2.ptr, es2.len * sz); emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz); StringExp es = ue.exp().isStringExp(); es.committed = es1.committed | es2.committed; es.type = type; assert(ue.exp().type); return ue; } else if (e2.op == EXP.string_ && e1.op == EXP.arrayLiteral && t1.nextOf().isintegral()) { // [chars] ~ string --> [chars] StringExp es = e2.isStringExp(); ArrayLiteralExp ea = e1.isArrayLiteralExp(); size_t len = es.len + ea.elements.length; auto elems = new Expressions(len); for (size_t i = 0; i < ea.elements.length; ++i) { (*elems)[i] = ea[i]; } emplaceExp!(ArrayLiteralExp)(&ue, e1.loc, type, elems); ArrayLiteralExp dest = ue.exp().isArrayLiteralExp(); sliceAssignArrayLiteralFromString(dest, es, ea.elements.length); assert(ue.exp().type); return ue; } else if (e1.op == EXP.string_ && e2.op == EXP.arrayLiteral && t2.nextOf().isintegral()) { // string ~ [chars] --> [chars] StringExp es = e1.isStringExp(); ArrayLiteralExp ea = e2.isArrayLiteralExp(); size_t len = es.len + ea.elements.length; auto elems = new Expressions(len); for (size_t i = 0; i < ea.elements.length; ++i) { (*elems)[es.len + i] = ea[i]; } emplaceExp!(ArrayLiteralExp)(&ue, e1.loc, type, elems); ArrayLiteralExp dest = ue.exp().isArrayLiteralExp(); sliceAssignArrayLiteralFromString(dest, es, 0); assert(ue.exp().type); return ue; } else if (e1.op == EXP.string_ && e2.op == EXP.int64) { // string ~ char --> string StringExp es1 = e1.isStringExp(); StringExp es; const sz = es1.sz; dinteger_t v = e2.toInteger(); // Is it a concatenation of homogenous types? // (char[] ~ char, wchar[]~wchar, or dchar[]~dchar) bool homoConcat = (sz == t2.size()); const len = es1.len + (homoConcat ? 1 : utf_codeLength(sz, cast(dchar)v)); void* s = mem.xmalloc(len * sz); const data1 = es1.peekData(); memcpy(s, data1.ptr, data1.length); if (homoConcat) Port.valcpy(cast(char*)s + (sz * es1.len), v, sz); else utf_encode(sz, cast(char*)s + (sz * es1.len), cast(dchar)v); emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz); es = ue.exp().isStringExp(); es.committed = es1.committed; es.type = type; assert(ue.exp().type); return ue; } else if (e1.op == EXP.int64 && e2.op == EXP.string_) { // [w|d]?char ~ string --> string // We assume that we only ever prepend one char of the same type // (wchar,dchar) as the string's characters. StringExp es2 = e2.isStringExp(); const len = 1 + es2.len; const sz = es2.sz; dinteger_t v = e1.toInteger(); void* s = mem.xmalloc(len * sz); Port.valcpy(cast(char*)s, v, sz); const data2 = es2.peekData(); memcpy(cast(char*)s + sz, data2.ptr, data2.length); emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz); StringExp es = ue.exp().isStringExp(); es.sz = sz; es.committed = es2.committed; es.type = type; assert(ue.exp().type); return ue; } else if (e1.op == EXP.arrayLiteral && e2.op == EXP.arrayLiteral && t1.nextOf().equals(t2.nextOf())) { // Concatenate the arrays auto elems = copyElements(e1, e2); emplaceExp!(ArrayLiteralExp)(&ue, e1.loc, cast(Type)null, elems); e = ue.exp(); if (type.toBasetype().ty == Tsarray) { e.type = t1.nextOf().sarrayOf(elems.length); } else e.type = type; assert(ue.exp().type); return ue; } else if (e1.op == EXP.arrayLiteral && e2.op == EXP.null_ && t1.nextOf().equals(t2.nextOf())) { e = e1; goto L3; } else if (e1.op == EXP.null_ && e2.op == EXP.arrayLiteral && t1.nextOf().equals(t2.nextOf())) { e = e2; L3: // Concatenate the array with null auto elems = copyElements(e); emplaceExp!(ArrayLiteralExp)(&ue, e.loc, cast(Type)null, elems); e = ue.exp(); if (type.toBasetype().ty == Tsarray) { e.type = t1.nextOf().sarrayOf(elems.length); } else e.type = type; assert(ue.exp().type); return ue; } else if ((e1.op == EXP.arrayLiteral || e1.op == EXP.null_) && e1.type.toBasetype().nextOf() && e1.type.toBasetype().nextOf().equals(e2.type)) { auto elems = (e1.op == EXP.arrayLiteral) ? copyElements(e1) : new Expressions(); elems.push(e2); emplaceExp!(ArrayLiteralExp)(&ue, loc, cast(Type)null, elems); e = ue.exp(); if (type.toBasetype().ty == Tsarray) { e.type = e2.type.sarrayOf(elems.length); } else e.type = type; assert(ue.exp().type); return ue; } else if (e2.op == EXP.arrayLiteral && e2.type.toBasetype().nextOf().equals(e1.type)) { auto elems = copyElements(e1, e2); emplaceExp!(ArrayLiteralExp)(&ue, loc, cast(Type)null, elems); e = ue.exp(); if (type.toBasetype().ty == Tsarray) { e.type = e1.type.sarrayOf(elems.length); } else e.type = type; assert(ue.exp().type); return ue; } else if (e1.op == EXP.null_ && e2.op == EXP.string_) { t = e1.type; e = e2; goto L1; } else if (e1.op == EXP.string_ && e2.op == EXP.null_) { e = e1; t = e2.type; L1: Type tb = t.toBasetype(); if (tb.ty == Tarray && tb.nextOf().equivalent(e.type)) { auto expressions = new Expressions(); expressions.push(e); emplaceExp!(ArrayLiteralExp)(&ue, loc, t, expressions); e = ue.exp(); } else { emplaceExp!(UnionExp)(&ue, e); e = ue.exp(); } if (!e.type.equals(type)) { StringExp se = e.copy().isStringExp(); e = se.castTo(null, type); emplaceExp!(UnionExp)(&ue, e); e = ue.exp(); } } else cantExp(ue); assert(ue.exp().type); return ue; } UnionExp Ptr(Type type, Expression e1) { //printf("Ptr(e1 = %s)\n", e1.toChars()); UnionExp ue = void; if (AddExp ae = e1.isAddExp()) { if (AddrExp ade = ae.e1.isAddrExp()) { if (ae.e2.op == EXP.int64) if (StructLiteralExp se = ade.e1.isStructLiteralExp()) { uint offset = cast(uint)ae.e2.toInteger(); Expression e = se.getField(type, offset); if (e) { emplaceExp!(UnionExp)(&ue, e); return ue; } } } } cantExp(ue); return ue; } ldc-1.40.0-src/dmd/delegatize.d0000644000000000000000000002075714727557031014675 0ustar rootroot/** * Implements conversion from expressions to delegates for lazy parameters. * * Specification: $(LINK2 https://dlang.org/spec/function.html#lazy-params, Lazy Parameters) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/delegatize.d, _delegatize.d) * Documentation: https://dlang.org/phobos/dmd_delegatize.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/delegatize.d */ module dmd.delegatize; import core.stdc.stdio; import dmd.astenums; import dmd.declaration; import dmd.dscope; import dmd.dsymbol; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.init; import dmd.initsem; import dmd.location; import dmd.mtype; import dmd.postordervisitor; import dmd.statement; import dmd.tokens; import dmd.visitor; /********************************* * Convert expression into a delegate. * * Used to convert the argument to a lazy parameter. * * Params: * e = argument to convert to a delegate * t = the type to be returned by the delegate * sc = context * Returns: * A delegate literal */ Expression toDelegate(Expression e, Type t, Scope* sc) { //printf("Expression::toDelegate(t = %s) %s\n", t.toChars(), e.toChars()); Loc loc = e.loc; auto tf = new TypeFunction(ParameterList(), t, LINK.d); if (t.hasWild()) tf.mod = MODFlags.wild; auto fld = new FuncLiteralDeclaration(loc, loc, tf, TOK.delegate_, null); lambdaSetParent(e, fld); sc = sc.push(); sc.parent = fld; // set current function to be the delegate bool r = lambdaCheckForNestedRef(e, sc); sc = sc.pop(); if (r) return ErrorExp.get(); Statement s; if (t.ty == Tvoid) s = new ExpStatement(loc, e); else s = new ReturnStatement(loc, e); fld.fbody = s; e = new FuncExp(loc, fld); e = e.expressionSemantic(sc); return e; } /****************************************** * Patch the parent of declarations to be the new function literal. * * Since the expression is going to be moved into a function literal, * the parent for declarations in the expression needs to be * reset to that function literal. * Params: * e = expression to check * fd = function literal symbol (the new parent) */ private void lambdaSetParent(Expression e, FuncDeclaration fd) { extern (C++) final class LambdaSetParent : StoppableVisitor { alias visit = typeof(super).visit; FuncDeclaration fd; private void setParent(Dsymbol s) { VarDeclaration vd = s.isVarDeclaration(); FuncDeclaration pfd = s.parent ? s.parent.isFuncDeclaration() : null; s.parent = fd; if (!vd || !pfd) return; // move to fd's closure when applicable foreach (i; 0 .. pfd.closureVars.length) { if (vd == pfd.closureVars[i]) { pfd.closureVars.remove(i); fd.closureVars.push(vd); break; } } } public: extern (D) this(FuncDeclaration fd) scope @safe { this.fd = fd; } override void visit(Expression) { } override void visit(DeclarationExp e) { setParent(e.declaration); e.declaration.accept(this); } override void visit(IndexExp e) { if (e.lengthVar) { //printf("lengthVar\n"); setParent(e.lengthVar); e.lengthVar.accept(this); } } override void visit(SliceExp e) { if (e.lengthVar) { //printf("lengthVar\n"); setParent(e.lengthVar); e.lengthVar.accept(this); } } override void visit(Dsymbol) { } override void visit(VarDeclaration v) { if (v._init) v._init.accept(this); } override void visit(Initializer) { } override void visit(ExpInitializer ei) { walkPostorder(ei.exp ,this); } override void visit(StructInitializer si) { foreach (i, const id; si.field) if (Initializer iz = si.value[i]) iz.accept(this); } override void visit(ArrayInitializer ai) { foreach (i, ex; ai.index) { if (ex) walkPostorder(ex, this); if (Initializer iz = ai.value[i]) iz.accept(this); } } } scope LambdaSetParent lsp = new LambdaSetParent(fd); walkPostorder(e, lsp); } /******************************************* * Look for references to variables in a scope enclosing the new function literal. * * Essentially just calls `checkNestedReference() for each variable reference in `e`. * Params: * sc = context * e = expression to check * Returns: * true if error occurs. */ bool lambdaCheckForNestedRef(Expression e, Scope* sc) { extern (C++) final class LambdaCheckForNestedRef : StoppableVisitor { alias visit = typeof(super).visit; public: Scope* sc; bool result; extern (D) this(Scope* sc) scope @safe { this.sc = sc; } override void visit(Expression) { } override void visit(SymOffExp e) { VarDeclaration v = e.var.isVarDeclaration(); if (v) result = v.checkNestedReference(sc, Loc.initial); } override void visit(VarExp e) { VarDeclaration v = e.var.isVarDeclaration(); if (v) result = v.checkNestedReference(sc, Loc.initial); } override void visit(ThisExp e) { if (e.var) result = e.var.checkNestedReference(sc, Loc.initial); } override void visit(DeclarationExp e) { VarDeclaration v = e.declaration.isVarDeclaration(); if (v) { result = v.checkNestedReference(sc, Loc.initial); if (result) return; /* Some expressions cause the frontend to create a temporary. * For example, structs with cpctors replace the original * expression e with: * __cpcttmp = __cpcttmp.cpctor(e); * * In this instance, we need to ensure that the original * expression e does not have any nested references by * checking the declaration initializer too. */ if (v._init && v._init.isExpInitializer()) { Expression ie = v._init.initializerToExpression(); result = lambdaCheckForNestedRef(ie, sc); } } } } scope LambdaCheckForNestedRef v = new LambdaCheckForNestedRef(sc); walkPostorder(e, v); return v.result; } /***************************************** * See if context `s` is nested within context `p`, meaning * it `p` is reachable at runtime by walking the static links. * If any of the intervening contexts are function literals, * make sure they are delegates. * Params: * s = inner context * p = outer context * Returns: * true means it is accessible by walking the context pointers at runtime * References: * for static links see https://en.wikipedia.org/wiki/Call_stack#Functions_of_the_call_stack */ bool ensureStaticLinkTo(Dsymbol s, Dsymbol p) { while (s) { if (s == p) // hit! return true; if (auto fd = s.isFuncDeclaration()) { if (!fd.isThis() && !fd.isNested()) break; // https://issues.dlang.org/show_bug.cgi?id=15332 // change to delegate if fd is actually nested. if (auto fld = fd.isFuncLiteralDeclaration()) fld.tok = TOK.delegate_; } if (auto ad = s.isAggregateDeclaration()) { if (ad.storage_class & STC.static_) break; } s = s.toParentP(p); } return false; } ldc-1.40.0-src/dmd/dinterpret.d0000644000000000000000000100337514727557031014736 0ustar rootroot/** * The entry point for CTFE. * * Specification: ($LINK2 https://dlang.org/spec/function.html#interpretation, Compile Time Function Execution (CTFE)) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dinterpret.d, _dinterpret.d) * Documentation: https://dlang.org/phobos/dmd_dinterpret.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dinterpret.d */ module dmd.dinterpret; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.builtin; import dmd.constfold; import dmd.ctfeexpr; import dmd.dcast; import dmd.dclass; import dmd.declaration; import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.init; import dmd.initsem; import dmd.location; import dmd.mtype; import dmd.root.rmem; import dmd.root.array; import dmd.root.ctfloat; import dmd.root.region; import dmd.rootobject; import dmd.root.utf; import dmd.statement; import dmd.tokens; import dmd.typesem : mutableOf, equivalent, pointerTo, sarrayOf, arrayOf, size; import dmd.utils : arrayCastBigEndian; import dmd.visitor; /************************************* * Entry point for CTFE. * A compile-time result is required. Give an error if not possible. * * `e` must be semantically valid expression. In other words, it should not * contain any `ErrorExp`s in it. But, CTFE interpretation will cross over * functions and may invoke a function that contains `ErrorStatement` in its body. * If that, the "CTFE failed because of previous errors" error is raised. */ public Expression ctfeInterpret(Expression e) { switch (e.op) { case EXP.int64: case EXP.float64: case EXP.complex80: case EXP.null_: case EXP.void_: case EXP.string_: case EXP.this_: case EXP.super_: case EXP.type: case EXP.typeid_: case EXP.template_: // non-eponymous template/instance case EXP.scope_: // ditto case EXP.dotTemplateDeclaration: // ditto, e.e1 doesn't matter here case EXP.dotTemplateInstance: // ditto case EXP.dot: // ditto if (e.type.ty == Terror) return ErrorExp.get(); goto case EXP.error; case EXP.error: return e; default: break; } assert(e.type); // https://issues.dlang.org/show_bug.cgi?id=14642 //assert(e.type.ty != Terror); // FIXME if (e.type.ty == Terror) return ErrorExp.get(); auto rgnpos = ctfeGlobals.region.savePos(); version (IN_LLVM) { import driver.timetrace, std.format, std.conv; auto timeScope = TimeTraceScope(text("CTFE start: ", e.toChars()), e.toChars().to!string, e.loc); } Expression result = interpret(e, null); // Report an error if the expression contained a `ThrowException` and // hence generated an uncaught exception if (auto tee = result.isThrownExceptionExp()) { tee.generateUncaughtError(); result = CTFEExp.cantexp; } else result = copyRegionExp(result); if (!CTFEExp.isCantExp(result)) result = scrubReturnValue(e.loc, result); if (CTFEExp.isCantExp(result)) result = ErrorExp.get(); ctfeGlobals.region.release(rgnpos); return result; } /* Run CTFE on the expression, but allow the expression to be a TypeExp * or a tuple containing a TypeExp. (This is required by pragma(msg)). */ public Expression ctfeInterpretForPragmaMsg(Expression e) { if (e.op == EXP.error || e.op == EXP.type) return e; // It's also OK for it to be a function declaration (happens only with // __traits(getOverloads)) if (auto ve = e.isVarExp()) if (ve.var.isFuncDeclaration()) { return e; } auto tup = e.isTupleExp(); if (!tup) return e.ctfeInterpret(); // Tuples need to be treated separately, since they are // allowed to contain a TypeExp in this case. Expressions* expsx = null; foreach (i, g; *tup.exps) { auto h = ctfeInterpretForPragmaMsg(g); if (h != g) { if (!expsx) { expsx = tup.exps.copy(); } (*expsx)[i] = h; } } if (expsx) { auto te = new TupleExp(e.loc, expsx); expandTuples(te.exps); te.type = new TypeTuple(te.exps); return te; } return e; } public Expression getValue(VarDeclaration vd) { return ctfeGlobals.stack.getValue(vd); } /************************************************* * Allocate an Expression in the ctfe region. * Params: * T = type of Expression to allocate * args = arguments to Expression's constructor * Returns: * allocated Expression */ T ctfeEmplaceExp(T : Expression, Args...)(Args args) { if (mem.isGCEnabled) return new T(args); auto p = ctfeGlobals.region.malloc(__traits(classInstanceSize, T)); emplaceExp!T(p, args); return cast(T)p; } // CTFE diagnostic information public extern (C++) void printCtfePerformanceStats() { debug (SHOWPERFORMANCE) { printf(" ---- CTFE Performance ----\n"); printf("max call depth = %d\tmax stack = %d\n", ctfeGlobals.maxCallDepth, ctfeGlobals.stack.maxStackUsage()); printf("array allocs = %d\tassignments = %d\n\n", ctfeGlobals.numArrayAllocs, ctfeGlobals.numAssignments); } } /************************** */ void incArrayAllocs() { ++ctfeGlobals.numArrayAllocs; } /* ================================================ Implementation ======================================= */ private: /*************** * Collect together globals used by CTFE */ struct CtfeGlobals { Region region; CtfeStack stack; int callDepth = 0; // current number of recursive calls // When printing a stack trace, suppress this number of calls int stackTraceCallsToSuppress = 0; int maxCallDepth = 0; // highest number of recursive calls int numArrayAllocs = 0; // Number of allocated arrays int numAssignments = 0; // total number of assignments executed } __gshared CtfeGlobals ctfeGlobals; enum CTFEGoal : int { RValue, /// Must return an Rvalue (== CTFE value) LValue, /// Must return an Lvalue (== CTFE reference) Nothing, /// The return value is not required } //debug = LOG; //debug = LOGASSIGN; //debug = LOGCOMPILE; //debug = SHOWPERFORMANCE; // Maximum allowable recursive function calls in CTFE enum CTFE_RECURSION_LIMIT = 1000; /** The values of all CTFE variables */ struct CtfeStack { private: /* The stack. Every declaration we encounter is pushed here, * together with the VarDeclaration, and the previous * stack address of that variable, so that we can restore it * when we leave the stack frame. * Note that when a function is forward referenced, the interpreter must * run semantic3, and that may start CTFE again with a NULL istate. Thus * the stack might not be empty when CTFE begins. * * Ctfe Stack addresses are just 0-based integers, but we save * them as 'void *' because Array can only do pointers. */ Expressions values; // values on the stack VarDeclarations vars; // corresponding variables Array!(void*) savedId; // id of the previous state of that var Array!(void*) frames; // all previous frame pointers Expressions savedThis; // all previous values of localThis /* Global constants get saved here after evaluation, so we never * have to redo them. This saves a lot of time and memory. */ Expressions globalValues; // values of global constants size_t framepointer; // current frame pointer size_t maxStackPointer; // most stack we've ever used Expression localThis; // value of 'this', or NULL if none public: size_t stackPointer() @safe { return values.length; } // The current value of 'this', or NULL if none Expression getThis() @safe { return localThis; } // Largest number of stack positions we've used size_t maxStackUsage() @safe { return maxStackPointer; } // Start a new stack frame, using the provided 'this'. void startFrame(Expression thisexp) { frames.push(cast(void*)cast(size_t)framepointer); savedThis.push(localThis); framepointer = stackPointer(); localThis = thisexp; } void endFrame() { size_t oldframe = cast(size_t)frames[frames.length - 1]; localThis = savedThis[savedThis.length - 1]; popAll(framepointer); framepointer = oldframe; frames.setDim(frames.length - 1); savedThis.setDim(savedThis.length - 1); } bool isInCurrentFrame(VarDeclaration v) { if (v.isDataseg() && !v.isCTFE()) return false; // It's a global return v.ctfeAdrOnStack >= framepointer; } Expression getValue(VarDeclaration v) { //printf("getValue() %s\n", v.toChars()); if ((v.isDataseg() || v.storage_class & STC.manifest) && !v.isCTFE()) { assert(v.ctfeAdrOnStack < globalValues.length); return globalValues[v.ctfeAdrOnStack]; } assert(v.ctfeAdrOnStack < stackPointer()); return values[v.ctfeAdrOnStack]; } void setValue(VarDeclaration v, Expression e) { //printf("setValue() %s : %s\n", v.toChars(), e.toChars()); assert(!v.isDataseg() || v.isCTFE()); assert(v.ctfeAdrOnStack < stackPointer()); values[v.ctfeAdrOnStack] = e; } void push(VarDeclaration v) { //printf("push() %s\n", v.toChars()); assert(!v.isDataseg() || v.isCTFE()); if (v.ctfeAdrOnStack != VarDeclaration.AdrOnStackNone && v.ctfeAdrOnStack >= framepointer) { // Already exists in this frame, reuse it. values[v.ctfeAdrOnStack] = null; return; } savedId.push(cast(void*)cast(size_t)v.ctfeAdrOnStack); v.ctfeAdrOnStack = cast(uint)values.length; vars.push(v); values.push(null); } void pop(VarDeclaration v) { assert(!v.isDataseg() || v.isCTFE()); assert(!v.isReference()); const oldid = v.ctfeAdrOnStack; v.ctfeAdrOnStack = cast(uint)cast(size_t)savedId[oldid]; if (v.ctfeAdrOnStack == values.length - 1) { values.pop(); vars.pop(); savedId.pop(); } } void popAll(size_t stackpointer) { if (stackPointer() > maxStackPointer) maxStackPointer = stackPointer(); assert(values.length >= stackpointer); for (size_t i = stackpointer; i < values.length; ++i) { VarDeclaration v = vars[i]; v.ctfeAdrOnStack = cast(uint)cast(size_t)savedId[i]; } values.setDim(stackpointer); vars.setDim(stackpointer); savedId.setDim(stackpointer); } void saveGlobalConstant(VarDeclaration v, Expression e) { assert(v._init && (v.isConst() || v.isImmutable() || v.storage_class & STC.manifest) && !v.isCTFE()); v.ctfeAdrOnStack = cast(uint)globalValues.length; globalValues.push(copyRegionExp(e)); } } private struct InterState { InterState* caller; // calling function's InterState FuncDeclaration fd; // function being interpreted Statement start; // if !=NULL, start execution at this statement /* target of CTFEExp result; also * target of labelled CTFEExp or * CTFEExp. (null if no label). */ Statement gotoTarget; } /************************************* * Attempt to interpret a function given the arguments. * Params: * pue = storage for result * fd = function being called * istate = state for calling function (NULL if none) * arguments = function arguments * thisarg = 'this', if a needThis() function, NULL if not. * * Returns: * result expression if successful, EXP.cantExpression if not, * or CTFEExp if function returned void. */ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterState* istate, Expressions* arguments, Expression thisarg) { debug (LOG) { printf("\n********\n%s FuncDeclaration::interpret(istate = %p) %s\n", fd.loc.toChars(), istate, fd.toChars()); } version (IN_LLVM) { import driver.timetrace, std.format, std.conv; scope dlg = () { import dmd.common.outbuffer; auto strbuf = OutBuffer(20); strbuf.writestring(fd.toPrettyChars()); strbuf.write("("); if (arguments) { foreach (i, arg; *arguments) { if (i > 0) strbuf.write(", "); strbuf.writestring(arg.toChars()); } } strbuf.write(")"); return strbuf.extractSlice(); }; auto timeScope = TimeTraceScopeDelayedDetail(text("CTFE func: ", fd.toChars()), dlg, fd.loc); } void fdError(const(char)* msg) { error(fd.loc, "%s `%s` %s", fd.kind, fd.toPrettyChars, msg); } assert(pue); if (fd.semanticRun == PASS.semantic3) { fdError("circular dependency. Functions cannot be interpreted while being compiled"); return CTFEExp.cantexp; } if (!functionSemantic3(fd)) return CTFEExp.cantexp; if (fd.semanticRun < PASS.semantic3done) { fdError("circular dependency. Functions cannot be interpreted while being compiled"); return CTFEExp.cantexp; } auto tf = fd.type.toBasetype().isTypeFunction(); if (tf.parameterList.varargs != VarArg.none && arguments && ((fd.parameters && arguments.length != fd.parameters.length) || (!fd.parameters && arguments.length))) { fdError("C-style variadic functions are not yet implemented in CTFE"); return CTFEExp.cantexp; } // Nested functions always inherit the 'this' pointer from the parent, // except for delegates. (Note that the 'this' pointer may be null). // Func literals report isNested() even if they are in global scope, // so we need to check that the parent is a function. if (fd.isNested() && fd.toParentLocal().isFuncDeclaration() && !thisarg && istate) thisarg = ctfeGlobals.stack.getThis(); if (fd.needThis() && !thisarg) { // error, no this. Prevent segfault. // Here should be unreachable by the strict 'this' check in front-end. error(fd.loc, "%s `%s` need `this` to access member `%s`", fd.kind, fd.toPrettyChars, fd.toChars()); return CTFEExp.cantexp; } // Place to hold all the arguments to the function while // we are evaluating them. size_t dim = arguments ? arguments.length : 0; assert((fd.parameters ? fd.parameters.length : 0) == dim); /* Evaluate all the arguments to the function, * store the results in eargs[] */ Expressions eargs = Expressions(dim); for (size_t i = 0; i < dim; i++) { Expression earg = (*arguments)[i]; Parameter fparam = tf.parameterList[i]; if (fparam.isReference()) { if (!istate && (fparam.storageClass & STC.out_)) { // initializing an out parameter involves writing to it. error(earg.loc, "global `%s` cannot be passed as an `out` parameter at compile time", earg.toChars()); return CTFEExp.cantexp; } // Convert all reference arguments into lvalue references earg = interpretRegion(earg, istate, CTFEGoal.LValue); if (CTFEExp.isCantExp(earg)) return earg; } else if (fparam.isLazy()) { } else { /* Value parameters */ Type ta = fparam.type.toBasetype(); if (ta.ty == Tsarray) if (auto eaddr = earg.isAddrExp()) { /* Static arrays are passed by a simple pointer. * Skip past this to get at the actual arg. */ earg = eaddr.e1; } earg = interpretRegion(earg, istate); if (CTFEExp.isCantExp(earg)) return earg; /* Struct literals are passed by value, but we don't need to * copy them if they are passed as const */ if (earg.op == EXP.structLiteral && !(fparam.storageClass & (STC.const_ | STC.immutable_))) earg = copyLiteral(earg).copy(); } if (auto tee = earg.isThrownExceptionExp()) { if (istate) return tee; tee.generateUncaughtError(); return CTFEExp.cantexp; } eargs[i] = earg; } // Now that we've evaluated all the arguments, we can start the frame // (this is the moment when the 'call' actually takes place). InterState istatex; istatex.caller = istate; istatex.fd = fd; if (fd.hasDualContext()) { Expression arg0 = thisarg; if (arg0 && arg0.type.ty == Tstruct) { Type t = arg0.type.pointerTo(); arg0 = ctfeEmplaceExp!AddrExp(arg0.loc, arg0); arg0.type = t; } auto elements = new Expressions(2); (*elements)[0] = arg0; (*elements)[1] = ctfeGlobals.stack.getThis(); Type t2 = Type.tvoidptr.sarrayOf(2); const loc = thisarg ? thisarg.loc : fd.loc; thisarg = ctfeEmplaceExp!ArrayLiteralExp(loc, t2, elements); thisarg = ctfeEmplaceExp!AddrExp(loc, thisarg); thisarg.type = t2.pointerTo(); } ctfeGlobals.stack.startFrame(thisarg); if (fd.vthis && thisarg) { ctfeGlobals.stack.push(fd.vthis); setValue(fd.vthis, thisarg); } for (size_t i = 0; i < dim; i++) { Expression earg = eargs[i]; Parameter fparam = tf.parameterList[i]; VarDeclaration v = (*fd.parameters)[i]; debug (LOG) { printf("arg[%zu] = %s\n", i, earg.toChars()); } ctfeGlobals.stack.push(v); if (fparam.isReference() && earg.op == EXP.variable && earg.isVarExp().var.toParent2() == fd) { VarDeclaration vx = earg.isVarExp().var.isVarDeclaration(); if (!vx) { error(fd.loc, "%s `%s` cannot interpret `%s` as a `ref` parameter", fd.kind, fd.toPrettyChars, earg.toChars()); return CTFEExp.cantexp; } /* vx is a variable that is declared in fd. * It means that fd is recursively called. e.g. * * void fd(int n, ref int v = dummy) { * int vx; * if (n == 1) fd(2, vx); * } * fd(1); * * The old value of vx on the stack in fd(1) * should be saved at the start of fd(2, vx) call. */ const oldadr = vx.ctfeAdrOnStack; ctfeGlobals.stack.push(vx); assert(!hasValue(vx)); // vx is made uninitialized // https://issues.dlang.org/show_bug.cgi?id=14299 // v.ctfeAdrOnStack should be saved already // in the stack before the overwrite. v.ctfeAdrOnStack = oldadr; assert(hasValue(v)); // ref parameter v should refer existing value. } else { // Value parameters and non-trivial references setValueWithoutChecking(v, earg); } debug (LOG) { printf("interpreted arg[%zu] = %s\n", i, earg.toChars()); showCtfeExpr(earg); } debug (LOGASSIGN) { printf("interpreted arg[%zu] = %s\n", i, earg.toChars()); showCtfeExpr(earg); } } if (fd.vresult) ctfeGlobals.stack.push(fd.vresult); // Enter the function ++ctfeGlobals.callDepth; if (ctfeGlobals.callDepth > ctfeGlobals.maxCallDepth) ctfeGlobals.maxCallDepth = ctfeGlobals.callDepth; Expression e = null; while (1) { if (ctfeGlobals.callDepth > CTFE_RECURSION_LIMIT) { fdError("CTFE recursion limit exceeded"); e = CTFEExp.cantexp; break; } e = interpretStatement(pue, fd.fbody, &istatex); if (CTFEExp.isCantExp(e)) { debug (LOG) { printf("function body failed to interpret\n"); } } if (istatex.start) { error(fd.loc, "%s `%s` CTFE internal error: failed to resume at statement `%s`", fd.kind, fd.toPrettyChars, istatex.start.toChars()); return CTFEExp.cantexp; } /* This is how we deal with a recursive statement AST * that has arbitrary goto statements in it. * Bubble up a 'result' which is the target of the goto * statement, then go recursively down the AST looking * for that statement, then execute starting there. */ if (CTFEExp.isGotoExp(e)) { istatex.start = istatex.gotoTarget; // set starting statement istatex.gotoTarget = null; } else { assert(!e || (e.op != EXP.continue_ && e.op != EXP.break_)); break; } } // If fell off the end of a void function, return void if (!e) { if (tf.next.ty == Tvoid) e = CTFEExp.voidexp; else { /* missing a return statement can happen with C functions * https://issues.dlang.org/show_bug.cgi?id=23056 */ fdError("no return value from function"); e = CTFEExp.cantexp; } } if (tf.isref && e.op == EXP.variable && e.isVarExp().var == fd.vthis) e = thisarg; if (tf.isref && fd.hasDualContext() && e.op == EXP.index) { auto ie = e.isIndexExp(); auto pe = ie.e1.isPtrExp(); auto ve = !pe ? null : pe.e1.isVarExp(); if (ve && ve.var == fd.vthis) { auto ne = ie.e2.isIntegerExp(); assert(ne); auto ale = thisarg.isAddrExp().e1.isArrayLiteralExp(); e = (*ale.elements)[cast(size_t)ne.getInteger()]; if (auto ae = e.isAddrExp()) { e = ae.e1; } } } // Leave the function --ctfeGlobals.callDepth; ctfeGlobals.stack.endFrame(); // If it generated an uncaught exception, report error. if (!istate && e.isThrownExceptionExp()) { if (e == pue.exp()) e = pue.copy(); e.isThrownExceptionExp().generateUncaughtError(); e = CTFEExp.cantexp; } return e; } /// used to collect coverage information in ctfe void incUsageCtfe(InterState* istate, const ref Loc loc) { if (global.params.ctfe_cov && istate) { auto line = loc.linnum; auto mod = istate.fd.getModule(); ++mod.ctfe_cov[line]; } } /*********************************** * Interpret the statement. * Params: * s = Statement to interpret * istate = context * Returns: * NULL continue to next statement * EXP.cantExpression cannot interpret statement at compile time * !NULL expression from return statement, or thrown exception */ Expression interpretStatement(Statement s, InterState* istate) { UnionExp ue = void; auto result = interpretStatement(&ue, s, istate); if (result == ue.exp()) result = ue.copy(); return result; } /// Expression interpretStatement(UnionExp* pue, Statement s, InterState* istate) { Expression result; // If e is EXP.throw_exception or EXP.cantExpression, // set it to 'result' and returns true. bool exceptionOrCant(Expression e) { if (exceptionOrCantInterpret(e)) { // Make sure e is not pointing to a stack temporary result = (e.op == EXP.cantExpression) ? CTFEExp.cantexp : e; return true; } return false; } /******************************** Statement ***************************/ void visitDefaultCase(Statement s) { debug (LOG) { printf("%s Statement::interpret() %s\n", s.loc.toChars(), s.toChars()); } if (istate.start) { if (istate.start != s) return; istate.start = null; } error(s.loc, "statement `%s` cannot be interpreted at compile time", s.toChars()); result = CTFEExp.cantexp; } void visitExp(ExpStatement s) { debug (LOG) { printf("%s ExpStatement::interpret(%s)\n", s.loc.toChars(), s.exp ? s.exp.toChars() : ""); } if (istate.start) { if (istate.start != s) return; istate.start = null; } if (s.exp && s.exp.hasCode) incUsageCtfe(istate, s.loc); Expression e = interpret(pue, s.exp, istate, CTFEGoal.Nothing); if (exceptionOrCant(e)) return; } void visitDtorExp(DtorExpStatement s) { visitExp(s); } void visitCompound(CompoundStatement s) { debug (LOG) { printf("%s CompoundStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; const dim = s.statements ? s.statements.length : 0; foreach (i; 0 .. dim) { Statement sx = (*s.statements)[i]; result = interpretStatement(pue, sx, istate); if (result) break; } debug (LOG) { printf("%s -CompoundStatement::interpret() %p\n", s.loc.toChars(), result); } } void visitCompoundAsm(CompoundAsmStatement s) { visitCompound(s); } void visitUnrolledLoop(UnrolledLoopStatement s) { debug (LOG) { printf("%s UnrolledLoopStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; const dim = s.statements ? s.statements.length : 0; foreach (i; 0 .. dim) { Statement sx = (*s.statements)[i]; Expression e = interpretStatement(pue, sx, istate); if (!e) // succeeds to interpret, or goto target was not found continue; if (exceptionOrCant(e)) return; if (e.op == EXP.break_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // break at a higher level return; } istate.gotoTarget = null; result = null; return; } if (e.op == EXP.continue_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // continue at a higher level return; } istate.gotoTarget = null; continue; } // expression from return statement, or thrown exception result = e; break; } } void visitIf(IfStatement s) { debug (LOG) { printf("%s IfStatement::interpret(%s)\n", s.loc.toChars(), s.condition.toChars()); } incUsageCtfe(istate, s.loc); if (istate.start == s) istate.start = null; if (istate.start) { Expression e = null; e = interpretStatement(s.ifbody, istate); if (!e && istate.start) e = interpretStatement(s.elsebody, istate); result = e; return; } UnionExp ue = void; Expression e = interpret(&ue, s.condition, istate); assert(e); if (exceptionOrCant(e)) return; if (isTrueBool(e)) result = interpretStatement(pue, s.ifbody, istate); else if (e.toBool().hasValue(false)) result = interpretStatement(pue, s.elsebody, istate); else { // no error, or assert(0)? result = CTFEExp.cantexp; } } void visitScope(ScopeStatement s) { debug (LOG) { printf("%s ScopeStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; result = interpretStatement(pue, s.statement, istate); } void visitReturn(ReturnStatement s) { debug (LOG) { printf("%s ReturnStatement::interpret(%s)\n", s.loc.toChars(), s.exp ? s.exp.toChars() : ""); } if (istate.start) { if (istate.start != s) return; istate.start = null; } if (!s.exp) { result = CTFEExp.voidexp; return; } incUsageCtfe(istate, s.loc); assert(istate && istate.fd && istate.fd.type && istate.fd.type.ty == Tfunction); TypeFunction tf = cast(TypeFunction)istate.fd.type; /* If the function returns a ref AND it's been called from an assignment, * we need to return an lvalue. Otherwise, just do an (rvalue) interpret. */ if (tf.isref) { result = interpret(pue, s.exp, istate, CTFEGoal.LValue); return; } if (tf.next && tf.next.ty == Tdelegate && istate.fd.closureVars.length > 0) { // To support this, we need to copy all the closure vars // into the delegate literal. error(s.loc, "closures are not yet supported in CTFE"); result = CTFEExp.cantexp; return; } // We need to treat pointers specially, because EXP.symbolOffset can be used to // return a value OR a pointer Expression e = interpret(pue, s.exp, istate); if (exceptionOrCant(e)) return; /** * Interpret `return a ~= b` (i.e. `return _d_arrayappendT{,Trace}(a, b)`) as: * a ~= b; * return a; * This is needed because `a ~= b` has to be interpreted as an lvalue, in order to avoid * assigning a larger array into a smaller one, such as: * `a = [1, 2], a ~= [3]` => `[1, 2] ~= [3]` => `[1, 2] = [1, 2, 3]` */ if (isRuntimeHook(s.exp, Id._d_arrayappendT) || isRuntimeHook(s.exp, Id._d_arrayappendTTrace)) { auto rs = new ReturnStatement(s.loc, e); visitReturn(rs); return; } // Disallow returning pointers to stack-allocated variables (bug 7876) if (!stopPointersEscaping(s.loc, e)) { result = CTFEExp.cantexp; return; } if (needToCopyLiteral(e)) e = copyLiteral(e).copy(); debug (LOGASSIGN) { printf("RETURN %s\n", s.loc.toChars()); showCtfeExpr(e); } result = e; } void visitBreak(BreakStatement s) { debug (LOG) { printf("%s BreakStatement::interpret()\n", s.loc.toChars()); } incUsageCtfe(istate, s.loc); if (istate.start) { if (istate.start != s) return; istate.start = null; } istate.gotoTarget = findGotoTarget(istate, s.ident); result = CTFEExp.breakexp; } void visitContinue(ContinueStatement s) { debug (LOG) { printf("%s ContinueStatement::interpret()\n", s.loc.toChars()); } incUsageCtfe(istate, s.loc); if (istate.start) { if (istate.start != s) return; istate.start = null; } istate.gotoTarget = findGotoTarget(istate, s.ident); result = CTFEExp.continueexp; } void visitWhile(WhileStatement s) { debug (LOG) { printf("WhileStatement::interpret()\n"); } assert(0); // rewritten to ForStatement } void visitDo(DoStatement s) { debug (LOG) { printf("%s DoStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; while (1) { Expression e = interpretStatement(s._body, istate); if (!e && istate.start) // goto target was not found return; assert(!istate.start); if (exceptionOrCant(e)) return; if (e && e.op == EXP.break_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // break at a higher level return; } istate.gotoTarget = null; break; } if (e && e.op == EXP.continue_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // continue at a higher level return; } istate.gotoTarget = null; e = null; } if (e) { result = e; // bubbled up from ReturnStatement return; } UnionExp ue = void; incUsageCtfe(istate, s.condition.loc); e = interpret(&ue, s.condition, istate); if (exceptionOrCant(e)) return; if (!e.isConst()) { result = CTFEExp.cantexp; return; } if (e.toBool().hasValue(false)) break; assert(isTrueBool(e)); } assert(result is null); } void visitFor(ForStatement s) { debug (LOG) { printf("%s ForStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; UnionExp ueinit = void; Expression ei = interpretStatement(&ueinit, s._init, istate); if (exceptionOrCant(ei)) return; assert(!ei); // s.init never returns from function, or jumps out from it while (1) { if (s.condition && !istate.start) { UnionExp ue = void; incUsageCtfe(istate, s.condition.loc); Expression e = interpret(&ue, s.condition, istate); if (exceptionOrCant(e)) return; if (e.toBool().hasValue(false)) break; assert(isTrueBool(e)); } Expression e = interpretStatement(pue, s._body, istate); if (!e && istate.start) // goto target was not found return; assert(!istate.start); if (exceptionOrCant(e)) return; if (e && e.op == EXP.break_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // break at a higher level return; } istate.gotoTarget = null; break; } if (e && e.op == EXP.continue_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // continue at a higher level return; } istate.gotoTarget = null; e = null; } if (e) { result = e; // bubbled up from ReturnStatement return; } UnionExp uei = void; if (s.increment) incUsageCtfe(istate, s.increment.loc); e = interpret(&uei, s.increment, istate, CTFEGoal.Nothing); if (exceptionOrCant(e)) return; } assert(result is null); } void visitForeach(ForeachStatement s) { assert(0); // rewritten to ForStatement } void visitForeachRange(ForeachRangeStatement s) { assert(0); // rewritten to ForStatement } void visitSwitch(SwitchStatement s) { debug (LOG) { printf("%s SwitchStatement::interpret()\n", s.loc.toChars()); } incUsageCtfe(istate, s.loc); if (istate.start == s) istate.start = null; if (istate.start) { Expression e = interpretStatement(s._body, istate); if (istate.start) // goto target was not found return; if (exceptionOrCant(e)) return; if (e && e.op == EXP.break_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // break at a higher level return; } istate.gotoTarget = null; e = null; } result = e; return; } UnionExp uecond = void; Expression econdition = interpret(&uecond, s.condition, istate); if (exceptionOrCant(econdition)) return; Statement scase = null; if (s.cases) foreach (cs; *s.cases) { UnionExp uecase = void; Expression ecase = interpret(&uecase, cs.exp, istate); if (exceptionOrCant(ecase)) return; if (ctfeEqual(cs.exp.loc, EXP.equal, econdition, ecase)) { scase = cs; break; } } if (!scase) { if (!s.hasDefault) error(s.loc, "no `default` or `case` for `%s` in `switch` statement", econdition.toChars()); scase = s.sdefault; } assert(scase); /* Jump to scase */ istate.start = scase; Expression e = interpretStatement(pue, s._body, istate); assert(!istate.start); // jump must not fail if (e && e.op == EXP.break_) { if (istate.gotoTarget && istate.gotoTarget != s) { result = e; // break at a higher level return; } istate.gotoTarget = null; e = null; } result = e; } void visitCase(CaseStatement s) { debug (LOG) { printf("%s CaseStatement::interpret(%s) this = %p\n", s.loc.toChars(), s.exp.toChars(), s); } incUsageCtfe(istate, s.loc); if (istate.start == s) istate.start = null; result = interpretStatement(pue, s.statement, istate); } void visitDefault(DefaultStatement s) { debug (LOG) { printf("%s DefaultStatement::interpret()\n", s.loc.toChars()); } incUsageCtfe(istate, s.loc); if (istate.start == s) istate.start = null; result = interpretStatement(pue, s.statement, istate); } void visitGoto(GotoStatement s) { debug (LOG) { printf("%s GotoStatement::interpret()\n", s.loc.toChars()); } if (istate.start) { if (istate.start != s) return; istate.start = null; } incUsageCtfe(istate, s.loc); assert(s.label && s.label.statement); istate.gotoTarget = s.label.statement; result = CTFEExp.gotoexp; } void visitGotoCase(GotoCaseStatement s) { debug (LOG) { printf("%s GotoCaseStatement::interpret()\n", s.loc.toChars()); } if (istate.start) { if (istate.start != s) return; istate.start = null; } incUsageCtfe(istate, s.loc); assert(s.cs); istate.gotoTarget = s.cs; result = CTFEExp.gotoexp; } void visitGotoDefault(GotoDefaultStatement s) { debug (LOG) { printf("%s GotoDefaultStatement::interpret()\n", s.loc.toChars()); } if (istate.start) { if (istate.start != s) return; istate.start = null; } incUsageCtfe(istate, s.loc); assert(s.sw && s.sw.sdefault); istate.gotoTarget = s.sw.sdefault; result = CTFEExp.gotoexp; } void visitLabel(LabelStatement s) { debug (LOG) { printf("%s LabelStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; result = interpretStatement(pue, s.statement, istate); } void visitTryCatch(TryCatchStatement s) { debug (LOG) { printf("%s TryCatchStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; if (istate.start) { Expression e = null; e = interpretStatement(pue, s._body, istate); foreach (ca; *s.catches) { if (e || !istate.start) // goto target was found break; e = interpretStatement(pue, ca.handler, istate); } result = e; return; } Expression e = interpretStatement(s._body, istate); // An exception was thrown if (e && e.isThrownExceptionExp()) { ThrownExceptionExp ex = e.isThrownExceptionExp(); Type extype = ex.thrown.originalClass().type; // Search for an appropriate catch clause. foreach (ca; *s.catches) { Type catype = ca.type; import dmd.typesem : isBaseOf; if (!catype.equals(extype) && !catype.isBaseOf(extype, null)) continue; // Execute the handler if (ca.var) { ctfeGlobals.stack.push(ca.var); setValue(ca.var, ex.thrown); } e = interpretStatement(ca.handler, istate); while (CTFEExp.isGotoExp(e)) { /* This is an optimization that relies on the locality of the jump target. * If the label is in the same catch handler, the following scan * would find it quickly and can reduce jump cost. * Otherwise, the catch block may be unnnecessary scanned again * so it would make CTFE speed slower. */ InterState istatex = *istate; istatex.start = istate.gotoTarget; // set starting statement istatex.gotoTarget = null; Expression eh = interpretStatement(ca.handler, &istatex); if (istatex.start) { // The goto target is outside the current scope. break; } // The goto target was within the body. if (CTFEExp.isCantExp(eh)) { e = eh; break; } *istate = istatex; e = eh; } break; } } result = e; } void visitTryFinally(TryFinallyStatement s) { debug (LOG) { printf("%s TryFinallyStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; if (istate.start) { Expression e = null; e = interpretStatement(pue, s._body, istate); // Jump into/out from finalbody is disabled in semantic analysis. // and jump inside will be handled by the ScopeStatement == finalbody. result = e; return; } Expression ex = interpretStatement(s._body, istate); if (CTFEExp.isCantExp(ex)) { result = ex; return; } while (CTFEExp.isGotoExp(ex)) { // If the goto target is within the body, we must not interpret the finally statement, // because that will call destructors for objects within the scope, which we should not do. InterState istatex = *istate; istatex.start = istate.gotoTarget; // set starting statement istatex.gotoTarget = null; Expression bex = interpretStatement(s._body, &istatex); if (istatex.start) { // The goto target is outside the current scope. break; } // The goto target was within the body. if (CTFEExp.isCantExp(bex)) { result = bex; return; } *istate = istatex; ex = bex; } Expression ey = interpretStatement(s.finalbody, istate); if (CTFEExp.isCantExp(ey)) { result = ey; return; } if (ey && ey.isThrownExceptionExp()) { // Check for collided exceptions if (ex && ex.isThrownExceptionExp()) ex = chainExceptions(ex.isThrownExceptionExp(), ey.isThrownExceptionExp()); else ex = ey; } result = ex; } void visitThrow(ThrowStatement s) { debug (LOG) { printf("%s ThrowStatement::interpret()\n", s.loc.toChars()); } if (istate.start) { if (istate.start != s) return; istate.start = null; } interpretThrow(result, s.exp, s.loc, istate); } void visitScopeGuard(ScopeGuardStatement s) { assert(0); } void visitWith(WithStatement s) { debug (LOG) { printf("%s WithStatement::interpret()\n", s.loc.toChars()); } if (istate.start == s) istate.start = null; if (istate.start) { result = s._body ? interpretStatement(s._body, istate) : null; return; } // If it is with(Enum) {...}, just execute the body. if (s.exp.op == EXP.scope_ || s.exp.op == EXP.type) { result = interpretStatement(pue, s._body, istate); return; } incUsageCtfe(istate, s.loc); Expression e = interpret(s.exp, istate); if (exceptionOrCant(e)) return; if (s.wthis.type.ty == Tpointer && s.exp.type.ty != Tpointer) { e = ctfeEmplaceExp!AddrExp(s.loc, e, s.wthis.type); } ctfeGlobals.stack.push(s.wthis); setValue(s.wthis, e); e = interpretStatement(s._body, istate); while (CTFEExp.isGotoExp(e)) { /* This is an optimization that relies on the locality of the jump target. * If the label is in the same WithStatement, the following scan * would find it quickly and can reduce jump cost. * Otherwise, the statement body may be unnnecessary scanned again * so it would make CTFE speed slower. */ InterState istatex = *istate; istatex.start = istate.gotoTarget; // set starting statement istatex.gotoTarget = null; Expression ex = interpretStatement(s._body, &istatex); if (istatex.start) { // The goto target is outside the current scope. break; } // The goto target was within the body. if (CTFEExp.isCantExp(ex)) { e = ex; break; } *istate = istatex; e = ex; } ctfeGlobals.stack.pop(s.wthis); result = e; } void visitAsm(AsmStatement s) { debug (LOG) { printf("%s AsmStatement::interpret()\n", s.loc.toChars()); } if (istate.start) { if (istate.start != s) return; istate.start = null; } error(s.loc, "`asm` statements cannot be interpreted at compile time"); result = CTFEExp.cantexp; } void visitInlineAsm(InlineAsmStatement s) { visitAsm(s); } void visitGccAsm(GccAsmStatement s) { visitAsm(s); } void visitImport(ImportStatement s) { debug (LOG) { printf("ImportStatement::interpret()\n"); } if (istate.start) { if (istate.start != s) return; istate.start = null; } } if (!s) return null; mixin VisitStatement!void visit; visit.VisitStatement(s); return result; } /// private extern (C++) final class Interpreter : Visitor { alias visit = Visitor.visit; public: InterState* istate; CTFEGoal goal; Expression result; UnionExp* pue; // storage for `result` extern (D) this(UnionExp* pue, InterState* istate, CTFEGoal goal) scope @safe { this.pue = pue; this.istate = istate; this.goal = goal; } // If e is EXP.throw_exception or EXP.cantExpression, // set it to 'result' and returns true. bool exceptionOrCant(Expression e) { if (exceptionOrCantInterpret(e)) { // Make sure e is not pointing to a stack temporary result = (e.op == EXP.cantExpression) ? CTFEExp.cantexp : e; return true; } return false; } /******************************** Expression ***************************/ override void visit(Expression e) { debug (LOG) { printf("%s Expression::interpret() '%s' %s\n", e.loc.toChars(), EXPtoString(e.op).ptr, e.toChars()); printf("type = %s\n", e.type.toChars()); showCtfeExpr(e); } error(e.loc, "cannot interpret `%s` at compile time", e.toChars()); result = CTFEExp.cantexp; } override void visit(TypeExp e) { debug (LOG) { printf("%s TypeExp.interpret() %s\n", e.loc.toChars(), e.toChars()); } result = e; } override void visit(ThisExp e) { debug (LOG) { printf("%s ThisExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (goal == CTFEGoal.LValue) { // We might end up here with istate being zero // https://issues.dlang.org/show_bug.cgi?id=16382 if (istate && istate.fd.vthis) { result = ctfeEmplaceExp!VarExp(e.loc, istate.fd.vthis); if (istate.fd.hasDualContext()) { result = ctfeEmplaceExp!PtrExp(e.loc, result); result.type = Type.tvoidptr.sarrayOf(2); result = ctfeEmplaceExp!IndexExp(e.loc, result, IntegerExp.literal!0); } result.type = e.type; } else result = e; return; } result = ctfeGlobals.stack.getThis(); if (result) { if (istate && istate.fd.hasDualContext()) { assert(result.op == EXP.address); result = result.isAddrExp().e1; assert(result.op == EXP.arrayLiteral); result = (*result.isArrayLiteralExp().elements)[0]; if (e.type.ty == Tstruct) { result = result.isAddrExp().e1; } return; } assert(result.op == EXP.structLiteral || result.op == EXP.classReference || result.op == EXP.type); return; } error(e.loc, "value of `this` is not known at compile time"); result = CTFEExp.cantexp; } override void visit(NullExp e) { result = e; } override void visit(IntegerExp e) { debug (LOG) { printf("%s IntegerExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } result = e; } override void visit(RealExp e) { debug (LOG) { printf("%s RealExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } result = e; } override void visit(ComplexExp e) { result = e; } override void visit(StringExp e) { debug (LOG) { printf("%s StringExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted the string { result = e; return; } if (e.type.ty != Tsarray || (cast(TypeNext)e.type).next.mod & (MODFlags.const_ | MODFlags.immutable_)) { // If it's immutable, we don't need to dup it. Attempts to modify // string literals are prevented in BinExp::interpretAssignCommon. result = e; } else { // https://issues.dlang.org/show_bug.cgi?id=20811 // Create a copy of mutable string literals, so that any change in // value via an index or slice will not survive CTFE. *pue = copyLiteral(e); result = pue.exp(); } } override void visit(FuncExp e) { debug (LOG) { printf("%s FuncExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } result = e; } override void visit(SymOffExp e) { debug (LOG) { printf("%s SymOffExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (e.var.isFuncDeclaration() && e.offset == 0) { result = e; return; } if (isTypeInfo_Class(e.type) && e.offset == 0) { result = e; return; } if (e.type.ty != Tpointer) { // Probably impossible error(e.loc, "cannot interpret `%s` at compile time", e.toChars()); result = CTFEExp.cantexp; return; } Type pointee = (cast(TypePointer)e.type).next; if (e.var.isThreadlocal()) { error(e.loc, "cannot take address of thread-local variable %s at compile time", e.var.toChars()); result = CTFEExp.cantexp; return; } // Check for taking an address of a shared variable. // If the shared variable is an array, the offset might not be zero. Type fromType = null; if (e.var.type.ty == Tarray || e.var.type.ty == Tsarray) { fromType = (cast(TypeArray)e.var.type).next; } if (e.var.isDataseg() && ((e.offset == 0 && isSafePointerCast(e.var.type, pointee)) || (fromType && isSafePointerCast(fromType, pointee)) || (e.var.isCsymbol() && e.offset + pointee.size() <= e.var.type.size()))) { result = e; return; } Expression val = getVarExp(e.loc, istate, e.var, goal); if (exceptionOrCant(val)) return; if (val.type.ty == Tarray || val.type.ty == Tsarray) { // Check for unsupported type painting operations Type elemtype = (cast(TypeArray)val.type).next; const elemsize = elemtype.size(); // It's OK to cast from fixed length to fixed length array, eg &int[n] to int[d]*. if (val.type.ty == Tsarray && pointee.ty == Tsarray && elemsize == pointee.nextOf().size()) { size_t d = cast(size_t)(cast(TypeSArray)pointee).dim.toInteger(); Expression elwr = ctfeEmplaceExp!IntegerExp(e.loc, e.offset / elemsize, Type.tsize_t); Expression eupr = ctfeEmplaceExp!IntegerExp(e.loc, e.offset / elemsize + d, Type.tsize_t); // Create a CTFE pointer &val[ofs..ofs+d] auto se = ctfeEmplaceExp!SliceExp(e.loc, val, elwr, eupr); se.type = pointee; emplaceExp!(AddrExp)(pue, e.loc, se, e.type); result = pue.exp(); return; } if (!isSafePointerCast(elemtype, pointee)) { // It's also OK to cast from &string to string*. if (e.offset == 0 && isSafePointerCast(e.var.type, pointee)) { // Create a CTFE pointer &var auto ve = ctfeEmplaceExp!VarExp(e.loc, e.var); ve.type = elemtype; emplaceExp!(AddrExp)(pue, e.loc, ve, e.type); result = pue.exp(); return; } error(e.loc, "reinterpreting cast from `%s` to `%s` is not supported in CTFE", val.type.toChars(), e.type.toChars()); result = CTFEExp.cantexp; return; } const dinteger_t sz = pointee.size(); dinteger_t indx = e.offset / sz; assert(sz * indx == e.offset); Expression aggregate = null; if (val.op == EXP.arrayLiteral || val.op == EXP.string_) { aggregate = val; } else if (auto se = val.isSliceExp()) { aggregate = se.e1; UnionExp uelwr = void; Expression lwr = interpret(&uelwr, se.lwr, istate); indx += lwr.toInteger(); } if (aggregate) { // Create a CTFE pointer &aggregate[ofs] auto ofs = ctfeEmplaceExp!IntegerExp(e.loc, indx, Type.tsize_t); auto ei = ctfeEmplaceExp!IndexExp(e.loc, aggregate, ofs); ei.type = elemtype; emplaceExp!(AddrExp)(pue, e.loc, ei, e.type); result = pue.exp(); return; } } else if (e.offset == 0 && isSafePointerCast(e.var.type, pointee)) { // Create a CTFE pointer &var auto ve = ctfeEmplaceExp!VarExp(e.loc, e.var); ve.type = e.var.type; emplaceExp!(AddrExp)(pue, e.loc, ve, e.type); result = pue.exp(); return; } error(e.loc, "cannot convert `&%s` to `%s` at compile time", e.var.type.toChars(), e.type.toChars()); result = CTFEExp.cantexp; } override void visit(AddrExp e) { debug (LOG) { printf("%s AddrExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (auto ve = e.e1.isVarExp()) { Declaration decl = ve.var; // We cannot take the address of an imported symbol at compile time if (decl.isImportedSymbol()) { error(e.loc, "cannot take address of imported symbol `%s` at compile time", decl.toChars()); result = CTFEExp.cantexp; return; } if (decl.isDataseg()) { // Normally this is already done by optimize() // Do it here in case optimize(WANTvalue) wasn't run before CTFE emplaceExp!(SymOffExp)(pue, e.loc, e.e1.isVarExp().var, 0); result = pue.exp(); result.type = e.type; return; } } auto er = interpret(e.e1, istate, CTFEGoal.LValue); if (auto ve = er.isVarExp()) if (istate && ve.var == istate.fd.vthis) er = interpret(er, istate); if (exceptionOrCant(er)) return; // Return a simplified address expression emplaceExp!(AddrExp)(pue, e.loc, er, e.type); result = pue.exp(); } override void visit(DelegateExp e) { debug (LOG) { printf("%s DelegateExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } // TODO: Really we should create a CTFE-only delegate expression // of a pointer and a funcptr. // If it is &nestedfunc, just return it // TODO: We should save the context pointer if (auto ve1 = e.e1.isVarExp()) if (ve1.var == e.func) { result = e; return; } auto er = interpret(pue, e.e1, istate); if (exceptionOrCant(er)) return; if (er == e.e1) { // If it has already been CTFE'd, just return it result = e; } else { er = (er == pue.exp()) ? pue.copy() : er; emplaceExp!(DelegateExp)(pue, e.loc, er, e.func, false); result = pue.exp(); result.type = e.type; } } static Expression getVarExp(const ref Loc loc, InterState* istate, Declaration d, CTFEGoal goal) { Expression e = CTFEExp.cantexp; if (VarDeclaration v = d.isVarDeclaration()) { /* Magic variable __ctfe always returns true when interpreting */ if (v.ident == Id.ctfe) return IntegerExp.createBool(true); if (!v.originalType && v.semanticRun < PASS.semanticdone) // semantic() not yet run { v.dsymbolSemantic(null); if (v.type.ty == Terror) return CTFEExp.cantexp; } if ((v.isConst() || v.isImmutable() || v.storage_class & STC.manifest) && !hasValue(v) && v._init && !v.isCTFE()) { if (v.inuse) { error(loc, "circular initialization of %s `%s`", v.kind(), v.toPrettyChars()); return CTFEExp.cantexp; } if (v._scope) { v.inuse++; v._init = v._init.initializerSemantic(v._scope, v.type, INITinterpret); // might not be run on aggregate members v.inuse--; } e = v._init.initializerToExpression(v.type); if (!e) return CTFEExp.cantexp; assert(e.type); // There's a terrible hack in `dmd.dsymbolsem` that special case // a struct with all zeros to an `ExpInitializer(BlitExp(IntegerExp(0)))` // There's matching code for it in e2ir (toElem's visitAssignExp), // so we need the same hack here. // This does not trigger for global as they get a normal initializer. if (auto ts = e.type.isTypeStruct()) if (auto ae = e.isBlitExp()) if (ae.e2.op == EXP.int64) e = ts.defaultInitLiteral(loc); if (e.op == EXP.construct || e.op == EXP.blit) { AssignExp ae = cast(AssignExp)e; e = ae.e2; } if (e.op == EXP.error) { // FIXME: Ultimately all errors should be detected in prior semantic analysis stage. } else if (v.isDataseg() || (v.storage_class & STC.manifest)) { /* https://issues.dlang.org/show_bug.cgi?id=14304 * e is a value that is not yet owned by CTFE. * Mark as "cached", and use it directly during interpretation. */ e = scrubCacheValue(e); ctfeGlobals.stack.saveGlobalConstant(v, e); } else { v.inuse++; e = interpret(e, istate); v.inuse--; if (CTFEExp.isCantExp(e) && !global.gag && !ctfeGlobals.stackTraceCallsToSuppress) errorSupplemental(loc, "while evaluating %s.init", v.toChars()); if (exceptionOrCantInterpret(e)) return e; } } else if (v.isCTFE() && !hasValue(v)) { if (v._init && v.type.size() != 0) { if (v._init.isVoidInitializer()) { // var should have been initialized when it was created error(loc, "CTFE internal error: trying to access uninitialized var"); assert(0); } e = v._init.initializerToExpression(); } else // Zero-length arrays don't have an initializer e = v.type.defaultInitLiteral(e.loc); e = interpret(e, istate); } else if (!(v.isDataseg() || v.storage_class & STC.manifest) && !v.isCTFE() && !istate) { error(loc, "variable `%s` cannot be read at compile time", v.toChars()); return CTFEExp.cantexp; } else { e = hasValue(v) ? getValue(v) : null; if (!e) { // Zero-length arrays don't have an initializer if (v.type.size() == 0) e = v.type.defaultInitLiteral(loc); else if (!v.isCTFE() && v.isDataseg()) { error(loc, "static variable `%s` cannot be read at compile time", v.toChars()); return CTFEExp.cantexp; } else { assert(!(v._init && v._init.isVoidInitializer())); // CTFE initiated from inside a function error(loc, "variable `%s` cannot be read at compile time", v.toChars()); return CTFEExp.cantexp; } } if (auto vie = e.isVoidInitExp()) { error(loc, "cannot read uninitialized variable `%s` in ctfe", v.toPrettyChars()); errorSupplemental(vie.var.loc, "`%s` was uninitialized and used before set", vie.var.toChars()); return CTFEExp.cantexp; } if (goal != CTFEGoal.LValue && v.isReference()) e = interpret(e, istate, goal); } if (!e) e = CTFEExp.cantexp; } else if (SymbolDeclaration s = d.isSymbolDeclaration()) { // exclude void[]-typed `__traits(initSymbol)` if (auto ta = s.type.toBasetype().isTypeDArray()) { assert(ta.next.ty == Tvoid); error(loc, "cannot determine the address of the initializer symbol during CTFE"); return CTFEExp.cantexp; } // Struct static initializers, for example e = s.dsym.type.defaultInitLiteral(loc); if (e.op == EXP.error) error(loc, "CTFE failed because of previous errors in `%s.init`", s.toChars()); e = e.expressionSemantic(null); if (e.op == EXP.error) e = CTFEExp.cantexp; else // Convert NULL to CTFEExp e = interpret(e, istate, goal); } else error(loc, "cannot interpret declaration `%s` at compile time", d.toChars()); return e; } override void visit(VarExp e) { debug (LOG) { printf("%s VarExp::interpret() `%s`, goal = %d\n", e.loc.toChars(), e.toChars(), goal); } if (e.var.isFuncDeclaration()) { result = e; return; } if (goal == CTFEGoal.LValue) { if (auto v = e.var.isVarDeclaration()) { if (!hasValue(v)) { // Compile-time known non-CTFE variable from an outer context // e.g. global or from a ref argument if (v.isConst() || v.isImmutable()) { result = getVarExp(e.loc, istate, v, goal); return; } if (!v.isCTFE() && v.isDataseg()) error(e.loc, "static variable `%s` cannot be read at compile time", v.toChars()); else // CTFE initiated from inside a function error(e.loc, "variable `%s` cannot be read at compile time", v.toChars()); result = CTFEExp.cantexp; return; } if (v.storage_class & (STC.out_ | STC.ref_)) { // Strip off the nest of ref variables Expression ev = getValue(v); if (ev.op == EXP.variable || ev.op == EXP.index || (ev.op == EXP.slice && ev.type.toBasetype().ty == Tsarray) || ev.op == EXP.dotVariable) { result = interpret(pue, ev, istate, goal); return; } } } result = e; return; } result = getVarExp(e.loc, istate, e.var, goal); if (exceptionOrCant(result)) return; // Visit the default initializer for noreturn variables // (Custom initializers would abort the current function call and exit above) if (result.type.ty == Tnoreturn) { result.accept(this); return; } if ((e.var.storage_class & (STC.ref_ | STC.out_)) == 0 && e.type.baseElemOf().ty != Tstruct) { /* Ultimately, STC.ref_|STC.out_ check should be enough to see the * necessity of type repainting. But currently front-end paints * non-ref struct variables by the const type. * * auto foo(ref const S cs); * S s; * foo(s); // VarExp('s') will have const(S) */ // A VarExp may include an implicit cast. It must be done explicitly. result = paintTypeOntoLiteral(pue, e.type, result); } } override void visit(DeclarationExp e) { debug (LOG) { printf("%s DeclarationExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Dsymbol s = e.declaration; while (s.isAttribDeclaration()) { auto ad = cast(AttribDeclaration)s; assert(ad.decl && ad.decl.length == 1); // Currently, only one allowed when parsing s = (*ad.decl)[0]; } if (VarDeclaration v = s.isVarDeclaration()) { if (TupleDeclaration td = v.toAlias().isTupleDeclaration()) { result = null; // Reserve stack space for all tuple members td.foreachVar((s) { VarDeclaration v2 = s.isVarDeclaration(); assert(v2); if (v2.isDataseg() && !v2.isCTFE()) return 0; ctfeGlobals.stack.push(v2); if (v2._init) { Expression einit; if (ExpInitializer ie = v2._init.isExpInitializer()) { einit = interpretRegion(ie.exp, istate, goal); if (exceptionOrCant(einit)) return 1; } else if (v2._init.isVoidInitializer()) { einit = voidInitLiteral(v2.type, v2).copy(); } else { error(e.loc, "declaration `%s` is not yet implemented in CTFE", e.toChars()); result = CTFEExp.cantexp; return 1; } setValue(v2, einit); } return 0; }); return; } if (v.isStatic()) { // Just ignore static variables which aren't read or written yet result = null; return; } if (!(v.isDataseg() || v.storage_class & STC.manifest) || v.isCTFE()) ctfeGlobals.stack.push(v); if (v._init) { if (ExpInitializer ie = v._init.isExpInitializer()) { result = interpretRegion(ie.exp, istate, goal); return; } else if (v._init.isVoidInitializer()) { result = voidInitLiteral(v.type, v).copy(); // There is no AssignExp for void initializers, // so set it here. setValue(v, result); return; } else if (v._init.isArrayInitializer()) { result = v._init.initializerToExpression(v.type); if (result !is null) return; } error(e.loc, "declaration `%s` is not yet implemented in CTFE", e.toChars()); result = CTFEExp.cantexp; } else if (v.type.size() == 0) { // Zero-length arrays don't need an initializer result = v.type.defaultInitLiteral(e.loc); } else { error(e.loc, "variable `%s` cannot be modified at compile time", v.toChars()); result = CTFEExp.cantexp; } return; } if (s.isTemplateMixin() || s.isTupleDeclaration()) { // These can be made to work, too lazy now error(e.loc, "declaration `%s` is not yet implemented in CTFE", e.toChars()); result = CTFEExp.cantexp; return; } // Others should not contain executable code, so are trivial to evaluate result = null; debug (LOG) { printf("-DeclarationExp::interpret(%s): %p\n", e.toChars(), result); } } override void visit(TypeidExp e) { debug (LOG) { printf("%s TypeidExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (Type t = isType(e.obj)) { result = e; return; } if (Expression ex = isExpression(e.obj)) { result = interpret(pue, ex, istate); if (exceptionOrCant(ex)) return; if (result.op == EXP.null_) { error(e.loc, "null pointer dereference evaluating typeid. `%s` is `null`", ex.toChars()); result = CTFEExp.cantexp; return; } if (result.op != EXP.classReference) { error(e.loc, "CTFE internal error: determining classinfo"); result = CTFEExp.cantexp; return; } ClassDeclaration cd = result.isClassReferenceExp().originalClass(); assert(cd); emplaceExp!(TypeidExp)(pue, e.loc, cd.type); result = pue.exp(); result.type = e.type; return; } visit(cast(Expression)e); } override void visit(TupleExp e) { debug (LOG) { printf("%s TupleExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (exceptionOrCant(interpretRegion(e.e0, istate, CTFEGoal.Nothing))) return; auto expsx = e.exps; foreach (i, exp; *expsx) { Expression ex = interpretRegion(exp, istate); if (exceptionOrCant(ex)) return; // A tuple of assignments can contain void (Bug 5676). if (goal == CTFEGoal.Nothing) continue; if (ex.op == EXP.voidExpression) { error(e.loc, "CTFE internal error: void element `%s` in sequence", exp.toChars()); assert(0); } /* If any changes, do Copy On Write */ if (ex !is exp) { expsx = copyArrayOnWrite(expsx, e.exps); (*expsx)[i] = copyRegionExp(ex); } } if (expsx !is e.exps) { expandTuples(expsx); emplaceExp!(TupleExp)(pue, e.loc, expsx); result = pue.exp(); result.type = new TypeTuple(expsx); } else result = e; } override void visit(ArrayLiteralExp e) { debug (LOG) { printf("%s ArrayLiteralExp::interpret() %s, %s\n", e.loc.toChars(), e.type.toChars(), e.toChars()); } if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted all the elements { result = e; return; } Type tb = e.type.toBasetype(); Type tn = tb.nextOf().toBasetype(); bool wantCopy = (tn.ty == Tsarray || tn.ty == Tstruct); auto basis = interpretRegion(e.basis, istate); if (exceptionOrCant(basis)) return; auto expsx = e.elements; size_t dim = expsx ? expsx.length : 0; for (size_t i = 0; i < dim; i++) { Expression exp = (*expsx)[i]; Expression ex; if (!exp) { ex = copyLiteral(basis).copy(); } else { // segfault bug 6250 assert(exp.op != EXP.index || exp.isIndexExp().e1 != e); ex = interpretRegion(exp, istate); if (exceptionOrCant(ex)) return; /* Each elements should have distinct CTFE memory. * int[1] z = 7; * int[1][] pieces = [z,z]; // here */ if (wantCopy) ex = copyLiteral(ex).copy(); } /* If any changes, do Copy On Write */ if (ex !is exp) { expsx = copyArrayOnWrite(expsx, e.elements); (*expsx)[i] = ex; } } if (expsx !is e.elements) { // todo: all tuple expansions should go in semantic phase. expandTuples(expsx); if (expsx.length != dim) { error(e.loc, "CTFE internal error: invalid array literal"); result = CTFEExp.cantexp; return; } emplaceExp!(ArrayLiteralExp)(pue, e.loc, e.type, basis, expsx); auto ale = pue.exp().isArrayLiteralExp(); ale.ownedByCtfe = OwnedBy.ctfe; result = ale; } else if ((cast(TypeNext)e.type).next.mod & (MODFlags.const_ | MODFlags.immutable_)) { // If it's immutable, we don't need to dup it result = e; } else { *pue = copyLiteral(e); result = pue.exp(); } } override void visit(AssocArrayLiteralExp e) { debug (LOG) { printf("%s AssocArrayLiteralExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted all the elements { result = e; return; } auto keysx = e.keys; auto valuesx = e.values; foreach (i, ekey; *keysx) { auto evalue = (*valuesx)[i]; auto ek = interpretRegion(ekey, istate); if (exceptionOrCant(ek)) return; auto ev = interpretRegion(evalue, istate); if (exceptionOrCant(ev)) return; /* If any changes, do Copy On Write */ if (ek !is ekey || ev !is evalue) { keysx = copyArrayOnWrite(keysx, e.keys); valuesx = copyArrayOnWrite(valuesx, e.values); (*keysx)[i] = ek; (*valuesx)[i] = ev; } } if (keysx !is e.keys) expandTuples(keysx); if (valuesx !is e.values) expandTuples(valuesx); if (keysx.length != valuesx.length) { error(e.loc, "CTFE internal error: invalid AA"); result = CTFEExp.cantexp; return; } /* Remove duplicate keys */ for (size_t i = 1; i < keysx.length; i++) { auto ekey = (*keysx)[i - 1]; for (size_t j = i; j < keysx.length; j++) { auto ekey2 = (*keysx)[j]; if (!ctfeEqual(e.loc, EXP.equal, ekey, ekey2)) continue; // Remove ekey keysx = copyArrayOnWrite(keysx, e.keys); valuesx = copyArrayOnWrite(valuesx, e.values); keysx.remove(i - 1); valuesx.remove(i - 1); i -= 1; // redo the i'th iteration break; } } if (keysx !is e.keys || valuesx !is e.values) { assert(keysx !is e.keys && valuesx !is e.values); auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx); aae.type = e.type; aae.ownedByCtfe = OwnedBy.ctfe; result = aae; } else { *pue = copyLiteral(e); result = pue.exp(); } } override void visit(StructLiteralExp e) { debug (LOG) { printf("%s StructLiteralExp::interpret() %s ownedByCtfe = %d\n", e.loc.toChars(), e.toChars(), e.ownedByCtfe); } if (e.ownedByCtfe >= OwnedBy.ctfe) { result = e; return; } size_t dim = e.elements ? e.elements.length : 0; auto expsx = e.elements; if (dim != e.sd.fields.length) { // guaranteed by AggregateDeclaration.fill and TypeStruct.defaultInitLiteral const nvthis = e.sd.fields.length - e.sd.nonHiddenFields(); assert(e.sd.fields.length - dim == nvthis); /* If a nested struct has no initialized hidden pointer, * set it to null to match the runtime behaviour. */ foreach (const i; 0 .. nvthis) { auto ne = ctfeEmplaceExp!NullExp(e.loc); auto vthis = i == 0 ? e.sd.vthis : e.sd.vthis2; ne.type = vthis.type; expsx = copyArrayOnWrite(expsx, e.elements); expsx.push(ne); ++dim; } } assert(dim == e.sd.fields.length); foreach (i; 0 .. dim) { auto v = e.sd.fields[i]; Expression exp = (*expsx)[i]; Expression ex; if (!exp) { ex = voidInitLiteral(v.type, v).copy(); } else { ex = interpretRegion(exp, istate); if (exceptionOrCant(ex)) return; if ((v.type.ty != ex.type.ty) && v.type.ty == Tsarray) { // Block assignment from inside struct literals auto tsa = cast(TypeSArray)v.type; auto len = cast(size_t)tsa.dim.toInteger(); UnionExp ue = void; ex = createBlockDuplicatedArrayLiteral(&ue, ex.loc, v.type, ex, len); if (ex == ue.exp()) ex = ue.copy(); } } /* If any changes, do Copy On Write */ if (ex !is exp) { expsx = copyArrayOnWrite(expsx, e.elements); (*expsx)[i] = ex; } } if (expsx !is e.elements) { expandTuples(expsx); if (expsx.length != e.sd.fields.length) { error(e.loc, "CTFE internal error: invalid struct literal"); result = CTFEExp.cantexp; return; } emplaceExp!(StructLiteralExp)(pue, e.loc, e.sd, expsx); auto sle = pue.exp().isStructLiteralExp(); sle.type = e.type; sle.ownedByCtfe = OwnedBy.ctfe; sle.origin = e.origin; result = sle; } else { *pue = copyLiteral(e); result = pue.exp(); } } // Create an array literal of type 'newtype' with dimensions given by // 'arguments'[argnum..$] static Expression recursivelyCreateArrayLiteral(UnionExp* pue, const ref Loc loc, Type newtype, InterState* istate, Expressions* arguments, int argnum) { Expression lenExpr = interpret(pue, (*arguments)[argnum], istate); if (exceptionOrCantInterpret(lenExpr)) return lenExpr; size_t len = cast(size_t)lenExpr.toInteger(); Type elemType = (cast(TypeArray)newtype).next; if (elemType.ty == Tarray && argnum < arguments.length - 1) { Expression elem = recursivelyCreateArrayLiteral(pue, loc, elemType, istate, arguments, argnum + 1); if (exceptionOrCantInterpret(elem)) return elem; auto elements = new Expressions(len); foreach (ref element; *elements) element = copyLiteral(elem).copy(); emplaceExp!(ArrayLiteralExp)(pue, loc, newtype, elements); auto ae = pue.exp().isArrayLiteralExp(); ae.ownedByCtfe = OwnedBy.ctfe; return ae; } assert(argnum == arguments.length - 1); if (elemType.ty.isSomeChar) { const ch = cast(dchar)elemType.defaultInitLiteral(loc).toInteger(); const sz = cast(ubyte)elemType.size(); return createBlockDuplicatedStringLiteral(pue, loc, newtype, ch, len, sz); } else { auto el = interpret(elemType.defaultInitLiteral(loc), istate); return createBlockDuplicatedArrayLiteral(pue, loc, newtype, el, len); } } override void visit(NewExp e) { debug (LOG) { printf("%s NewExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression epre = interpret(pue, e.argprefix, istate, CTFEGoal.Nothing); if (exceptionOrCant(epre)) return; if (e.newtype.ty == Tarray && e.arguments) { result = recursivelyCreateArrayLiteral(pue, e.loc, e.newtype, istate, e.arguments, 0); return; } if (auto ts = e.newtype.toBasetype().isTypeStruct()) { if (e.member) { Expression se = e.newtype.defaultInitLiteral(e.loc); se = interpret(se, istate); if (exceptionOrCant(se)) return; result = interpretFunction(pue, e.member, istate, e.arguments, se); // Repaint as same as CallExp::interpret() does. result.loc = e.loc; } else { StructDeclaration sd = ts.sym; auto exps = new Expressions(); exps.reserve(sd.fields.length); if (e.arguments) { exps.setDim(e.arguments.length); foreach (i, ex; *e.arguments) { ex = interpretRegion(ex, istate); if (exceptionOrCant(ex)) return; (*exps)[i] = ex; } } sd.fill(e.loc, *exps, false); auto se = ctfeEmplaceExp!StructLiteralExp(e.loc, sd, exps, e.newtype); se.origin = se; se.type = e.newtype; se.ownedByCtfe = OwnedBy.ctfe; result = interpret(pue, se, istate); } if (exceptionOrCant(result)) return; Expression ev = (result == pue.exp()) ? pue.copy() : result; emplaceExp!(AddrExp)(pue, e.loc, ev, e.type); result = pue.exp(); return; } if (auto tc = e.newtype.toBasetype().isTypeClass()) { ClassDeclaration cd = tc.sym; size_t totalFieldCount = 0; for (ClassDeclaration c = cd; c; c = c.baseClass) totalFieldCount += c.fields.length; auto elems = new Expressions(totalFieldCount); size_t fieldsSoFar = totalFieldCount; for (ClassDeclaration c = cd; c; c = c.baseClass) { fieldsSoFar -= c.fields.length; foreach (i, v; c.fields) { if (v.inuse) { error(e.loc, "circular reference to `%s`", v.toPrettyChars()); result = CTFEExp.cantexp; return; } Expression m; if (v._init) { if (v._init.isVoidInitializer()) m = voidInitLiteral(v.type, v).copy(); else m = v.getConstInitializer(true); } else if (v.type.isTypeNoreturn()) { // Noreturn field with default initializer (*elems)[fieldsSoFar + i] = null; continue; } else m = v.type.defaultInitLiteral(e.loc); if (exceptionOrCant(m)) return; (*elems)[fieldsSoFar + i] = copyLiteral(m).copy(); } } // Hack: we store a ClassDeclaration instead of a StructDeclaration. // We probably won't get away with this. // auto se = new StructLiteralExp(e.loc, cast(StructDeclaration)cd, elems, e.newtype); auto se = ctfeEmplaceExp!StructLiteralExp(e.loc, cast(StructDeclaration)cd, elems, e.newtype); se.origin = se; se.ownedByCtfe = OwnedBy.ctfe; Expression eref = ctfeEmplaceExp!ClassReferenceExp(e.loc, se, e.type); if (e.member) { // Call constructor if (!e.member.fbody) { Expression ctorfail = evaluateIfBuiltin(pue, istate, e.loc, e.member, e.arguments, eref); if (ctorfail) { if (exceptionOrCant(ctorfail)) return; result = eref; return; } auto m = e.member; error(m.loc, "%s `%s` `%s` cannot be constructed at compile time, because the constructor has no available source code", m.kind, m.toPrettyChars, e.newtype.toChars()); result = CTFEExp.cantexp; return; } UnionExp ue = void; Expression ctorfail = interpretFunction(&ue, e.member, istate, e.arguments, eref); if (exceptionOrCant(ctorfail)) return; /* https://issues.dlang.org/show_bug.cgi?id=14465 * Repaint the loc, because a super() call * in the constructor modifies the loc of ClassReferenceExp * in CallExp::interpret(). */ eref.loc = e.loc; } result = eref; return; } if (e.newtype.toBasetype().isscalar()) { Expression newval; if (e.arguments && e.arguments.length) newval = (*e.arguments)[0]; else newval = e.newtype.defaultInitLiteral(e.loc); newval = interpretRegion(newval, istate); if (exceptionOrCant(newval)) return; // Create a CTFE pointer &[newval][0] auto elements = new Expressions(1); (*elements)[0] = newval; auto ae = ctfeEmplaceExp!ArrayLiteralExp(e.loc, e.newtype.arrayOf(), elements); ae.ownedByCtfe = OwnedBy.ctfe; auto ei = ctfeEmplaceExp!IndexExp(e.loc, ae, ctfeEmplaceExp!IntegerExp(Loc.initial, 0, Type.tsize_t)); ei.type = e.newtype; emplaceExp!(AddrExp)(pue, e.loc, ei, e.type); result = pue.exp(); return; } error(e.loc, "cannot interpret `%s` at compile time", e.toChars()); result = CTFEExp.cantexp; } override void visit(UnaExp e) { debug (LOG) { printf("%s UnaExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } UnionExp ue = void; Expression e1 = interpret(&ue, e.e1, istate); if (exceptionOrCant(e1)) return; switch (e.op) { case EXP.negate: *pue = Neg(e.type, e1); break; case EXP.tilde: *pue = Com(e.type, e1); break; case EXP.not: *pue = Not(e.type, e1); break; default: assert(0); } result = (*pue).exp(); } override void visit(DotTypeExp e) { debug (LOG) { printf("%s DotTypeExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } UnionExp ue = void; Expression e1 = interpret(&ue, e.e1, istate); if (exceptionOrCant(e1)) return; if (e1 == e.e1) result = e; // optimize: reuse this CTFE reference else { auto edt = e.copy().isDotTypeExp(); edt.e1 = (e1 == ue.exp()) ? e1.copy() : e1; // don't return pointer to ue result = edt; } } private alias fp_t = extern (D) UnionExp function(const ref Loc loc, Type, Expression, Expression); private alias fp2_t = extern (D) bool function(const ref Loc loc, EXP, Expression, Expression); extern (D) private void interpretCommon(BinExp e, fp_t fp) { debug (LOG) { printf("%s BinExp::interpretCommon() %s\n", e.loc.toChars(), e.toChars()); } if (e.e1.type.ty == Tpointer && e.e2.type.ty == Tpointer && e.op == EXP.min) { UnionExp ue1 = void; Expression e1 = interpret(&ue1, e.e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2 = void; Expression e2 = interpret(&ue2, e.e2, istate); if (exceptionOrCant(e2)) return; result = pointerDifference(pue, e.loc, e.type, e1, e2); return; } if (e.e1.type.ty == Tpointer && e.e2.type.isintegral()) { UnionExp ue1 = void; Expression e1 = interpret(&ue1, e.e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2 = void; Expression e2 = interpret(&ue2, e.e2, istate); if (exceptionOrCant(e2)) return; result = pointerArithmetic(pue, e.loc, e.op, e.type, e1, e2); return; } if (e.e2.type.ty == Tpointer && e.e1.type.isintegral() && e.op == EXP.add) { UnionExp ue1 = void; Expression e1 = interpret(&ue1, e.e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2 = void; Expression e2 = interpret(&ue2, e.e2, istate); if (exceptionOrCant(e2)) return; result = pointerArithmetic(pue, e.loc, e.op, e.type, e2, e1); return; } if (e.e1.type.ty == Tpointer || e.e2.type.ty == Tpointer) { error(e.loc, "pointer expression `%s` cannot be interpreted at compile time", e.toChars()); result = CTFEExp.cantexp; return; } bool evalOperand(UnionExp* pue, Expression ex, out Expression er) { er = interpret(pue, ex, istate); if (exceptionOrCant(er)) return false; return true; } UnionExp ue1 = void; Expression e1; if (!evalOperand(&ue1, e.e1, e1)) return; UnionExp ue2 = void; Expression e2; if (!evalOperand(&ue2, e.e2, e2)) return; if (e.op == EXP.rightShift || e.op == EXP.leftShift || e.op == EXP.unsignedRightShift) { const sinteger_t i2 = e2.toInteger(); const uinteger_t sz = e1.type.size() * 8; if (i2 < 0 || i2 >= sz) { error(e.loc, "shift by %lld is outside the range 0..%llu", i2, cast(ulong)sz - 1); result = CTFEExp.cantexp; return; } } /****************************************** * Perform the operation fp on operands e1 and e2. */ UnionExp evaluate(Loc loc, Type type, Expression e1, Expression e2) { UnionExp ue = void; auto ae1 = e1.isArrayLiteralExp(); auto ae2 = e2.isArrayLiteralExp(); if (ae1 || ae2) { /* Cases: * 1. T[] op T[] * 2. T op T[] * 3. T[] op T */ if (ae1 && e2.implicitConvTo(e1.type.toBasetype().nextOf())) // case 3 ae2 = null; else if (ae2 && e1.implicitConvTo(e2.type.toBasetype().nextOf())) // case 2 ae1 = null; // else case 1 auto aex = ae1 ? ae1 : ae2; if (!aex.elements) { emplaceExp!ArrayLiteralExp(&ue, loc, type, cast(Expressions*) null); return ue; } const length = aex.elements.length; Expressions* elements = new Expressions(length); emplaceExp!ArrayLiteralExp(&ue, loc, type, elements); foreach (i; 0 .. length) { Expression e1x = ae1 ? ae1[i] : e1; Expression e2x = ae2 ? ae2[i] : e2; UnionExp uex = evaluate(loc, e1x.type, e1x, e2x); // This can be made more efficient by making use of ue.basis (*elements)[i] = uex.copy(); } return ue; } if (e1.isConst() != 1) { // The following should really be an assert() error(e1.loc, "CTFE internal error: non-constant value `%s`", e1.toChars()); emplaceExp!CTFEExp(&ue, EXP.cantExpression); return ue; } if (e2.isConst() != 1) { error(e2.loc, "CTFE internal error: non-constant value `%s`", e2.toChars()); emplaceExp!CTFEExp(&ue, EXP.cantExpression); return ue; } return (*fp)(loc, type, e1, e2); } *pue = evaluate(e.loc, e.type, e1, e2); result = (*pue).exp(); if (CTFEExp.isCantExp(result)) error(e.loc, "`%s` cannot be interpreted at compile time", e.toChars()); } extern (D) private void interpretCompareCommon(BinExp e, fp2_t fp) { debug (LOG) { printf("%s BinExp::interpretCompareCommon() %s\n", e.loc.toChars(), e.toChars()); } UnionExp ue1 = void; UnionExp ue2 = void; if (e.e1.type.ty == Tpointer && e.e2.type.ty == Tpointer) { Expression e1 = interpret(&ue1, e.e1, istate); if (exceptionOrCant(e1)) return; Expression e2 = interpret(&ue2, e.e2, istate); if (exceptionOrCant(e2)) return; //printf("e1 = %s %s, e2 = %s %s\n", e1.type.toChars(), e1.toChars(), e2.type.toChars(), e2.toChars()); dinteger_t ofs1, ofs2; Expression agg1 = getAggregateFromPointer(e1, &ofs1); Expression agg2 = getAggregateFromPointer(e2, &ofs2); //printf("agg1 = %p %s, agg2 = %p %s\n", agg1, agg1.toChars(), agg2, agg2.toChars()); const cmp = comparePointers(e.op, agg1, ofs1, agg2, ofs2); if (cmp == -1) { char dir = (e.op == EXP.greaterThan || e.op == EXP.greaterOrEqual) ? '<' : '>'; error(e.loc, "the ordering of pointers to unrelated memory blocks is indeterminate in CTFE. To check if they point to the same memory block, use both `>` and `<` inside `&&` or `||`, eg `%s && %s %c= %s + 1`", e.toChars(), e.e1.toChars(), dir, e.e2.toChars()); result = CTFEExp.cantexp; return; } if (e.type.equals(Type.tbool)) result = IntegerExp.createBool(cmp != 0); else { emplaceExp!(IntegerExp)(pue, e.loc, cmp, e.type); result = (*pue).exp(); } return; } Expression e1 = interpret(&ue1, e.e1, istate); if (exceptionOrCant(e1)) return; if (!isCtfeComparable(e1)) { error(e.loc, "cannot compare `%s` at compile time", e1.toChars()); result = CTFEExp.cantexp; return; } Expression e2 = interpret(&ue2, e.e2, istate); if (exceptionOrCant(e2)) return; if (!isCtfeComparable(e2)) { error(e.loc, "cannot compare `%s` at compile time", e2.toChars()); result = CTFEExp.cantexp; return; } const cmp = (*fp)(e.loc, e.op, e1, e2); if (e.type.equals(Type.tbool)) result = IntegerExp.createBool(cmp); else { emplaceExp!(IntegerExp)(pue, e.loc, cmp, e.type); result = (*pue).exp(); } } override void visit(BinExp e) { switch (e.op) { case EXP.add: interpretCommon(e, &Add); return; case EXP.min: interpretCommon(e, &Min); return; case EXP.mul: interpretCommon(e, &Mul); return; case EXP.div: interpretCommon(e, &Div); return; case EXP.mod: interpretCommon(e, &Mod); return; case EXP.leftShift: interpretCommon(e, &Shl); return; case EXP.rightShift: interpretCommon(e, &Shr); return; case EXP.unsignedRightShift: interpretCommon(e, &Ushr); return; case EXP.and: interpretCommon(e, &And); return; case EXP.or: interpretCommon(e, &Or); return; case EXP.xor: interpretCommon(e, &Xor); return; case EXP.pow: interpretCommon(e, &Pow); return; case EXP.equal: case EXP.notEqual: interpretCompareCommon(e, &ctfeEqual); return; case EXP.identity: case EXP.notIdentity: interpretCompareCommon(e, &ctfeIdentity); return; case EXP.lessThan: case EXP.lessOrEqual: case EXP.greaterThan: case EXP.greaterOrEqual: interpretCompareCommon(e, &ctfeCmp); return; default: printf("be = '%s' %s at [%s]\n", EXPtoString(e.op).ptr, e.toChars(), e.loc.toChars()); assert(0); } } /* Helper functions for BinExp::interpretAssignCommon */ // Returns the variable which is eventually modified, or NULL if an rvalue. // thisval is the current value of 'this'. static VarDeclaration findParentVar(Expression e) @safe { for (;;) { if (auto ve = e.isVarExp()) { VarDeclaration v = ve.var.isVarDeclaration(); assert(v); return v; } if (auto ie = e.isIndexExp()) e = ie.e1; else if (auto dve = e.isDotVarExp()) e = dve.e1; else if (auto dtie = e.isDotTemplateInstanceExp()) e = dtie.e1; else if (auto se = e.isSliceExp()) e = se.e1; else return null; } } extern (D) private void interpretAssignCommon(BinExp e, fp_t fp, int post = 0) { debug (LOG) { printf("%s BinExp::interpretAssignCommon() %s\n", e.loc.toChars(), e.toChars()); } result = CTFEExp.cantexp; Expression e1 = e.e1; if (!istate) { error(e.loc, "value of `%s` is not known at compile time", e1.toChars()); return; } ++ctfeGlobals.numAssignments; /* Before we begin, we need to know if this is a reference assignment * (dynamic array, AA, or class) or a value assignment. * Determining this for slice assignments are tricky: we need to know * if it is a block assignment (a[] = e) rather than a direct slice * assignment (a[] = b[]). Note that initializers of multi-dimensional * static arrays can have 2D block assignments (eg, int[7][7] x = 6;). * So we need to recurse to determine if it is a block assignment. */ bool isBlockAssignment = false; if (e1.op == EXP.slice) { // a[] = e can have const e. So we compare the naked types. Type tdst = e1.type.toBasetype(); Type tsrc = e.e2.type.toBasetype(); while (tdst.ty == Tsarray || tdst.ty == Tarray) { tdst = (cast(TypeArray)tdst).next.toBasetype(); if (tsrc.equivalent(tdst)) { isBlockAssignment = true; break; } } } // --------------------------------------- // Deal with reference assignment // --------------------------------------- // If it is a construction of a ref variable, it is a ref assignment if ((e.op == EXP.construct || e.op == EXP.blit) && ((cast(AssignExp)e).memset == MemorySet.referenceInit)) { assert(!fp); Expression newval = interpretRegion(e.e2, istate, CTFEGoal.LValue); if (exceptionOrCant(newval)) return; VarDeclaration v = e1.isVarExp().var.isVarDeclaration(); setValue(v, newval); // Get the value to return. Note that 'newval' is an Lvalue, // so if we need an Rvalue, we have to interpret again. if (goal == CTFEGoal.RValue) result = interpretRegion(newval, istate); else result = e1; // VarExp is a CTFE reference return; } if (fp) { while (e1.op == EXP.cast_) { CastExp ce = e1.isCastExp(); e1 = ce.e1; } } // --------------------------------------- // Interpret left hand side // --------------------------------------- AssocArrayLiteralExp existingAA = null; Expression lastIndex = null; Expression oldval = null; if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray) { // --------------------------------------- // Deal with AA index assignment // --------------------------------------- /* This needs special treatment if the AA doesn't exist yet. * There are two special cases: * (1) If the AA is itself an index of another AA, we may need to create * multiple nested AA literals before we can insert the new value. * (2) If the ultimate AA is null, no insertion happens at all. Instead, * we create nested AA literals, and change it into a assignment. */ IndexExp ie = e1.isIndexExp(); int depth = 0; // how many nested AA indices are there? while (ie.e1.op == EXP.index && ie.e1.isIndexExp().e1.type.toBasetype().ty == Taarray) { assert(ie.modifiable); ie = ie.e1.isIndexExp(); ++depth; } // Get the AA value to be modified. Expression aggregate = interpretRegion(ie.e1, istate); if (exceptionOrCant(aggregate)) return; if ((existingAA = aggregate.isAssocArrayLiteralExp()) !is null) { // Normal case, ultimate parent AA already exists // We need to walk from the deepest index up, checking that an AA literal // already exists on each level. lastIndex = interpretRegion(e1.isIndexExp().e2, istate); lastIndex = resolveSlice(lastIndex); // only happens with AA assignment if (exceptionOrCant(lastIndex)) return; while (depth > 0) { // Walk the syntax tree to find the indexExp at this depth IndexExp xe = e1.isIndexExp(); foreach (d; 0 .. depth) xe = xe.e1.isIndexExp(); Expression ekey = interpretRegion(xe.e2, istate); if (exceptionOrCant(ekey)) return; UnionExp ekeyTmp = void; ekey = resolveSlice(ekey, &ekeyTmp); // only happens with AA assignment // Look up this index in it up in the existing AA, to get the next level of AA. AssocArrayLiteralExp newAA = cast(AssocArrayLiteralExp)findKeyInAA(e.loc, existingAA, ekey); if (exceptionOrCant(newAA)) return; if (!newAA) { // Doesn't exist yet, create an empty AA... auto keysx = new Expressions(); auto valuesx = new Expressions(); newAA = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx); newAA.type = xe.type; newAA.ownedByCtfe = OwnedBy.ctfe; //... and insert it into the existing AA. existingAA.keys.push(ekey); existingAA.values.push(newAA); } existingAA = newAA; --depth; } if (fp) { oldval = findKeyInAA(e.loc, existingAA, lastIndex); if (!oldval) oldval = copyLiteral(e.e1.type.defaultInitLiteral(e.loc)).copy(); } } else { /* The AA is currently null. 'aggregate' is actually a reference to * whatever contains it. It could be anything: var, dotvarexp, ... * We rewrite the assignment from: * aa[i][j] op= newval; * into: * aa = [i:[j:T.init]]; * aa[j] op= newval; */ oldval = copyLiteral(e.e1.type.defaultInitLiteral(e.loc)).copy(); Expression newaae = oldval; while (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray) { Expression ekey = interpretRegion(e1.isIndexExp().e2, istate); if (exceptionOrCant(ekey)) return; ekey = resolveSlice(ekey); // only happens with AA assignment auto keysx = new Expressions(); auto valuesx = new Expressions(); keysx.push(ekey); valuesx.push(newaae); auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx); aae.type = e1.isIndexExp().e1.type; aae.ownedByCtfe = OwnedBy.ctfe; if (!existingAA) { existingAA = aae; lastIndex = ekey; } newaae = aae; e1 = e1.isIndexExp().e1; } // We must set to aggregate with newaae e1 = interpretRegion(e1, istate, CTFEGoal.LValue); if (exceptionOrCant(e1)) return; e1 = assignToLvalue(e, e1, newaae); if (exceptionOrCant(e1)) return; } assert(existingAA && lastIndex); e1 = null; // stomp } else if (e1.op == EXP.arrayLength) { oldval = interpretRegion(e1, istate); if (exceptionOrCant(oldval)) return; } else if (e.op == EXP.construct || e.op == EXP.blit) { // Unless we have a simple var assignment, we're // only modifying part of the variable. So we need to make sure // that the parent variable exists. VarDeclaration ultimateVar = findParentVar(e1); if (auto ve = e1.isVarExp()) { VarDeclaration v = ve.var.isVarDeclaration(); assert(v); if (v.storage_class & STC.out_) goto L1; } else if (ultimateVar && !getValue(ultimateVar)) { Expression ex = interpretRegion(ultimateVar.type.defaultInitLiteral(e.loc), istate); if (exceptionOrCant(ex)) return; setValue(ultimateVar, ex); } else goto L1; } else { L1: e1 = interpretRegion(e1, istate, CTFEGoal.LValue); if (exceptionOrCant(e1)) return; if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray) { IndexExp ie = e1.isIndexExp(); assert(ie.e1.op == EXP.assocArrayLiteral); existingAA = ie.e1.isAssocArrayLiteralExp(); lastIndex = ie.e2; } } // --------------------------------------- // Interpret right hand side // --------------------------------------- Expression newval = interpretRegion(e.e2, istate); if (exceptionOrCant(newval)) return; if (e.op == EXP.blit && newval.op == EXP.int64) { Type tbn = e.type.baseElemOf(); if (tbn.ty == Tstruct) { /* Look for special case of struct being initialized with 0. */ newval = e.type.defaultInitLiteral(e.loc); if (newval.op == EXP.error) { result = CTFEExp.cantexp; return; } newval = interpretRegion(newval, istate); // copy and set ownedByCtfe flag if (exceptionOrCant(newval)) return; } } // ---------------------------------------------------- // Deal with read-modify-write assignments. // Set 'newval' to the final assignment value // Also determine the return value (except for slice // assignments, which are more complicated) // ---------------------------------------------------- if (fp) { if (!oldval) { // Load the left hand side after interpreting the right hand side. oldval = interpretRegion(e1, istate); if (exceptionOrCant(oldval)) return; } if (e.e1.type.ty != Tpointer) { // ~= can create new values (see bug 6052) if (e.op == EXP.concatenateAssign || e.op == EXP.concatenateElemAssign || e.op == EXP.concatenateDcharAssign) { // We need to dup it and repaint the type. For a dynamic array // we can skip duplication, because it gets copied later anyway. if (newval.type.ty != Tarray) { newval = copyLiteral(newval).copy(); newval.type = e.e2.type; // repaint type } else { newval = paintTypeOntoLiteral(e.e2.type, newval); newval = resolveSlice(newval); } } oldval = resolveSlice(oldval); newval = (*fp)(e.loc, e.type, oldval, newval).copy(); } else if (e.e2.type.isintegral() && (e.op == EXP.addAssign || e.op == EXP.minAssign || e.op == EXP.plusPlus || e.op == EXP.minusMinus)) { newval = pointerArithmetic(pue, e.loc, e.op, e.type, oldval, newval).copy(); if (newval == pue.exp()) newval = pue.copy(); } else { error(e.loc, "pointer expression `%s` cannot be interpreted at compile time", e.toChars()); result = CTFEExp.cantexp; return; } if (exceptionOrCant(newval)) { if (CTFEExp.isCantExp(newval)) error(e.loc, "cannot interpret `%s` at compile time", e.toChars()); return; } } if (existingAA) { if (existingAA.ownedByCtfe != OwnedBy.ctfe) { error(e.loc, "cannot modify read-only constant `%s`", existingAA.toChars()); result = CTFEExp.cantexp; return; } //printf("\t+L%d existingAA = %s, lastIndex = %s, oldval = %s, newval = %s\n", // __LINE__, existingAA.toChars(), lastIndex.toChars(), oldval ? oldval.toChars() : NULL, newval.toChars()); assignAssocArrayElement(e.loc, existingAA, lastIndex, newval); // Determine the return value result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval); return; } if (e1.op == EXP.arrayLength) { /* Change the assignment from: * arr.length = n; * into: * arr = new_length_array; (result is n) */ // Determine the return value result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval); if (exceptionOrCant(result)) return; if (result == pue.exp()) result = pue.copy(); size_t oldlen = cast(size_t)oldval.toInteger(); size_t newlen = cast(size_t)newval.toInteger(); if (oldlen == newlen) // no change required -- we're done! return; // We have changed it into a reference assignment // Note that returnValue is still the new length. e1 = e1.isArrayLengthExp().e1; Type t = e1.type.toBasetype(); if (t.ty != Tarray) { error(e.loc, "`%s` is not yet supported at compile time", e.toChars()); result = CTFEExp.cantexp; return; } e1 = interpretRegion(e1, istate, CTFEGoal.LValue); if (exceptionOrCant(e1)) return; if (oldlen != 0) // Get the old array literal. oldval = interpretRegion(e1, istate); UnionExp utmp = void; oldval = resolveSlice(oldval, &utmp); newval = changeArrayLiteralLength(pue, e.loc, cast(TypeArray)t, oldval, oldlen, newlen); if (newval == pue.exp()) newval = pue.copy(); e1 = assignToLvalue(e, e1, newval); if (exceptionOrCant(e1)) return; return; } if (!isBlockAssignment) { newval = ctfeCast(pue, e.loc, e.type, e.type, newval); if (exceptionOrCant(newval)) return; if (newval == pue.exp()) newval = pue.copy(); // Determine the return value if (goal == CTFEGoal.LValue) // https://issues.dlang.org/show_bug.cgi?id=14371 result = e1; else { result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval); if (result == pue.exp()) result = pue.copy(); } if (exceptionOrCant(result)) return; } if (exceptionOrCant(newval)) return; debug (LOGASSIGN) { printf("ASSIGN: %s=%s\n", e1.toChars(), newval.toChars()); showCtfeExpr(newval); } /* Block assignment or element-wise assignment. */ if (e1.op == EXP.slice || e1.op == EXP.vector || e1.op == EXP.arrayLiteral || e1.op == EXP.string_ || e1.op == EXP.null_ && e1.type.toBasetype().ty == Tarray) { // Note that slice assignments don't support things like ++, so // we don't need to remember 'returnValue'. result = interpretAssignToSlice(pue, e, e1, newval, isBlockAssignment); if (exceptionOrCant(result)) return; if (auto se = e.e1.isSliceExp()) { Expression e1x = interpretRegion(se.e1, istate, CTFEGoal.LValue); if (auto dve = e1x.isDotVarExp()) { auto ex = dve.e1; auto sle = ex.op == EXP.structLiteral ? ex.isStructLiteralExp() : ex.op == EXP.classReference ? ex.isClassReferenceExp().value : null; auto v = dve.var.isVarDeclaration(); if (!sle || !v) { error(e.loc, "CTFE internal error: dotvar slice assignment"); result = CTFEExp.cantexp; return; } stompOverlappedFields(sle, v); } } return; } assert(result); /* Assignment to a CTFE reference. */ if (Expression ex = assignToLvalue(e, e1, newval)) result = ex; return; } /* Set all sibling fields which overlap with v to VoidExp. */ private void stompOverlappedFields(StructLiteralExp sle, VarDeclaration v) { if (!v.overlapped) return; foreach (size_t i, v2; sle.sd.fields) { if (v is v2 || !v.isOverlappedWith(v2)) continue; auto e = (*sle.elements)[i]; if (e !is null && e.op != EXP.void_) (*sle.elements)[i] = voidInitLiteral(e.type, v).copy(); } } private Expression assignToLvalue(BinExp e, Expression e1, Expression newval) { //printf("assignToLvalue() e: %s e1: %s newval: %s\n", e.toChars(), e1.toChars(), newval.toChars()); VarDeclaration vd = null; Expression* payload = null; // dead-store to prevent spurious warning Expression oldval; if (auto ve = e1.isVarExp()) { vd = ve.var.isVarDeclaration(); oldval = getValue(vd); } else if (auto dve = e1.isDotVarExp()) { /* Assignment to member variable of the form: * e.v = newval */ auto ex = dve.e1; auto sle = ex.op == EXP.structLiteral ? ex.isStructLiteralExp() : ex.op == EXP.classReference ? ex.isClassReferenceExp().value : null; auto v = e1.isDotVarExp().var.isVarDeclaration(); if (!sle || !v) { error(e.loc, "CTFE internal error: dotvar assignment"); return CTFEExp.cantexp; } if (sle.ownedByCtfe != OwnedBy.ctfe) { error(e.loc, "cannot modify read-only constant `%s`", sle.toChars()); return CTFEExp.cantexp; } int fieldi = ex.op == EXP.structLiteral ? findFieldIndexByName(sle.sd, v) : ex.isClassReferenceExp().findFieldIndexByName(v); if (fieldi == -1) { error(e.loc, "CTFE internal error: cannot find field `%s` in `%s`", v.toChars(), ex.toChars()); return CTFEExp.cantexp; } assert(0 <= fieldi && fieldi < sle.elements.length); // If it's a union, set all other members of this union to void stompOverlappedFields(sle, v); payload = &(*sle.elements)[fieldi]; oldval = *payload; if (auto ival = newval.isIntegerExp()) { if (auto bf = v.isBitFieldDeclaration()) { sinteger_t value = ival.toInteger(); if (bf.type.isunsigned()) value &= (1L << bf.fieldWidth) - 1; // zero extra bits else { // sign extend extra bits value = value << (64 - bf.fieldWidth); value = value >> (64 - bf.fieldWidth); } ival.setInteger(value); } } } else if (auto ie = e1.isIndexExp()) { assert(ie.e1.type.toBasetype().ty != Taarray); Expression aggregate; uinteger_t indexToModify; if (!resolveIndexing(ie, istate, &aggregate, &indexToModify, true)) { return CTFEExp.cantexp; } size_t index = cast(size_t)indexToModify; if (auto existingSE = aggregate.isStringExp()) { if (existingSE.ownedByCtfe != OwnedBy.ctfe) { error(e.loc, "cannot modify read-only string literal `%s`", ie.e1.toChars()); return CTFEExp.cantexp; } existingSE.setCodeUnit(index, cast(dchar)newval.toInteger()); return null; } if (aggregate.op != EXP.arrayLiteral) { error(e.loc, "index assignment `%s` is not yet supported in CTFE ", e.toChars()); return CTFEExp.cantexp; } ArrayLiteralExp existingAE = aggregate.isArrayLiteralExp(); if (existingAE.ownedByCtfe != OwnedBy.ctfe) { error(e.loc, "cannot modify read-only constant `%s`", existingAE.toChars()); return CTFEExp.cantexp; } payload = &(*existingAE.elements)[index]; oldval = *payload; } else { error(e.loc, "`%s` cannot be evaluated at compile time", e.toChars()); return CTFEExp.cantexp; } Type t1b = e1.type.toBasetype(); bool wantCopy = t1b.baseElemOf().ty == Tstruct; if (auto ve = newval.isVectorExp()) { // Ensure ve is an array literal, and not a broadcast if (ve.e1.op == EXP.int64 || ve.e1.op == EXP.float64) // if broadcast { UnionExp ue = void; Expression ex = interpretVectorToArray(&ue, ve); ve.e1 = (ex == ue.exp()) ? ue.copy() : ex; } } if (newval.op == EXP.structLiteral && oldval) { assert(oldval.op == EXP.structLiteral || oldval.op == EXP.arrayLiteral || oldval.op == EXP.string_); newval = copyLiteral(newval).copy(); assignInPlace(oldval, newval); } else if (wantCopy && (e.op == EXP.assign || e.op == EXP.loweredAssignExp)) { // Currently postblit/destructor calls on static array are done // in the druntime internal functions so they don't appear in AST. // Therefore interpreter should handle them specially. assert(oldval); version (all) // todo: instead we can directly access to each elements of the slice { newval = resolveSlice(newval); if (CTFEExp.isCantExp(newval)) { error(e.loc, "CTFE internal error: assignment `%s`", e.toChars()); return CTFEExp.cantexp; } } assert(oldval.op == EXP.arrayLiteral); assert(newval.op == EXP.arrayLiteral); Expressions* oldelems = oldval.isArrayLiteralExp().elements; Expressions* newelems = newval.isArrayLiteralExp().elements; assert(oldelems.length == newelems.length); Type elemtype = oldval.type.nextOf(); foreach (i, ref oldelem; *oldelems) { Expression newelem = paintTypeOntoLiteral(elemtype, (*newelems)[i]); // https://issues.dlang.org/show_bug.cgi?id=9245 if (e.e2.isLvalue()) { if (Expression ex = evaluatePostblit(istate, newelem)) return ex; } // https://issues.dlang.org/show_bug.cgi?id=13661 if (Expression ex = evaluateDtor(istate, oldelem)) return ex; oldelem = newelem; } } else { // e1 has its own payload, so we have to create a new literal. if (wantCopy) newval = copyLiteral(newval).copy(); if (t1b.ty == Tsarray && e.op == EXP.construct && e.e2.isLvalue()) { // https://issues.dlang.org/show_bug.cgi?id=9245 if (Expression ex = evaluatePostblit(istate, newval)) return ex; } oldval = newval; } if (vd) setValue(vd, oldval); else *payload = oldval; // Blit assignment should return the newly created value. if (e.op == EXP.blit) return oldval; return null; } /************* * Deal with assignments of the form: * dest[] = newval * dest[low..upp] = newval * where newval has already been interpreted * * This could be a slice assignment or a block assignment, and * dest could be either an array literal, or a string. * * Returns EXP.cantExpression on failure. If there are no errors, * it returns aggregate[low..upp], except that as an optimisation, * if goal == CTFEGoal.Nothing, it will return NULL */ private Expression interpretAssignToSlice(UnionExp* pue, BinExp e, Expression e1, Expression newval, bool isBlockAssignment) { //printf("interpretAssignToSlice(e: %s e1: %s newval: %s\n", e.toChars(), e1.toChars(), newval.toChars()); dinteger_t lowerbound; dinteger_t upperbound; dinteger_t firstIndex; Expression aggregate; if (auto se = e1.isSliceExp()) { // ------------------------------ // aggregate[] = newval // aggregate[low..upp] = newval // ------------------------------ aggregate = interpretRegion(se.e1, istate); lowerbound = se.lwr ? se.lwr.toInteger() : 0; upperbound = se.upr ? se.upr.toInteger() : resolveArrayLength(aggregate); // Slice of a slice --> change the bounds if (auto oldse = aggregate.isSliceExp()) { aggregate = oldse.e1; firstIndex = lowerbound + oldse.lwr.toInteger(); } else firstIndex = lowerbound; } else { if (auto ale = e1.isArrayLiteralExp()) { lowerbound = 0; upperbound = ale.elements.length; } else if (auto se = e1.isStringExp()) { lowerbound = 0; upperbound = se.len; } else if (e1.op == EXP.null_) { lowerbound = 0; upperbound = 0; } else if (VectorExp ve = e1.isVectorExp()) { // ve is not handled but a proper error message is returned // this is to prevent https://issues.dlang.org/show_bug.cgi?id=20042 lowerbound = 0; upperbound = ve.dim; } else assert(0); aggregate = e1; firstIndex = lowerbound; } if (upperbound == lowerbound) return newval; // For slice assignment, we check that the lengths match. if (!isBlockAssignment && e1.type.ty != Tpointer) { const srclen = resolveArrayLength(newval); if (srclen != (upperbound - lowerbound)) { error(e.loc, "array length mismatch assigning `[0..%llu]` to `[%llu..%llu]`", ulong(srclen), ulong(lowerbound), ulong(upperbound)); return CTFEExp.cantexp; } } if (auto existingSE = aggregate.isStringExp()) { if (existingSE.ownedByCtfe != OwnedBy.ctfe) { error(e.loc, "cannot modify read-only string literal `%s`", existingSE.toChars()); return CTFEExp.cantexp; } if (auto se = newval.isSliceExp()) { auto aggr2 = se.e1; const srclower = se.lwr.toInteger(); const srcupper = se.upr.toInteger(); if (aggregate == aggr2 && lowerbound < srcupper && srclower < upperbound) { error(e.loc, "overlapping slice assignment `[%llu..%llu] = [%llu..%llu]`", ulong(lowerbound), ulong(upperbound), ulong(srclower), ulong(srcupper)); return CTFEExp.cantexp; } version (all) // todo: instead we can directly access to each elements of the slice { Expression orignewval = newval; newval = resolveSlice(newval); if (CTFEExp.isCantExp(newval)) { error(e.loc, "CTFE internal error: slice `%s`", orignewval.toChars()); return CTFEExp.cantexp; } } assert(newval.op != EXP.slice); } if (auto se = newval.isStringExp()) { sliceAssignStringFromString(existingSE, se, cast(size_t)firstIndex); return newval; } if (auto ale = newval.isArrayLiteralExp()) { /* Mixed slice: it was initialized as a string literal. * Now a slice of it is being set with an array literal. */ sliceAssignStringFromArrayLiteral(existingSE, ale, cast(size_t)firstIndex); return newval; } // String literal block slice assign const value = cast(dchar)newval.toInteger(); foreach (i; 0 .. upperbound - lowerbound) { existingSE.setCodeUnit(cast(size_t)(i + firstIndex), value); } if (goal == CTFEGoal.Nothing) return null; // avoid creating an unused literal auto retslice = ctfeEmplaceExp!SliceExp(e.loc, existingSE, ctfeEmplaceExp!IntegerExp(e.loc, firstIndex, Type.tsize_t), ctfeEmplaceExp!IntegerExp(e.loc, firstIndex + upperbound - lowerbound, Type.tsize_t)); retslice.type = e.type; return interpret(pue, retslice, istate); } if (auto existingAE = aggregate.isArrayLiteralExp()) { if (existingAE.ownedByCtfe != OwnedBy.ctfe) { error(e.loc, "cannot modify read-only constant `%s`", existingAE.toChars()); return CTFEExp.cantexp; } if (newval.op == EXP.slice && !isBlockAssignment) { auto se = newval.isSliceExp(); auto aggr2 = se.e1; const srclower = se.lwr.toInteger(); const srcupper = se.upr.toInteger(); const wantCopy = (newval.type.toBasetype().nextOf().baseElemOf().ty == Tstruct); //printf("oldval = %p %s[%d..%u]\nnewval = %p %s[%llu..%llu] wantCopy = %d\n", // aggregate, aggregate.toChars(), lowerbound, upperbound, // aggr2, aggr2.toChars(), srclower, srcupper, wantCopy); if (wantCopy) { // Currently overlapping for struct array is allowed. // The order of elements processing depends on the overlapping. // https://issues.dlang.org/show_bug.cgi?id=14024 assert(aggr2.op == EXP.arrayLiteral); Expressions* oldelems = existingAE.elements; Expressions* newelems = aggr2.isArrayLiteralExp().elements; Type elemtype = aggregate.type.nextOf(); bool needsPostblit = e.e2.isLvalue(); if (aggregate == aggr2 && srclower < lowerbound && lowerbound < srcupper) { // reverse order for (auto i = upperbound - lowerbound; 0 < i--;) { Expression oldelem = (*oldelems)[cast(size_t)(i + firstIndex)]; Expression newelem = (*newelems)[cast(size_t)(i + srclower)]; newelem = copyLiteral(newelem).copy(); newelem.type = elemtype; if (needsPostblit) { if (Expression x = evaluatePostblit(istate, newelem)) return x; } if (Expression x = evaluateDtor(istate, oldelem)) return x; (*oldelems)[cast(size_t)(lowerbound + i)] = newelem; } } else { // normal order for (auto i = 0; i < upperbound - lowerbound; i++) { Expression oldelem = (*oldelems)[cast(size_t)(i + firstIndex)]; Expression newelem = (*newelems)[cast(size_t)(i + srclower)]; newelem = copyLiteral(newelem).copy(); newelem.type = elemtype; if (needsPostblit) { if (Expression x = evaluatePostblit(istate, newelem)) return x; } if (Expression x = evaluateDtor(istate, oldelem)) return x; (*oldelems)[cast(size_t)(lowerbound + i)] = newelem; } } //assert(0); return newval; // oldval? } if (aggregate == aggr2 && lowerbound < srcupper && srclower < upperbound) { error(e.loc, "overlapping slice assignment `[%llu..%llu] = [%llu..%llu]`", ulong(lowerbound), ulong(upperbound), ulong(srclower), ulong(srcupper)); return CTFEExp.cantexp; } version (all) // todo: instead we can directly access to each elements of the slice { Expression orignewval = newval; newval = resolveSlice(newval); if (CTFEExp.isCantExp(newval)) { error(e.loc, "CTFE internal error: slice `%s`", orignewval.toChars()); return CTFEExp.cantexp; } } // no overlapping //length? assert(newval.op != EXP.slice); } if (newval.op == EXP.string_ && !isBlockAssignment) { /* Mixed slice: it was initialized as an array literal of chars/integers. * Now a slice of it is being set with a string. */ sliceAssignArrayLiteralFromString(existingAE, newval.isStringExp(), cast(size_t)firstIndex); return newval; } if (newval.op == EXP.arrayLiteral && !isBlockAssignment) { Expressions* oldelems = existingAE.elements; Expressions* newelems = newval.isArrayLiteralExp().elements; Type elemtype = existingAE.type.nextOf(); bool needsPostblit = e.op != EXP.blit && e.e2.isLvalue(); foreach (j, newelem; *newelems) { newelem = paintTypeOntoLiteral(elemtype, newelem); if (needsPostblit) { Expression x = evaluatePostblit(istate, newelem); if (exceptionOrCantInterpret(x)) return x; } (*oldelems)[cast(size_t)(j + firstIndex)] = newelem; } return newval; } /* Block assignment, initialization of static arrays * x[] = newval * x may be a multidimensional static array. (Note that this * only happens with array literals, never with strings). */ struct RecursiveBlock { InterState* istate; Expression newval; bool refCopy; bool needsPostblit; bool needsDtor; Expression assignTo(ArrayLiteralExp ae) { return assignTo(ae, 0, ae.elements.length); } Expression assignTo(ArrayLiteralExp ae, size_t lwr, size_t upr) { Expressions* w = ae.elements; assert(ae.type.ty == Tsarray || ae.type.ty == Tarray || ae.type.ty == Tpointer); bool directblk = (cast(TypeNext)ae.type).next.equivalent(newval.type); for (size_t k = lwr; k < upr; k++) { if (!directblk && (*w)[k].op == EXP.arrayLiteral) { // Multidimensional array block assign if (Expression ex = assignTo((*w)[k].isArrayLiteralExp())) return ex; } else if (refCopy) { (*w)[k] = newval; } else if (!needsPostblit && !needsDtor) { assignInPlace((*w)[k], newval); } else { Expression oldelem = (*w)[k]; Expression tmpelem = needsDtor ? copyLiteral(oldelem).copy() : null; assignInPlace(oldelem, newval); if (needsPostblit) { if (Expression ex = evaluatePostblit(istate, oldelem)) return ex; } if (needsDtor) { // https://issues.dlang.org/show_bug.cgi?id=14860 if (Expression ex = evaluateDtor(istate, tmpelem)) return ex; } } } return null; } } Type tn = newval.type.toBasetype(); bool wantRef = (tn.ty == Tarray || isAssocArray(tn) || tn.ty == Tclass); bool cow = newval.op != EXP.structLiteral && newval.op != EXP.arrayLiteral && newval.op != EXP.string_; Type tb = tn.baseElemOf(); StructDeclaration sd = (tb.ty == Tstruct ? (cast(TypeStruct)tb).sym : null); RecursiveBlock rb; rb.istate = istate; rb.newval = newval; rb.refCopy = wantRef || cow; rb.needsPostblit = sd && sd.postblit && e.op != EXP.blit && e.e2.isLvalue(); rb.needsDtor = sd && sd.dtor && (e.op == EXP.assign || e.op == EXP.loweredAssignExp); if (Expression ex = rb.assignTo(existingAE, cast(size_t)lowerbound, cast(size_t)upperbound)) return ex; if (goal == CTFEGoal.Nothing) return null; // avoid creating an unused literal auto retslice = ctfeEmplaceExp!SliceExp(e.loc, existingAE, ctfeEmplaceExp!IntegerExp(e.loc, firstIndex, Type.tsize_t), ctfeEmplaceExp!IntegerExp(e.loc, firstIndex + upperbound - lowerbound, Type.tsize_t)); retslice.type = e.type; return interpret(pue, retslice, istate); } error(e.loc, "slice operation `%s = %s` cannot be evaluated at compile time", e1.toChars(), newval.toChars()); return CTFEExp.cantexp; } override void visit(AssignExp e) { interpretAssignCommon(e, null); } override void visit(BinAssignExp e) { switch (e.op) { case EXP.addAssign: interpretAssignCommon(e, &Add); return; case EXP.minAssign: interpretAssignCommon(e, &Min); return; case EXP.concatenateAssign: case EXP.concatenateElemAssign: case EXP.concatenateDcharAssign: interpretAssignCommon(e, &ctfeCat); return; case EXP.mulAssign: interpretAssignCommon(e, &Mul); return; case EXP.divAssign: interpretAssignCommon(e, &Div); return; case EXP.modAssign: interpretAssignCommon(e, &Mod); return; case EXP.leftShiftAssign: interpretAssignCommon(e, &Shl); return; case EXP.rightShiftAssign: interpretAssignCommon(e, &Shr); return; case EXP.unsignedRightShiftAssign: interpretAssignCommon(e, &Ushr); return; case EXP.andAssign: interpretAssignCommon(e, &And); return; case EXP.orAssign: interpretAssignCommon(e, &Or); return; case EXP.xorAssign: interpretAssignCommon(e, &Xor); return; case EXP.powAssign: interpretAssignCommon(e, &Pow); return; default: assert(0); } } override void visit(PostExp e) { debug (LOG) { printf("%s PostExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (e.op == EXP.plusPlus) interpretAssignCommon(e, &Add, 1); else interpretAssignCommon(e, &Min, 1); debug (LOG) { if (CTFEExp.isCantExp(result)) printf("PostExp::interpret() CANT\n"); } } /* Return 1 if e is a p1 > p2 or p1 >= p2 pointer comparison; * -1 if e is a p1 < p2 or p1 <= p2 pointer comparison; * 0 otherwise */ static int isPointerCmpExp(Expression e, Expression* p1, Expression* p2) { int ret = 1; while (e.op == EXP.not) { ret *= -1; e = e.isNotExp().e1; } switch (e.op) { case EXP.lessThan: case EXP.lessOrEqual: ret *= -1; goto case; /+ fall through +/ case EXP.greaterThan: case EXP.greaterOrEqual: *p1 = e.isBinExp().e1; *p2 = e.isBinExp().e2; if (!(isPointer((*p1).type) && isPointer((*p2).type))) ret = 0; break; default: ret = 0; break; } return ret; } /** If this is a four pointer relation, evaluate it, else return NULL. * * This is an expression of the form (p1 > q1 && p2 < q2) or (p1 < q1 || p2 > q2) * where p1, p2 are expressions yielding pointers to memory block p, * and q1, q2 are expressions yielding pointers to memory block q. * This expression is valid even if p and q are independent memory * blocks and are therefore not normally comparable; the && form returns true * if [p1..p2] lies inside [q1..q2], and false otherwise; the || form returns * true if [p1..p2] lies outside [q1..q2], and false otherwise. * * Within the expression, any ordering of p1, p2, q1, q2 is permissible; * the comparison operators can be any of >, <, <=, >=, provided that * both directions (p > q and p < q) are checked. Additionally the * relational sub-expressions can be negated, eg * (!(q1 < p1) && p2 <= q2) is valid. */ private void interpretFourPointerRelation(UnionExp* pue, BinExp e) { assert(e.op == EXP.andAnd || e.op == EXP.orOr); /* It can only be an isInside expression, if both e1 and e2 are * directional pointer comparisons. * Note that this check can be made statically; it does not depends on * any runtime values. This allows a JIT implementation to compile a * special AndAndPossiblyInside, keeping the normal AndAnd case efficient. */ // Save the pointer expressions and the comparison directions, // so we can use them later. Expression p1 = null; Expression p2 = null; Expression p3 = null; Expression p4 = null; int dir1 = isPointerCmpExp(e.e1, &p1, &p2); int dir2 = isPointerCmpExp(e.e2, &p3, &p4); if (dir1 == 0 || dir2 == 0) { result = null; return; } //printf("FourPointerRelation %s\n", toChars()); UnionExp ue1 = void; UnionExp ue2 = void; UnionExp ue3 = void; UnionExp ue4 = void; // Evaluate the first two pointers p1 = interpret(&ue1, p1, istate); if (exceptionOrCant(p1)) return; p2 = interpret(&ue2, p2, istate); if (exceptionOrCant(p2)) return; dinteger_t ofs1, ofs2; Expression agg1 = getAggregateFromPointer(p1, &ofs1); Expression agg2 = getAggregateFromPointer(p2, &ofs2); if (!pointToSameMemoryBlock(agg1, agg2) && agg1.op != EXP.null_ && agg2.op != EXP.null_) { // Here it is either CANT_INTERPRET, // or an IsInside comparison returning false. p3 = interpret(&ue3, p3, istate); if (CTFEExp.isCantExp(p3)) return; // Note that it is NOT legal for it to throw an exception! Expression except = null; if (exceptionOrCantInterpret(p3)) except = p3; else { p4 = interpret(&ue4, p4, istate); if (CTFEExp.isCantExp(p4)) { result = p4; return; } if (exceptionOrCantInterpret(p4)) except = p4; } if (except) { error(e.loc, "comparison `%s` of pointers to unrelated memory blocks remains indeterminate at compile time because exception `%s` was thrown while evaluating `%s`", e.e1.toChars(), except.toChars(), e.e2.toChars()); result = CTFEExp.cantexp; return; } dinteger_t ofs3, ofs4; Expression agg3 = getAggregateFromPointer(p3, &ofs3); Expression agg4 = getAggregateFromPointer(p4, &ofs4); // The valid cases are: // p1 > p2 && p3 > p4 (same direction, also for < && <) // p1 > p2 && p3 < p4 (different direction, also < && >) // Changing any > into >= doesn't affect the result if ((dir1 == dir2 && pointToSameMemoryBlock(agg1, agg4) && pointToSameMemoryBlock(agg2, agg3)) || (dir1 != dir2 && pointToSameMemoryBlock(agg1, agg3) && pointToSameMemoryBlock(agg2, agg4))) { // it's a legal two-sided comparison emplaceExp!(IntegerExp)(pue, e.loc, (e.op == EXP.andAnd) ? 0 : 1, e.type); result = pue.exp(); return; } // It's an invalid four-pointer comparison. Either the second // comparison is in the same direction as the first, or else // more than two memory blocks are involved (either two independent // invalid comparisons are present, or else agg3 == agg4). error(e.loc, "comparison `%s` of pointers to unrelated memory blocks is indeterminate at compile time, even when combined with `%s`.", e.e1.toChars(), e.e2.toChars()); result = CTFEExp.cantexp; return; } // The first pointer expression didn't need special treatment, so we // we need to interpret the entire expression exactly as a normal && or ||. // This is easy because we haven't evaluated e2 at all yet, and we already // know it will return a bool. // But we mustn't evaluate the pointer expressions in e1 again, in case // they have side-effects. bool nott = false; Expression ex = e.e1; while (1) { if (auto ne = ex.isNotExp()) { nott = !nott; ex = ne.e1; } else break; } /** Negate relational operator, eg >= becomes < * Params: * op = comparison operator to negate * Returns: * negate operator */ static EXP negateRelation(EXP op) pure { switch (op) { case EXP.greaterOrEqual: op = EXP.lessThan; break; case EXP.greaterThan: op = EXP.lessOrEqual; break; case EXP.lessOrEqual: op = EXP.greaterThan; break; case EXP.lessThan: op = EXP.greaterOrEqual; break; default: assert(0); } return op; } const EXP cmpop = nott ? negateRelation(ex.op) : ex.op; const cmp = comparePointers(cmpop, agg1, ofs1, agg2, ofs2); // We already know this is a valid comparison. assert(cmp >= 0); if (e.op == EXP.andAnd && cmp == 1 || e.op == EXP.orOr && cmp == 0) { result = interpret(pue, e.e2, istate); return; } emplaceExp!(IntegerExp)(pue, e.loc, (e.op == EXP.andAnd) ? 0 : 1, e.type); result = pue.exp(); } override void visit(LogicalExp e) { debug (LOG) { printf("%s LogicalExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } // Check for an insidePointer expression, evaluate it if so interpretFourPointerRelation(pue, e); if (result) return; UnionExp ue1 = void; result = interpret(&ue1, e.e1, istate); if (exceptionOrCant(result)) return; bool res; const andand = e.op == EXP.andAnd; if (andand ? result.toBool().hasValue(false) : isTrueBool(result)) res = !andand; else if (andand ? isTrueBool(result) : result.toBool().hasValue(false)) { UnionExp ue2 = void; result = interpret(&ue2, e.e2, istate); if (exceptionOrCant(result)) return; if (result.op == EXP.voidExpression) { assert(e.type.ty == Tvoid); result = null; return; } if (result.toBool().hasValue(false)) res = false; else if (isTrueBool(result)) res = true; else { error(e.loc, "`%s` does not evaluate to a `bool`", result.toChars()); result = CTFEExp.cantexp; return; } } else { error(e.loc, "`%s` cannot be interpreted as a `bool`", result.toChars()); result = CTFEExp.cantexp; return; } incUsageCtfe(istate, e.e2.loc); if (goal != CTFEGoal.Nothing) { if (e.type.equals(Type.tbool)) result = IntegerExp.createBool(res); else { emplaceExp!(IntegerExp)(pue, e.loc, res, e.type); result = pue.exp(); } } } // Print a stack trace, starting from callingExp which called fd. // To shorten the stack trace, try to detect recursion. private void showCtfeBackTrace(CallExp callingExp, FuncDeclaration fd) { if (ctfeGlobals.stackTraceCallsToSuppress > 0) { --ctfeGlobals.stackTraceCallsToSuppress; return; } errorSupplemental(callingExp.loc, "called from here: `%s`", callingExp.toChars()); // Quit if it's not worth trying to compress the stack trace if (ctfeGlobals.callDepth < 6 || global.params.v.verbose) return; // Recursion happens if the current function already exists in the call stack. int numToSuppress = 0; int recurseCount = 0; int depthSoFar = 0; InterState* lastRecurse = istate; for (InterState* cur = istate; cur; cur = cur.caller) { if (cur.fd == fd) { ++recurseCount; numToSuppress = depthSoFar; lastRecurse = cur; } ++depthSoFar; } // We need at least three calls to the same function, to make compression worthwhile if (recurseCount < 2) return; // We found a useful recursion. Print all the calls involved in the recursion errorSupplemental(fd.loc, "%d recursive calls to function `%s`", recurseCount, fd.toChars()); for (InterState* cur = istate; cur.fd != fd; cur = cur.caller) { errorSupplemental(cur.fd.loc, "recursively called from function `%s`", cur.fd.toChars()); } // We probably didn't enter the recursion in this function. // Go deeper to find the real beginning. InterState* cur = istate; while (lastRecurse.caller && cur.fd == lastRecurse.caller.fd) { cur = cur.caller; lastRecurse = lastRecurse.caller; ++numToSuppress; } ctfeGlobals.stackTraceCallsToSuppress = numToSuppress; } override void visit(CallExp e) { debug (LOG) { printf("%s CallExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression pthis = null; FuncDeclaration fd = null; Expression ecall = interpretRegion(e.e1, istate); if (exceptionOrCant(ecall)) return; if (auto dve = ecall.isDotVarExp()) { // Calling a member function pthis = dve.e1; fd = dve.var.isFuncDeclaration(); assert(fd); if (auto dte = pthis.isDotTypeExp()) pthis = dte.e1; } else if (auto ve = ecall.isVarExp()) { fd = ve.var.isFuncDeclaration(); assert(fd); // If `_d_HookTraceImpl` is found, resolve the underlying hook and replace `e` and `fd` with it. removeHookTraceImpl(e, fd); if (fd.ident == Id.__ArrayPostblit || fd.ident == Id.__ArrayDtor) { assert(e.arguments.length == 1); Expression ea = (*e.arguments)[0]; // printf("1 ea = %s %s\n", ea.type.toChars(), ea.toChars()); if (auto se = ea.isSliceExp()) ea = se.e1; if (auto ce = ea.isCastExp()) ea = ce.e1; // printf("2 ea = %s, %s %s\n", ea.type.toChars(), EXPtoString(ea.op).ptr, ea.toChars()); if (ea.op == EXP.variable || ea.op == EXP.symbolOffset) result = getVarExp(e.loc, istate, (cast(SymbolExp)ea).var, CTFEGoal.RValue); else if (auto ae = ea.isAddrExp()) result = interpretRegion(ae.e1, istate); // https://issues.dlang.org/show_bug.cgi?id=18871 // https://issues.dlang.org/show_bug.cgi?id=18819 else if (auto ale = ea.isArrayLiteralExp()) result = interpretRegion(ale, istate); else assert(0); if (CTFEExp.isCantExp(result)) return; if (fd.ident == Id.__ArrayPostblit) result = evaluatePostblit(istate, result); else result = evaluateDtor(istate, result); if (!result) result = CTFEExp.voidexp; return; } else if (isArrayConstruction(fd.ident)) { // In expressionsem.d, `T[x] ea = eb;` was lowered to: // `_d_array{,set}ctor(ea[], eb[]);`. // The following code will rewrite it back to `ea = eb` and // then interpret that expression. if (fd.ident == Id._d_arrayctor) assert(e.arguments.length == 3); else assert(e.arguments.length == 2); Expression ea = (*e.arguments)[0]; if (ea.isCastExp) ea = ea.isCastExp.e1; Expression eb = (*e.arguments)[1]; if (eb.isCastExp() && fd.ident == Id._d_arrayctor) eb = eb.isCastExp.e1; ConstructExp ce = new ConstructExp(e.loc, ea, eb); ce.type = ea.type; ce.type = ea.type; result = interpret(ce, istate); return; } else if (fd.ident == Id._d_arrayappendT || fd.ident == Id._d_arrayappendTTrace) { // In expressionsem.d `ea ~= eb` was lowered to `_d_arrayappendT{,Trace}({file, line, funcname}, ea, eb);`. // The following code will rewrite it back to `ea ~= eb` and then interpret that expression. Expression lhs, rhs; if (fd.ident == Id._d_arrayappendT) { assert(e.arguments.length == 2); lhs = (*e.arguments)[0]; rhs = (*e.arguments)[1]; } else { assert(e.arguments.length == 5); lhs = (*e.arguments)[3]; rhs = (*e.arguments)[4]; } auto cae = new CatAssignExp(e.loc, lhs, rhs); cae.type = e.type; result = interpretRegion(cae, istate, CTFEGoal.LValue); return; } else if (fd.ident == Id._d_arrayappendcTX) assert(0, "CTFE cannot interpret _d_arrayappendcTX!"); } else if (auto soe = ecall.isSymOffExp()) { fd = soe.var.isFuncDeclaration(); assert(fd && soe.offset == 0); } else if (auto de = ecall.isDelegateExp()) { // Calling a delegate fd = de.func; pthis = de.e1; // Special handling for: &nestedfunc --> DelegateExp(VarExp(nestedfunc), nestedfunc) if (auto ve = pthis.isVarExp()) if (ve.var == fd) pthis = null; // context is not necessary for CTFE } else if (auto fe = ecall.isFuncExp()) { // Calling a delegate literal fd = fe.fd; } else { // delegate.funcptr() // others error(e.loc, "cannot call `%s` at compile time", e.toChars()); result = CTFEExp.cantexp; return; } if (!fd) { error(e.loc, "CTFE internal error: cannot evaluate `%s` at compile time", e.toChars()); result = CTFEExp.cantexp; return; } if (pthis) { // Member function call // Currently this is satisfied because closure is not yet supported. assert(!fd.isNested() || fd.needThis()); if (pthis.op == EXP.typeid_) { error(pthis.loc, "static variable `%s` cannot be read at compile time", pthis.toChars()); result = CTFEExp.cantexp; return; } assert(pthis); if (pthis.op == EXP.null_) { assert(pthis.type.toBasetype().ty == Tclass); error(e.loc, "function call through null class reference `%s`", pthis.toChars()); result = CTFEExp.cantexp; return; } assert(pthis.op == EXP.structLiteral || pthis.op == EXP.classReference || pthis.op == EXP.type); if (fd.isVirtual() && !e.directcall) { // Make a virtual function call. // Get the function from the vtable of the original class ClassDeclaration cd = pthis.isClassReferenceExp().originalClass(); // We can't just use the vtable index to look it up, because // vtables for interfaces don't get populated until the glue layer. fd = cd.findFunc(fd.ident, fd.type.isTypeFunction()); assert(fd); } } if (fd && fd.semanticRun >= PASS.semantic3done && fd.hasSemantic3Errors()) { error(e.loc, "CTFE failed because of previous errors in `%s`", fd.toChars()); result = CTFEExp.cantexp; return; } // Check for built-in functions result = evaluateIfBuiltin(pue, istate, e.loc, fd, e.arguments, pthis); if (result) return; if (!fd.fbody) { error(e.loc, "`%s` cannot be interpreted at compile time, because it has no available source code", fd.toChars()); result = CTFEExp.showcontext; return; } result = interpretFunction(pue, fd, istate, e.arguments, pthis); if (result.op == EXP.voidExpression) return; if (!exceptionOrCantInterpret(result)) { if (goal != CTFEGoal.LValue) // Peel off CTFE reference if it's unnecessary { if (result == pue.exp()) result = pue.copy(); result = interpret(pue, result, istate); } } if (!exceptionOrCantInterpret(result)) { result = paintTypeOntoLiteral(pue, e.type, result); result.loc = e.loc; } else if (CTFEExp.isCantExp(result) && !global.gag) showCtfeBackTrace(e, fd); // Print a stack trace. } override void visit(CommaExp e) { /**************************************** * Find the first non-comma expression. * Params: * e = Expressions connected by commas * Returns: * left-most non-comma expression */ static inout(Expression) firstComma(inout Expression e) { Expression ex = cast()e; while (ex.op == EXP.comma) ex = (cast(CommaExp)ex).e1; return cast(inout)ex; } debug (LOG) { printf("%s CommaExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } bool isNewThrowableHook() { auto de = e.e1.isDeclarationExp(); if (de is null) return false; auto vd = de.declaration.isVarDeclaration(); if (vd is null) return false; auto ei = vd._init.isExpInitializer(); if (ei is null) return false; auto ce = ei.exp.isConstructExp(); if (ce is null) return false; return isRuntimeHook(ce.e2, Id._d_newThrowable) !is null; } if (auto ce = isRuntimeHook(e.e1, Id._d_arrayappendcTX)) { // In expressionsem.d `arr ~= elem` was lowered to // `_d_arrayappendcTX(arr, elem), arr[arr.length - 1] = elem, elem;`. // The following code will rewrite it back to `arr ~= elem` // and then interpret that expression. assert(ce.arguments.length == 2); auto arr = (*ce.arguments)[0]; auto elem = e.e2.isConstructExp().e2; assert(elem); auto cae = new CatAssignExp(e.loc, arr, elem); cae.type = arr.type; result = interpret(cae, istate); return; } else if (isNewThrowableHook()) { // In expressionsem.d `throw new Exception(args)` was lowered to // `throw (tmp = _d_newThrowable!Exception(), tmp.ctor(args), tmp)`. // The following code will rewrite it back to `throw new Exception(args)` // and then interpret this expression instead. auto ce = e.e2.isCallExp(); assert(ce); auto ne = new NewExp(e.loc, null, e.type, ce.arguments); ne.type = e.e1.type; result = interpret(ne, istate); return; } // If it creates a variable, and there's no context for // the variable to be created in, we need to create one now. InterState istateComma; if (!istate && firstComma(e.e1).op == EXP.declaration) { ctfeGlobals.stack.startFrame(null); istate = &istateComma; } void endTempStackFrame() { // If we created a temporary stack frame, end it now. if (istate == &istateComma) ctfeGlobals.stack.endFrame(); } result = CTFEExp.cantexp; // If the comma returns a temporary variable, it needs to be an lvalue // (this is particularly important for struct constructors) if (e.e1.op == EXP.declaration && e.e2.op == EXP.variable && e.e1.isDeclarationExp().declaration == e.e2.isVarExp().var && e.e2.isVarExp().var.storage_class & STC.ctfe) { VarExp ve = e.e2.isVarExp(); VarDeclaration v = ve.var.isVarDeclaration(); ctfeGlobals.stack.push(v); if (!v._init && !getValue(v)) { setValue(v, copyLiteral(v.type.defaultInitLiteral(e.loc)).copy()); } if (!getValue(v)) { Expression newval = v._init.initializerToExpression(); // Bug 4027. Copy constructors are a weird case where the // initializer is a void function (the variable is modified // through a reference parameter instead). newval = interpretRegion(newval, istate); if (exceptionOrCant(newval)) return endTempStackFrame(); if (newval.op != EXP.voidExpression) { // v isn't necessarily null. setValueWithoutChecking(v, copyLiteral(newval).copy()); } } } else { UnionExp ue = void; auto e1 = interpret(&ue, e.e1, istate, CTFEGoal.Nothing); if (exceptionOrCant(e1)) return endTempStackFrame(); } result = interpret(pue, e.e2, istate, goal); return endTempStackFrame(); } override void visit(CondExp e) { debug (LOG) { printf("%s CondExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } UnionExp uecond = void; Expression econd; econd = interpret(&uecond, e.econd, istate); if (exceptionOrCant(econd)) return; if (isPointer(e.econd.type)) { if (econd.op != EXP.null_) { econd = IntegerExp.createBool(true); } } if (isTrueBool(econd)) { result = interpret(pue, e.e1, istate, goal); incUsageCtfe(istate, e.e1.loc); } else if (econd.toBool().hasValue(false)) { result = interpret(pue, e.e2, istate, goal); incUsageCtfe(istate, e.e2.loc); } else { error(e.loc, "`%s` does not evaluate to boolean result at compile time", e.econd.toChars()); result = CTFEExp.cantexp; } } override void visit(ArrayLengthExp e) { debug (LOG) { printf("%s ArrayLengthExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } UnionExp ue1; Expression e1 = interpret(&ue1, e.e1, istate); assert(e1); if (exceptionOrCant(e1)) return; if (e1.op != EXP.string_ && e1.op != EXP.arrayLiteral && e1.op != EXP.slice && e1.op != EXP.null_) { error(e.loc, "`%s` cannot be evaluated at compile time", e.toChars()); result = CTFEExp.cantexp; return; } emplaceExp!(IntegerExp)(pue, e.loc, resolveArrayLength(e1), e.type); result = pue.exp(); } /** * Interpret the vector expression as an array literal. * Params: * pue = non-null pointer to temporary storage that can be used to store the return value * e = Expression to interpret * Returns: * resulting array literal or 'e' if unable to interpret */ static Expression interpretVectorToArray(UnionExp* pue, VectorExp e) { if (auto ale = e.e1.isArrayLiteralExp()) return ale; // it's already an array literal if (e.e1.op == EXP.int64 || e.e1.op == EXP.float64) { // Convert literal __vector(int) -> __vector([array]) auto elements = new Expressions(e.dim); foreach (ref element; *elements) element = copyLiteral(e.e1).copy(); auto type = (e.type.ty == Tvector) ? e.type.isTypeVector().basetype : e.type.isTypeSArray(); assert(type); emplaceExp!(ArrayLiteralExp)(pue, e.loc, type, elements); auto ale = pue.exp().isArrayLiteralExp(); ale.ownedByCtfe = OwnedBy.ctfe; return ale; } return e; } override void visit(VectorExp e) { debug (LOG) { printf("%s VectorExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted all the elements { result = e; return; } Expression e1 = interpret(pue, e.e1, istate); assert(e1); if (exceptionOrCant(e1)) return; if (e1.op != EXP.arrayLiteral && e1.op != EXP.int64 && e1.op != EXP.float64) { error(e.loc, "`%s` cannot be evaluated at compile time", e.toChars()); result = CTFEExp.cantexp; return; } if (e1 == pue.exp()) e1 = pue.copy(); emplaceExp!(VectorExp)(pue, e.loc, e1, e.to); auto ve = pue.exp().isVectorExp(); ve.type = e.type; ve.dim = e.dim; ve.ownedByCtfe = OwnedBy.ctfe; result = ve; } override void visit(VectorArrayExp e) { debug (LOG) { printf("%s VectorArrayExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression e1 = interpret(pue, e.e1, istate); assert(e1); if (exceptionOrCant(e1)) return; if (auto ve = e1.isVectorExp()) { result = interpretVectorToArray(pue, ve); if (result.op != EXP.vector) return; } error(e.loc, "`%s` cannot be evaluated at compile time", e.toChars()); result = CTFEExp.cantexp; } override void visit(DelegatePtrExp e) { debug (LOG) { printf("%s DelegatePtrExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression e1 = interpret(pue, e.e1, istate); assert(e1); if (exceptionOrCant(e1)) return; error(e.loc, "`%s` cannot be evaluated at compile time", e.toChars()); result = CTFEExp.cantexp; } override void visit(DelegateFuncptrExp e) { debug (LOG) { printf("%s DelegateFuncptrExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression e1 = interpret(pue, e.e1, istate); assert(e1); if (exceptionOrCant(e1)) return; error(e.loc, "`%s` cannot be evaluated at compile time", e.toChars()); result = CTFEExp.cantexp; } static bool resolveIndexing(IndexExp e, InterState* istate, Expression* pagg, uinteger_t* pidx, bool modify) { assert(e.e1.type.toBasetype().ty != Taarray); if (e.e1.type.toBasetype().ty == Tpointer) { // Indexing a pointer. Note that there is no $ in this case. Expression e1 = interpretRegion(e.e1, istate); if (exceptionOrCantInterpret(e1)) return false; Expression e2 = interpretRegion(e.e2, istate); if (exceptionOrCantInterpret(e2)) return false; sinteger_t indx = e2.toInteger(); dinteger_t ofs; Expression agg = getAggregateFromPointer(e1, &ofs); if (agg.op == EXP.null_) { error(e.loc, "cannot index through null pointer `%s`", e.e1.toChars()); return false; } if (agg.op == EXP.int64) { error(e.loc, "cannot index through invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars()); return false; } // Pointer to a non-array variable if (agg.op == EXP.symbolOffset) { error(e.loc, "mutable variable `%s` cannot be %s at compile time, even through a pointer", cast(char*)(modify ? "modified" : "read"), agg.isSymOffExp().var.toChars()); return false; } if (agg.op == EXP.arrayLiteral || agg.op == EXP.string_) { dinteger_t len = resolveArrayLength(agg); if (ofs + indx >= len) { error(e.loc, "pointer index `[%lld]` exceeds allocated memory block `[0..%lld]`", ofs + indx, len); return false; } } else { if (ofs + indx != 0) { error(e.loc, "pointer index `[%lld]` lies outside memory block `[0..1]`", ofs + indx); return false; } } *pagg = agg; *pidx = ofs + indx; return true; } Expression e1 = interpretRegion(e.e1, istate); if (exceptionOrCantInterpret(e1)) return false; if (e1.op == EXP.null_) { error(e.loc, "cannot index null array `%s`", e.e1.toChars()); return false; } if (auto ve = e1.isVectorExp()) { UnionExp ue = void; e1 = interpretVectorToArray(&ue, ve); e1 = (e1 == ue.exp()) ? ue.copy() : e1; } // Set the $ variable, and find the array literal to modify dinteger_t len; if (e1.op == EXP.variable && e1.type.toBasetype().ty == Tsarray) len = e1.type.toBasetype().isTypeSArray().dim.toInteger(); else { if (e1.op != EXP.arrayLiteral && e1.op != EXP.string_ && e1.op != EXP.slice && e1.op != EXP.vector) { error(e.loc, "cannot determine length of `%s` at compile time", e.e1.toChars()); return false; } len = resolveArrayLength(e1); } if (e.lengthVar) { Expression dollarExp = ctfeEmplaceExp!IntegerExp(e.loc, len, Type.tsize_t); ctfeGlobals.stack.push(e.lengthVar); setValue(e.lengthVar, dollarExp); } Expression e2 = interpretRegion(e.e2, istate); if (e.lengthVar) ctfeGlobals.stack.pop(e.lengthVar); // $ is defined only inside [] if (exceptionOrCantInterpret(e2)) return false; if (e2.op != EXP.int64) { error(e.loc, "CTFE internal error: non-integral index `[%s]`", e.e2.toChars()); return false; } if (auto se = e1.isSliceExp()) { // Simplify index of slice: agg[lwr..upr][indx] --> agg[indx'] uinteger_t index = e2.toInteger(); uinteger_t ilwr = se.lwr.toInteger(); uinteger_t iupr = se.upr.toInteger(); if (index > iupr - ilwr) { error(e.loc, "index %llu exceeds array length %llu", index, iupr - ilwr); return false; } *pagg = e1.isSliceExp().e1; *pidx = index + ilwr; } else { *pagg = e1; *pidx = e2.toInteger(); if (len <= *pidx) { error(e.loc, "array index %lld is out of bounds `[0..%lld]`", *pidx, len); return false; } } return true; } override void visit(IndexExp e) { debug (LOG) { printf("%s IndexExp::interpret() %s, goal = %d\n", e.loc.toChars(), e.toChars(), goal); } if (e.e1.type.toBasetype().ty == Tpointer) { Expression agg; uinteger_t indexToAccess; if (!resolveIndexing(e, istate, &agg, &indexToAccess, false)) { result = CTFEExp.cantexp; return; } if (agg.op == EXP.arrayLiteral || agg.op == EXP.string_) { if (goal == CTFEGoal.LValue) { // if we need a reference, IndexExp shouldn't be interpreting // the expression to a value, it should stay as a reference emplaceExp!(IndexExp)(pue, e.loc, agg, ctfeEmplaceExp!IntegerExp(e.e2.loc, indexToAccess, e.e2.type)); result = pue.exp(); result.type = e.type; return; } result = ctfeIndex(pue, e.loc, e.type, agg, indexToAccess); return; } else { assert(indexToAccess == 0); result = interpretRegion(agg, istate, goal); if (exceptionOrCant(result)) return; result = paintTypeOntoLiteral(pue, e.type, result); return; } } if (e.e1.type.toBasetype().ty == Taarray) { Expression e1 = interpretRegion(e.e1, istate); if (exceptionOrCant(e1)) return; if (e1.op == EXP.null_) { if (goal == CTFEGoal.LValue && e1.type.ty == Taarray && e.modifiable) { assert(0); // does not reach here? } error(e.loc, "cannot index null array `%s`", e.e1.toChars()); result = CTFEExp.cantexp; return; } Expression e2 = interpretRegion(e.e2, istate); if (exceptionOrCant(e2)) return; if (goal == CTFEGoal.LValue) { // Pointer or reference of a scalar type if (e1 == e.e1 && e2 == e.e2) result = e; else { emplaceExp!(IndexExp)(pue, e.loc, e1, e2); result = pue.exp(); result.type = e.type; } return; } assert(e1.op == EXP.assocArrayLiteral); UnionExp e2tmp = void; e2 = resolveSlice(e2, &e2tmp); result = findKeyInAA(e.loc, e1.isAssocArrayLiteralExp(), e2); if (!result) { error(e.loc, "key `%s` not found in associative array `%s`", e2.toChars(), e.e1.toChars()); result = CTFEExp.cantexp; } return; } Expression agg; uinteger_t indexToAccess; if (!resolveIndexing(e, istate, &agg, &indexToAccess, false)) { result = CTFEExp.cantexp; return; } if (goal == CTFEGoal.LValue) { Expression e2 = ctfeEmplaceExp!IntegerExp(e.e2.loc, indexToAccess, Type.tsize_t); emplaceExp!(IndexExp)(pue, e.loc, agg, e2); result = pue.exp(); result.type = e.type; return; } result = ctfeIndex(pue, e.loc, e.type, agg, indexToAccess); if (exceptionOrCant(result)) return; if (result.op == EXP.void_) { error(e.loc, "`%s` is used before initialized", e.toChars()); errorSupplemental(result.loc, "originally uninitialized here"); result = CTFEExp.cantexp; return; } if (result == pue.exp()) result = result.copy(); } override void visit(SliceExp e) { debug (LOG) { printf("%s SliceExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } if (e.e1.type.toBasetype().ty == Tpointer) { // Slicing a pointer. Note that there is no $ in this case. Expression e1 = interpretRegion(e.e1, istate); if (exceptionOrCant(e1)) return; if (e1.op == EXP.int64) { error(e.loc, "cannot slice invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars()); result = CTFEExp.cantexp; return; } /* Evaluate lower and upper bounds of slice */ Expression lwr = interpretRegion(e.lwr, istate); if (exceptionOrCant(lwr)) return; Expression upr = interpretRegion(e.upr, istate); if (exceptionOrCant(upr)) return; uinteger_t ilwr = lwr.toInteger(); uinteger_t iupr = upr.toInteger(); dinteger_t ofs; Expression agg = getAggregateFromPointer(e1, &ofs); ilwr += ofs; iupr += ofs; if (agg.op == EXP.null_) { if (iupr == ilwr) { result = ctfeEmplaceExp!NullExp(e.loc); result.type = e.type; return; } error(e.loc, "cannot slice null pointer `%s`", e.e1.toChars()); result = CTFEExp.cantexp; return; } if (agg.op == EXP.symbolOffset) { error(e.loc, "slicing pointers to static variables is not supported in CTFE"); result = CTFEExp.cantexp; return; } if (agg.op != EXP.arrayLiteral && agg.op != EXP.string_) { error(e.loc, "pointer `%s` cannot be sliced at compile time (it does not point to an array)", e.e1.toChars()); result = CTFEExp.cantexp; return; } assert(agg.op == EXP.arrayLiteral || agg.op == EXP.string_); dinteger_t len = ArrayLength(Type.tsize_t, agg).exp().toInteger(); //Type *pointee = ((TypePointer *)agg.type)->next; if (sliceBoundsCheck(0, len, ilwr, iupr)) { error(e.loc, "pointer slice `[%lld..%lld]` exceeds allocated memory block `[0..%lld]`", ilwr, iupr, len); result = CTFEExp.cantexp; return; } if (ofs != 0) { lwr = ctfeEmplaceExp!IntegerExp(e.loc, ilwr, lwr.type); upr = ctfeEmplaceExp!IntegerExp(e.loc, iupr, upr.type); } emplaceExp!(SliceExp)(pue, e.loc, agg, lwr, upr); result = pue.exp(); result.type = e.type; return; } CTFEGoal goal1 = CTFEGoal.RValue; if (goal == CTFEGoal.LValue) { if (e.e1.type.toBasetype().ty == Tsarray) if (auto ve = e.e1.isVarExp()) if (auto vd = ve.var.isVarDeclaration()) if (vd.storage_class & STC.ref_) goal1 = CTFEGoal.LValue; } Expression e1 = interpret(e.e1, istate, goal1); if (exceptionOrCant(e1)) return; if (!e.lwr) { result = paintTypeOntoLiteral(pue, e.type, e1); return; } if (auto ve = e1.isVectorExp()) { e1 = interpretVectorToArray(pue, ve); e1 = (e1 == pue.exp()) ? pue.copy() : e1; } /* Set dollar to the length of the array */ uinteger_t dollar; if ((e1.op == EXP.variable || e1.op == EXP.dotVariable) && e1.type.toBasetype().ty == Tsarray) dollar = e1.type.toBasetype().isTypeSArray().dim.toInteger(); else { if (e1.op != EXP.arrayLiteral && e1.op != EXP.string_ && e1.op != EXP.null_ && e1.op != EXP.slice && e1.op != EXP.vector) { error(e.loc, "cannot determine length of `%s` at compile time", e1.toChars()); result = CTFEExp.cantexp; return; } dollar = resolveArrayLength(e1); } /* Set the $ variable */ if (e.lengthVar) { auto dollarExp = ctfeEmplaceExp!IntegerExp(e.loc, dollar, Type.tsize_t); ctfeGlobals.stack.push(e.lengthVar); setValue(e.lengthVar, dollarExp); } /* Evaluate lower and upper bounds of slice */ Expression lwr = interpretRegion(e.lwr, istate); if (exceptionOrCant(lwr)) { if (e.lengthVar) ctfeGlobals.stack.pop(e.lengthVar); return; } Expression upr = interpretRegion(e.upr, istate); if (exceptionOrCant(upr)) { if (e.lengthVar) ctfeGlobals.stack.pop(e.lengthVar); return; } if (e.lengthVar) ctfeGlobals.stack.pop(e.lengthVar); // $ is defined only inside [L..U] uinteger_t ilwr = lwr.toInteger(); uinteger_t iupr = upr.toInteger(); if (e1.op == EXP.null_) { if (ilwr == 0 && iupr == 0) { result = e1; return; } error(e1.loc, "slice `[%llu..%llu]` is out of bounds", ilwr, iupr); result = CTFEExp.cantexp; return; } if (auto se = e1.isSliceExp()) { // Simplify slice of slice: // aggregate[lo1..up1][lwr..upr] ---> aggregate[lwr'..upr'] uinteger_t lo1 = se.lwr.toInteger(); uinteger_t up1 = se.upr.toInteger(); if (sliceBoundsCheck(0, up1 - lo1, ilwr, iupr)) { error(e.loc, "slice `[%llu..%llu]` exceeds array bounds `[0..%llu]`", ilwr, iupr, up1 - lo1); result = CTFEExp.cantexp; return; } ilwr += lo1; iupr += lo1; emplaceExp!(SliceExp)(pue, e.loc, se.e1, ctfeEmplaceExp!IntegerExp(e.loc, ilwr, lwr.type), ctfeEmplaceExp!IntegerExp(e.loc, iupr, upr.type)); result = pue.exp(); result.type = e.type; return; } if (e1.op == EXP.arrayLiteral || e1.op == EXP.string_) { if (sliceBoundsCheck(0, dollar, ilwr, iupr)) { error(e.loc, "slice `[%lld..%lld]` exceeds array bounds `[0..%lld]`", ilwr, iupr, dollar); result = CTFEExp.cantexp; return; } } emplaceExp!(SliceExp)(pue, e.loc, e1, lwr, upr); result = pue.exp(); result.type = e.type; } override void visit(InExp e) { debug (LOG) { printf("%s InExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression e1 = interpretRegion(e.e1, istate); if (exceptionOrCant(e1)) return; Expression e2 = interpretRegion(e.e2, istate); if (exceptionOrCant(e2)) return; if (e2.op == EXP.null_) { emplaceExp!(NullExp)(pue, e.loc, e.type); result = pue.exp(); return; } if (e2.op != EXP.assocArrayLiteral) { error(e.loc, "`%s` cannot be interpreted at compile time", e.toChars()); result = CTFEExp.cantexp; return; } e1 = resolveSlice(e1); result = findKeyInAA(e.loc, e2.isAssocArrayLiteralExp(), e1); if (exceptionOrCant(result)) return; if (!result) { emplaceExp!(NullExp)(pue, e.loc, e.type); result = pue.exp(); } else { // Create a CTFE pointer &aa[index] result = ctfeEmplaceExp!IndexExp(e.loc, e2, e1); result.type = e.type.nextOf(); emplaceExp!(AddrExp)(pue, e.loc, result, e.type); result = pue.exp(); } } override void visit(CatExp e) { debug (LOG) { printf("%s CatExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } UnionExp ue1 = void; Expression e1 = interpret(&ue1, e.e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2 = void; Expression e2 = interpret(&ue2, e.e2, istate); if (exceptionOrCant(e2)) return; UnionExp e1tmp = void; e1 = resolveSlice(e1, &e1tmp); UnionExp e2tmp = void; e2 = resolveSlice(e2, &e2tmp); /* e1 and e2 can't go on the stack because of x~[y] and [x]~y will * result in [x,y] and then x or y is on the stack. * But if they are both strings, we can, because it isn't the x~[y] case. */ if (!(e1.op == EXP.string_ && e2.op == EXP.string_)) { if (e1 == ue1.exp()) e1 = ue1.copy(); if (e2 == ue2.exp()) e2 = ue2.copy(); } Expression prepareCatOperand(Expression exp) { /* Convert `elem ~ array` to `[elem] ~ array` if `elem` is itself an * array. This is needed because interpreting the `CatExp` calls * `Cat()`, which cannot handle concatenations between different * types, except for strings and chars. */ auto tb = e.type.toBasetype(); auto tbNext = tb.nextOf(); auto expTb = exp.type.toBasetype(); if (exp.type.implicitConvTo(tbNext) >= MATCH.convert && (tb.ty == Tarray || tb.ty == Tsarray) && (expTb.ty == Tarray || expTb.ty == Tsarray)) return new ArrayLiteralExp(exp.loc, e.type, exp); return exp; } *pue = ctfeCat(e.loc, e.type, prepareCatOperand(e1), prepareCatOperand(e2)); result = pue.exp(); if (CTFEExp.isCantExp(result)) { error(e.loc, "`%s` cannot be interpreted at compile time", e.toChars()); return; } // We know we still own it, because we interpreted both e1 and e2 if (auto ale = result.isArrayLiteralExp()) { ale.ownedByCtfe = OwnedBy.ctfe; // https://issues.dlang.org/show_bug.cgi?id=14686 foreach (elem; *ale.elements) { Expression ex = evaluatePostblit(istate, elem); if (exceptionOrCant(ex)) return; } } else if (auto se = result.isStringExp()) se.ownedByCtfe = OwnedBy.ctfe; } override void visit(DeleteExp e) { debug (LOG) { printf("%s DeleteExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } result = interpretRegion(e.e1, istate); if (exceptionOrCant(result)) return; if (result.op == EXP.null_) { result = CTFEExp.voidexp; return; } auto tb = e.e1.type.toBasetype(); switch (tb.ty) { case Tclass: if (result.op != EXP.classReference) { error(e.loc, "`delete` on invalid class reference `%s`", result.toChars()); result = CTFEExp.cantexp; return; } auto cre = result.isClassReferenceExp(); auto cd = cre.originalClass(); // Find dtor(s) in inheritance chain do { if (cd.dtor) { result = interpretFunction(pue, cd.dtor, istate, null, cre); if (exceptionOrCant(result)) return; // Dtors of Non-extern(D) classes use implicit chaining (like structs) import dmd.aggregate : ClassKind; if (cd.classKind != ClassKind.d) break; } // Emulate manual chaining as done in rt_finalize2 cd = cd.baseClass; } while (cd); // Stop after Object break; default: assert(0); } result = CTFEExp.voidexp; } override void visit(CastExp e) { debug (LOG) { printf("%s CastExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression e1 = interpretRegion(e.e1, istate, goal); if (exceptionOrCant(e1)) return; // If the expression has been cast to void, do nothing. if (e.to.ty == Tvoid) { result = CTFEExp.voidexp; return; } if (e.to.ty == Tpointer && e1.op != EXP.null_) { Type pointee = (cast(TypePointer)e.type).next; // Implement special cases of normally-unsafe casts if (e1.op == EXP.int64) { // Happens with Windows HANDLEs, for example. result = paintTypeOntoLiteral(pue, e.to, e1); return; } bool castToSarrayPointer = false; bool castBackFromVoid = false; if (e1.type.ty == Tarray || e1.type.ty == Tsarray || e1.type.ty == Tpointer) { // Check for unsupported type painting operations // For slices, we need the type being sliced, // since it may have already been type painted Type elemtype = e1.type.nextOf(); if (auto se = e1.isSliceExp()) elemtype = se.e1.type.nextOf(); // Allow casts from X* to void *, and X** to void** for any X. // But don't allow cast from X* to void**. // So, we strip all matching * from source and target to find X. // Allow casts to X* from void* only if the 'void' was originally an X; // we check this later on. Type ultimatePointee = pointee; Type ultimateSrc = elemtype; while (ultimatePointee.ty == Tpointer && ultimateSrc.ty == Tpointer) { ultimatePointee = ultimatePointee.nextOf(); ultimateSrc = ultimateSrc.nextOf(); } if (ultimatePointee.ty == Tsarray && ultimatePointee.nextOf().equivalent(ultimateSrc)) { castToSarrayPointer = true; } else if (ultimatePointee.ty != Tvoid && ultimateSrc.ty != Tvoid && !isSafePointerCast(elemtype, pointee)) { error(e.loc, "reinterpreting cast from `%s*` to `%s*` is not supported in CTFE", elemtype.toChars(), pointee.toChars()); result = CTFEExp.cantexp; return; } if (ultimateSrc.ty == Tvoid) castBackFromVoid = true; } if (auto se = e1.isSliceExp()) { if (se.e1.op == EXP.null_) { result = paintTypeOntoLiteral(pue, e.type, se.e1); return; } // Create a CTFE pointer &aggregate[1..2] auto ei = ctfeEmplaceExp!IndexExp(e.loc, se.e1, se.lwr); ei.type = e.type.nextOf(); emplaceExp!(AddrExp)(pue, e.loc, ei, e.type); result = pue.exp(); return; } if (e1.op == EXP.arrayLiteral || e1.op == EXP.string_) { // Create a CTFE pointer &[1,2,3][0] or &"abc"[0] auto ei = ctfeEmplaceExp!IndexExp(e.loc, e1, ctfeEmplaceExp!IntegerExp(e.loc, 0, Type.tsize_t)); ei.type = e.type.nextOf(); emplaceExp!(AddrExp)(pue, e.loc, ei, e.type); result = pue.exp(); return; } if (e1.op == EXP.index && !e1.isIndexExp().e1.type.equals(e1.type)) { // type painting operation IndexExp ie = e1.isIndexExp(); if (castBackFromVoid) { // get the original type. For strings, it's just the type... Type origType = ie.e1.type.nextOf(); // ..but for arrays of type void*, it's the type of the element if (ie.e1.op == EXP.arrayLiteral && ie.e2.op == EXP.int64) { ArrayLiteralExp ale = ie.e1.isArrayLiteralExp(); const indx = cast(size_t)ie.e2.toInteger(); if (indx < ale.elements.length) { if (Expression xx = (*ale.elements)[indx]) { if (auto iex = xx.isIndexExp()) origType = iex.e1.type.nextOf(); else if (auto ae = xx.isAddrExp()) origType = ae.e1.type; else if (auto ve = xx.isVarExp()) origType = ve.var.type; } } } if (!isSafePointerCast(origType, pointee)) { error(e.loc, "using `void*` to reinterpret cast from `%s*` to `%s*` is not supported in CTFE", origType.toChars(), pointee.toChars()); result = CTFEExp.cantexp; return; } } emplaceExp!(IndexExp)(pue, e1.loc, ie.e1, ie.e2); result = pue.exp(); result.type = e.type; return; } if (auto ae = e1.isAddrExp()) { Type origType = ae.e1.type; if (isSafePointerCast(origType, pointee)) { emplaceExp!(AddrExp)(pue, e.loc, ae.e1, e.type); result = pue.exp(); return; } if (castToSarrayPointer && pointee.toBasetype().ty == Tsarray && ae.e1.op == EXP.index) { // &val[idx] dinteger_t dim = (cast(TypeSArray)pointee.toBasetype()).dim.toInteger(); IndexExp ie = ae.e1.isIndexExp(); Expression lwr = ie.e2; Expression upr = ctfeEmplaceExp!IntegerExp(ie.e2.loc, ie.e2.toInteger() + dim, Type.tsize_t); // Create a CTFE pointer &val[idx..idx+dim] auto er = ctfeEmplaceExp!SliceExp(e.loc, ie.e1, lwr, upr); er.type = pointee; emplaceExp!(AddrExp)(pue, e.loc, er, e.type); result = pue.exp(); return; } } if (e1.op == EXP.variable || e1.op == EXP.symbolOffset) { // type painting operation Type origType = (cast(SymbolExp)e1).var.type; if (castBackFromVoid && !isSafePointerCast(origType, pointee)) { error(e.loc, "using `void*` to reinterpret cast from `%s*` to `%s*` is not supported in CTFE", origType.toChars(), pointee.toChars()); result = CTFEExp.cantexp; return; } if (auto ve = e1.isVarExp()) emplaceExp!(VarExp)(pue, e.loc, ve.var); else emplaceExp!(SymOffExp)(pue, e.loc, e1.isSymOffExp().var, e1.isSymOffExp().offset); result = pue.exp(); result.type = e.to; return; } // Check if we have a null pointer (eg, inside a struct) e1 = interpretRegion(e1, istate); if (e1.op != EXP.null_) { error(e.loc, "pointer cast from `%s` to `%s` is not supported at compile time", e1.type.toChars(), e.to.toChars()); result = CTFEExp.cantexp; return; } } if (e.to.ty == Tsarray && e.e1.type.ty == Tvector) { // Special handling for: cast(float[4])__vector([w, x, y, z]) e1 = interpretRegion(e.e1, istate); if (exceptionOrCant(e1)) return; assert(e1.op == EXP.vector); e1 = interpretVectorToArray(pue, e1.isVectorExp()); } if (e.to.ty == Tarray && e1.op == EXP.slice) { // Note that the slice may be void[], so when checking for dangerous // casts, we need to use the original type, which is se.e1. SliceExp se = e1.isSliceExp(); if (!isSafePointerCast(se.e1.type.nextOf(), e.to.nextOf())) { error(e.loc, "array cast from `%s` to `%s` is not supported at compile time", se.e1.type.toChars(), e.to.toChars()); result = CTFEExp.cantexp; return; } emplaceExp!(SliceExp)(pue, e1.loc, se.e1, se.lwr, se.upr); result = pue.exp(); result.type = e.to; return; } // Disallow array type painting, except for conversions between built-in // types of identical size. if ((e.to.ty == Tsarray || e.to.ty == Tarray) && (e1.type.ty == Tsarray || e1.type.ty == Tarray) && !isSafePointerCast(e1.type.nextOf(), e.to.nextOf())) { auto se = e1.isStringExp(); // Allow casting a hex string literal to short[], int[] or long[] if (se && se.hexString && se.postfix == StringExp.NoPostfix && e.to.nextOf().isintegral) { const sz = cast(size_t) e.to.nextOf().size; if ((se.len % sz) != 0) { error(e.loc, "hex string length %d must be a multiple of %d to cast to `%s`", cast(int) se.len, cast(int) sz, e.to.toChars()); result = CTFEExp.cantexp; return; } auto str = arrayCastBigEndian(se.peekData(), sz); emplaceExp!(StringExp)(pue, e1.loc, str, se.len / sz, cast(ubyte) sz); result = pue.exp(); result.type = e.to; return; } error(e.loc, "array cast from `%s` to `%s` is not supported at compile time", e1.type.toChars(), e.to.toChars()); if (se && se.hexString && se.postfix != StringExp.NoPostfix) errorSupplemental(e.loc, "perhaps remove postfix `%s` from hex string", (cast(char) se.postfix ~ "\0").ptr); result = CTFEExp.cantexp; return; } if (e.to.ty == Tsarray) e1 = resolveSlice(e1); auto tobt = e.to.toBasetype(); if (tobt.ty == Tbool && e1.type.ty == Tpointer) { emplaceExp!(IntegerExp)(pue, e.loc, e1.op != EXP.null_, e.to); result = pue.exp(); return; } else if (tobt.isTypeBasic() && e1.op == EXP.null_) { if (tobt.isintegral()) emplaceExp!(IntegerExp)(pue, e.loc, 0, e.to); else if (tobt.isreal()) emplaceExp!(RealExp)(pue, e.loc, CTFloat.zero, e.to); result = pue.exp(); return; } result = ctfeCast(pue, e.loc, e.type, e.to, e1, true); } override void visit(AssertExp e) { debug (LOG) { printf("%s AssertExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression e1 = interpret(pue, e.e1, istate); if (exceptionOrCant(e1)) return; if (isTrueBool(e1)) { } else if (e1.toBool().hasValue(false)) { if (e.msg) { UnionExp ue = void; result = interpret(&ue, e.msg, istate); if (exceptionOrCant(result)) return; result = scrubReturnValue(e.loc, result); if (StringExp se = result.toStringExp()) error(e.loc, "%s", se.toStringz().ptr); else error(e.loc, "%s", result.toChars()); } else error(e.loc, "`%s` failed", e.toChars()); result = CTFEExp.cantexp; return; } else { error(e.loc, "`%s` is not a compile time boolean expression", e1.toChars()); result = CTFEExp.cantexp; return; } result = e1; return; } override void visit(ThrowExp te) { debug (LOG) { printf("%s ThrowExpression::interpret()\n", te.loc.toChars()); } interpretThrow(result, te.e1, te.loc, istate); } override void visit(PtrExp e) { // Called for both lvalues and rvalues const lvalue = goal == CTFEGoal.LValue; debug (LOG) { printf("%s PtrExp::interpret(%d) %s, %s\n", e.loc.toChars(), lvalue, e.type.toChars(), e.toChars()); } // Check for int<->float and long<->double casts. if (auto soe1 = e.e1.isSymOffExp()) if (soe1.offset == 0 && soe1.var.isVarDeclaration() && isFloatIntPaint(e.type, soe1.var.type)) { // *(cast(int*)&v), where v is a float variable result = paintFloatInt(pue, getVarExp(e.loc, istate, soe1.var, CTFEGoal.RValue), e.type); return; } if (auto ce1 = e.e1.isCastExp()) if (auto ae11 = ce1.e1.isAddrExp()) { // *(cast(int*)&x), where x is a float expression Expression x = ae11.e1; if (isFloatIntPaint(e.type, x.type)) { result = paintFloatInt(pue, interpretRegion(x, istate), e.type); return; } } // Constant fold *(&structliteral + offset) if (auto ae = e.e1.isAddExp()) { if (ae.e1.op == EXP.address && ae.e2.op == EXP.int64) { AddrExp ade = ae.e1.isAddrExp(); Expression ex = interpretRegion(ade.e1, istate); if (exceptionOrCant(ex)) return; if (auto se = ex.isStructLiteralExp()) { dinteger_t offset = ae.e2.toInteger(); result = se.getField(e.type, cast(uint)offset); if (result) return; } } } // It's possible we have an array bounds error. We need to make sure it // errors with this line number, not the one where the pointer was set. result = interpretRegion(e.e1, istate); if (exceptionOrCant(result)) return; if (result.op == EXP.function_) return; if (auto soe = result.isSymOffExp()) { if (soe.offset == 0 && soe.var.isFuncDeclaration()) return; if (soe.offset == 0 && soe.var.isVarDeclaration() && soe.var.isImmutable()) { result = getVarExp(e.loc, istate, soe.var, CTFEGoal.RValue); return; } error(e.loc, "cannot dereference pointer to static variable `%s` at compile time", soe.var.toChars()); result = CTFEExp.cantexp; return; } if (!lvalue && result.isArrayLiteralExp() && result.type.isTypePointer()) { /* A pointer variable can point to an array literal like `[3]`. * Dereferencing it means accessing the first element value. * Dereference it only if result should be an rvalue */ auto ae = result.isArrayLiteralExp(); if (ae.elements.length == 1) { result = (*ae.elements)[0]; return; } } if (result.isStringExp() || result.isArrayLiteralExp()) return; if (result.op != EXP.address) { if (result.op == EXP.null_) error(e.loc, "dereference of null pointer `%s`", e.e1.toChars()); else error(e.loc, "dereference of invalid pointer `%s`", result.toChars()); result = CTFEExp.cantexp; return; } // *(&x) ==> x result = result.isAddrExp().e1; if (result.op == EXP.slice && e.type.toBasetype().ty == Tsarray) { /* aggr[lwr..upr] * upr may exceed the upper boundary of aggr, but the check is deferred * until those out-of-bounds elements will be touched. */ return; } result = interpret(pue, result, istate, goal); if (exceptionOrCant(result)) return; debug (LOG) { if (CTFEExp.isCantExp(result)) printf("PtrExp::interpret() %s = CTFEExp::cantexp\n", e.toChars()); } } override void visit(DotVarExp e) { void notImplementedYet() { error(e.loc, "`%s.%s` is not yet implemented at compile time", e.e1.toChars(), e.var.toChars()); result = CTFEExp.cantexp; return; } debug (LOG) { printf("%s DotVarExp::interpret() %s, goal = %d\n", e.loc.toChars(), e.toChars(), goal); } Expression ex = interpretRegion(e.e1, istate); if (exceptionOrCant(ex)) return; if (FuncDeclaration f = e.var.isFuncDeclaration()) { if (ex == e.e1) result = e; // optimize: reuse this CTFE reference else { emplaceExp!(DotVarExp)(pue, e.loc, ex, f, false); result = pue.exp(); result.type = e.type; } return; } VarDeclaration v = e.var.isVarDeclaration(); if (!v) { error(e.loc, "CTFE internal error: `%s`", e.toChars()); result = CTFEExp.cantexp; return; } if (ex.op == EXP.null_) { if (ex.type.toBasetype().ty == Tclass) error(e.loc, "class `%s` is `null` and cannot be dereferenced", e.e1.toChars()); else error(e.loc, "CTFE internal error: null this `%s`", e.e1.toChars()); result = CTFEExp.cantexp; return; } StructLiteralExp se; int i; if (ex.op != EXP.structLiteral && ex.op != EXP.classReference && ex.op != EXP.typeid_) { return notImplementedYet(); } // We can't use getField, because it makes a copy if (ex.op == EXP.classReference) { se = ex.isClassReferenceExp().value; i = ex.isClassReferenceExp().findFieldIndexByName(v); } else if (ex.op == EXP.typeid_) { if (v.ident == Identifier.idPool("name")) { if (auto t = isType(ex.isTypeidExp().obj)) { import dmd.typesem : toDsymbol; auto sym = t.toDsymbol(null); if (auto ident = (sym ? sym.ident : null)) { result = new StringExp(e.loc, ident.toString()); result.expressionSemantic(null); return ; } } } return notImplementedYet(); } else { se = ex.isStructLiteralExp(); i = findFieldIndexByName(se.sd, v); } if (i == -1) { error(e.loc, "couldn't find field `%s` of type `%s` in `%s`", v.toChars(), e.type.toChars(), se.toChars()); result = CTFEExp.cantexp; return; } // https://issues.dlang.org/show_bug.cgi?id=19897 // https://issues.dlang.org/show_bug.cgi?id=20710 // Zero-elements fields don't have an initializer. See: scrubArray function if ((*se.elements)[i] is null) (*se.elements)[i] = voidInitLiteral(e.type, v).copy(); if (goal == CTFEGoal.LValue) { // just return the (simplified) dotvar expression as a CTFE reference if (e.e1 == ex) result = e; else { emplaceExp!(DotVarExp)(pue, e.loc, ex, v); result = pue.exp(); result.type = e.type; } return; } result = (*se.elements)[i]; if (!result) { error(e.loc, "internal compiler error: null field `%s`", v.toChars()); result = CTFEExp.cantexp; return; } if (auto vie = result.isVoidInitExp()) { const s = vie.var.toChars(); if (v.overlapped) { error(e.loc, "reinterpretation through overlapped field `%s` is not allowed in CTFE", s); result = CTFEExp.cantexp; return; } error(e.loc, "cannot read uninitialized variable `%s` in CTFE", s); result = CTFEExp.cantexp; return; } if (v.type.ty != result.type.ty && v.type.ty == Tsarray) { // Block assignment from inside struct literals auto tsa = cast(TypeSArray)v.type; auto len = cast(size_t)tsa.dim.toInteger(); UnionExp ue = void; result = createBlockDuplicatedArrayLiteral(&ue, e.loc, v.type, result, len); if (result == ue.exp()) result = ue.copy(); (*se.elements)[i] = result; } debug (LOG) { if (CTFEExp.isCantExp(result)) printf("DotVarExp::interpret() %s = CTFEExp::cantexp\n", e.toChars()); } } override void visit(RemoveExp e) { debug (LOG) { printf("%s RemoveExp::interpret() %s\n", e.loc.toChars(), e.toChars()); } Expression agg = interpret(e.e1, istate); if (exceptionOrCant(agg)) return; Expression index = interpret(e.e2, istate); if (exceptionOrCant(index)) return; if (agg.op == EXP.null_) { result = CTFEExp.voidexp; return; } AssocArrayLiteralExp aae = agg.isAssocArrayLiteralExp(); Expressions* keysx = aae.keys; Expressions* valuesx = aae.values; size_t removed = 0; foreach (j, evalue; *valuesx) { Expression ekey = (*keysx)[j]; int eq = ctfeEqual(e.loc, EXP.equal, ekey, index); if (eq) ++removed; else if (removed != 0) { (*keysx)[j - removed] = ekey; (*valuesx)[j - removed] = evalue; } } valuesx.length = valuesx.length - removed; keysx.length = keysx.length - removed; result = IntegerExp.createBool(removed != 0); } override void visit(ClassReferenceExp e) { //printf("ClassReferenceExp::interpret() %s\n", e.value.toChars()); result = e; } override void visit(VoidInitExp e) { error(e.loc, "CTFE internal error: trying to read uninitialized variable"); assert(0); } override void visit(ThrownExceptionExp e) { assert(0); // This should never be interpreted } } /// Interpret `throw ` found at the specified location `loc` private void interpretThrow(ref Expression result, Expression exp, const ref Loc loc, InterState* istate) { incUsageCtfe(istate, loc); Expression e = interpretRegion(exp, istate); if (exceptionOrCantInterpret(e)) { // Make sure e is not pointing to a stack temporary result = (e.op == EXP.cantExpression) ? CTFEExp.cantexp : e; } else if (e.op == EXP.classReference) { result = ctfeEmplaceExp!ThrownExceptionExp(loc, e.isClassReferenceExp()); } else { error(exp.loc, "to be thrown `%s` must be non-null", exp.toChars()); result = ErrorExp.get(); } } /********************************************* * Checks if the given expresion is a call to the runtime hook `id`. * * Params: * e = the expression to check * id = the identifier of the runtime hook * Returns: * `e` cast to `CallExp` if it's the hook, `null` otherwise */ public CallExp isRuntimeHook(Expression e, Identifier id) { if (auto ce = e.isCallExp()) { if (auto ve = ce.e1.isVarExp()) { if (auto fd = ve.var.isFuncDeclaration()) { // If `_d_HookTraceImpl` is found, resolve the underlying hook // and replace `e` and `fd` with it. removeHookTraceImpl(ce, fd); return fd.ident == id ? ce : null; } } } return null; } /******************************************** * Interpret the expression. * Params: * pue = non-null pointer to temporary storage that can be used to store the return value * e = Expression to interpret * istate = context * goal = what the result will be used for * Returns: * resulting expression */ Expression interpret(UnionExp* pue, Expression e, InterState* istate, CTFEGoal goal = CTFEGoal.RValue) { if (!e) return null; //printf("+interpret() e : %s, %s\n", e.type.toChars(), e.toChars()); scope Interpreter v = new Interpreter(pue, istate, goal); e.accept(v); Expression ex = v.result; assert(goal == CTFEGoal.Nothing || ex !is null); //if (ex) printf("-interpret() ex: %s, %s\n", ex.type.toChars(), ex.toChars()); else printf("-interpret()\n"); return ex; } /// Expression interpret(Expression e, InterState* istate, CTFEGoal goal = CTFEGoal.RValue) { UnionExp ue = void; auto result = interpret(&ue, e, istate, goal); if (result == ue.exp()) result = ue.copy(); return result; } /***************************** * Same as interpret(), but return result allocated in Region. * Params: * e = Expression to interpret * istate = context * goal = what the result will be used for * Returns: * resulting expression */ Expression interpretRegion(Expression e, InterState* istate, CTFEGoal goal = CTFEGoal.RValue) { UnionExp ue = void; auto result = interpret(&ue, e, istate, goal); auto uexp = ue.exp(); if (result != uexp) return result; if (mem.isGCEnabled) return ue.copy(); // mimicking UnionExp.copy, but with region allocation switch (uexp.op) { case EXP.cantExpression: return CTFEExp.cantexp; case EXP.voidExpression: return CTFEExp.voidexp; case EXP.break_: return CTFEExp.breakexp; case EXP.continue_: return CTFEExp.continueexp; case EXP.goto_: return CTFEExp.gotoexp; default: break; } auto p = ctfeGlobals.region.malloc(uexp.size); return cast(Expression)memcpy(p, cast(void*)uexp, uexp.size); } private Expressions* copyArrayOnWrite(Expressions* exps, Expressions* original) { if (exps is original) { if (!original) exps = new Expressions(); else exps = original.copy(); ++ctfeGlobals.numArrayAllocs; } return exps; } /** Given an expression e which is about to be returned from the current function, generate an error if it contains pointers to local variables. Only checks expressions passed by value (pointers to local variables may already be stored in members of classes, arrays, or AAs which were passed as mutable function parameters). Returns: true if it is safe to return, false if an error was generated. */ private bool stopPointersEscaping(const ref Loc loc, Expression e) { import dmd.typesem : hasPointers; if (!e.type.hasPointers()) return true; if (isPointer(e.type)) { Expression x = e; if (auto eaddr = e.isAddrExp()) x = eaddr.e1; VarDeclaration v; while (x.op == EXP.variable && (v = x.isVarExp().var.isVarDeclaration()) !is null) { if (v.storage_class & STC.ref_) { x = getValue(v); if (auto eaddr = e.isAddrExp()) eaddr.e1 = x; continue; } if (ctfeGlobals.stack.isInCurrentFrame(v)) { error(loc, "returning a pointer to a local stack variable"); return false; } else break; } // TODO: If it is a EXP.dotVariable or EXP.index, we should check that it is not // pointing to a local struct or static array. } if (auto se = e.isStructLiteralExp()) { return stopPointersEscapingFromArray(loc, se.elements); } if (auto ale = e.isArrayLiteralExp()) { return stopPointersEscapingFromArray(loc, ale.elements); } if (auto aae = e.isAssocArrayLiteralExp()) { if (!stopPointersEscapingFromArray(loc, aae.keys)) return false; return stopPointersEscapingFromArray(loc, aae.values); } return true; } // Check all elements of an array for escaping local variables. Return false if error private bool stopPointersEscapingFromArray(const ref Loc loc, Expressions* elems) { foreach (e; *elems) { if (e && !stopPointersEscaping(loc, e)) return false; } return true; } private Statement findGotoTarget(InterState* istate, Identifier ident) { Statement target = null; if (ident) { LabelDsymbol label = istate.fd.searchLabel(ident, Loc.initial); assert(label && label.statement); LabelStatement ls = label.statement; target = ls.gotoTarget ? ls.gotoTarget : ls.statement; } return target; } private ThrownExceptionExp chainExceptions(ThrownExceptionExp oldest, ThrownExceptionExp newest) { debug (LOG) { printf("Collided exceptions %s %s\n", oldest.thrown.toChars(), newest.thrown.toChars()); } // Little sanity check to make sure it's really a Throwable ClassReferenceExp boss = oldest.thrown; const next = 5; // index of Throwable.next assert((*boss.value.elements)[next].type.ty == Tclass); // Throwable.next ClassReferenceExp collateral = newest.thrown; if (collateral.originalClass().isErrorException() && !boss.originalClass().isErrorException()) { /* Find the index of the Error.bypassException field */ auto bypass = next + 1; if ((*collateral.value.elements)[bypass].type.ty == Tuns32) bypass += 1; // skip over _refcount field assert((*collateral.value.elements)[bypass].type.ty == Tclass); // The new exception bypass the existing chain (*collateral.value.elements)[bypass] = boss; return newest; } while ((*boss.value.elements)[next].op == EXP.classReference) { boss = (*boss.value.elements)[next].isClassReferenceExp(); } (*boss.value.elements)[next] = collateral; return oldest; } /** * All results destined for use outside of CTFE need to have their CTFE-specific * features removed. * In particular, * 1. all slices must be resolved. * 2. all .ownedByCtfe set to OwnedBy.code */ private Expression scrubReturnValue(const ref Loc loc, Expression e) { /* Returns: true if e is void, * or is an array literal or struct literal of void elements. */ static bool isVoid(const Expression e, bool checkArrayType = false) pure { if (e.op == EXP.void_) return true; static bool isEntirelyVoid(const Expressions* elems) { foreach (e; *elems) { // It can be NULL for performance reasons, // see StructLiteralExp::interpret(). if (e && !isVoid(e)) return false; } return true; } if (auto sle = e.isStructLiteralExp()) return isEntirelyVoid(sle.elements); if (checkArrayType && e.type.ty != Tsarray) return false; if (auto ale = e.isArrayLiteralExp()) return isEntirelyVoid(ale.elements); return false; } /* Scrub all elements of elems[]. * Returns: null for success, error Expression for failure */ Expression scrubArray(Expressions* elems, bool structlit = false) { foreach (ref e; *elems) { // It can be NULL for performance reasons, // see StructLiteralExp::interpret(). if (!e) continue; // A struct .init may contain void members. // Static array members are a weird special case https://issues.dlang.org/show_bug.cgi?id=10994 if (structlit && isVoid(e, true)) { e = null; } else { e = scrubReturnValue(loc, e); if (CTFEExp.isCantExp(e) || e.op == EXP.error) return e; } } return null; } Expression scrubSE(StructLiteralExp sle) { sle.ownedByCtfe = OwnedBy.code; if (!(sle.stageflags & stageScrub)) { const old = sle.stageflags; sle.stageflags |= stageScrub; // prevent infinite recursion if (auto ex = scrubArray(sle.elements, true)) return ex; sle.stageflags = old; } return null; } if (e.op == EXP.classReference) { StructLiteralExp sle = e.isClassReferenceExp().value; if (auto ex = scrubSE(sle)) return ex; } else if (auto vie = e.isVoidInitExp()) { error(loc, "uninitialized variable `%s` cannot be returned from CTFE", vie.var.toChars()); return ErrorExp.get(); } e = resolveSlice(e); if (auto sle = e.isStructLiteralExp()) { if (auto ex = scrubSE(sle)) return ex; } else if (auto se = e.isStringExp()) { se.ownedByCtfe = OwnedBy.code; } else if (auto ale = e.isArrayLiteralExp()) { ale.ownedByCtfe = OwnedBy.code; if (auto ex = scrubArray(ale.elements)) return ex; } else if (auto aae = e.isAssocArrayLiteralExp()) { aae.ownedByCtfe = OwnedBy.code; if (auto ex = scrubArray(aae.keys)) return ex; if (auto ex = scrubArray(aae.values)) return ex; aae.type = toBuiltinAAType(aae.type); } else if (auto ve = e.isVectorExp()) { ve.ownedByCtfe = OwnedBy.code; if (auto ale = ve.e1.isArrayLiteralExp()) { ale.ownedByCtfe = OwnedBy.code; if (auto ex = scrubArray(ale.elements)) return ex; } } return e; } /************************************** * Transitively set all .ownedByCtfe to OwnedBy.cache */ private Expression scrubCacheValue(Expression e) { if (!e) return e; Expression scrubArrayCache(Expressions* elems) { foreach (ref e; *elems) e = scrubCacheValue(e); return null; } Expression scrubSE(StructLiteralExp sle) { sle.ownedByCtfe = OwnedBy.cache; if (!(sle.stageflags & stageScrub)) { const old = sle.stageflags; sle.stageflags |= stageScrub; // prevent infinite recursion if (auto ex = scrubArrayCache(sle.elements)) return ex; sle.stageflags = old; } return null; } if (e.op == EXP.classReference) { if (auto ex = scrubSE(e.isClassReferenceExp().value)) return ex; } else if (auto sle = e.isStructLiteralExp()) { if (auto ex = scrubSE(sle)) return ex; } else if (auto se = e.isStringExp()) { se.ownedByCtfe = OwnedBy.cache; } else if (auto ale = e.isArrayLiteralExp()) { ale.ownedByCtfe = OwnedBy.cache; if (Expression ex = scrubArrayCache(ale.elements)) return ex; } else if (auto aae = e.isAssocArrayLiteralExp()) { aae.ownedByCtfe = OwnedBy.cache; if (auto ex = scrubArrayCache(aae.keys)) return ex; if (auto ex = scrubArrayCache(aae.values)) return ex; } else if (auto ve = e.isVectorExp()) { ve.ownedByCtfe = OwnedBy.cache; if (auto ale = ve.e1.isArrayLiteralExp()) { ale.ownedByCtfe = OwnedBy.cache; if (auto ex = scrubArrayCache(ale.elements)) return ex; } } return e; } /******************************************** * Transitively replace all Expressions allocated in ctfeGlobals.region * with Mem owned copies. * Params: * e = possible ctfeGlobals.region owned expression * Returns: * Mem owned expression */ private Expression copyRegionExp(Expression e) { if (!e) return e; static void copyArray(Expressions* elems) { foreach (ref e; *elems) { auto ex = e; e = null; e = copyRegionExp(ex); } } static void copySE(StructLiteralExp sle) { if (1 || !(sle.stageflags & stageScrub)) { const old = sle.stageflags; sle.stageflags |= stageScrub; // prevent infinite recursion copyArray(sle.elements); sle.stageflags = old; } } switch (e.op) { case EXP.classReference: { auto cre = e.isClassReferenceExp(); cre.value = copyRegionExp(cre.value).isStructLiteralExp(); break; } case EXP.structLiteral: { auto sle = e.isStructLiteralExp(); /* The following is to take care of updating sle.origin correctly, * which may have multiple objects pointing to it. */ if (sle.isOriginal && !ctfeGlobals.region.contains(cast(void*)sle.origin)) { /* This means sle has already been moved out of the region, * and sle.origin is the new location. */ return sle.origin; } copySE(sle); sle.isOriginal = sle is sle.origin; auto slec = ctfeGlobals.region.contains(cast(void*)e) ? e.copy().isStructLiteralExp() // move sle out of region to slec : sle; if (ctfeGlobals.region.contains(cast(void*)sle.origin)) { auto sleo = sle.origin == sle ? slec : sle.origin.copy().isStructLiteralExp(); sle.origin = sleo; slec.origin = sleo; } return slec; } case EXP.arrayLiteral: { auto ale = e.isArrayLiteralExp(); ale.basis = copyRegionExp(ale.basis); copyArray(ale.elements); break; } case EXP.assocArrayLiteral: copyArray(e.isAssocArrayLiteralExp().keys); copyArray(e.isAssocArrayLiteralExp().values); break; case EXP.slice: { auto se = e.isSliceExp(); se.e1 = copyRegionExp(se.e1); se.upr = copyRegionExp(se.upr); se.lwr = copyRegionExp(se.lwr); break; } case EXP.tuple: { auto te = e.isTupleExp(); te.e0 = copyRegionExp(te.e0); copyArray(te.exps); break; } case EXP.address: case EXP.delegate_: case EXP.vector: case EXP.dotVariable: { UnaExp ue = e.isUnaExp(); ue.e1 = copyRegionExp(ue.e1); break; } case EXP.index: { BinExp be = e.isBinExp(); be.e1 = copyRegionExp(be.e1); be.e2 = copyRegionExp(be.e2); break; } case EXP.this_: case EXP.super_: case EXP.variable: case EXP.type: case EXP.function_: case EXP.typeid_: case EXP.string_: case EXP.int64: case EXP.error: case EXP.float64: case EXP.complex80: case EXP.null_: case EXP.void_: case EXP.symbolOffset: break; case EXP.cantExpression: case EXP.voidExpression: case EXP.showCtfeContext: return e; default: printf("e: %s, %s\n", EXPtoString(e.op).ptr, e.toChars()); assert(0); } if (ctfeGlobals.region.contains(cast(void*)e)) { return e.copy(); } return e; } /******************************* Special Functions ***************************/ private Expression interpret_length(UnionExp* pue, InterState* istate, Expression earg) { //printf("interpret_length()\n"); earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; dinteger_t len = 0; if (auto aae = earg.isAssocArrayLiteralExp()) len = aae.keys.length; else assert(earg.op == EXP.null_); emplaceExp!(IntegerExp)(pue, earg.loc, len, Type.tsize_t); return pue.exp(); } private Expression interpret_keys(UnionExp* pue, InterState* istate, Expression earg, Type returnType) { debug (LOG) { printf("interpret_keys()\n"); } earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; if (earg.op == EXP.null_) { emplaceExp!(NullExp)(pue, earg.loc, earg.type); return pue.exp(); } if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray) return null; AssocArrayLiteralExp aae = earg.isAssocArrayLiteralExp(); auto ae = ctfeEmplaceExp!ArrayLiteralExp(aae.loc, returnType, aae.keys); ae.ownedByCtfe = aae.ownedByCtfe; *pue = copyLiteral(ae); return pue.exp(); } private Expression interpret_values(UnionExp* pue, InterState* istate, Expression earg, Type returnType) { debug (LOG) { printf("interpret_values()\n"); } earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; if (earg.op == EXP.null_) { emplaceExp!(NullExp)(pue, earg.loc, earg.type); return pue.exp(); } if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray) return null; auto aae = earg.isAssocArrayLiteralExp(); auto ae = ctfeEmplaceExp!ArrayLiteralExp(aae.loc, returnType, aae.values); ae.ownedByCtfe = aae.ownedByCtfe; //printf("result is %s\n", e.toChars()); *pue = copyLiteral(ae); return pue.exp(); } private Expression interpret_dup(UnionExp* pue, InterState* istate, Expression earg) { debug (LOG) { printf("interpret_dup()\n"); } earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; if (earg.op == EXP.null_) { emplaceExp!(NullExp)(pue, earg.loc, earg.type); return pue.exp(); } if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray) return null; auto aae = copyLiteral(earg).copy().isAssocArrayLiteralExp(); for (size_t i = 0; i < aae.keys.length; i++) { if (Expression e = evaluatePostblit(istate, (*aae.keys)[i])) return e; if (Expression e = evaluatePostblit(istate, (*aae.values)[i])) return e; } aae.type = earg.type.mutableOf(); // repaint type from const(int[int]) to const(int)[int] //printf("result is %s\n", aae.toChars()); return aae; } // signature is int delegate(ref Value) OR int delegate(ref Key, ref Value) private Expression interpret_aaApply(UnionExp* pue, InterState* istate, Expression aa, Expression deleg) { aa = interpret(aa, istate); if (exceptionOrCantInterpret(aa)) return aa; if (aa.op != EXP.assocArrayLiteral) { emplaceExp!(IntegerExp)(pue, deleg.loc, 0, Type.tsize_t); return pue.exp(); } FuncDeclaration fd = null; Expression pthis = null; if (auto de = deleg.isDelegateExp()) { fd = de.func; pthis = de.e1; } else if (auto fe = deleg.isFuncExp()) fd = fe.fd; assert(fd && fd.fbody); assert(fd.parameters); size_t numParams = fd.parameters.length; assert(numParams == 1 || numParams == 2); Parameter fparam = fd.type.isTypeFunction().parameterList[numParams - 1]; const wantRefValue = fparam.isReference(); Expressions args = Expressions(numParams); AssocArrayLiteralExp ae = aa.isAssocArrayLiteralExp(); if (!ae.keys || ae.keys.length == 0) return ctfeEmplaceExp!IntegerExp(deleg.loc, 0, Type.tsize_t); Expression eresult; for (size_t i = 0; i < ae.keys.length; ++i) { Expression ekey = (*ae.keys)[i]; Expression evalue = (*ae.values)[i]; if (wantRefValue) { Type t = evalue.type; evalue = ctfeEmplaceExp!IndexExp(deleg.loc, ae, ekey); evalue.type = t; } args[numParams - 1] = evalue; if (numParams == 2) args[0] = ekey; UnionExp ue = void; eresult = interpretFunction(&ue, fd, istate, &args, pthis); if (eresult == ue.exp()) eresult = ue.copy(); if (exceptionOrCantInterpret(eresult)) return eresult; if (eresult.isIntegerExp().getInteger() != 0) return eresult; } return eresult; } /// Returns: equivalent `StringExp` from `ArrayLiteralExp ale` containing only `IntegerExp` elements StringExp arrayLiteralToString(ArrayLiteralExp ale) { const len = ale.elements ? ale.elements.length : 0; const size = ale.type.nextOf().size(); StringExp impl(T)() { T[] result = new T[len]; foreach (i; 0 .. len) result[i] = cast(T) (*ale.elements)[i].isIntegerExp().getInteger(); return new StringExp(ale.loc, result[], len, cast(ubyte) size); } switch (size) { case 1: return impl!char(); case 2: return impl!wchar(); case 4: return impl!dchar(); default: assert(0); } } /* Decoding UTF strings for foreach loops. Duplicates the functionality of * the twelve _aApplyXXn functions in aApply.d in the runtime. */ private Expression foreachApplyUtf(UnionExp* pue, InterState* istate, Expression str, Expression deleg, bool rvs) { debug (LOG) { printf("foreachApplyUtf(%s, %s)\n", str.toChars(), deleg.toChars()); } FuncDeclaration fd = null; Expression pthis = null; if (auto de = deleg.isDelegateExp()) { fd = de.func; pthis = de.e1; } else if (auto fe = deleg.isFuncExp()) fd = fe.fd; assert(fd && fd.fbody); assert(fd.parameters); size_t numParams = fd.parameters.length; assert(numParams == 1 || numParams == 2); Type charType = (*fd.parameters)[numParams - 1].type; Type indexType = numParams == 2 ? (*fd.parameters)[0].type : Type.tsize_t; size_t len = cast(size_t)resolveArrayLength(str); if (len == 0) { emplaceExp!(IntegerExp)(pue, deleg.loc, 0, indexType); return pue.exp(); } UnionExp strTmp = void; str = resolveSlice(str, &strTmp); auto se = str.isStringExp(); if (auto ale = str.isArrayLiteralExp()) se = arrayLiteralToString(ale); if (!se) { error(str.loc, "CTFE internal error: cannot foreach `%s`", str.toChars()); return CTFEExp.cantexp; } Expressions args = Expressions(numParams); Expression eresult = null; // ded-store to prevent spurious warning // Buffers for encoding char[4] utf8buf = void; wchar[2] utf16buf = void; size_t start = rvs ? len : 0; size_t end = rvs ? 0 : len; for (size_t indx = start; indx != end;) { // Step 1: Decode the next dchar from the string. string errmsg = null; // Used for reporting decoding errors dchar rawvalue; // Holds the decoded dchar size_t currentIndex = indx; // The index of the decoded character // String literals size_t saveindx; // used for reverse iteration switch (se.sz) { case 1: { if (rvs) { // find the start of the string --indx; while (indx > 0 && ((se.getCodeUnit(indx) & 0xC0) == 0x80)) --indx; saveindx = indx; } auto slice = se.peekString(); errmsg = utf_decodeChar(slice, indx, rawvalue); if (rvs) indx = saveindx; break; } case 2: if (rvs) { // find the start --indx; auto wc = se.getCodeUnit(indx); if (wc >= 0xDC00 && wc <= 0xDFFF) --indx; saveindx = indx; } const slice = se.peekWstring(); errmsg = utf_decodeWchar(slice, indx, rawvalue); if (rvs) indx = saveindx; break; case 4: if (rvs) --indx; rawvalue = se.getCodeUnit(indx); if (!rvs) ++indx; break; default: assert(0); } if (errmsg) { error(deleg.loc, "`%.*s`", cast(int)errmsg.length, errmsg.ptr); return CTFEExp.cantexp; } // Step 2: encode the dchar in the target encoding int charlen = 1; // How many codepoints are involved? switch (charType.size()) { case 1: charlen = utf_codeLengthChar(rawvalue); utf_encodeChar(&utf8buf[0], rawvalue); break; case 2: charlen = utf_codeLengthWchar(rawvalue); utf_encodeWchar(&utf16buf[0], rawvalue); break; case 4: break; default: assert(0); } if (rvs) currentIndex = indx; // Step 3: call the delegate once for each code point // The index only needs to be set once if (numParams == 2) args[0] = ctfeEmplaceExp!IntegerExp(deleg.loc, currentIndex, indexType); Expression val = null; foreach (k; 0 .. charlen) { dchar codepoint; switch (charType.size()) { case 1: codepoint = utf8buf[k]; break; case 2: codepoint = utf16buf[k]; break; case 4: codepoint = rawvalue; break; default: assert(0); } val = ctfeEmplaceExp!IntegerExp(str.loc, codepoint, charType); args[numParams - 1] = val; UnionExp ue = void; eresult = interpretFunction(&ue, fd, istate, &args, pthis); if (eresult == ue.exp()) eresult = ue.copy(); if (exceptionOrCantInterpret(eresult)) return eresult; if (eresult.isIntegerExp().getInteger() != 0) return eresult; } } return eresult; } /* If this is a built-in function, return the interpreted result, * Otherwise, return NULL. */ private Expression evaluateIfBuiltin(UnionExp* pue, InterState* istate, const ref Loc loc, FuncDeclaration fd, Expressions* arguments, Expression pthis) { Expression e = null; size_t nargs = arguments ? arguments.length : 0; if (!pthis) { if (isBuiltin(fd) != BUILTIN.unimp) { Expressions args = Expressions(nargs); foreach (i, ref arg; args) { Expression earg = (*arguments)[i]; earg = interpret(earg, istate); if (exceptionOrCantInterpret(earg)) return earg; arg = earg; } e = eval_builtin(loc, fd, &args); if (!e) { error(loc, "cannot evaluate unimplemented builtin `%s` at compile time", fd.toChars()); e = CTFEExp.cantexp; } } } if (!pthis) { if (nargs == 1 || nargs == 3) { Expression firstarg = (*arguments)[0]; if (auto firstAAtype = firstarg.type.toBasetype().isTypeAArray()) { const id = fd.ident; if (nargs == 1) { if (id == Id.aaLen) return interpret_length(pue, istate, firstarg); if (fd.toParent2().ident == Id.object) { if (id == Id.keys) return interpret_keys(pue, istate, firstarg, firstAAtype.index.arrayOf()); if (id == Id.values) return interpret_values(pue, istate, firstarg, firstAAtype.nextOf().arrayOf()); if (id == Id.rehash) return interpret(pue, firstarg, istate); if (id == Id.dup) return interpret_dup(pue, istate, firstarg); } } else // (nargs == 3) { if (id == Id._aaApply) return interpret_aaApply(pue, istate, firstarg, (*arguments)[2]); if (id == Id._aaApply2) return interpret_aaApply(pue, istate, firstarg, (*arguments)[2]); } } } } if (pthis && !fd.fbody && fd.isCtorDeclaration() && fd.parent && fd.parent.parent && fd.parent.parent.ident == Id.object) { if (pthis.op == EXP.classReference && fd.parent.ident == Id.Throwable) { // At present, the constructors just copy their arguments into the struct. // But we might need some magic if stack tracing gets added to druntime. StructLiteralExp se = pthis.isClassReferenceExp().value; assert(arguments.length <= se.elements.length); foreach (i, arg; *arguments) { auto elem = interpret(arg, istate); if (exceptionOrCantInterpret(elem)) return elem; (*se.elements)[i] = elem; } return CTFEExp.voidexp; } } if (nargs == 1 && !pthis && (fd.ident == Id.criticalenter || fd.ident == Id.criticalexit)) { // Support synchronized{} as a no-op return CTFEExp.voidexp; } if (!pthis) { const idlen = fd.ident.toString().length; const id = fd.ident.toChars(); if (nargs == 2 && (idlen == 10 || idlen == 11) && !strncmp(id, "_aApply", 7)) { // Functions from aApply.d and aApplyR.d in the runtime bool rvs = (idlen == 11); // true if foreach_reverse char c = id[idlen - 3]; // char width: 'c', 'w', or 'd' char s = id[idlen - 2]; // string width: 'c', 'w', or 'd' char n = id[idlen - 1]; // numParams: 1 or 2. // There are 12 combinations if ((n == '1' || n == '2') && (c == 'c' || c == 'w' || c == 'd') && (s == 'c' || s == 'w' || s == 'd') && c != s) { Expression str = (*arguments)[0]; str = interpret(str, istate); if (exceptionOrCantInterpret(str)) return str; return foreachApplyUtf(pue, istate, str, (*arguments)[1], rvs); } } } return e; } private Expression evaluatePostblit(InterState* istate, Expression e) { auto ts = e.type.baseElemOf().isTypeStruct(); if (!ts) return null; StructDeclaration sd = ts.sym; if (!sd.postblit) return null; if (auto ale = e.isArrayLiteralExp()) { foreach (elem; *ale.elements) { if (auto ex = evaluatePostblit(istate, elem)) return ex; } return null; } if (e.op == EXP.structLiteral) { // e.__postblit() UnionExp ue = void; e = interpretFunction(&ue, sd.postblit, istate, null, e); if (e == ue.exp()) e = ue.copy(); if (exceptionOrCantInterpret(e)) return e; return null; } assert(0); } private Expression evaluateDtor(InterState* istate, Expression e) { auto ts = e.type.baseElemOf().isTypeStruct(); if (!ts) return null; StructDeclaration sd = ts.sym; if (!sd.dtor) return null; UnionExp ue = void; if (auto ale = e.isArrayLiteralExp()) { foreach_reverse (elem; *ale.elements) e = evaluateDtor(istate, elem); } else if (e.op == EXP.structLiteral) { // e.__dtor() e = interpretFunction(&ue, sd.dtor, istate, null, e); } else assert(0); if (exceptionOrCantInterpret(e)) { if (e == ue.exp()) e = ue.copy(); return e; } return null; } /*************************** CTFE Sanity Checks ***************************/ /* Setter functions for CTFE variable values. * These functions exist to check for compiler CTFE bugs. */ private bool hasValue(VarDeclaration vd) { return vd.ctfeAdrOnStack != VarDeclaration.AdrOnStackNone && getValue(vd) !is null; } // Don't check for validity private void setValueWithoutChecking(VarDeclaration vd, Expression newval) { ctfeGlobals.stack.setValue(vd, newval); } private void setValue(VarDeclaration vd, Expression newval) { //printf("setValue() vd: %s newval: %s\n", vd.toChars(), newval.toChars()); version (none) { if (!((vd.storage_class & (STC.out_ | STC.ref_)) ? isCtfeReferenceValid(newval) : isCtfeValueValid(newval))) { printf("[%s] vd = %s %s, newval = %s\n", vd.loc.toChars(), vd.type.toChars(), vd.toChars(), newval.toChars()); } } assert((vd.storage_class & (STC.out_ | STC.ref_)) ? isCtfeReferenceValid(newval) : isCtfeValueValid(newval)); ctfeGlobals.stack.setValue(vd, newval); } /** * Removes `_d_HookTraceImpl` if found from `ce` and `fd`. * This is needed for the CTFE interception code to be able to find hooks that are called though the hook's `*Trace` * wrapper. * * This is done by replacing `_d_HookTraceImpl!(T, Hook, errMsg)(..., parameters)` with `Hook(parameters)`. * Parameters: * ce = The CallExp that possible will be be replaced * fd = Fully resolve function declaration that `ce` would call */ private void removeHookTraceImpl(ref CallExp ce, ref FuncDeclaration fd) { if (fd.ident != Id._d_HookTraceImpl) return; auto oldCE = ce; // Get the Hook from the second template parameter TemplateInstance templateInstance = fd.parent.isTemplateInstance; RootObject hook = (*templateInstance.tiargs)[1]; assert(hook.isDsymbol(), "Expected _d_HookTraceImpl's second template parameter to be an alias to the hook!"); fd = (cast(Dsymbol)hook).isFuncDeclaration; // Remove the first three trace parameters auto arguments = new Expressions(); arguments.reserve(ce.arguments.length - 3); arguments.pushSlice((*ce.arguments)[3 .. $]); ce = ctfeEmplaceExp!CallExp(ce.loc, ctfeEmplaceExp!VarExp(ce.loc, fd, false), arguments); if (global.params.v.verbose) message("strip %s =>\n %s", oldCE.toChars(), ce.toChars()); } ldc-1.40.0-src/dmd/foreachvar.d0000644000000000000000000002116414727557031014671 0ustar rootroot/** * Utility to visit every variable in an expression. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/foreachvar.d, _foreachvar.d) * Documentation: https://dlang.org/phobos/dmd_foreachvar.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/foreachvar.d */ module dmd.foreachvar; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.dclass; import dmd.declaration; import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.func; import dmd.id; import dmd.identifier; import dmd.init; import dmd.initsem; import dmd.mtype; import dmd.postordervisitor; import dmd.printast; import dmd.root.array; import dmd.rootobject; import dmd.statement; import dmd.tokens; import dmd.visitor; /********************************************* * Visit each Expression in e, and call dgVar() on each variable declared in it. * Params: * e = expression tree to visit * dgVar = call when a variable is declared */ void foreachVar(Expression e, void delegate(VarDeclaration) dgVar) { if (!e) return; extern (C++) final class VarWalker : StoppableVisitor { alias visit = typeof(super).visit; extern (D) void delegate(VarDeclaration) dgVar; extern (D) this(void delegate(VarDeclaration) dgVar) scope @safe { this.dgVar = dgVar; } override void visit(Expression e) { } override void visit(ErrorExp e) { } override void visit(DeclarationExp e) { VarDeclaration v = e.declaration.isVarDeclaration(); if (!v) return; if (TupleDeclaration td = v.toAlias().isTupleDeclaration()) td.foreachVar((s) { dgVar(s.isVarDeclaration()); }); else dgVar(v); Dsymbol s = v.toAlias(); if (s == v && !v.isStatic() && v._init) { if (auto ie = v._init.isExpInitializer()) ie.exp.foreachVar(dgVar); } } override void visit(IndexExp e) { if (e.lengthVar) dgVar(e.lengthVar); } override void visit(SliceExp e) { if (e.lengthVar) dgVar(e.lengthVar); } } scope VarWalker v = new VarWalker(dgVar); walkPostorder(e, v); } /*************** * Transitively walk Statement s, pass Expressions to dgExp(), VarDeclarations to dgVar(). * Params: * s = Statement to traverse * dgExp = delegate to pass found Expressions to * dgVar = delegate to pass found VarDeclarations to */ void foreachExpAndVar(Statement s, void delegate(Expression) dgExp, void delegate(VarDeclaration) dgVar) { void visit(Statement s) { void visitExp(ExpStatement s) { if (s.exp) dgExp(s.exp); } void visitDtorExp(DtorExpStatement s) { if (s.exp) dgExp(s.exp); } void visitIf(IfStatement s) { dgExp(s.condition); visit(s.ifbody); visit(s.elsebody); } void visitDo(DoStatement s) { dgExp(s.condition); visit(s._body); } void visitFor(ForStatement s) { visit(s._init); if (s.condition) dgExp(s.condition); if (s.increment) dgExp(s.increment); visit(s._body); } void visitSwitch(SwitchStatement s) { dgExp(s.condition); // Note that the body contains the Case and Default // statements, so we only need to compile the expressions foreach (cs; *s.cases) { dgExp(cs.exp); } visit(s._body); } void visitCase(CaseStatement s) { visit(s.statement); } void visitReturn(ReturnStatement s) { if (s.exp) dgExp(s.exp); } void visitCompound(CompoundStatement s) { if (s.statements) { foreach (s2; *s.statements) { visit(s2); } } } void visitCompoundDeclaration(CompoundDeclarationStatement s) { visitCompound(s); } void visitUnrolledLoop(UnrolledLoopStatement s) { foreach (s2; *s.statements) { visit(s2); } } void visitScope(ScopeStatement s) { visit(s.statement); } void visitDefault(DefaultStatement s) { visit(s.statement); } void visitWith(WithStatement s) { // If it is with(Enum) {...}, just execute the body. if (s.exp.op == EXP.scope_ || s.exp.op == EXP.type) { } else { dgVar(s.wthis); dgExp(s.exp); } visit(s._body); } void visitTryCatch(TryCatchStatement s) { visit(s._body); foreach (ca; *s.catches) { if (ca.var) dgVar(ca.var); visit(ca.handler); } } void visitTryFinally(TryFinallyStatement s) { visit(s._body); visit(s.finalbody); } void visitThrow(ThrowStatement s) { dgExp(s.exp); } void visitLabel(LabelStatement s) { visit(s.statement); } if (!s) return; final switch (s.stmt) { case STMT.Exp: visitExp(s.isExpStatement()); break; case STMT.DtorExp: visitDtorExp(s.isDtorExpStatement()); break; case STMT.Compound: visitCompound(s.isCompoundStatement()); break; case STMT.CompoundDeclaration: visitCompoundDeclaration(s.isCompoundDeclarationStatement()); break; case STMT.UnrolledLoop: visitUnrolledLoop(s.isUnrolledLoopStatement()); break; case STMT.Scope: visitScope(s.isScopeStatement()); break; case STMT.Do: visitDo(s.isDoStatement()); break; case STMT.For: visitFor(s.isForStatement()); break; case STMT.If: visitIf(s.isIfStatement()); break; case STMT.Switch: visitSwitch(s.isSwitchStatement()); break; case STMT.Case: visitCase(s.isCaseStatement()); break; case STMT.Default: visitDefault(s.isDefaultStatement()); break; case STMT.Return: visitReturn(s.isReturnStatement()); break; case STMT.With: visitWith(s.isWithStatement()); break; case STMT.TryCatch: visitTryCatch(s.isTryCatchStatement()); break; case STMT.TryFinally: visitTryFinally(s.isTryFinallyStatement()); break; case STMT.Throw: visitThrow(s.isThrowStatement()); break; case STMT.Label: visitLabel(s.isLabelStatement()); break; case STMT.CompoundAsm: case STMT.Asm: case STMT.InlineAsm: case STMT.GccAsm: case STMT.Break: case STMT.Continue: case STMT.GotoDefault: case STMT.GotoCase: case STMT.SwitchError: case STMT.Goto: case STMT.Pragma: case STMT.Import: case STMT.Error: break; // ignore these case STMT.ScopeGuard: case STMT.Foreach: case STMT.ForeachRange: case STMT.Debug: case STMT.CaseRange: case STMT.StaticForeach: case STMT.StaticAssert: case STMT.Conditional: case STMT.While: case STMT.Forwarding: case STMT.Mixin: case STMT.Peel: case STMT.Synchronized: assert(0); // should have been rewritten } } visit(s); } ldc-1.40.0-src/dmd/id.h0000644000000000000000000000642514727557031013154 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 2017-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/id.h */ #pragma once #if IN_LLVM class Identifier; #endif struct Id { static void initialize(); #if IN_LLVM static Identifier *___in; static Identifier *__int; static Identifier *___out; static Identifier *__LOCAL_SIZE; static Identifier *dollar; static Identifier *ptr; static Identifier *offset; static Identifier *offsetof; static Identifier *__c_long; static Identifier *__c_ulong; static Identifier *__c_longlong; static Identifier *__c_ulonglong; static Identifier *__c_long_double; static Identifier *__c_complex_float; static Identifier *__c_complex_double; static Identifier *__c_complex_real; static Identifier *__switch; static Identifier *crt_constructor; static Identifier *crt_destructor; static Identifier *lib; static Identifier *linkerDirective; static Identifier *ldc; static Identifier *dcompute; static Identifier *dcPointer; static Identifier *object; static Identifier *core; static Identifier *etc; static Identifier *std; static Identifier *ensure; static Identifier *require; static Identifier *xopEquals; static Identifier *xopCmp; static Identifier *xtoHash; static Identifier *empty; static Identifier *ctfe; static Identifier *_arguments; static Identifier *_argptr; static Identifier *LDC_intrinsic; static Identifier *LDC_global_crt_ctor; static Identifier *LDC_global_crt_dtor; static Identifier *LDC_no_typeinfo; static Identifier *LDC_no_moduleinfo; static Identifier *LDC_alloca; static Identifier *LDC_va_start; static Identifier *LDC_va_copy; static Identifier *LDC_va_end; static Identifier *LDC_va_arg; static Identifier *LDC_fence; static Identifier *LDC_atomic_load; static Identifier *LDC_atomic_store; static Identifier *LDC_atomic_cmp_xchg; static Identifier *LDC_atomic_rmw; static Identifier *LDC_verbose; static Identifier *LDC_inline_asm; static Identifier *LDC_inline_ir; static Identifier *LDC_extern_weak; static Identifier *LDC_profile_instr; static Identifier *dcReflect; static Identifier *opencl; static Identifier *criticalenter; static Identifier *criticalexit; static Identifier *attributes; static Identifier *udaSection; static Identifier *udaOptStrategy; static Identifier *udaTarget; static Identifier *udaAssumeUsed; static Identifier *udaCallingConvention; static Identifier *udaWeak; static Identifier *udaAllocSize; static Identifier *udaLLVMAttr; static Identifier *udaLLVMFastMathFlag; static Identifier *udaKernel; static Identifier *udaCompute; static Identifier *udaDynamicCompile; static Identifier *udaDynamicCompileConst; static Identifier *udaDynamicCompileEmit; static Identifier *udaHidden; static Identifier *udaNoSanitize; static Identifier *udaNoSplitStack; static Identifier *io; #endif }; ldc-1.40.0-src/dmd/ast_node.h0000644000000000000000000000076214727557031014352 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/ast_node.h */ #pragma once #include "rootobject.h" class Visitor; class ASTNode : public RootObject { virtual void accept(Visitor*) = 0; }; ldc-1.40.0-src/dmd/entity.d0000644000000000000000000044233514727557031014074 0ustar rootroot/** * Defines the named entities to support the "\\&Entity;" escape sequence for strings / character literals. * * Specification $(LINK2 https://dlang.org/spec/entity.html, Named Character Entities) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/entity.d, _entity.d) * Documentation: https://dlang.org/phobos/dmd_entity.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/entity.d */ module dmd.entity; import core.stdc.ctype; nothrow: /********************************** * See if `name` is an HTML Named Entity * Params: * name = name of the entity * Returns: * code point corresponding to the named entity * ~0 for not recognized as a named entity */ public uint[2] HtmlNamedEntity(scope const char[] name) pure @nogc @safe { const firstC = tolower(name[0]); if (firstC >= 'a' && firstC <= 'z') { // Linear search (use hash table instead?) foreach (entity; namesTable[firstC - 'a']) { if (entity.name == name) return [entity.value, entity.value2]; } } return [0, 0]; } private: /********************************************* * Convert from named entity to its encoding. * For reference: * https://www.htmlhelp.com/reference/html40/entities/ * https://www.w3.org/2003/entities/2007/w3centities-f.ent */ struct NameId { string name; uint value; uint value2; } // @todo@ order namesTable and names? by frequency immutable NameId[][] namesTable = [ namesA, namesB, namesC, namesD, namesE, namesF, namesG, namesH, namesI, namesJ, namesK, namesL, namesM, namesN, namesO, namesP, namesQ, namesR, namesS, namesT, namesU, namesV, namesW, namesX, namesY, namesZ ]; immutable NameId[] namesA = [ {"Aacgr", 0x00386}, // GREEK CAPITAL LETTER ALPHA WITH TONOS {"aacgr", 0x003AC}, // GREEK SMALL LETTER ALPHA WITH TONOS {"Aacute", 0x000C1}, // LATIN CAPITAL LETTER A WITH ACUTE {"aacute", 0x000E1}, // LATIN SMALL LETTER A WITH ACUTE {"Abreve", 0x00102}, // LATIN CAPITAL LETTER A WITH BREVE {"abreve", 0x00103}, // LATIN SMALL LETTER A WITH BREVE {"ac", 0x0223E}, // INVERTED LAZY S {"acd", 0x0223F}, // SINE WAVE {"acE", 0x0223E, 0x00333}, // INVERTED LAZY S with double underline {"Acirc", 0x000C2}, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX {"acirc", 0x000E2}, // LATIN SMALL LETTER A WITH CIRCUMFLEX {"acute", 0x000B4}, // ACUTE ACCENT {"Acy", 0x00410}, // CYRILLIC CAPITAL LETTER A {"acy", 0x00430}, // CYRILLIC SMALL LETTER A {"AElig", 0x000C6}, // LATIN CAPITAL LETTER AE {"aelig", 0x000E6}, // LATIN SMALL LETTER AE {"af", 0x02061}, // FUNCTION APPLICATION {"Afr", 0x1D504}, // MATHEMATICAL FRAKTUR CAPITAL A {"afr", 0x1D51E}, // MATHEMATICAL FRAKTUR SMALL A {"Agr", 0x00391}, // GREEK CAPITAL LETTER ALPHA {"agr", 0x003B1}, // GREEK SMALL LETTER ALPHA {"Agrave", 0x000C0}, // LATIN CAPITAL LETTER A WITH GRAVE {"agrave", 0x000E0}, // LATIN SMALL LETTER A WITH GRAVE {"alefsym", 0x02135}, // ALEF SYMBOL {"aleph", 0x02135}, // ALEF SYMBOL {"Alpha", 0x00391}, // GREEK CAPITAL LETTER ALPHA {"alpha", 0x003B1}, // GREEK SMALL LETTER ALPHA {"Amacr", 0x00100}, // LATIN CAPITAL LETTER A WITH MACRON {"amacr", 0x00101}, // LATIN SMALL LETTER A WITH MACRON {"amalg", 0x02A3F}, // AMALGAMATION OR COPRODUCT {"amp", 0x00026}, // AMPERSAND {"AMP", 0x00026}, // AMPERSAND {"and", 0x02227}, // LOGICAL AND {"And", 0x02A53}, // DOUBLE LOGICAL AND {"andand", 0x02A55}, // TWO INTERSECTING LOGICAL AND {"andd", 0x02A5C}, // LOGICAL AND WITH HORIZONTAL DASH {"andslope", 0x02A58}, // SLOPING LARGE AND {"andv", 0x02A5A}, // LOGICAL AND WITH MIDDLE STEM {"ang", 0x02220}, // ANGLE {"ange", 0x029A4}, // ANGLE WITH UNDERBAR {"angle", 0x02220}, // ANGLE {"angmsd", 0x02221}, // MEASURED ANGLE {"angmsdaa", 0x029A8}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT {"angmsdab", 0x029A9}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT {"angmsdac", 0x029AA}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT {"angmsdad", 0x029AB}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT {"angmsdae", 0x029AC}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP {"angmsdaf", 0x029AD}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP {"angmsdag", 0x029AE}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN {"angmsdah", 0x029AF}, // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN {"angrt", 0x0221F}, // RIGHT ANGLE {"angrtvb", 0x022BE}, // RIGHT ANGLE WITH ARC {"angrtvbd", 0x0299D}, // MEASURED RIGHT ANGLE WITH DOT {"angsph", 0x02222}, // SPHERICAL ANGLE {"angst", 0x000C5}, // LATIN CAPITAL LETTER A WITH RING ABOVE {"angzarr", 0x0237C}, // RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW {"Aogon", 0x00104}, // LATIN CAPITAL LETTER A WITH OGONEK {"aogon", 0x00105}, // LATIN SMALL LETTER A WITH OGONEK {"Aopf", 0x1D538}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL A {"aopf", 0x1D552}, // MATHEMATICAL DOUBLE-STRUCK SMALL A {"ap", 0x02248}, // ALMOST EQUAL TO {"apacir", 0x02A6F}, // ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT {"ape", 0x0224A}, // ALMOST EQUAL OR EQUAL TO {"apE", 0x02A70}, // APPROXIMATELY EQUAL OR EQUAL TO {"apid", 0x0224B}, // TRIPLE TILDE {"apos", 0x00027}, // APOSTROPHE {"ApplyFunction", 0x02061}, // FUNCTION APPLICATION {"approx", 0x02248}, // ALMOST EQUAL TO {"approxeq", 0x0224A}, // ALMOST EQUAL OR EQUAL TO {"Aring", 0x000C5}, // LATIN CAPITAL LETTER A WITH RING ABOVE {"aring", 0x000E5}, // LATIN SMALL LETTER A WITH RING ABOVE {"Ascr", 0x1D49C}, // MATHEMATICAL SCRIPT CAPITAL A {"ascr", 0x1D4B6}, // MATHEMATICAL SCRIPT SMALL A {"Assign", 0x02254}, // COLON EQUALS {"ast", 0x0002A}, // ASTERISK {"asymp", 0x02248}, // ALMOST EQUAL TO {"asympeq", 0x0224D}, // EQUIVALENT TO {"Atilde", 0x000C3}, // LATIN CAPITAL LETTER A WITH TILDE {"atilde", 0x000E3}, // LATIN SMALL LETTER A WITH TILDE {"Auml", 0x000C4}, // LATIN CAPITAL LETTER A WITH DIAERESIS {"auml", 0x000E4}, // LATIN SMALL LETTER A WITH DIAERESIS {"awconint", 0x02233}, // ANTICLOCKWISE CONTOUR INTEGRAL {"awint", 0x02A11}, // ANTICLOCKWISE INTEGRATION ]; immutable NameId[] namesB = [ {"backcong", 0x0224C}, // ALL EQUAL TO {"backepsilon", 0x003F6}, // GREEK REVERSED LUNATE EPSILON SYMBOL {"backprime", 0x02035}, // REVERSED PRIME {"backsim", 0x0223D}, // REVERSED TILDE {"backsimeq", 0x022CD}, // REVERSED TILDE EQUALS {"Backslash", 0x02216}, // SET MINUS {"Barv", 0x02AE7}, // SHORT DOWN TACK WITH OVERBAR {"barvee", 0x022BD}, // NOR {"barwed", 0x02305}, // PROJECTIVE {"Barwed", 0x02306}, // PERSPECTIVE {"barwedge", 0x02305}, // PROJECTIVE {"bbrk", 0x023B5}, // BOTTOM SQUARE BRACKET {"bbrktbrk", 0x023B6}, // BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET {"bcong", 0x0224C}, // ALL EQUAL TO {"Bcy", 0x00411}, // CYRILLIC CAPITAL LETTER BE {"bcy", 0x00431}, // CYRILLIC SMALL LETTER BE {"bdquo", 0x0201E}, // DOUBLE LOW-9 QUOTATION MARK {"becaus", 0x02235}, // BECAUSE {"because", 0x02235}, // BECAUSE {"Because", 0x02235}, // BECAUSE {"bemptyv", 0x029B0}, // REVERSED EMPTY SET {"bepsi", 0x003F6}, // GREEK REVERSED LUNATE EPSILON SYMBOL {"bernou", 0x0212C}, // SCRIPT CAPITAL B {"Bernoullis", 0x0212C}, // SCRIPT CAPITAL B {"Beta", 0x00392}, // GREEK CAPITAL LETTER BETA {"beta", 0x003B2}, // GREEK SMALL LETTER BETA {"beth", 0x02136}, // BET SYMBOL {"between", 0x0226C}, // BETWEEN {"Bfr", 0x1D505}, // MATHEMATICAL FRAKTUR CAPITAL B {"bfr", 0x1D51F}, // MATHEMATICAL FRAKTUR SMALL B {"Bgr", 0x00392}, // GREEK CAPITAL LETTER BETA {"bgr", 0x003B2}, // GREEK SMALL LETTER BETA {"bigcap", 0x022C2}, // N-ARY INTERSECTION {"bigcirc", 0x025EF}, // LARGE CIRCLE {"bigcup", 0x022C3}, // N-ARY UNION {"bigodot", 0x02A00}, // N-ARY CIRCLED DOT OPERATOR {"bigoplus", 0x02A01}, // N-ARY CIRCLED PLUS OPERATOR {"bigotimes", 0x02A02}, // N-ARY CIRCLED TIMES OPERATOR {"bigsqcup", 0x02A06}, // N-ARY SQUARE UNION OPERATOR {"bigstar", 0x02605}, // BLACK STAR {"bigtriangledown", 0x025BD}, // WHITE DOWN-POINTING TRIANGLE {"bigtriangleup", 0x025B3}, // WHITE UP-POINTING TRIANGLE {"biguplus", 0x02A04}, // N-ARY UNION OPERATOR WITH PLUS {"bigvee", 0x022C1}, // N-ARY LOGICAL OR {"bigwedge", 0x022C0}, // N-ARY LOGICAL AND {"bkarow", 0x0290D}, // RIGHTWARDS DOUBLE DASH ARROW {"blacklozenge", 0x029EB}, // BLACK LOZENGE {"blacksquare", 0x025AA}, // BLACK SMALL SQUARE {"blacktriangle", 0x025B4}, // BLACK UP-POINTING SMALL TRIANGLE {"blacktriangledown", 0x025BE}, // BLACK DOWN-POINTING SMALL TRIANGLE {"blacktriangleleft", 0x025C2}, // BLACK LEFT-POINTING SMALL TRIANGLE {"blacktriangleright", 0x025B8}, // BLACK RIGHT-POINTING SMALL TRIANGLE {"blank", 0x02423}, // OPEN BOX {"blk12", 0x02592}, // MEDIUM SHADE {"blk14", 0x02591}, // LIGHT SHADE {"blk34", 0x02593}, // DARK SHADE {"block", 0x02588}, // FULL BLOCK {"bne", 0x0003D, 0x020E5}, // EQUALS SIGN with reverse slash {"bnequiv", 0x02261, 0x020E5}, // IDENTICAL TO with reverse slash {"bnot", 0x02310}, // REVERSED NOT SIGN {"bNot", 0x02AED}, // REVERSED DOUBLE STROKE NOT SIGN {"Bopf", 0x1D539}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL B {"bopf", 0x1D553}, // MATHEMATICAL DOUBLE-STRUCK SMALL B {"bot", 0x022A5}, // UP TACK {"bottom", 0x022A5}, // UP TACK {"bowtie", 0x022C8}, // BOWTIE {"boxbox", 0x029C9}, // TWO JOINED SQUARES {"boxdl", 0x02510}, // BOX DRAWINGS LIGHT DOWN AND LEFT {"boxdL", 0x02555}, // BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE {"boxDl", 0x02556}, // BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE {"boxDL", 0x02557}, // BOX DRAWINGS DOUBLE DOWN AND LEFT {"boxdr", 0x0250C}, // BOX DRAWINGS LIGHT DOWN AND RIGHT {"boxdR", 0x02552}, // BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE {"boxDr", 0x02553}, // BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE {"boxDR", 0x02554}, // BOX DRAWINGS DOUBLE DOWN AND RIGHT {"boxh", 0x02500}, // BOX DRAWINGS LIGHT HORIZONTAL {"boxH", 0x02550}, // BOX DRAWINGS DOUBLE HORIZONTAL {"boxhd", 0x0252C}, // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL {"boxHd", 0x02564}, // BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE {"boxhD", 0x02565}, // BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE {"boxHD", 0x02566}, // BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL {"boxhu", 0x02534}, // BOX DRAWINGS LIGHT UP AND HORIZONTAL {"boxHu", 0x02567}, // BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE {"boxhU", 0x02568}, // BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE {"boxHU", 0x02569}, // BOX DRAWINGS DOUBLE UP AND HORIZONTAL {"boxminus", 0x0229F}, // SQUARED MINUS {"boxplus", 0x0229E}, // SQUARED PLUS {"boxtimes", 0x022A0}, // SQUARED TIMES {"boxul", 0x02518}, // BOX DRAWINGS LIGHT UP AND LEFT {"boxuL", 0x0255B}, // BOX DRAWINGS UP SINGLE AND LEFT DOUBLE {"boxUl", 0x0255C}, // BOX DRAWINGS UP DOUBLE AND LEFT SINGLE {"boxUL", 0x0255D}, // BOX DRAWINGS DOUBLE UP AND LEFT {"boxur", 0x02514}, // BOX DRAWINGS LIGHT UP AND RIGHT {"boxuR", 0x02558}, // BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE {"boxUr", 0x02559}, // BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE {"boxUR", 0x0255A}, // BOX DRAWINGS DOUBLE UP AND RIGHT {"boxv", 0x02502}, // BOX DRAWINGS LIGHT VERTICAL {"boxV", 0x02551}, // BOX DRAWINGS DOUBLE VERTICAL {"boxvh", 0x0253C}, // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL {"boxvH", 0x0256A}, // BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE {"boxVh", 0x0256B}, // BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE {"boxVH", 0x0256C}, // BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL {"boxvl", 0x02524}, // BOX DRAWINGS LIGHT VERTICAL AND LEFT {"boxvL", 0x02561}, // BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE {"boxVl", 0x02562}, // BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE {"boxVL", 0x02563}, // BOX DRAWINGS DOUBLE VERTICAL AND LEFT {"boxvr", 0x0251C}, // BOX DRAWINGS LIGHT VERTICAL AND RIGHT {"boxvR", 0x0255E}, // BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE {"boxVr", 0x0255F}, // BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE {"boxVR", 0x02560}, // BOX DRAWINGS DOUBLE VERTICAL AND RIGHT {"bprime", 0x02035}, // REVERSED PRIME {"breve", 0x002D8}, // BREVE {"Breve", 0x002D8}, // BREVE {"brvbar", 0x000A6}, // BROKEN BAR {"Bscr", 0x0212C}, // SCRIPT CAPITAL B {"bscr", 0x1D4B7}, // MATHEMATICAL SCRIPT SMALL B {"bsemi", 0x0204F}, // REVERSED SEMICOLON {"bsim", 0x0223D}, // REVERSED TILDE {"bsime", 0x022CD}, // REVERSED TILDE EQUALS {"bsol", 0x0005C}, // REVERSE SOLIDUS {"bsolb", 0x029C5}, // SQUARED FALLING DIAGONAL SLASH {"bsolhsub", 0x027C8}, // REVERSE SOLIDUS PRECEDING SUBSET {"bull", 0x02022}, // BULLET {"bullet", 0x02022}, // BULLET {"bump", 0x0224E}, // GEOMETRICALLY EQUIVALENT TO {"bumpe", 0x0224F}, // DIFFERENCE BETWEEN {"bumpE", 0x02AAE}, // EQUALS SIGN WITH BUMPY ABOVE {"Bumpeq", 0x0224E}, // GEOMETRICALLY EQUIVALENT TO {"bumpeq", 0x0224F}, // DIFFERENCE BETWEEN ]; immutable NameId[] namesC = [ {"Cacute", 0x00106}, // LATIN CAPITAL LETTER C WITH ACUTE {"cacute", 0x00107}, // LATIN SMALL LETTER C WITH ACUTE {"cap", 0x02229}, // INTERSECTION {"Cap", 0x022D2}, // DOUBLE INTERSECTION {"capand", 0x02A44}, // INTERSECTION WITH LOGICAL AND {"capbrcup", 0x02A49}, // INTERSECTION ABOVE BAR ABOVE UNION {"capcap", 0x02A4B}, // INTERSECTION BESIDE AND JOINED WITH INTERSECTION {"capcup", 0x02A47}, // INTERSECTION ABOVE UNION {"capdot", 0x02A40}, // INTERSECTION WITH DOT {"CapitalDifferentialD", 0x02145}, // DOUBLE-STRUCK ITALIC CAPITAL D {"caps", 0x02229, 0x0FE00}, // INTERSECTION with serifs {"caret", 0x02041}, // CARET INSERTION POINT {"caron", 0x002C7}, // CARON {"Cayleys", 0x0212D}, // BLACK-LETTER CAPITAL C {"ccaps", 0x02A4D}, // CLOSED INTERSECTION WITH SERIFS {"Ccaron", 0x0010C}, // LATIN CAPITAL LETTER C WITH CARON {"ccaron", 0x0010D}, // LATIN SMALL LETTER C WITH CARON {"Ccedil", 0x000C7}, // LATIN CAPITAL LETTER C WITH CEDILLA {"ccedil", 0x000E7}, // LATIN SMALL LETTER C WITH CEDILLA {"Ccirc", 0x00108}, // LATIN CAPITAL LETTER C WITH CIRCUMFLEX {"ccirc", 0x00109}, // LATIN SMALL LETTER C WITH CIRCUMFLEX {"Cconint", 0x02230}, // VOLUME INTEGRAL {"ccups", 0x02A4C}, // CLOSED UNION WITH SERIFS {"ccupssm", 0x02A50}, // CLOSED UNION WITH SERIFS AND SMASH PRODUCT {"Cdot", 0x0010A}, // LATIN CAPITAL LETTER C WITH DOT ABOVE {"cdot", 0x0010B}, // LATIN SMALL LETTER C WITH DOT ABOVE {"cedil", 0x000B8}, // CEDILLA {"Cedilla", 0x000B8}, // CEDILLA {"cemptyv", 0x029B2}, // EMPTY SET WITH SMALL CIRCLE ABOVE {"cent", 0x000A2}, // CENT SIGN {"centerdot", 0x000B7}, // MIDDLE DOT {"CenterDot", 0x000B7}, // MIDDLE DOT {"Cfr", 0x0212D}, // BLACK-LETTER CAPITAL C {"cfr", 0x1D520}, // MATHEMATICAL FRAKTUR SMALL C {"CHcy", 0x00427}, // CYRILLIC CAPITAL LETTER CHE {"chcy", 0x00447}, // CYRILLIC SMALL LETTER CHE {"check", 0x02713}, // CHECK MARK {"checkmark", 0x02713}, // CHECK MARK {"Chi", 0x003A7}, // GREEK CAPITAL LETTER CHI {"chi", 0x003C7}, // GREEK SMALL LETTER CHI {"cir", 0x025CB}, // WHITE CIRCLE {"circ", 0x002C6}, // MODIFIER LETTER CIRCUMFLEX ACCENT {"circeq", 0x02257}, // RING EQUAL TO {"circlearrowleft", 0x021BA}, // ANTICLOCKWISE OPEN CIRCLE ARROW {"circlearrowright", 0x021BB}, // CLOCKWISE OPEN CIRCLE ARROW {"circledast", 0x0229B}, // CIRCLED ASTERISK OPERATOR {"circledcirc", 0x0229A}, // CIRCLED RING OPERATOR {"circleddash", 0x0229D}, // CIRCLED DASH {"CircleDot", 0x02299}, // CIRCLED DOT OPERATOR {"circledR", 0x000AE}, // REGISTERED SIGN {"circledS", 0x024C8}, // CIRCLED LATIN CAPITAL LETTER S {"CircleMinus", 0x02296}, // CIRCLED MINUS {"CirclePlus", 0x02295}, // CIRCLED PLUS {"CircleTimes", 0x02297}, // CIRCLED TIMES {"cire", 0x02257}, // RING EQUAL TO {"cirE", 0x029C3}, // CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT {"cirfnint", 0x02A10}, // CIRCULATION FUNCTION {"cirmid", 0x02AEF}, // VERTICAL LINE WITH CIRCLE ABOVE {"cirscir", 0x029C2}, // CIRCLE WITH SMALL CIRCLE TO THE RIGHT {"ClockwiseContourIntegral", 0x02232}, // CLOCKWISE CONTOUR INTEGRAL {"CloseCurlyDoubleQuote", 0x0201D}, // RIGHT DOUBLE QUOTATION MARK {"CloseCurlyQuote", 0x02019}, // RIGHT SINGLE QUOTATION MARK {"clubs", 0x02663}, // BLACK CLUB SUIT {"clubsuit", 0x02663}, // BLACK CLUB SUIT {"colon", 0x0003A}, // COLON {"Colon", 0x02237}, // PROPORTION {"colone", 0x02254}, // COLON EQUALS {"Colone", 0x02A74}, // DOUBLE COLON EQUAL {"coloneq", 0x02254}, // COLON EQUALS {"comma", 0x0002C}, // COMMA {"commat", 0x00040}, // COMMERCIAL AT {"comp", 0x02201}, // COMPLEMENT {"compfn", 0x02218}, // RING OPERATOR {"complement", 0x02201}, // COMPLEMENT {"complexes", 0x02102}, // DOUBLE-STRUCK CAPITAL C {"cong", 0x02245}, // APPROXIMATELY EQUAL TO {"congdot", 0x02A6D}, // CONGRUENT WITH DOT ABOVE {"Congruent", 0x02261}, // IDENTICAL TO {"conint", 0x0222E}, // CONTOUR INTEGRAL {"Conint", 0x0222F}, // SURFACE INTEGRAL {"ContourIntegral", 0x0222E}, // CONTOUR INTEGRAL {"Copf", 0x02102}, // DOUBLE-STRUCK CAPITAL C {"copf", 0x1D554}, // MATHEMATICAL DOUBLE-STRUCK SMALL C {"coprod", 0x02210}, // N-ARY COPRODUCT {"Coproduct", 0x02210}, // N-ARY COPRODUCT {"copy", 0x000A9}, // COPYRIGHT SIGN {"COPY", 0x000A9}, // COPYRIGHT SIGN {"copysr", 0x02117}, // SOUND RECORDING COPYRIGHT {"CounterClockwiseContourIntegral", 0x02233}, // ANTICLOCKWISE CONTOUR INTEGRAL {"crarr", 0x021B5}, // DOWNWARDS ARROW WITH CORNER LEFTWARDS {"cross", 0x02717}, // BALLOT X {"Cross", 0x02A2F}, // VECTOR OR CROSS PRODUCT {"Cscr", 0x1D49E}, // MATHEMATICAL SCRIPT CAPITAL C {"cscr", 0x1D4B8}, // MATHEMATICAL SCRIPT SMALL C {"csub", 0x02ACF}, // CLOSED SUBSET {"csube", 0x02AD1}, // CLOSED SUBSET OR EQUAL TO {"csup", 0x02AD0}, // CLOSED SUPERSET {"csupe", 0x02AD2}, // CLOSED SUPERSET OR EQUAL TO {"ctdot", 0x022EF}, // MIDLINE HORIZONTAL ELLIPSIS {"cudarrl", 0x02938}, // RIGHT-SIDE ARC CLOCKWISE ARROW {"cudarrr", 0x02935}, // ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS {"cuepr", 0x022DE}, // EQUAL TO OR PRECEDES {"cuesc", 0x022DF}, // EQUAL TO OR SUCCEEDS {"cularr", 0x021B6}, // ANTICLOCKWISE TOP SEMICIRCLE ARROW {"cularrp", 0x0293D}, // TOP ARC ANTICLOCKWISE ARROW WITH PLUS {"cup", 0x0222A}, // UNION {"Cup", 0x022D3}, // DOUBLE UNION {"cupbrcap", 0x02A48}, // UNION ABOVE BAR ABOVE INTERSECTION {"CupCap", 0x0224D}, // EQUIVALENT TO {"cupcap", 0x02A46}, // UNION ABOVE INTERSECTION {"cupcup", 0x02A4A}, // UNION BESIDE AND JOINED WITH UNION {"cupdot", 0x0228D}, // MULTISET MULTIPLICATION {"cupor", 0x02A45}, // UNION WITH LOGICAL OR {"cups", 0x0222A, 0x0FE00}, // UNION with serifs {"curarr", 0x021B7}, // CLOCKWISE TOP SEMICIRCLE ARROW {"curarrm", 0x0293C}, // TOP ARC CLOCKWISE ARROW WITH MINUS {"curlyeqprec", 0x022DE}, // EQUAL TO OR PRECEDES {"curlyeqsucc", 0x022DF}, // EQUAL TO OR SUCCEEDS {"curlyvee", 0x022CE}, // CURLY LOGICAL OR {"curlywedge", 0x022CF}, // CURLY LOGICAL AND {"curren", 0x000A4}, // CURRENCY SIGN {"curvearrowleft", 0x021B6}, // ANTICLOCKWISE TOP SEMICIRCLE ARROW {"curvearrowright", 0x021B7}, // CLOCKWISE TOP SEMICIRCLE ARROW {"cuvee", 0x022CE}, // CURLY LOGICAL OR {"cuwed", 0x022CF}, // CURLY LOGICAL AND {"cwconint", 0x02232}, // CLOCKWISE CONTOUR INTEGRAL {"cwint", 0x02231}, // CLOCKWISE INTEGRAL {"cylcty", 0x0232D}, // CYLINDRICITY ]; immutable NameId[] namesD = [ {"dagger", 0x02020}, // DAGGER {"Dagger", 0x02021}, // DOUBLE DAGGER {"daleth", 0x02138}, // DALET SYMBOL {"darr", 0x02193}, // DOWNWARDS ARROW {"Darr", 0x021A1}, // DOWNWARDS TWO HEADED ARROW {"dArr", 0x021D3}, // DOWNWARDS DOUBLE ARROW {"dash", 0x02010}, // HYPHEN {"dashv", 0x022A3}, // LEFT TACK {"Dashv", 0x02AE4}, // VERTICAL BAR DOUBLE LEFT TURNSTILE {"dbkarow", 0x0290F}, // RIGHTWARDS TRIPLE DASH ARROW {"dblac", 0x002DD}, // DOUBLE ACUTE ACCENT {"Dcaron", 0x0010E}, // LATIN CAPITAL LETTER D WITH CARON {"dcaron", 0x0010F}, // LATIN SMALL LETTER D WITH CARON {"Dcy", 0x00414}, // CYRILLIC CAPITAL LETTER DE {"dcy", 0x00434}, // CYRILLIC SMALL LETTER DE {"DD", 0x02145}, // DOUBLE-STRUCK ITALIC CAPITAL D {"dd", 0x02146}, // DOUBLE-STRUCK ITALIC SMALL D {"ddagger", 0x02021}, // DOUBLE DAGGER {"ddarr", 0x021CA}, // DOWNWARDS PAIRED ARROWS {"DDotrahd", 0x02911}, // RIGHTWARDS ARROW WITH DOTTED STEM {"ddotseq", 0x02A77}, // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW {"deg", 0x000B0}, // DEGREE SIGN {"Del", 0x02207}, // NABLA {"Delta", 0x00394}, // GREEK CAPITAL LETTER DELTA {"delta", 0x003B4}, // GREEK SMALL LETTER DELTA {"demptyv", 0x029B1}, // EMPTY SET WITH OVERBAR {"dfisht", 0x0297F}, // DOWN FISH TAIL {"Dfr", 0x1D507}, // MATHEMATICAL FRAKTUR CAPITAL D {"dfr", 0x1D521}, // MATHEMATICAL FRAKTUR SMALL D {"Dgr", 0x00394}, // GREEK CAPITAL LETTER DELTA {"dgr", 0x003B4}, // GREEK SMALL LETTER DELTA {"dHar", 0x02965}, // DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT {"dharl", 0x021C3}, // DOWNWARDS HARPOON WITH BARB LEFTWARDS {"dharr", 0x021C2}, // DOWNWARDS HARPOON WITH BARB RIGHTWARDS {"DiacriticalAcute", 0x000B4}, // ACUTE ACCENT {"DiacriticalDot", 0x002D9}, // DOT ABOVE {"DiacriticalDoubleAcute", 0x002DD}, // DOUBLE ACUTE ACCENT {"DiacriticalGrave", 0x00060}, // GRAVE ACCENT {"DiacriticalTilde", 0x002DC}, // SMALL TILDE {"diam", 0x022C4}, // DIAMOND OPERATOR {"diamond", 0x022C4}, // DIAMOND OPERATOR {"Diamond", 0x022C4}, // DIAMOND OPERATOR {"diamondsuit", 0x02666}, // BLACK DIAMOND SUIT {"diams", 0x02666}, // BLACK DIAMOND SUIT {"die", 0x000A8}, // DIAERESIS {"DifferentialD", 0x02146}, // DOUBLE-STRUCK ITALIC SMALL D {"digamma", 0x003DD}, // GREEK SMALL LETTER DIGAMMA {"disin", 0x022F2}, // ELEMENT OF WITH LONG HORIZONTAL STROKE {"div", 0x000F7}, // DIVISION SIGN {"divide", 0x000F7}, // DIVISION SIGN {"divideontimes", 0x022C7}, // DIVISION TIMES {"divonx", 0x022C7}, // DIVISION TIMES {"DJcy", 0x00402}, // CYRILLIC CAPITAL LETTER DJE {"djcy", 0x00452}, // CYRILLIC SMALL LETTER DJE {"dlcorn", 0x0231E}, // BOTTOM LEFT CORNER {"dlcrop", 0x0230D}, // BOTTOM LEFT CROP {"dollar", 0x00024}, // DOLLAR SIGN {"Dopf", 0x1D53B}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL D {"dopf", 0x1D555}, // MATHEMATICAL DOUBLE-STRUCK SMALL D {"Dot", 0x000A8}, // DIAERESIS {"dot", 0x002D9}, // DOT ABOVE {"DotDot", 0x020DC}, // COMBINING FOUR DOTS ABOVE {"doteq", 0x02250}, // APPROACHES THE LIMIT {"doteqdot", 0x02251}, // GEOMETRICALLY EQUAL TO {"DotEqual", 0x02250}, // APPROACHES THE LIMIT {"dotminus", 0x02238}, // DOT MINUS {"dotplus", 0x02214}, // DOT PLUS {"dotsquare", 0x022A1}, // SQUARED DOT OPERATOR {"doublebarwedge", 0x02306}, // PERSPECTIVE {"DoubleContourIntegral", 0x0222F}, // SURFACE INTEGRAL {"DoubleDot", 0x000A8}, // DIAERESIS {"DoubleDownArrow", 0x021D3}, // DOWNWARDS DOUBLE ARROW {"DoubleLeftArrow", 0x021D0}, // LEFTWARDS DOUBLE ARROW {"DoubleLeftRightArrow", 0x021D4}, // LEFT RIGHT DOUBLE ARROW {"DoubleLeftTee", 0x02AE4}, // VERTICAL BAR DOUBLE LEFT TURNSTILE {"DoubleLongLeftArrow", 0x027F8}, // LONG LEFTWARDS DOUBLE ARROW {"DoubleLongLeftRightArrow", 0x027FA}, // LONG LEFT RIGHT DOUBLE ARROW {"DoubleLongRightArrow", 0x027F9}, // LONG RIGHTWARDS DOUBLE ARROW {"DoubleRightArrow", 0x021D2}, // RIGHTWARDS DOUBLE ARROW {"DoubleRightTee", 0x022A8}, // TRUE {"DoubleUpArrow", 0x021D1}, // UPWARDS DOUBLE ARROW {"DoubleUpDownArrow", 0x021D5}, // UP DOWN DOUBLE ARROW {"DoubleVerticalBar", 0x02225}, // PARALLEL TO {"downarrow", 0x02193}, // DOWNWARDS ARROW {"DownArrow", 0x02193}, // DOWNWARDS ARROW {"Downarrow", 0x021D3}, // DOWNWARDS DOUBLE ARROW {"DownArrowBar", 0x02913}, // DOWNWARDS ARROW TO BAR {"DownArrowUpArrow", 0x021F5}, // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW {"DownBreve", 0x00311}, // COMBINING INVERTED BREVE {"downdownarrows", 0x021CA}, // DOWNWARDS PAIRED ARROWS {"downharpoonleft", 0x021C3}, // DOWNWARDS HARPOON WITH BARB LEFTWARDS {"downharpoonright", 0x021C2}, // DOWNWARDS HARPOON WITH BARB RIGHTWARDS {"DownLeftRightVector", 0x02950}, // LEFT BARB DOWN RIGHT BARB DOWN HARPOON {"DownLeftTeeVector", 0x0295E}, // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR {"DownLeftVector", 0x021BD}, // LEFTWARDS HARPOON WITH BARB DOWNWARDS {"DownLeftVectorBar", 0x02956}, // LEFTWARDS HARPOON WITH BARB DOWN TO BAR {"DownRightTeeVector", 0x0295F}, // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR {"DownRightVector", 0x021C1}, // RIGHTWARDS HARPOON WITH BARB DOWNWARDS {"DownRightVectorBar", 0x02957}, // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR {"DownTee", 0x022A4}, // DOWN TACK {"DownTeeArrow", 0x021A7}, // DOWNWARDS ARROW FROM BAR {"drbkarow", 0x02910}, // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW {"drcorn", 0x0231F}, // BOTTOM RIGHT CORNER {"drcrop", 0x0230C}, // BOTTOM RIGHT CROP {"Dscr", 0x1D49F}, // MATHEMATICAL SCRIPT CAPITAL D {"dscr", 0x1D4B9}, // MATHEMATICAL SCRIPT SMALL D {"DScy", 0x00405}, // CYRILLIC CAPITAL LETTER DZE {"dscy", 0x00455}, // CYRILLIC SMALL LETTER DZE {"dsol", 0x029F6}, // SOLIDUS WITH OVERBAR {"Dstrok", 0x00110}, // LATIN CAPITAL LETTER D WITH STROKE {"dstrok", 0x00111}, // LATIN SMALL LETTER D WITH STROKE {"dtdot", 0x022F1}, // DOWN RIGHT DIAGONAL ELLIPSIS {"dtri", 0x025BF}, // WHITE DOWN-POINTING SMALL TRIANGLE {"dtrif", 0x025BE}, // BLACK DOWN-POINTING SMALL TRIANGLE {"duarr", 0x021F5}, // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW {"duhar", 0x0296F}, // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT {"dwangle", 0x029A6}, // OBLIQUE ANGLE OPENING UP {"DZcy", 0x0040F}, // CYRILLIC CAPITAL LETTER DZHE {"dzcy", 0x0045F}, // CYRILLIC SMALL LETTER DZHE {"dzigrarr", 0x027FF}, // LONG RIGHTWARDS SQUIGGLE ARROW ]; immutable NameId[] namesE = [ {"Eacgr", 0x00388}, // GREEK CAPITAL LETTER EPSILON WITH TONOS {"eacgr", 0x003AD}, // GREEK SMALL LETTER EPSILON WITH TONOS {"Eacute", 0x000C9}, // LATIN CAPITAL LETTER E WITH ACUTE {"eacute", 0x000E9}, // LATIN SMALL LETTER E WITH ACUTE {"easter", 0x02A6E}, // EQUALS WITH ASTERISK {"Ecaron", 0x0011A}, // LATIN CAPITAL LETTER E WITH CARON {"ecaron", 0x0011B}, // LATIN SMALL LETTER E WITH CARON {"ecir", 0x02256}, // RING IN EQUAL TO {"Ecirc", 0x000CA}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX {"ecirc", 0x000EA}, // LATIN SMALL LETTER E WITH CIRCUMFLEX {"ecolon", 0x02255}, // EQUALS COLON {"Ecy", 0x0042D}, // CYRILLIC CAPITAL LETTER E {"ecy", 0x0044D}, // CYRILLIC SMALL LETTER E {"eDDot", 0x02A77}, // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW {"Edot", 0x00116}, // LATIN CAPITAL LETTER E WITH DOT ABOVE {"edot", 0x00117}, // LATIN SMALL LETTER E WITH DOT ABOVE {"eDot", 0x02251}, // GEOMETRICALLY EQUAL TO {"ee", 0x02147}, // DOUBLE-STRUCK ITALIC SMALL E {"EEacgr", 0x00389}, // GREEK CAPITAL LETTER ETA WITH TONOS {"eeacgr", 0x003AE}, // GREEK SMALL LETTER ETA WITH TONOS {"EEgr", 0x00397}, // GREEK CAPITAL LETTER ETA {"eegr", 0x003B7}, // GREEK SMALL LETTER ETA {"efDot", 0x02252}, // APPROXIMATELY EQUAL TO OR THE IMAGE OF {"Efr", 0x1D508}, // MATHEMATICAL FRAKTUR CAPITAL E {"efr", 0x1D522}, // MATHEMATICAL FRAKTUR SMALL E {"eg", 0x02A9A}, // DOUBLE-LINE EQUAL TO OR GREATER-THAN {"Egr", 0x00395}, // GREEK CAPITAL LETTER EPSILON {"egr", 0x003B5}, // GREEK SMALL LETTER EPSILON {"Egrave", 0x000C8}, // LATIN CAPITAL LETTER E WITH GRAVE {"egrave", 0x000E8}, // LATIN SMALL LETTER E WITH GRAVE {"egs", 0x02A96}, // SLANTED EQUAL TO OR GREATER-THAN {"egsdot", 0x02A98}, // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE {"el", 0x02A99}, // DOUBLE-LINE EQUAL TO OR LESS-THAN {"Element", 0x02208}, // ELEMENT OF {"elinters", 0x023E7}, // ELECTRICAL INTERSECTION {"ell", 0x02113}, // SCRIPT SMALL L {"els", 0x02A95}, // SLANTED EQUAL TO OR LESS-THAN {"elsdot", 0x02A97}, // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE {"Emacr", 0x00112}, // LATIN CAPITAL LETTER E WITH MACRON {"emacr", 0x00113}, // LATIN SMALL LETTER E WITH MACRON {"empty", 0x02205}, // EMPTY SET {"emptyset", 0x02205}, // EMPTY SET {"EmptySmallSquare", 0x025FB}, // WHITE MEDIUM SQUARE {"emptyv", 0x02205}, // EMPTY SET {"EmptyVerySmallSquare", 0x025AB}, // WHITE SMALL SQUARE {"emsp", 0x02003}, // EM SPACE {"emsp13", 0x02004}, // THREE-PER-EM SPACE {"emsp14", 0x02005}, // FOUR-PER-EM SPACE {"ENG", 0x0014A}, // LATIN CAPITAL LETTER ENG {"eng", 0x0014B}, // LATIN SMALL LETTER ENG {"ensp", 0x02002}, // EN SPACE {"Eogon", 0x00118}, // LATIN CAPITAL LETTER E WITH OGONEK {"eogon", 0x00119}, // LATIN SMALL LETTER E WITH OGONEK {"Eopf", 0x1D53C}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL E {"eopf", 0x1D556}, // MATHEMATICAL DOUBLE-STRUCK SMALL E {"epar", 0x022D5}, // EQUAL AND PARALLEL TO {"eparsl", 0x029E3}, // EQUALS SIGN AND SLANTED PARALLEL {"eplus", 0x02A71}, // EQUALS SIGN ABOVE PLUS SIGN {"epsi", 0x003B5}, // GREEK SMALL LETTER EPSILON {"Epsilon", 0x00395}, // GREEK CAPITAL LETTER EPSILON {"epsilon", 0x003B5}, // GREEK SMALL LETTER EPSILON {"epsiv", 0x003F5}, // GREEK LUNATE EPSILON SYMBOL {"eqcirc", 0x02256}, // RING IN EQUAL TO {"eqcolon", 0x02255}, // EQUALS COLON {"eqsim", 0x02242}, // MINUS TILDE {"eqslantgtr", 0x02A96}, // SLANTED EQUAL TO OR GREATER-THAN {"eqslantless", 0x02A95}, // SLANTED EQUAL TO OR LESS-THAN {"Equal", 0x02A75}, // TWO CONSECUTIVE EQUALS SIGNS {"equals", 0x0003D}, // EQUALS SIGN {"EqualTilde", 0x02242}, // MINUS TILDE {"equest", 0x0225F}, // QUESTIONED EQUAL TO {"Equilibrium", 0x021CC}, // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON {"equiv", 0x02261}, // IDENTICAL TO {"equivDD", 0x02A78}, // EQUIVALENT WITH FOUR DOTS ABOVE {"eqvparsl", 0x029E5}, // IDENTICAL TO AND SLANTED PARALLEL {"erarr", 0x02971}, // EQUALS SIGN ABOVE RIGHTWARDS ARROW {"erDot", 0x02253}, // IMAGE OF OR APPROXIMATELY EQUAL TO {"escr", 0x0212F}, // SCRIPT SMALL E {"Escr", 0x02130}, // SCRIPT CAPITAL E {"esdot", 0x02250}, // APPROACHES THE LIMIT {"esim", 0x02242}, // MINUS TILDE {"Esim", 0x02A73}, // EQUALS SIGN ABOVE TILDE OPERATOR {"Eta", 0x00397}, // GREEK CAPITAL LETTER ETA {"eta", 0x003B7}, // GREEK SMALL LETTER ETA {"ETH", 0x000D0}, // LATIN CAPITAL LETTER ETH {"eth", 0x000F0}, // LATIN SMALL LETTER ETH {"Euml", 0x000CB}, // LATIN CAPITAL LETTER E WITH DIAERESIS {"euml", 0x000EB}, // LATIN SMALL LETTER E WITH DIAERESIS {"euro", 0x020AC}, // EURO SIGN {"excl", 0x00021}, // EXCLAMATION MARK {"exist", 0x02203}, // THERE EXISTS {"Exists", 0x02203}, // THERE EXISTS {"expectation", 0x02130}, // SCRIPT CAPITAL E {"exponentiale", 0x02147}, // DOUBLE-STRUCK ITALIC SMALL E {"ExponentialE", 0x02147}, // DOUBLE-STRUCK ITALIC SMALL E ]; immutable NameId[] namesF = [ {"fallingdotseq", 0x02252}, // APPROXIMATELY EQUAL TO OR THE IMAGE OF {"Fcy", 0x00424}, // CYRILLIC CAPITAL LETTER EF {"fcy", 0x00444}, // CYRILLIC SMALL LETTER EF {"female", 0x02640}, // FEMALE SIGN {"ffilig", 0x0FB03}, // LATIN SMALL LIGATURE FFI {"fflig", 0x0FB00}, // LATIN SMALL LIGATURE FF {"ffllig", 0x0FB04}, // LATIN SMALL LIGATURE FFL {"Ffr", 0x1D509}, // MATHEMATICAL FRAKTUR CAPITAL F {"ffr", 0x1D523}, // MATHEMATICAL FRAKTUR SMALL F {"filig", 0x0FB01}, // LATIN SMALL LIGATURE FI {"FilledSmallSquare", 0x025FC}, // BLACK MEDIUM SQUARE {"FilledVerySmallSquare", 0x025AA}, // BLACK SMALL SQUARE {"fjlig", 0x00066, 0x0006A}, // fj ligature {"flat", 0x0266D}, // MUSIC FLAT SIGN {"fllig", 0x0FB02}, // LATIN SMALL LIGATURE FL {"fltns", 0x025B1}, // WHITE PARALLELOGRAM {"fnof", 0x00192}, // LATIN SMALL LETTER F WITH HOOK {"Fopf", 0x1D53D}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL F {"fopf", 0x1D557}, // MATHEMATICAL DOUBLE-STRUCK SMALL F {"forall", 0x02200}, // FOR ALL {"ForAll", 0x02200}, // FOR ALL {"fork", 0x022D4}, // PITCHFORK {"forkv", 0x02AD9}, // ELEMENT OF OPENING DOWNWARDS {"Fouriertrf", 0x02131}, // SCRIPT CAPITAL F {"fpartint", 0x02A0D}, // FINITE PART INTEGRAL {"frac12", 0x000BD}, // VULGAR FRACTION ONE HALF {"frac13", 0x02153}, // VULGAR FRACTION ONE THIRD {"frac14", 0x000BC}, // VULGAR FRACTION ONE QUARTER {"frac15", 0x02155}, // VULGAR FRACTION ONE FIFTH {"frac16", 0x02159}, // VULGAR FRACTION ONE SIXTH {"frac18", 0x0215B}, // VULGAR FRACTION ONE EIGHTH {"frac23", 0x02154}, // VULGAR FRACTION TWO THIRDS {"frac25", 0x02156}, // VULGAR FRACTION TWO FIFTHS {"frac34", 0x000BE}, // VULGAR FRACTION THREE QUARTERS {"frac35", 0x02157}, // VULGAR FRACTION THREE FIFTHS {"frac38", 0x0215C}, // VULGAR FRACTION THREE EIGHTHS {"frac45", 0x02158}, // VULGAR FRACTION FOUR FIFTHS {"frac56", 0x0215A}, // VULGAR FRACTION FIVE SIXTHS {"frac58", 0x0215D}, // VULGAR FRACTION FIVE EIGHTHS {"frac78", 0x0215E}, // VULGAR FRACTION SEVEN EIGHTHS {"frasl", 0x02044}, // FRACTION SLASH {"frown", 0x02322}, // FROWN {"Fscr", 0x02131}, // SCRIPT CAPITAL F {"fscr", 0x1D4BB}, // MATHEMATICAL SCRIPT SMALL F ]; immutable NameId[] namesG = [ {"gacute", 0x001F5}, // LATIN SMALL LETTER G WITH ACUTE {"Gamma", 0x00393}, // GREEK CAPITAL LETTER GAMMA {"gamma", 0x003B3}, // GREEK SMALL LETTER GAMMA {"Gammad", 0x003DC}, // GREEK LETTER DIGAMMA {"gammad", 0x003DD}, // GREEK SMALL LETTER DIGAMMA {"gap", 0x02A86}, // GREATER-THAN OR APPROXIMATE {"Gbreve", 0x0011E}, // LATIN CAPITAL LETTER G WITH BREVE {"gbreve", 0x0011F}, // LATIN SMALL LETTER G WITH BREVE {"Gcedil", 0x00122}, // LATIN CAPITAL LETTER G WITH CEDILLA {"Gcirc", 0x0011C}, // LATIN CAPITAL LETTER G WITH CIRCUMFLEX {"gcirc", 0x0011D}, // LATIN SMALL LETTER G WITH CIRCUMFLEX {"Gcy", 0x00413}, // CYRILLIC CAPITAL LETTER GHE {"gcy", 0x00433}, // CYRILLIC SMALL LETTER GHE {"Gdot", 0x00120}, // LATIN CAPITAL LETTER G WITH DOT ABOVE {"gdot", 0x00121}, // LATIN SMALL LETTER G WITH DOT ABOVE {"ge", 0x02265}, // GREATER-THAN OR EQUAL TO {"gE", 0x02267}, // GREATER-THAN OVER EQUAL TO {"gel", 0x022DB}, // GREATER-THAN EQUAL TO OR LESS-THAN {"gEl", 0x02A8C}, // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN {"geq", 0x02265}, // GREATER-THAN OR EQUAL TO {"geqq", 0x02267}, // GREATER-THAN OVER EQUAL TO {"geqslant", 0x02A7E}, // GREATER-THAN OR SLANTED EQUAL TO {"ges", 0x02A7E}, // GREATER-THAN OR SLANTED EQUAL TO {"gescc", 0x02AA9}, // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL {"gesdot", 0x02A80}, // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE {"gesdoto", 0x02A82}, // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE {"gesdotol", 0x02A84}, // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT {"gesl", 0x022DB, 0x0FE00}, // GREATER-THAN slanted EQUAL TO OR LESS-THAN {"gesles", 0x02A94}, // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL {"Gfr", 0x1D50A}, // MATHEMATICAL FRAKTUR CAPITAL G {"gfr", 0x1D524}, // MATHEMATICAL FRAKTUR SMALL G {"gg", 0x0226B}, // MUCH GREATER-THAN {"Gg", 0x022D9}, // VERY MUCH GREATER-THAN {"ggg", 0x022D9}, // VERY MUCH GREATER-THAN {"Ggr", 0x00393}, // GREEK CAPITAL LETTER GAMMA {"ggr", 0x003B3}, // GREEK SMALL LETTER GAMMA {"gimel", 0x02137}, // GIMEL SYMBOL {"GJcy", 0x00403}, // CYRILLIC CAPITAL LETTER GJE {"gjcy", 0x00453}, // CYRILLIC SMALL LETTER GJE {"gl", 0x02277}, // GREATER-THAN OR LESS-THAN {"gla", 0x02AA5}, // GREATER-THAN BESIDE LESS-THAN {"glE", 0x02A92}, // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL {"glj", 0x02AA4}, // GREATER-THAN OVERLAPPING LESS-THAN {"gnap", 0x02A8A}, // GREATER-THAN AND NOT APPROXIMATE {"gnapprox", 0x02A8A}, // GREATER-THAN AND NOT APPROXIMATE {"gnE", 0x02269}, // GREATER-THAN BUT NOT EQUAL TO {"gne", 0x02A88}, // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO {"gneq", 0x02A88}, // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO {"gneqq", 0x02269}, // GREATER-THAN BUT NOT EQUAL TO {"gnsim", 0x022E7}, // GREATER-THAN BUT NOT EQUIVALENT TO {"Gopf", 0x1D53E}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL G {"gopf", 0x1D558}, // MATHEMATICAL DOUBLE-STRUCK SMALL G {"grave", 0x00060}, // GRAVE ACCENT {"GreaterEqual", 0x02265}, // GREATER-THAN OR EQUAL TO {"GreaterEqualLess", 0x022DB}, // GREATER-THAN EQUAL TO OR LESS-THAN {"GreaterFullEqual", 0x02267}, // GREATER-THAN OVER EQUAL TO {"GreaterGreater", 0x02AA2}, // DOUBLE NESTED GREATER-THAN {"GreaterLess", 0x02277}, // GREATER-THAN OR LESS-THAN {"GreaterSlantEqual", 0x02A7E}, // GREATER-THAN OR SLANTED EQUAL TO {"GreaterTilde", 0x02273}, // GREATER-THAN OR EQUIVALENT TO {"gscr", 0x0210A}, // SCRIPT SMALL G {"Gscr", 0x1D4A2}, // MATHEMATICAL SCRIPT CAPITAL G {"gsim", 0x02273}, // GREATER-THAN OR EQUIVALENT TO {"gsime", 0x02A8E}, // GREATER-THAN ABOVE SIMILAR OR EQUAL {"gsiml", 0x02A90}, // GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN {"gt", 0x0003E}, // GREATER-THAN SIGN {"GT", 0x0003E}, // GREATER-THAN SIGN {"Gt", 0x0226B}, // MUCH GREATER-THAN {"gtcc", 0x02AA7}, // GREATER-THAN CLOSED BY CURVE {"gtcir", 0x02A7A}, // GREATER-THAN WITH CIRCLE INSIDE {"gtdot", 0x022D7}, // GREATER-THAN WITH DOT {"gtlPar", 0x02995}, // DOUBLE LEFT ARC GREATER-THAN BRACKET {"gtquest", 0x02A7C}, // GREATER-THAN WITH QUESTION MARK ABOVE {"gtrapprox", 0x02A86}, // GREATER-THAN OR APPROXIMATE {"gtrarr", 0x02978}, // GREATER-THAN ABOVE RIGHTWARDS ARROW {"gtrdot", 0x022D7}, // GREATER-THAN WITH DOT {"gtreqless", 0x022DB}, // GREATER-THAN EQUAL TO OR LESS-THAN {"gtreqqless", 0x02A8C}, // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN {"gtrless", 0x02277}, // GREATER-THAN OR LESS-THAN {"gtrsim", 0x02273}, // GREATER-THAN OR EQUIVALENT TO {"gvertneqq", 0x02269, 0x0FE00}, // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke {"gvnE", 0x02269, 0x0FE00}, // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke ]; immutable NameId[] namesH = [ {"Hacek", 0x002C7}, // CARON {"hairsp", 0x0200A}, // HAIR SPACE {"half", 0x000BD}, // VULGAR FRACTION ONE HALF {"hamilt", 0x0210B}, // SCRIPT CAPITAL H {"HARDcy", 0x0042A}, // CYRILLIC CAPITAL LETTER HARD SIGN {"hardcy", 0x0044A}, // CYRILLIC SMALL LETTER HARD SIGN {"harr", 0x02194}, // LEFT RIGHT ARROW {"hArr", 0x021D4}, // LEFT RIGHT DOUBLE ARROW {"harrcir", 0x02948}, // LEFT RIGHT ARROW THROUGH SMALL CIRCLE {"harrw", 0x021AD}, // LEFT RIGHT WAVE ARROW {"Hat", 0x0005E}, // CIRCUMFLEX ACCENT {"hbar", 0x0210F}, // PLANCK CONSTANT OVER TWO PI {"Hcirc", 0x00124}, // LATIN CAPITAL LETTER H WITH CIRCUMFLEX {"hcirc", 0x00125}, // LATIN SMALL LETTER H WITH CIRCUMFLEX {"hearts", 0x02665}, // BLACK HEART SUIT {"heartsuit", 0x02665}, // BLACK HEART SUIT {"hellip", 0x02026}, // HORIZONTAL ELLIPSIS {"hercon", 0x022B9}, // HERMITIAN CONJUGATE MATRIX {"Hfr", 0x0210C}, // BLACK-LETTER CAPITAL H {"hfr", 0x1D525}, // MATHEMATICAL FRAKTUR SMALL H {"HilbertSpace", 0x0210B}, // SCRIPT CAPITAL H {"hksearow", 0x02925}, // SOUTH EAST ARROW WITH HOOK {"hkswarow", 0x02926}, // SOUTH WEST ARROW WITH HOOK {"hoarr", 0x021FF}, // LEFT RIGHT OPEN-HEADED ARROW {"homtht", 0x0223B}, // HOMOTHETIC {"hookleftarrow", 0x021A9}, // LEFTWARDS ARROW WITH HOOK {"hookrightarrow", 0x021AA}, // RIGHTWARDS ARROW WITH HOOK {"Hopf", 0x0210D}, // DOUBLE-STRUCK CAPITAL H {"hopf", 0x1D559}, // MATHEMATICAL DOUBLE-STRUCK SMALL H {"horbar", 0x02015}, // HORIZONTAL BAR {"HorizontalLine", 0x02500}, // BOX DRAWINGS LIGHT HORIZONTAL {"Hscr", 0x0210B}, // SCRIPT CAPITAL H {"hscr", 0x1D4BD}, // MATHEMATICAL SCRIPT SMALL H {"hslash", 0x0210F}, // PLANCK CONSTANT OVER TWO PI {"Hstrok", 0x00126}, // LATIN CAPITAL LETTER H WITH STROKE {"hstrok", 0x00127}, // LATIN SMALL LETTER H WITH STROKE {"HumpDownHump", 0x0224E}, // GEOMETRICALLY EQUIVALENT TO {"HumpEqual", 0x0224F}, // DIFFERENCE BETWEEN {"hybull", 0x02043}, // HYPHEN BULLET {"hyphen", 0x02010}, // HYPHEN ]; immutable NameId[] namesI = [ {"Iacgr", 0x0038A}, // GREEK CAPITAL LETTER IOTA WITH TONOS {"iacgr", 0x003AF}, // GREEK SMALL LETTER IOTA WITH TONOS {"Iacute", 0x000CD}, // LATIN CAPITAL LETTER I WITH ACUTE {"iacute", 0x000ED}, // LATIN SMALL LETTER I WITH ACUTE {"ic", 0x02063}, // INVISIBLE SEPARATOR {"Icirc", 0x000CE}, // LATIN CAPITAL LETTER I WITH CIRCUMFLEX {"icirc", 0x000EE}, // LATIN SMALL LETTER I WITH CIRCUMFLEX {"Icy", 0x00418}, // CYRILLIC CAPITAL LETTER I {"icy", 0x00438}, // CYRILLIC SMALL LETTER I {"idiagr", 0x00390}, // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS {"Idigr", 0x003AA}, // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA {"idigr", 0x003CA}, // GREEK SMALL LETTER IOTA WITH DIALYTIKA {"Idot", 0x00130}, // LATIN CAPITAL LETTER I WITH DOT ABOVE {"IEcy", 0x00415}, // CYRILLIC CAPITAL LETTER IE {"iecy", 0x00435}, // CYRILLIC SMALL LETTER IE {"iexcl", 0x000A1}, // INVERTED EXCLAMATION MARK {"iff", 0x021D4}, // LEFT RIGHT DOUBLE ARROW {"Ifr", 0x02111}, // BLACK-LETTER CAPITAL I {"ifr", 0x1D526}, // MATHEMATICAL FRAKTUR SMALL I {"Igr", 0x00399}, // GREEK CAPITAL LETTER IOTA {"igr", 0x003B9}, // GREEK SMALL LETTER IOTA {"Igrave", 0x000CC}, // LATIN CAPITAL LETTER I WITH GRAVE {"igrave", 0x000EC}, // LATIN SMALL LETTER I WITH GRAVE {"ii", 0x02148}, // DOUBLE-STRUCK ITALIC SMALL I {"iiiint", 0x02A0C}, // QUADRUPLE INTEGRAL OPERATOR {"iiint", 0x0222D}, // TRIPLE INTEGRAL {"iinfin", 0x029DC}, // INCOMPLETE INFINITY {"iiota", 0x02129}, // TURNED GREEK SMALL LETTER IOTA {"IJlig", 0x00132}, // LATIN CAPITAL LIGATURE IJ {"ijlig", 0x00133}, // LATIN SMALL LIGATURE IJ {"Im", 0x02111}, // BLACK-LETTER CAPITAL I {"Imacr", 0x0012A}, // LATIN CAPITAL LETTER I WITH MACRON {"imacr", 0x0012B}, // LATIN SMALL LETTER I WITH MACRON {"image", 0x02111}, // BLACK-LETTER CAPITAL I {"ImaginaryI", 0x02148}, // DOUBLE-STRUCK ITALIC SMALL I {"imagline", 0x02110}, // SCRIPT CAPITAL I {"imagpart", 0x02111}, // BLACK-LETTER CAPITAL I {"imath", 0x00131}, // LATIN SMALL LETTER DOTLESS I {"imof", 0x022B7}, // IMAGE OF {"imped", 0x001B5}, // LATIN CAPITAL LETTER Z WITH STROKE {"Implies", 0x021D2}, // RIGHTWARDS DOUBLE ARROW {"in", 0x02208}, // ELEMENT OF {"incare", 0x02105}, // CARE OF {"infin", 0x0221E}, // INFINITY {"infintie", 0x029DD}, // TIE OVER INFINITY {"inodot", 0x00131}, // LATIN SMALL LETTER DOTLESS I {"int", 0x0222B}, // INTEGRAL {"Int", 0x0222C}, // DOUBLE INTEGRAL {"intcal", 0x022BA}, // INTERCALATE {"integers", 0x02124}, // DOUBLE-STRUCK CAPITAL Z {"Integral", 0x0222B}, // INTEGRAL {"intercal", 0x022BA}, // INTERCALATE {"Intersection", 0x022C2}, // N-ARY INTERSECTION {"intlarhk", 0x02A17}, // INTEGRAL WITH LEFTWARDS ARROW WITH HOOK {"intprod", 0x02A3C}, // INTERIOR PRODUCT {"InvisibleComma", 0x02063}, // INVISIBLE SEPARATOR {"InvisibleTimes", 0x02062}, // INVISIBLE TIMES {"IOcy", 0x00401}, // CYRILLIC CAPITAL LETTER IO {"iocy", 0x00451}, // CYRILLIC SMALL LETTER IO {"Iogon", 0x0012E}, // LATIN CAPITAL LETTER I WITH OGONEK {"iogon", 0x0012F}, // LATIN SMALL LETTER I WITH OGONEK {"Iopf", 0x1D540}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL I {"iopf", 0x1D55A}, // MATHEMATICAL DOUBLE-STRUCK SMALL I {"Iota", 0x00399}, // GREEK CAPITAL LETTER IOTA {"iota", 0x003B9}, // GREEK SMALL LETTER IOTA {"iprod", 0x02A3C}, // INTERIOR PRODUCT {"iquest", 0x000BF}, // INVERTED QUESTION MARK {"Iscr", 0x02110}, // SCRIPT CAPITAL I {"iscr", 0x1D4BE}, // MATHEMATICAL SCRIPT SMALL I {"isin", 0x02208}, // ELEMENT OF {"isindot", 0x022F5}, // ELEMENT OF WITH DOT ABOVE {"isinE", 0x022F9}, // ELEMENT OF WITH TWO HORIZONTAL STROKES {"isins", 0x022F4}, // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE {"isinsv", 0x022F3}, // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE {"isinv", 0x02208}, // ELEMENT OF {"it", 0x02062}, // INVISIBLE TIMES {"Itilde", 0x00128}, // LATIN CAPITAL LETTER I WITH TILDE {"itilde", 0x00129}, // LATIN SMALL LETTER I WITH TILDE {"Iukcy", 0x00406}, // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I {"iukcy", 0x00456}, // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I {"Iuml", 0x000CF}, // LATIN CAPITAL LETTER I WITH DIAERESIS {"iuml", 0x000EF}, // LATIN SMALL LETTER I WITH DIAERESIS ]; immutable NameId[] namesJ = [ {"Jcirc", 0x00134}, // LATIN CAPITAL LETTER J WITH CIRCUMFLEX {"jcirc", 0x00135}, // LATIN SMALL LETTER J WITH CIRCUMFLEX {"Jcy", 0x00419}, // CYRILLIC CAPITAL LETTER SHORT I {"jcy", 0x00439}, // CYRILLIC SMALL LETTER SHORT I {"Jfr", 0x1D50D}, // MATHEMATICAL FRAKTUR CAPITAL J {"jfr", 0x1D527}, // MATHEMATICAL FRAKTUR SMALL J {"jmath", 0x00237}, // LATIN SMALL LETTER DOTLESS J {"Jopf", 0x1D541}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL J {"jopf", 0x1D55B}, // MATHEMATICAL DOUBLE-STRUCK SMALL J {"Jscr", 0x1D4A5}, // MATHEMATICAL SCRIPT CAPITAL J {"jscr", 0x1D4BF}, // MATHEMATICAL SCRIPT SMALL J {"Jsercy", 0x00408}, // CYRILLIC CAPITAL LETTER JE {"jsercy", 0x00458}, // CYRILLIC SMALL LETTER JE {"Jukcy", 0x00404}, // CYRILLIC CAPITAL LETTER UKRAINIAN IE {"jukcy", 0x00454}, // CYRILLIC SMALL LETTER UKRAINIAN IE ]; immutable NameId[] namesK = [ {"Kappa", 0x0039A}, // GREEK CAPITAL LETTER KAPPA {"kappa", 0x003BA}, // GREEK SMALL LETTER KAPPA {"kappav", 0x003F0}, // GREEK KAPPA SYMBOL {"Kcedil", 0x00136}, // LATIN CAPITAL LETTER K WITH CEDILLA {"kcedil", 0x00137}, // LATIN SMALL LETTER K WITH CEDILLA {"Kcy", 0x0041A}, // CYRILLIC CAPITAL LETTER KA {"kcy", 0x0043A}, // CYRILLIC SMALL LETTER KA {"Kfr", 0x1D50E}, // MATHEMATICAL FRAKTUR CAPITAL K {"kfr", 0x1D528}, // MATHEMATICAL FRAKTUR SMALL K {"Kgr", 0x0039A}, // GREEK CAPITAL LETTER KAPPA {"kgr", 0x003BA}, // GREEK SMALL LETTER KAPPA {"kgreen", 0x00138}, // LATIN SMALL LETTER KRA {"KHcy", 0x00425}, // CYRILLIC CAPITAL LETTER HA {"khcy", 0x00445}, // CYRILLIC SMALL LETTER HA {"KHgr", 0x003A7}, // GREEK CAPITAL LETTER CHI {"khgr", 0x003C7}, // GREEK SMALL LETTER CHI {"KJcy", 0x0040C}, // CYRILLIC CAPITAL LETTER KJE {"kjcy", 0x0045C}, // CYRILLIC SMALL LETTER KJE {"Kopf", 0x1D542}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL K {"kopf", 0x1D55C}, // MATHEMATICAL DOUBLE-STRUCK SMALL K {"Kscr", 0x1D4A6}, // MATHEMATICAL SCRIPT CAPITAL K {"kscr", 0x1D4C0}, // MATHEMATICAL SCRIPT SMALL K ]; immutable NameId[] namesL = [ {"lAarr", 0x021DA}, // LEFTWARDS TRIPLE ARROW {"Lacute", 0x00139}, // LATIN CAPITAL LETTER L WITH ACUTE {"lacute", 0x0013A}, // LATIN SMALL LETTER L WITH ACUTE {"laemptyv", 0x029B4}, // EMPTY SET WITH LEFT ARROW ABOVE {"lagran", 0x02112}, // SCRIPT CAPITAL L {"Lambda", 0x0039B}, // GREEK CAPITAL LETTER LAMDA {"lambda", 0x003BB}, // GREEK SMALL LETTER LAMDA {"lang", 0x027E8}, // MATHEMATICAL LEFT ANGLE BRACKET {"Lang", 0x027EA}, // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET {"langd", 0x02991}, // LEFT ANGLE BRACKET WITH DOT {"langle", 0x027E8}, // MATHEMATICAL LEFT ANGLE BRACKET {"lap", 0x02A85}, // LESS-THAN OR APPROXIMATE {"Laplacetrf", 0x02112}, // SCRIPT CAPITAL L {"laquo", 0x000AB}, // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK {"larr", 0x02190}, // LEFTWARDS ARROW {"Larr", 0x0219E}, // LEFTWARDS TWO HEADED ARROW {"lArr", 0x021D0}, // LEFTWARDS DOUBLE ARROW {"larrb", 0x021E4}, // LEFTWARDS ARROW TO BAR {"larrbfs", 0x0291F}, // LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND {"larrfs", 0x0291D}, // LEFTWARDS ARROW TO BLACK DIAMOND {"larrhk", 0x021A9}, // LEFTWARDS ARROW WITH HOOK {"larrlp", 0x021AB}, // LEFTWARDS ARROW WITH LOOP {"larrpl", 0x02939}, // LEFT-SIDE ARC ANTICLOCKWISE ARROW {"larrsim", 0x02973}, // LEFTWARDS ARROW ABOVE TILDE OPERATOR {"larrtl", 0x021A2}, // LEFTWARDS ARROW WITH TAIL {"lat", 0x02AAB}, // LARGER THAN {"latail", 0x02919}, // LEFTWARDS ARROW-TAIL {"lAtail", 0x0291B}, // LEFTWARDS DOUBLE ARROW-TAIL {"late", 0x02AAD}, // LARGER THAN OR EQUAL TO {"lates", 0x02AAD, 0x0FE00}, // LARGER THAN OR slanted EQUAL {"lbarr", 0x0290C}, // LEFTWARDS DOUBLE DASH ARROW {"lBarr", 0x0290E}, // LEFTWARDS TRIPLE DASH ARROW {"lbbrk", 0x02772}, // LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT {"lbrace", 0x0007B}, // LEFT CURLY BRACKET {"lbrack", 0x0005B}, // LEFT SQUARE BRACKET {"lbrke", 0x0298B}, // LEFT SQUARE BRACKET WITH UNDERBAR {"lbrksld", 0x0298F}, // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER {"lbrkslu", 0x0298D}, // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER {"Lcaron", 0x0013D}, // LATIN CAPITAL LETTER L WITH CARON {"lcaron", 0x0013E}, // LATIN SMALL LETTER L WITH CARON {"Lcedil", 0x0013B}, // LATIN CAPITAL LETTER L WITH CEDILLA {"lcedil", 0x0013C}, // LATIN SMALL LETTER L WITH CEDILLA {"lceil", 0x02308}, // LEFT CEILING {"lcub", 0x0007B}, // LEFT CURLY BRACKET {"Lcy", 0x0041B}, // CYRILLIC CAPITAL LETTER EL {"lcy", 0x0043B}, // CYRILLIC SMALL LETTER EL {"ldca", 0x02936}, // ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS {"ldquo", 0x0201C}, // LEFT DOUBLE QUOTATION MARK {"ldquor", 0x0201E}, // DOUBLE LOW-9 QUOTATION MARK {"ldrdhar", 0x02967}, // LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN {"ldrushar", 0x0294B}, // LEFT BARB DOWN RIGHT BARB UP HARPOON {"ldsh", 0x021B2}, // DOWNWARDS ARROW WITH TIP LEFTWARDS {"le", 0x02264}, // LESS-THAN OR EQUAL TO {"lE", 0x02266}, // LESS-THAN OVER EQUAL TO {"LeftAngleBracket", 0x027E8}, // MATHEMATICAL LEFT ANGLE BRACKET {"leftarrow", 0x02190}, // LEFTWARDS ARROW {"LeftArrow", 0x02190}, // LEFTWARDS ARROW {"Leftarrow", 0x021D0}, // LEFTWARDS DOUBLE ARROW {"LeftArrowBar", 0x021E4}, // LEFTWARDS ARROW TO BAR {"LeftArrowRightArrow", 0x021C6}, // LEFTWARDS ARROW OVER RIGHTWARDS ARROW {"leftarrowtail", 0x021A2}, // LEFTWARDS ARROW WITH TAIL {"LeftCeiling", 0x02308}, // LEFT CEILING {"LeftDoubleBracket", 0x027E6}, // MATHEMATICAL LEFT WHITE SQUARE BRACKET {"LeftDownTeeVector", 0x02961}, // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR {"LeftDownVector", 0x021C3}, // DOWNWARDS HARPOON WITH BARB LEFTWARDS {"LeftDownVectorBar", 0x02959}, // DOWNWARDS HARPOON WITH BARB LEFT TO BAR {"LeftFloor", 0x0230A}, // LEFT FLOOR {"leftharpoondown", 0x021BD}, // LEFTWARDS HARPOON WITH BARB DOWNWARDS {"leftharpoonup", 0x021BC}, // LEFTWARDS HARPOON WITH BARB UPWARDS {"leftleftarrows", 0x021C7}, // LEFTWARDS PAIRED ARROWS {"leftrightarrow", 0x02194}, // LEFT RIGHT ARROW {"LeftRightArrow", 0x02194}, // LEFT RIGHT ARROW {"Leftrightarrow", 0x021D4}, // LEFT RIGHT DOUBLE ARROW {"leftrightarrows", 0x021C6}, // LEFTWARDS ARROW OVER RIGHTWARDS ARROW {"leftrightharpoons", 0x021CB}, // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON {"leftrightsquigarrow", 0x021AD}, // LEFT RIGHT WAVE ARROW {"LeftRightVector", 0x0294E}, // LEFT BARB UP RIGHT BARB UP HARPOON {"LeftTee", 0x022A3}, // LEFT TACK {"LeftTeeArrow", 0x021A4}, // LEFTWARDS ARROW FROM BAR {"LeftTeeVector", 0x0295A}, // LEFTWARDS HARPOON WITH BARB UP FROM BAR {"leftthreetimes", 0x022CB}, // LEFT SEMIDIRECT PRODUCT {"LeftTriangle", 0x022B2}, // NORMAL SUBGROUP OF {"LeftTriangleBar", 0x029CF}, // LEFT TRIANGLE BESIDE VERTICAL BAR {"LeftTriangleEqual", 0x022B4}, // NORMAL SUBGROUP OF OR EQUAL TO {"LeftUpDownVector", 0x02951}, // UP BARB LEFT DOWN BARB LEFT HARPOON {"LeftUpTeeVector", 0x02960}, // UPWARDS HARPOON WITH BARB LEFT FROM BAR {"LeftUpVector", 0x021BF}, // UPWARDS HARPOON WITH BARB LEFTWARDS {"LeftUpVectorBar", 0x02958}, // UPWARDS HARPOON WITH BARB LEFT TO BAR {"LeftVector", 0x021BC}, // LEFTWARDS HARPOON WITH BARB UPWARDS {"LeftVectorBar", 0x02952}, // LEFTWARDS HARPOON WITH BARB UP TO BAR {"leg", 0x022DA}, // LESS-THAN EQUAL TO OR GREATER-THAN {"lEg", 0x02A8B}, // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN {"leq", 0x02264}, // LESS-THAN OR EQUAL TO {"leqq", 0x02266}, // LESS-THAN OVER EQUAL TO {"leqslant", 0x02A7D}, // LESS-THAN OR SLANTED EQUAL TO {"les", 0x02A7D}, // LESS-THAN OR SLANTED EQUAL TO {"lescc", 0x02AA8}, // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL {"lesdot", 0x02A7F}, // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE {"lesdoto", 0x02A81}, // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE {"lesdotor", 0x02A83}, // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT {"lesg", 0x022DA, 0x0FE00}, // LESS-THAN slanted EQUAL TO OR GREATER-THAN {"lesges", 0x02A93}, // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL {"lessapprox", 0x02A85}, // LESS-THAN OR APPROXIMATE {"lessdot", 0x022D6}, // LESS-THAN WITH DOT {"lesseqgtr", 0x022DA}, // LESS-THAN EQUAL TO OR GREATER-THAN {"lesseqqgtr", 0x02A8B}, // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN {"LessEqualGreater", 0x022DA}, // LESS-THAN EQUAL TO OR GREATER-THAN {"LessFullEqual", 0x02266}, // LESS-THAN OVER EQUAL TO {"LessGreater", 0x02276}, // LESS-THAN OR GREATER-THAN {"lessgtr", 0x02276}, // LESS-THAN OR GREATER-THAN {"LessLess", 0x02AA1}, // DOUBLE NESTED LESS-THAN {"lesssim", 0x02272}, // LESS-THAN OR EQUIVALENT TO {"LessSlantEqual", 0x02A7D}, // LESS-THAN OR SLANTED EQUAL TO {"LessTilde", 0x02272}, // LESS-THAN OR EQUIVALENT TO {"lfisht", 0x0297C}, // LEFT FISH TAIL {"lfloor", 0x0230A}, // LEFT FLOOR {"Lfr", 0x1D50F}, // MATHEMATICAL FRAKTUR CAPITAL L {"lfr", 0x1D529}, // MATHEMATICAL FRAKTUR SMALL L {"lg", 0x02276}, // LESS-THAN OR GREATER-THAN {"lgE", 0x02A91}, // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL {"Lgr", 0x0039B}, // GREEK CAPITAL LETTER LAMDA {"lgr", 0x003BB}, // GREEK SMALL LETTER LAMDA {"lHar", 0x02962}, // LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN {"lhard", 0x021BD}, // LEFTWARDS HARPOON WITH BARB DOWNWARDS {"lharu", 0x021BC}, // LEFTWARDS HARPOON WITH BARB UPWARDS {"lharul", 0x0296A}, // LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH {"lhblk", 0x02584}, // LOWER HALF BLOCK {"LJcy", 0x00409}, // CYRILLIC CAPITAL LETTER LJE {"ljcy", 0x00459}, // CYRILLIC SMALL LETTER LJE {"ll", 0x0226A}, // MUCH LESS-THAN {"Ll", 0x022D8}, // VERY MUCH LESS-THAN {"llarr", 0x021C7}, // LEFTWARDS PAIRED ARROWS {"llcorner", 0x0231E}, // BOTTOM LEFT CORNER {"Lleftarrow", 0x021DA}, // LEFTWARDS TRIPLE ARROW {"llhard", 0x0296B}, // LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH {"lltri", 0x025FA}, // LOWER LEFT TRIANGLE {"Lmidot", 0x0013F}, // LATIN CAPITAL LETTER L WITH MIDDLE DOT {"lmidot", 0x00140}, // LATIN SMALL LETTER L WITH MIDDLE DOT {"lmoust", 0x023B0}, // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION {"lmoustache", 0x023B0}, // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION {"lnap", 0x02A89}, // LESS-THAN AND NOT APPROXIMATE {"lnapprox", 0x02A89}, // LESS-THAN AND NOT APPROXIMATE {"lnE", 0x02268}, // LESS-THAN BUT NOT EQUAL TO {"lne", 0x02A87}, // LESS-THAN AND SINGLE-LINE NOT EQUAL TO {"lneq", 0x02A87}, // LESS-THAN AND SINGLE-LINE NOT EQUAL TO {"lneqq", 0x02268}, // LESS-THAN BUT NOT EQUAL TO {"lnsim", 0x022E6}, // LESS-THAN BUT NOT EQUIVALENT TO {"loang", 0x027EC}, // MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET {"loarr", 0x021FD}, // LEFTWARDS OPEN-HEADED ARROW {"lobrk", 0x027E6}, // MATHEMATICAL LEFT WHITE SQUARE BRACKET {"longleftarrow", 0x027F5}, // LONG LEFTWARDS ARROW {"LongLeftArrow", 0x027F5}, // LONG LEFTWARDS ARROW {"Longleftarrow", 0x027F8}, // LONG LEFTWARDS DOUBLE ARROW {"longleftrightarrow", 0x027F7}, // LONG LEFT RIGHT ARROW {"LongLeftRightArrow", 0x027F7}, // LONG LEFT RIGHT ARROW {"Longleftrightarrow", 0x027FA}, // LONG LEFT RIGHT DOUBLE ARROW {"longmapsto", 0x027FC}, // LONG RIGHTWARDS ARROW FROM BAR {"longrightarrow", 0x027F6}, // LONG RIGHTWARDS ARROW {"LongRightArrow", 0x027F6}, // LONG RIGHTWARDS ARROW {"Longrightarrow", 0x027F9}, // LONG RIGHTWARDS DOUBLE ARROW {"looparrowleft", 0x021AB}, // LEFTWARDS ARROW WITH LOOP {"looparrowright", 0x021AC}, // RIGHTWARDS ARROW WITH LOOP {"lopar", 0x02985}, // LEFT WHITE PARENTHESIS {"Lopf", 0x1D543}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL L {"lopf", 0x1D55D}, // MATHEMATICAL DOUBLE-STRUCK SMALL L {"loplus", 0x02A2D}, // PLUS SIGN IN LEFT HALF CIRCLE {"lotimes", 0x02A34}, // MULTIPLICATION SIGN IN LEFT HALF CIRCLE {"lowast", 0x02217}, // ASTERISK OPERATOR {"lowbar", 0x0005F}, // LOW LINE {"LowerLeftArrow", 0x02199}, // SOUTH WEST ARROW {"LowerRightArrow", 0x02198}, // SOUTH EAST ARROW {"loz", 0x025CA}, // LOZENGE {"lozenge", 0x025CA}, // LOZENGE {"lozf", 0x029EB}, // BLACK LOZENGE {"lpar", 0x00028}, // LEFT PARENTHESIS {"lparlt", 0x02993}, // LEFT ARC LESS-THAN BRACKET {"lrarr", 0x021C6}, // LEFTWARDS ARROW OVER RIGHTWARDS ARROW {"lrcorner", 0x0231F}, // BOTTOM RIGHT CORNER {"lrhar", 0x021CB}, // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON {"lrhard", 0x0296D}, // RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH {"lrm", 0x0200E}, // LEFT-TO-RIGHT MARK {"lrtri", 0x022BF}, // RIGHT TRIANGLE {"lsaquo", 0x02039}, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK {"Lscr", 0x02112}, // SCRIPT CAPITAL L {"lscr", 0x1D4C1}, // MATHEMATICAL SCRIPT SMALL L {"lsh", 0x021B0}, // UPWARDS ARROW WITH TIP LEFTWARDS {"Lsh", 0x021B0}, // UPWARDS ARROW WITH TIP LEFTWARDS {"lsim", 0x02272}, // LESS-THAN OR EQUIVALENT TO {"lsime", 0x02A8D}, // LESS-THAN ABOVE SIMILAR OR EQUAL {"lsimg", 0x02A8F}, // LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN {"lsqb", 0x0005B}, // LEFT SQUARE BRACKET {"lsquo", 0x02018}, // LEFT SINGLE QUOTATION MARK {"lsquor", 0x0201A}, // SINGLE LOW-9 QUOTATION MARK {"Lstrok", 0x00141}, // LATIN CAPITAL LETTER L WITH STROKE {"lstrok", 0x00142}, // LATIN SMALL LETTER L WITH STROKE {"lt", 0x0003C}, // LESS-THAN SIGN {"LT", 0x0003C}, // LESS-THAN SIGN {"Lt", 0x0226A}, // MUCH LESS-THAN {"ltcc", 0x02AA6}, // LESS-THAN CLOSED BY CURVE {"ltcir", 0x02A79}, // LESS-THAN WITH CIRCLE INSIDE {"ltdot", 0x022D6}, // LESS-THAN WITH DOT {"lthree", 0x022CB}, // LEFT SEMIDIRECT PRODUCT {"ltimes", 0x022C9}, // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT {"ltlarr", 0x02976}, // LESS-THAN ABOVE LEFTWARDS ARROW {"ltquest", 0x02A7B}, // LESS-THAN WITH QUESTION MARK ABOVE {"ltri", 0x025C3}, // WHITE LEFT-POINTING SMALL TRIANGLE {"ltrie", 0x022B4}, // NORMAL SUBGROUP OF OR EQUAL TO {"ltrif", 0x025C2}, // BLACK LEFT-POINTING SMALL TRIANGLE {"ltrPar", 0x02996}, // DOUBLE RIGHT ARC LESS-THAN BRACKET {"lurdshar", 0x0294A}, // LEFT BARB UP RIGHT BARB DOWN HARPOON {"luruhar", 0x02966}, // LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP {"lvertneqq", 0x02268, 0x0FE00}, // LESS-THAN BUT NOT EQUAL TO - with vertical stroke {"lvnE", 0x02268, 0x0FE00}, // LESS-THAN BUT NOT EQUAL TO - with vertical stroke ]; immutable NameId[] namesM = [ {"macr", 0x000AF}, // MACRON {"male", 0x02642}, // MALE SIGN {"malt", 0x02720}, // MALTESE CROSS {"maltese", 0x02720}, // MALTESE CROSS {"map", 0x021A6}, // RIGHTWARDS ARROW FROM BAR {"Map", 0x02905}, // RIGHTWARDS TWO-HEADED ARROW FROM BAR {"mapsto", 0x021A6}, // RIGHTWARDS ARROW FROM BAR {"mapstodown", 0x021A7}, // DOWNWARDS ARROW FROM BAR {"mapstoleft", 0x021A4}, // LEFTWARDS ARROW FROM BAR {"mapstoup", 0x021A5}, // UPWARDS ARROW FROM BAR {"marker", 0x025AE}, // BLACK VERTICAL RECTANGLE {"mcomma", 0x02A29}, // MINUS SIGN WITH COMMA ABOVE {"Mcy", 0x0041C}, // CYRILLIC CAPITAL LETTER EM {"mcy", 0x0043C}, // CYRILLIC SMALL LETTER EM {"mdash", 0x02014}, // EM DASH {"mDDot", 0x0223A}, // GEOMETRIC PROPORTION {"measuredangle", 0x02221}, // MEASURED ANGLE {"MediumSpace", 0x0205F}, // MEDIUM MATHEMATICAL SPACE {"Mellintrf", 0x02133}, // SCRIPT CAPITAL M {"Mfr", 0x1D510}, // MATHEMATICAL FRAKTUR CAPITAL M {"mfr", 0x1D52A}, // MATHEMATICAL FRAKTUR SMALL M {"Mgr", 0x0039C}, // GREEK CAPITAL LETTER MU {"mgr", 0x003BC}, // GREEK SMALL LETTER MU {"mho", 0x02127}, // INVERTED OHM SIGN {"micro", 0x000B5}, // MICRO SIGN {"mid", 0x02223}, // DIVIDES {"midast", 0x0002A}, // ASTERISK {"midcir", 0x02AF0}, // VERTICAL LINE WITH CIRCLE BELOW {"middot", 0x000B7}, // MIDDLE DOT {"minus", 0x02212}, // MINUS SIGN {"minusb", 0x0229F}, // SQUARED MINUS {"minusd", 0x02238}, // DOT MINUS {"minusdu", 0x02A2A}, // MINUS SIGN WITH DOT BELOW {"MinusPlus", 0x02213}, // MINUS-OR-PLUS SIGN {"mlcp", 0x02ADB}, // TRANSVERSAL INTERSECTION {"mldr", 0x02026}, // HORIZONTAL ELLIPSIS {"mnplus", 0x02213}, // MINUS-OR-PLUS SIGN {"models", 0x022A7}, // MODELS {"Mopf", 0x1D544}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL M {"mopf", 0x1D55E}, // MATHEMATICAL DOUBLE-STRUCK SMALL M {"mp", 0x02213}, // MINUS-OR-PLUS SIGN {"Mscr", 0x02133}, // SCRIPT CAPITAL M {"mscr", 0x1D4C2}, // MATHEMATICAL SCRIPT SMALL M {"mstpos", 0x0223E}, // INVERTED LAZY S {"Mu", 0x0039C}, // GREEK CAPITAL LETTER MU {"mu", 0x003BC}, // GREEK SMALL LETTER MU {"multimap", 0x022B8}, // MULTIMAP {"mumap", 0x022B8}, // MULTIMAP ]; immutable NameId[] namesN = [ {"nabla", 0x02207}, // NABLA {"Nacute", 0x00143}, // LATIN CAPITAL LETTER N WITH ACUTE {"nacute", 0x00144}, // LATIN SMALL LETTER N WITH ACUTE {"nang", 0x02220, 0x020D2}, // ANGLE with vertical line {"nap", 0x02249}, // NOT ALMOST EQUAL TO {"napE", 0x02A70, 0x00338}, // APPROXIMATELY EQUAL OR EQUAL TO with slash {"napid", 0x0224B, 0x00338}, // TRIPLE TILDE with slash {"napos", 0x00149}, // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE {"napprox", 0x02249}, // NOT ALMOST EQUAL TO {"natur", 0x0266E}, // MUSIC NATURAL SIGN {"natural", 0x0266E}, // MUSIC NATURAL SIGN {"naturals", 0x02115}, // DOUBLE-STRUCK CAPITAL N {"nbsp", 0x000A0}, // NO-BREAK SPACE {"nbump", 0x0224E, 0x00338}, // GEOMETRICALLY EQUIVALENT TO with slash {"nbumpe", 0x0224F, 0x00338}, // DIFFERENCE BETWEEN with slash {"ncap", 0x02A43}, // INTERSECTION WITH OVERBAR {"Ncaron", 0x00147}, // LATIN CAPITAL LETTER N WITH CARON {"ncaron", 0x00148}, // LATIN SMALL LETTER N WITH CARON {"Ncedil", 0x00145}, // LATIN CAPITAL LETTER N WITH CEDILLA {"ncedil", 0x00146}, // LATIN SMALL LETTER N WITH CEDILLA {"ncong", 0x02247}, // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO {"ncongdot", 0x02A6D, 0x00338}, // CONGRUENT WITH DOT ABOVE with slash {"ncup", 0x02A42}, // UNION WITH OVERBAR {"Ncy", 0x0041D}, // CYRILLIC CAPITAL LETTER EN {"ncy", 0x0043D}, // CYRILLIC SMALL LETTER EN {"ndash", 0x02013}, // EN DASH {"ne", 0x02260}, // NOT EQUAL TO {"nearhk", 0x02924}, // NORTH EAST ARROW WITH HOOK {"nearr", 0x02197}, // NORTH EAST ARROW {"neArr", 0x021D7}, // NORTH EAST DOUBLE ARROW {"nearrow", 0x02197}, // NORTH EAST ARROW {"nedot", 0x02250, 0x00338}, // APPROACHES THE LIMIT with slash {"NegativeMediumSpace", 0x0200B}, // ZERO WIDTH SPACE {"NegativeThickSpace", 0x0200B}, // ZERO WIDTH SPACE {"NegativeThinSpace", 0x0200B}, // ZERO WIDTH SPACE {"NegativeVeryThinSpace", 0x0200B}, // ZERO WIDTH SPACE {"nequiv", 0x02262}, // NOT IDENTICAL TO {"nesear", 0x02928}, // NORTH EAST ARROW AND SOUTH EAST ARROW {"nesim", 0x02242, 0x00338}, // MINUS TILDE with slash {"NestedGreaterGreater", 0x0226B}, // MUCH GREATER-THAN {"NestedLessLess", 0x0226A}, // MUCH LESS-THAN {"NewLine", 0x0000A}, // LINE FEED (LF) {"nexist", 0x02204}, // THERE DOES NOT EXIST {"nexists", 0x02204}, // THERE DOES NOT EXIST {"Nfr", 0x1D511}, // MATHEMATICAL FRAKTUR CAPITAL N {"nfr", 0x1D52B}, // MATHEMATICAL FRAKTUR SMALL N {"ngE", 0x02267, 0x00338}, // GREATER-THAN OVER EQUAL TO with slash {"nge", 0x02271}, // NEITHER GREATER-THAN NOR EQUAL TO {"ngeq", 0x02271}, // NEITHER GREATER-THAN NOR EQUAL TO {"ngeqq", 0x02267, 0x00338}, // GREATER-THAN OVER EQUAL TO with slash {"ngeqslant", 0x02A7E, 0x00338}, // GREATER-THAN OR SLANTED EQUAL TO with slash {"nges", 0x02A7E, 0x00338}, // GREATER-THAN OR SLANTED EQUAL TO with slash {"nGg", 0x022D9, 0x00338}, // VERY MUCH GREATER-THAN with slash {"Ngr", 0x0039D}, // GREEK CAPITAL LETTER NU {"ngr", 0x003BD}, // GREEK SMALL LETTER NU {"ngsim", 0x02275}, // NEITHER GREATER-THAN NOR EQUIVALENT TO {"nGt", 0x0226B, 0x020D2}, // MUCH GREATER THAN with vertical line {"ngt", 0x0226F}, // NOT GREATER-THAN {"ngtr", 0x0226F}, // NOT GREATER-THAN {"nGtv", 0x0226B, 0x00338}, // MUCH GREATER THAN with slash {"nharr", 0x021AE}, // LEFT RIGHT ARROW WITH STROKE {"nhArr", 0x021CE}, // LEFT RIGHT DOUBLE ARROW WITH STROKE {"nhpar", 0x02AF2}, // PARALLEL WITH HORIZONTAL STROKE {"ni", 0x0220B}, // CONTAINS AS MEMBER {"nis", 0x022FC}, // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE {"nisd", 0x022FA}, // CONTAINS WITH LONG HORIZONTAL STROKE {"niv", 0x0220B}, // CONTAINS AS MEMBER {"NJcy", 0x0040A}, // CYRILLIC CAPITAL LETTER NJE {"njcy", 0x0045A}, // CYRILLIC SMALL LETTER NJE {"nlarr", 0x0219A}, // LEFTWARDS ARROW WITH STROKE {"nlArr", 0x021CD}, // LEFTWARDS DOUBLE ARROW WITH STROKE {"nldr", 0x02025}, // TWO DOT LEADER {"nlE", 0x02266, 0x00338}, // LESS-THAN OVER EQUAL TO with slash {"nle", 0x02270}, // NEITHER LESS-THAN NOR EQUAL TO {"nleftarrow", 0x0219A}, // LEFTWARDS ARROW WITH STROKE {"nLeftarrow", 0x021CD}, // LEFTWARDS DOUBLE ARROW WITH STROKE {"nleftrightarrow", 0x021AE}, // LEFT RIGHT ARROW WITH STROKE {"nLeftrightarrow", 0x021CE}, // LEFT RIGHT DOUBLE ARROW WITH STROKE {"nleq", 0x02270}, // NEITHER LESS-THAN NOR EQUAL TO {"nleqq", 0x02266, 0x00338}, // LESS-THAN OVER EQUAL TO with slash {"nleqslant", 0x02A7D, 0x00338}, // LESS-THAN OR SLANTED EQUAL TO with slash {"nles", 0x02A7D, 0x00338}, // LESS-THAN OR SLANTED EQUAL TO with slash {"nless", 0x0226E}, // NOT LESS-THAN {"nLl", 0x022D8, 0x00338}, // VERY MUCH LESS-THAN with slash {"nlsim", 0x02274}, // NEITHER LESS-THAN NOR EQUIVALENT TO {"nLt", 0x0226A, 0x020D2}, // MUCH LESS THAN with vertical line {"nlt", 0x0226E}, // NOT LESS-THAN {"nltri", 0x022EA}, // NOT NORMAL SUBGROUP OF {"nltrie", 0x022EC}, // NOT NORMAL SUBGROUP OF OR EQUAL TO {"nLtv", 0x0226A, 0x00338}, // MUCH LESS THAN with slash {"nmid", 0x02224}, // DOES NOT DIVIDE {"NoBreak", 0x02060}, // WORD JOINER {"NonBreakingSpace", 0x000A0}, // NO-BREAK SPACE {"Nopf", 0x02115}, // DOUBLE-STRUCK CAPITAL N {"nopf", 0x1D55F}, // MATHEMATICAL DOUBLE-STRUCK SMALL N {"not", 0x000AC}, // NOT SIGN {"Not", 0x02AEC}, // DOUBLE STROKE NOT SIGN {"NotCongruent", 0x02262}, // NOT IDENTICAL TO {"NotCupCap", 0x0226D}, // NOT EQUIVALENT TO {"NotDoubleVerticalBar", 0x02226}, // NOT PARALLEL TO {"NotElement", 0x02209}, // NOT AN ELEMENT OF {"NotEqual", 0x02260}, // NOT EQUAL TO {"NotEqualTilde", 0x02242, 0x00338}, // MINUS TILDE with slash {"NotExists", 0x02204}, // THERE DOES NOT EXIST {"NotGreater", 0x0226F}, // NOT GREATER-THAN {"NotGreaterEqual", 0x02271}, // NEITHER GREATER-THAN NOR EQUAL TO {"NotGreaterFullEqual", 0x02267, 0x00338}, // GREATER-THAN OVER EQUAL TO with slash {"NotGreaterGreater", 0x0226B, 0x00338}, // MUCH GREATER THAN with slash {"NotGreaterLess", 0x02279}, // NEITHER GREATER-THAN NOR LESS-THAN {"NotGreaterSlantEqual", 0x02A7E, 0x00338}, // GREATER-THAN OR SLANTED EQUAL TO with slash {"NotGreaterTilde", 0x02275}, // NEITHER GREATER-THAN NOR EQUIVALENT TO {"NotHumpDownHump", 0x0224E, 0x00338}, // GEOMETRICALLY EQUIVALENT TO with slash {"NotHumpEqual", 0x0224F, 0x00338}, // DIFFERENCE BETWEEN with slash {"notin", 0x02209}, // NOT AN ELEMENT OF {"notindot", 0x022F5, 0x00338}, // ELEMENT OF WITH DOT ABOVE with slash {"notinE", 0x022F9, 0x00338}, // ELEMENT OF WITH TWO HORIZONTAL STROKES with slash {"notinva", 0x02209}, // NOT AN ELEMENT OF {"notinvb", 0x022F7}, // SMALL ELEMENT OF WITH OVERBAR {"notinvc", 0x022F6}, // ELEMENT OF WITH OVERBAR {"NotLeftTriangle", 0x022EA}, // NOT NORMAL SUBGROUP OF {"NotLeftTriangleBar", 0x029CF, 0x00338}, // LEFT TRIANGLE BESIDE VERTICAL BAR with slash {"NotLeftTriangleEqual", 0x022EC}, // NOT NORMAL SUBGROUP OF OR EQUAL TO {"NotLess", 0x0226E}, // NOT LESS-THAN {"NotLessEqual", 0x02270}, // NEITHER LESS-THAN NOR EQUAL TO {"NotLessGreater", 0x02278}, // NEITHER LESS-THAN NOR GREATER-THAN {"NotLessLess", 0x0226A, 0x00338}, // MUCH LESS THAN with slash {"NotLessSlantEqual", 0x02A7D, 0x00338}, // LESS-THAN OR SLANTED EQUAL TO with slash {"NotLessTilde", 0x02274}, // NEITHER LESS-THAN NOR EQUIVALENT TO {"NotNestedGreaterGreater", 0x02AA2, 0x00338}, // DOUBLE NESTED GREATER-THAN with slash {"NotNestedLessLess", 0x02AA1, 0x00338}, // DOUBLE NESTED LESS-THAN with slash {"notni", 0x0220C}, // DOES NOT CONTAIN AS MEMBER {"notniva", 0x0220C}, // DOES NOT CONTAIN AS MEMBER {"notnivb", 0x022FE}, // SMALL CONTAINS WITH OVERBAR {"notnivc", 0x022FD}, // CONTAINS WITH OVERBAR {"NotPrecedes", 0x02280}, // DOES NOT PRECEDE {"NotPrecedesEqual", 0x02AAF, 0x00338}, // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash {"NotPrecedesSlantEqual", 0x022E0}, // DOES NOT PRECEDE OR EQUAL {"NotReverseElement", 0x0220C}, // DOES NOT CONTAIN AS MEMBER {"NotRightTriangle", 0x022EB}, // DOES NOT CONTAIN AS NORMAL SUBGROUP {"NotRightTriangleBar", 0x029D0, 0x00338}, // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash {"NotRightTriangleEqual", 0x022ED}, // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL {"NotSquareSubset", 0x0228F, 0x00338}, // SQUARE IMAGE OF with slash {"NotSquareSubsetEqual", 0x022E2}, // NOT SQUARE IMAGE OF OR EQUAL TO {"NotSquareSuperset", 0x02290, 0x00338}, // SQUARE ORIGINAL OF with slash {"NotSquareSupersetEqual", 0x022E3}, // NOT SQUARE ORIGINAL OF OR EQUAL TO {"NotSubset", 0x02282, 0x020D2}, // SUBSET OF with vertical line {"NotSubsetEqual", 0x02288}, // NEITHER A SUBSET OF NOR EQUAL TO {"NotSucceeds", 0x02281}, // DOES NOT SUCCEED {"NotSucceedsEqual", 0x02AB0, 0x00338}, // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash {"NotSucceedsSlantEqual", 0x022E1}, // DOES NOT SUCCEED OR EQUAL {"NotSucceedsTilde", 0x0227F, 0x00338}, // SUCCEEDS OR EQUIVALENT TO with slash {"NotSuperset", 0x02283, 0x020D2}, // SUPERSET OF with vertical line {"NotSupersetEqual", 0x02289}, // NEITHER A SUPERSET OF NOR EQUAL TO {"NotTilde", 0x02241}, // NOT TILDE {"NotTildeEqual", 0x02244}, // NOT ASYMPTOTICALLY EQUAL TO {"NotTildeFullEqual", 0x02247}, // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO {"NotTildeTilde", 0x02249}, // NOT ALMOST EQUAL TO {"NotVerticalBar", 0x02224}, // DOES NOT DIVIDE {"npar", 0x02226}, // NOT PARALLEL TO {"nparallel", 0x02226}, // NOT PARALLEL TO {"nparsl", 0x02AFD, 0x020E5}, // DOUBLE SOLIDUS OPERATOR with reverse slash {"npart", 0x02202, 0x00338}, // PARTIAL DIFFERENTIAL with slash {"npolint", 0x02A14}, // LINE INTEGRATION NOT INCLUDING THE POLE {"npr", 0x02280}, // DOES NOT PRECEDE {"nprcue", 0x022E0}, // DOES NOT PRECEDE OR EQUAL {"npre", 0x02AAF, 0x00338}, // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash {"nprec", 0x02280}, // DOES NOT PRECEDE {"npreceq", 0x02AAF, 0x00338}, // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash {"nrarr", 0x0219B}, // RIGHTWARDS ARROW WITH STROKE {"nrArr", 0x021CF}, // RIGHTWARDS DOUBLE ARROW WITH STROKE {"nrarrc", 0x02933, 0x00338}, // WAVE ARROW POINTING DIRECTLY RIGHT with slash {"nrarrw", 0x0219D, 0x00338}, // RIGHTWARDS WAVE ARROW with slash {"nrightarrow", 0x0219B}, // RIGHTWARDS ARROW WITH STROKE {"nRightarrow", 0x021CF}, // RIGHTWARDS DOUBLE ARROW WITH STROKE {"nrtri", 0x022EB}, // DOES NOT CONTAIN AS NORMAL SUBGROUP {"nrtrie", 0x022ED}, // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL {"nsc", 0x02281}, // DOES NOT SUCCEED {"nsccue", 0x022E1}, // DOES NOT SUCCEED OR EQUAL {"nsce", 0x02AB0, 0x00338}, // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash {"Nscr", 0x1D4A9}, // MATHEMATICAL SCRIPT CAPITAL N {"nscr", 0x1D4C3}, // MATHEMATICAL SCRIPT SMALL N {"nshortmid", 0x02224}, // DOES NOT DIVIDE {"nshortparallel", 0x02226}, // NOT PARALLEL TO {"nsim", 0x02241}, // NOT TILDE {"nsime", 0x02244}, // NOT ASYMPTOTICALLY EQUAL TO {"nsimeq", 0x02244}, // NOT ASYMPTOTICALLY EQUAL TO {"nsmid", 0x02224}, // DOES NOT DIVIDE {"nspar", 0x02226}, // NOT PARALLEL TO {"nsqsube", 0x022E2}, // NOT SQUARE IMAGE OF OR EQUAL TO {"nsqsupe", 0x022E3}, // NOT SQUARE ORIGINAL OF OR EQUAL TO {"nsub", 0x02284}, // NOT A SUBSET OF {"nsube", 0x02288}, // NEITHER A SUBSET OF NOR EQUAL TO {"nsubE", 0x02AC5, 0x00338}, // SUBSET OF ABOVE EQUALS SIGN with slash {"nsubset", 0x02282, 0x020D2}, // SUBSET OF with vertical line {"nsubseteq", 0x02288}, // NEITHER A SUBSET OF NOR EQUAL TO {"nsubseteqq", 0x02AC5, 0x00338}, // SUBSET OF ABOVE EQUALS SIGN with slash {"nsucc", 0x02281}, // DOES NOT SUCCEED {"nsucceq", 0x02AB0, 0x00338}, // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash {"nsup", 0x02285}, // NOT A SUPERSET OF {"nsupe", 0x02289}, // NEITHER A SUPERSET OF NOR EQUAL TO {"nsupE", 0x02AC6, 0x00338}, // SUPERSET OF ABOVE EQUALS SIGN with slash {"nsupset", 0x02283, 0x020D2}, // SUPERSET OF with vertical line {"nsupseteq", 0x02289}, // NEITHER A SUPERSET OF NOR EQUAL TO {"nsupseteqq", 0x02AC6, 0x00338}, // SUPERSET OF ABOVE EQUALS SIGN with slash {"ntgl", 0x02279}, // NEITHER GREATER-THAN NOR LESS-THAN {"Ntilde", 0x000D1}, // LATIN CAPITAL LETTER N WITH TILDE {"ntilde", 0x000F1}, // LATIN SMALL LETTER N WITH TILDE {"ntlg", 0x02278}, // NEITHER LESS-THAN NOR GREATER-THAN {"ntriangleleft", 0x022EA}, // NOT NORMAL SUBGROUP OF {"ntrianglelefteq", 0x022EC}, // NOT NORMAL SUBGROUP OF OR EQUAL TO {"ntriangleright", 0x022EB}, // DOES NOT CONTAIN AS NORMAL SUBGROUP {"ntrianglerighteq", 0x022ED}, // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL {"Nu", 0x0039D}, // GREEK CAPITAL LETTER NU {"nu", 0x003BD}, // GREEK SMALL LETTER NU {"num", 0x00023}, // NUMBER SIGN {"numero", 0x02116}, // NUMERO SIGN {"numsp", 0x02007}, // FIGURE SPACE {"nvap", 0x0224D, 0x020D2}, // EQUIVALENT TO with vertical line {"nvdash", 0x022AC}, // DOES NOT PROVE {"nvDash", 0x022AD}, // NOT TRUE {"nVdash", 0x022AE}, // DOES NOT FORCE {"nVDash", 0x022AF}, // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE {"nvge", 0x02265, 0x020D2}, // GREATER-THAN OR EQUAL TO with vertical line {"nvgt", 0x0003E, 0x020D2}, // GREATER-THAN SIGN with vertical line {"nvHarr", 0x02904}, // LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE {"nvinfin", 0x029DE}, // INFINITY NEGATED WITH VERTICAL BAR {"nvlArr", 0x02902}, // LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE {"nvle", 0x02264, 0x020D2}, // LESS-THAN OR EQUAL TO with vertical line {"nvlt", 0x0003C, 0x020D2}, // LESS-THAN SIGN with vertical line {"nvltrie", 0x022B4, 0x020D2}, // NORMAL SUBGROUP OF OR EQUAL TO with vertical line {"nvrArr", 0x02903}, // RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE {"nvrtrie", 0x022B5, 0x020D2}, // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line {"nvsim", 0x0223C, 0x020D2}, // TILDE OPERATOR with vertical line {"nwarhk", 0x02923}, // NORTH WEST ARROW WITH HOOK {"nwarr", 0x02196}, // NORTH WEST ARROW {"nwArr", 0x021D6}, // NORTH WEST DOUBLE ARROW {"nwarrow", 0x02196}, // NORTH WEST ARROW {"nwnear", 0x02927}, // NORTH WEST ARROW AND NORTH EAST ARROW ]; immutable NameId[] namesO = [ {"Oacgr", 0x0038C}, // GREEK CAPITAL LETTER OMICRON WITH TONOS {"oacgr", 0x003CC}, // GREEK SMALL LETTER OMICRON WITH TONOS {"Oacute", 0x000D3}, // LATIN CAPITAL LETTER O WITH ACUTE {"oacute", 0x000F3}, // LATIN SMALL LETTER O WITH ACUTE {"oast", 0x0229B}, // CIRCLED ASTERISK OPERATOR {"ocir", 0x0229A}, // CIRCLED RING OPERATOR {"Ocirc", 0x000D4}, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX {"ocirc", 0x000F4}, // LATIN SMALL LETTER O WITH CIRCUMFLEX {"Ocy", 0x0041E}, // CYRILLIC CAPITAL LETTER O {"ocy", 0x0043E}, // CYRILLIC SMALL LETTER O {"odash", 0x0229D}, // CIRCLED DASH {"Odblac", 0x00150}, // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE {"odblac", 0x00151}, // LATIN SMALL LETTER O WITH DOUBLE ACUTE {"odiv", 0x02A38}, // CIRCLED DIVISION SIGN {"odot", 0x02299}, // CIRCLED DOT OPERATOR {"odsold", 0x029BC}, // CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN {"OElig", 0x00152}, // LATIN CAPITAL LIGATURE OE {"oelig", 0x00153}, // LATIN SMALL LIGATURE OE {"ofcir", 0x029BF}, // CIRCLED BULLET {"Ofr", 0x1D512}, // MATHEMATICAL FRAKTUR CAPITAL O {"ofr", 0x1D52C}, // MATHEMATICAL FRAKTUR SMALL O {"ogon", 0x002DB}, // OGONEK {"Ogr", 0x0039F}, // GREEK CAPITAL LETTER OMICRON {"ogr", 0x003BF}, // GREEK SMALL LETTER OMICRON {"Ograve", 0x000D2}, // LATIN CAPITAL LETTER O WITH GRAVE {"ograve", 0x000F2}, // LATIN SMALL LETTER O WITH GRAVE {"ogt", 0x029C1}, // CIRCLED GREATER-THAN {"OHacgr", 0x0038F}, // GREEK CAPITAL LETTER OMEGA WITH TONOS {"ohacgr", 0x003CE}, // GREEK SMALL LETTER OMEGA WITH TONOS {"ohbar", 0x029B5}, // CIRCLE WITH HORIZONTAL BAR {"OHgr", 0x003A9}, // GREEK CAPITAL LETTER OMEGA {"ohgr", 0x003C9}, // GREEK SMALL LETTER OMEGA {"ohm", 0x003A9}, // GREEK CAPITAL LETTER OMEGA {"oint", 0x0222E}, // CONTOUR INTEGRAL {"olarr", 0x021BA}, // ANTICLOCKWISE OPEN CIRCLE ARROW {"olcir", 0x029BE}, // CIRCLED WHITE BULLET {"olcross", 0x029BB}, // CIRCLE WITH SUPERIMPOSED X {"oline", 0x0203E}, // OVERLINE {"olt", 0x029C0}, // CIRCLED LESS-THAN {"Omacr", 0x0014C}, // LATIN CAPITAL LETTER O WITH MACRON {"omacr", 0x0014D}, // LATIN SMALL LETTER O WITH MACRON {"Omega", 0x003A9}, // GREEK CAPITAL LETTER OMEGA {"omega", 0x003C9}, // GREEK SMALL LETTER OMEGA {"Omicron", 0x0039F}, // GREEK CAPITAL LETTER OMICRON {"omicron", 0x003BF}, // GREEK SMALL LETTER OMICRON {"omid", 0x029B6}, // CIRCLED VERTICAL BAR {"ominus", 0x02296}, // CIRCLED MINUS {"Oopf", 0x1D546}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL O {"oopf", 0x1D560}, // MATHEMATICAL DOUBLE-STRUCK SMALL O {"opar", 0x029B7}, // CIRCLED PARALLEL {"OpenCurlyDoubleQuote", 0x0201C}, // LEFT DOUBLE QUOTATION MARK {"OpenCurlyQuote", 0x02018}, // LEFT SINGLE QUOTATION MARK {"operp", 0x029B9}, // CIRCLED PERPENDICULAR {"oplus", 0x02295}, // CIRCLED PLUS {"or", 0x02228}, // LOGICAL OR {"Or", 0x02A54}, // DOUBLE LOGICAL OR {"orarr", 0x021BB}, // CLOCKWISE OPEN CIRCLE ARROW {"ord", 0x02A5D}, // LOGICAL OR WITH HORIZONTAL DASH {"order", 0x02134}, // SCRIPT SMALL O {"orderof", 0x02134}, // SCRIPT SMALL O {"ordf", 0x000AA}, // FEMININE ORDINAL INDICATOR {"ordm", 0x000BA}, // MASCULINE ORDINAL INDICATOR {"origof", 0x022B6}, // ORIGINAL OF {"oror", 0x02A56}, // TWO INTERSECTING LOGICAL OR {"orslope", 0x02A57}, // SLOPING LARGE OR {"orv", 0x02A5B}, // LOGICAL OR WITH MIDDLE STEM {"oS", 0x024C8}, // CIRCLED LATIN CAPITAL LETTER S {"oscr", 0x02134}, // SCRIPT SMALL O {"Oscr", 0x1D4AA}, // MATHEMATICAL SCRIPT CAPITAL O {"Oslash", 0x000D8}, // LATIN CAPITAL LETTER O WITH STROKE {"oslash", 0x000F8}, // LATIN SMALL LETTER O WITH STROKE {"osol", 0x02298}, // CIRCLED DIVISION SLASH {"Otilde", 0x000D5}, // LATIN CAPITAL LETTER O WITH TILDE {"otilde", 0x000F5}, // LATIN SMALL LETTER O WITH TILDE {"otimes", 0x02297}, // CIRCLED TIMES {"Otimes", 0x02A37}, // MULTIPLICATION SIGN IN DOUBLE CIRCLE {"otimesas", 0x02A36}, // CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT {"Ouml", 0x000D6}, // LATIN CAPITAL LETTER O WITH DIAERESIS {"ouml", 0x000F6}, // LATIN SMALL LETTER O WITH DIAERESIS {"ovbar", 0x0233D}, // APL FUNCTIONAL SYMBOL CIRCLE STILE {"OverBar", 0x0203E}, // OVERLINE {"OverBrace", 0x023DE}, // TOP CURLY BRACKET {"OverBracket", 0x023B4}, // TOP SQUARE BRACKET {"OverParenthesis", 0x023DC}, // TOP PARENTHESIS ]; immutable NameId[] namesP = [ {"par", 0x02225}, // PARALLEL TO {"para", 0x000B6}, // PILCROW SIGN {"parallel", 0x02225}, // PARALLEL TO {"parsim", 0x02AF3}, // PARALLEL WITH TILDE OPERATOR {"parsl", 0x02AFD}, // DOUBLE SOLIDUS OPERATOR {"part", 0x02202}, // PARTIAL DIFFERENTIAL {"PartialD", 0x02202}, // PARTIAL DIFFERENTIAL {"Pcy", 0x0041F}, // CYRILLIC CAPITAL LETTER PE {"pcy", 0x0043F}, // CYRILLIC SMALL LETTER PE {"percnt", 0x00025}, // PERCENT SIGN {"period", 0x0002E}, // FULL STOP {"permil", 0x02030}, // PER MILLE SIGN {"perp", 0x022A5}, // UP TACK {"pertenk", 0x02031}, // PER TEN THOUSAND SIGN {"Pfr", 0x1D513}, // MATHEMATICAL FRAKTUR CAPITAL P {"pfr", 0x1D52D}, // MATHEMATICAL FRAKTUR SMALL P {"Pgr", 0x003A0}, // GREEK CAPITAL LETTER PI {"pgr", 0x003C0}, // GREEK SMALL LETTER PI {"PHgr", 0x003A6}, // GREEK CAPITAL LETTER PHI {"phgr", 0x003C6}, // GREEK SMALL LETTER PHI {"Phi", 0x003A6}, // GREEK CAPITAL LETTER PHI {"phi", 0x003C6}, // GREEK SMALL LETTER PHI {"phiv", 0x003D5}, // GREEK PHI SYMBOL {"phmmat", 0x02133}, // SCRIPT CAPITAL M {"phone", 0x0260E}, // BLACK TELEPHONE {"Pi", 0x003A0}, // GREEK CAPITAL LETTER PI {"pi", 0x003C0}, // GREEK SMALL LETTER PI {"pitchfork", 0x022D4}, // PITCHFORK {"piv", 0x003D6}, // GREEK PI SYMBOL {"planck", 0x0210F}, // PLANCK CONSTANT OVER TWO PI {"planckh", 0x0210E}, // PLANCK CONSTANT {"plankv", 0x0210F}, // PLANCK CONSTANT OVER TWO PI {"plus", 0x0002B}, // PLUS SIGN {"plusacir", 0x02A23}, // PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE {"plusb", 0x0229E}, // SQUARED PLUS {"pluscir", 0x02A22}, // PLUS SIGN WITH SMALL CIRCLE ABOVE {"plusdo", 0x02214}, // DOT PLUS {"plusdu", 0x02A25}, // PLUS SIGN WITH DOT BELOW {"pluse", 0x02A72}, // PLUS SIGN ABOVE EQUALS SIGN {"PlusMinus", 0x000B1}, // PLUS-MINUS SIGN {"plusmn", 0x000B1}, // PLUS-MINUS SIGN {"plussim", 0x02A26}, // PLUS SIGN WITH TILDE BELOW {"plustwo", 0x02A27}, // PLUS SIGN WITH SUBSCRIPT TWO {"pm", 0x000B1}, // PLUS-MINUS SIGN {"Poincareplane", 0x0210C}, // BLACK-LETTER CAPITAL H {"pointint", 0x02A15}, // INTEGRAL AROUND A POINT OPERATOR {"Popf", 0x02119}, // DOUBLE-STRUCK CAPITAL P {"popf", 0x1D561}, // MATHEMATICAL DOUBLE-STRUCK SMALL P {"pound", 0x000A3}, // POUND SIGN {"pr", 0x0227A}, // PRECEDES {"Pr", 0x02ABB}, // DOUBLE PRECEDES {"prap", 0x02AB7}, // PRECEDES ABOVE ALMOST EQUAL TO {"prcue", 0x0227C}, // PRECEDES OR EQUAL TO {"pre", 0x02AAF}, // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN {"prE", 0x02AB3}, // PRECEDES ABOVE EQUALS SIGN {"prec", 0x0227A}, // PRECEDES {"precapprox", 0x02AB7}, // PRECEDES ABOVE ALMOST EQUAL TO {"preccurlyeq", 0x0227C}, // PRECEDES OR EQUAL TO {"Precedes", 0x0227A}, // PRECEDES {"PrecedesEqual", 0x02AAF}, // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN {"PrecedesSlantEqual", 0x0227C}, // PRECEDES OR EQUAL TO {"PrecedesTilde", 0x0227E}, // PRECEDES OR EQUIVALENT TO {"preceq", 0x02AAF}, // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN {"precnapprox", 0x02AB9}, // PRECEDES ABOVE NOT ALMOST EQUAL TO {"precneqq", 0x02AB5}, // PRECEDES ABOVE NOT EQUAL TO {"precnsim", 0x022E8}, // PRECEDES BUT NOT EQUIVALENT TO {"precsim", 0x0227E}, // PRECEDES OR EQUIVALENT TO {"prime", 0x02032}, // PRIME {"Prime", 0x02033}, // DOUBLE PRIME {"primes", 0x02119}, // DOUBLE-STRUCK CAPITAL P {"prnap", 0x02AB9}, // PRECEDES ABOVE NOT ALMOST EQUAL TO {"prnE", 0x02AB5}, // PRECEDES ABOVE NOT EQUAL TO {"prnsim", 0x022E8}, // PRECEDES BUT NOT EQUIVALENT TO {"prod", 0x0220F}, // N-ARY PRODUCT {"Product", 0x0220F}, // N-ARY PRODUCT {"profalar", 0x0232E}, // ALL AROUND-PROFILE {"profline", 0x02312}, // ARC {"profsurf", 0x02313}, // SEGMENT {"prop", 0x0221D}, // PROPORTIONAL TO {"Proportion", 0x02237}, // PROPORTION {"Proportional", 0x0221D}, // PROPORTIONAL TO {"propto", 0x0221D}, // PROPORTIONAL TO {"prsim", 0x0227E}, // PRECEDES OR EQUIVALENT TO {"prurel", 0x022B0}, // PRECEDES UNDER RELATION {"Pscr", 0x1D4AB}, // MATHEMATICAL SCRIPT CAPITAL P {"pscr", 0x1D4C5}, // MATHEMATICAL SCRIPT SMALL P {"PSgr", 0x003A8}, // GREEK CAPITAL LETTER PSI {"psgr", 0x003C8}, // GREEK SMALL LETTER PSI {"Psi", 0x003A8}, // GREEK CAPITAL LETTER PSI {"psi", 0x003C8}, // GREEK SMALL LETTER PSI {"puncsp", 0x02008}, // PUNCTUATION SPACE ]; immutable NameId[] namesQ = [ {"Qfr", 0x1D514}, // MATHEMATICAL FRAKTUR CAPITAL Q {"qfr", 0x1D52E}, // MATHEMATICAL FRAKTUR SMALL Q {"qint", 0x02A0C}, // QUADRUPLE INTEGRAL OPERATOR {"Qopf", 0x0211A}, // DOUBLE-STRUCK CAPITAL Q {"qopf", 0x1D562}, // MATHEMATICAL DOUBLE-STRUCK SMALL Q {"qprime", 0x02057}, // QUADRUPLE PRIME {"Qscr", 0x1D4AC}, // MATHEMATICAL SCRIPT CAPITAL Q {"qscr", 0x1D4C6}, // MATHEMATICAL SCRIPT SMALL Q {"quaternions", 0x0210D}, // DOUBLE-STRUCK CAPITAL H {"quatint", 0x02A16}, // QUATERNION INTEGRAL OPERATOR {"quest", 0x0003F}, // QUESTION MARK {"questeq", 0x0225F}, // QUESTIONED EQUAL TO {"quot", 0x00022}, // QUOTATION MARK {"QUOT", 0x00022}, // QUOTATION MARK ]; immutable NameId[] namesR = [ {"rAarr", 0x021DB}, // RIGHTWARDS TRIPLE ARROW {"race", 0x0223D, 0x00331}, // REVERSED TILDE with underline {"Racute", 0x00154}, // LATIN CAPITAL LETTER R WITH ACUTE {"racute", 0x00155}, // LATIN SMALL LETTER R WITH ACUTE {"radic", 0x0221A}, // SQUARE ROOT {"raemptyv", 0x029B3}, // EMPTY SET WITH RIGHT ARROW ABOVE {"rang", 0x027E9}, // MATHEMATICAL RIGHT ANGLE BRACKET {"Rang", 0x027EB}, // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET {"rangd", 0x02992}, // RIGHT ANGLE BRACKET WITH DOT {"range", 0x029A5}, // REVERSED ANGLE WITH UNDERBAR {"rangle", 0x027E9}, // MATHEMATICAL RIGHT ANGLE BRACKET {"raquo", 0x000BB}, // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK {"rarr", 0x02192}, // RIGHTWARDS ARROW {"Rarr", 0x021A0}, // RIGHTWARDS TWO HEADED ARROW {"rArr", 0x021D2}, // RIGHTWARDS DOUBLE ARROW {"rarrap", 0x02975}, // RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO {"rarrb", 0x021E5}, // RIGHTWARDS ARROW TO BAR {"rarrbfs", 0x02920}, // RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND {"rarrc", 0x02933}, // WAVE ARROW POINTING DIRECTLY RIGHT {"rarrfs", 0x0291E}, // RIGHTWARDS ARROW TO BLACK DIAMOND {"rarrhk", 0x021AA}, // RIGHTWARDS ARROW WITH HOOK {"rarrlp", 0x021AC}, // RIGHTWARDS ARROW WITH LOOP {"rarrpl", 0x02945}, // RIGHTWARDS ARROW WITH PLUS BELOW {"rarrsim", 0x02974}, // RIGHTWARDS ARROW ABOVE TILDE OPERATOR {"rarrtl", 0x021A3}, // RIGHTWARDS ARROW WITH TAIL {"Rarrtl", 0x02916}, // RIGHTWARDS TWO-HEADED ARROW WITH TAIL {"rarrw", 0x0219D}, // RIGHTWARDS WAVE ARROW {"ratail", 0x0291A}, // RIGHTWARDS ARROW-TAIL {"rAtail", 0x0291C}, // RIGHTWARDS DOUBLE ARROW-TAIL {"ratio", 0x02236}, // RATIO {"rationals", 0x0211A}, // DOUBLE-STRUCK CAPITAL Q {"rbarr", 0x0290D}, // RIGHTWARDS DOUBLE DASH ARROW {"rBarr", 0x0290F}, // RIGHTWARDS TRIPLE DASH ARROW {"RBarr", 0x02910}, // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW {"rbbrk", 0x02773}, // LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT {"rbrace", 0x0007D}, // RIGHT CURLY BRACKET {"rbrack", 0x0005D}, // RIGHT SQUARE BRACKET {"rbrke", 0x0298C}, // RIGHT SQUARE BRACKET WITH UNDERBAR {"rbrksld", 0x0298E}, // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER {"rbrkslu", 0x02990}, // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER {"Rcaron", 0x00158}, // LATIN CAPITAL LETTER R WITH CARON {"rcaron", 0x00159}, // LATIN SMALL LETTER R WITH CARON {"Rcedil", 0x00156}, // LATIN CAPITAL LETTER R WITH CEDILLA {"rcedil", 0x00157}, // LATIN SMALL LETTER R WITH CEDILLA {"rceil", 0x02309}, // RIGHT CEILING {"rcub", 0x0007D}, // RIGHT CURLY BRACKET {"Rcy", 0x00420}, // CYRILLIC CAPITAL LETTER ER {"rcy", 0x00440}, // CYRILLIC SMALL LETTER ER {"rdca", 0x02937}, // ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS {"rdldhar", 0x02969}, // RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN {"rdquo", 0x0201D}, // RIGHT DOUBLE QUOTATION MARK {"rdquor", 0x0201D}, // RIGHT DOUBLE QUOTATION MARK {"rdsh", 0x021B3}, // DOWNWARDS ARROW WITH TIP RIGHTWARDS {"Re", 0x0211C}, // BLACK-LETTER CAPITAL R {"real", 0x0211C}, // BLACK-LETTER CAPITAL R {"realine", 0x0211B}, // SCRIPT CAPITAL R {"realpart", 0x0211C}, // BLACK-LETTER CAPITAL R {"reals", 0x0211D}, // DOUBLE-STRUCK CAPITAL R {"rect", 0x025AD}, // WHITE RECTANGLE {"reg", 0x000AE}, // REGISTERED SIGN {"REG", 0x000AE}, // REGISTERED SIGN {"ReverseElement", 0x0220B}, // CONTAINS AS MEMBER {"ReverseEquilibrium", 0x021CB}, // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON {"ReverseUpEquilibrium", 0x0296F}, // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT {"rfisht", 0x0297D}, // RIGHT FISH TAIL {"rfloor", 0x0230B}, // RIGHT FLOOR {"Rfr", 0x0211C}, // BLACK-LETTER CAPITAL R {"rfr", 0x1D52F}, // MATHEMATICAL FRAKTUR SMALL R {"Rgr", 0x003A1}, // GREEK CAPITAL LETTER RHO {"rgr", 0x003C1}, // GREEK SMALL LETTER RHO {"rHar", 0x02964}, // RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN {"rhard", 0x021C1}, // RIGHTWARDS HARPOON WITH BARB DOWNWARDS {"rharu", 0x021C0}, // RIGHTWARDS HARPOON WITH BARB UPWARDS {"rharul", 0x0296C}, // RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH {"Rho", 0x003A1}, // GREEK CAPITAL LETTER RHO {"rho", 0x003C1}, // GREEK SMALL LETTER RHO {"rhov", 0x003F1}, // GREEK RHO SYMBOL {"RightAngleBracket", 0x027E9}, // MATHEMATICAL RIGHT ANGLE BRACKET {"rightarrow", 0x02192}, // RIGHTWARDS ARROW {"RightArrow", 0x02192}, // RIGHTWARDS ARROW {"Rightarrow", 0x021D2}, // RIGHTWARDS DOUBLE ARROW {"RightArrowBar", 0x021E5}, // RIGHTWARDS ARROW TO BAR {"RightArrowLeftArrow", 0x021C4}, // RIGHTWARDS ARROW OVER LEFTWARDS ARROW {"rightarrowtail", 0x021A3}, // RIGHTWARDS ARROW WITH TAIL {"RightCeiling", 0x02309}, // RIGHT CEILING {"RightDoubleBracket", 0x027E7}, // MATHEMATICAL RIGHT WHITE SQUARE BRACKET {"RightDownTeeVector", 0x0295D}, // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR {"RightDownVector", 0x021C2}, // DOWNWARDS HARPOON WITH BARB RIGHTWARDS {"RightDownVectorBar", 0x02955}, // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR {"RightFloor", 0x0230B}, // RIGHT FLOOR {"rightharpoondown", 0x021C1}, // RIGHTWARDS HARPOON WITH BARB DOWNWARDS {"rightharpoonup", 0x021C0}, // RIGHTWARDS HARPOON WITH BARB UPWARDS {"rightleftarrows", 0x021C4}, // RIGHTWARDS ARROW OVER LEFTWARDS ARROW {"rightleftharpoons", 0x021CC}, // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON {"rightrightarrows", 0x021C9}, // RIGHTWARDS PAIRED ARROWS {"rightsquigarrow", 0x0219D}, // RIGHTWARDS WAVE ARROW {"RightTee", 0x022A2}, // RIGHT TACK {"RightTeeArrow", 0x021A6}, // RIGHTWARDS ARROW FROM BAR {"RightTeeVector", 0x0295B}, // RIGHTWARDS HARPOON WITH BARB UP FROM BAR {"rightthreetimes", 0x022CC}, // RIGHT SEMIDIRECT PRODUCT {"RightTriangle", 0x022B3}, // CONTAINS AS NORMAL SUBGROUP {"RightTriangleBar", 0x029D0}, // VERTICAL BAR BESIDE RIGHT TRIANGLE {"RightTriangleEqual", 0x022B5}, // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO {"RightUpDownVector", 0x0294F}, // UP BARB RIGHT DOWN BARB RIGHT HARPOON {"RightUpTeeVector", 0x0295C}, // UPWARDS HARPOON WITH BARB RIGHT FROM BAR {"RightUpVector", 0x021BE}, // UPWARDS HARPOON WITH BARB RIGHTWARDS {"RightUpVectorBar", 0x02954}, // UPWARDS HARPOON WITH BARB RIGHT TO BAR {"RightVector", 0x021C0}, // RIGHTWARDS HARPOON WITH BARB UPWARDS {"RightVectorBar", 0x02953}, // RIGHTWARDS HARPOON WITH BARB UP TO BAR {"ring", 0x002DA}, // RING ABOVE {"risingdotseq", 0x02253}, // IMAGE OF OR APPROXIMATELY EQUAL TO {"rlarr", 0x021C4}, // RIGHTWARDS ARROW OVER LEFTWARDS ARROW {"rlhar", 0x021CC}, // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON {"rlm", 0x0200F}, // RIGHT-TO-LEFT MARK {"rmoust", 0x023B1}, // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION {"rmoustache", 0x023B1}, // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION {"rnmid", 0x02AEE}, // DOES NOT DIVIDE WITH REVERSED NEGATION SLASH {"roang", 0x027ED}, // MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET {"roarr", 0x021FE}, // RIGHTWARDS OPEN-HEADED ARROW {"robrk", 0x027E7}, // MATHEMATICAL RIGHT WHITE SQUARE BRACKET {"ropar", 0x02986}, // RIGHT WHITE PARENTHESIS {"Ropf", 0x0211D}, // DOUBLE-STRUCK CAPITAL R {"ropf", 0x1D563}, // MATHEMATICAL DOUBLE-STRUCK SMALL R {"roplus", 0x02A2E}, // PLUS SIGN IN RIGHT HALF CIRCLE {"rotimes", 0x02A35}, // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE {"RoundImplies", 0x02970}, // RIGHT DOUBLE ARROW WITH ROUNDED HEAD {"rpar", 0x00029}, // RIGHT PARENTHESIS {"rpargt", 0x02994}, // RIGHT ARC GREATER-THAN BRACKET {"rppolint", 0x02A12}, // LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE {"rrarr", 0x021C9}, // RIGHTWARDS PAIRED ARROWS {"Rrightarrow", 0x021DB}, // RIGHTWARDS TRIPLE ARROW {"rsaquo", 0x0203A}, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK {"Rscr", 0x0211B}, // SCRIPT CAPITAL R {"rscr", 0x1D4C7}, // MATHEMATICAL SCRIPT SMALL R {"rsh", 0x021B1}, // UPWARDS ARROW WITH TIP RIGHTWARDS {"Rsh", 0x021B1}, // UPWARDS ARROW WITH TIP RIGHTWARDS {"rsqb", 0x0005D}, // RIGHT SQUARE BRACKET {"rsquo", 0x02019}, // RIGHT SINGLE QUOTATION MARK {"rsquor", 0x02019}, // RIGHT SINGLE QUOTATION MARK {"rthree", 0x022CC}, // RIGHT SEMIDIRECT PRODUCT {"rtimes", 0x022CA}, // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT {"rtri", 0x025B9}, // WHITE RIGHT-POINTING SMALL TRIANGLE {"rtrie", 0x022B5}, // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO {"rtrif", 0x025B8}, // BLACK RIGHT-POINTING SMALL TRIANGLE {"rtriltri", 0x029CE}, // RIGHT TRIANGLE ABOVE LEFT TRIANGLE {"RuleDelayed", 0x029F4}, // RULE-DELAYED {"ruluhar", 0x02968}, // RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP {"rx", 0x0211E}, // PRESCRIPTION TAKE ]; immutable NameId[] namesS = [ {"Sacute", 0x0015A}, // LATIN CAPITAL LETTER S WITH ACUTE {"sacute", 0x0015B}, // LATIN SMALL LETTER S WITH ACUTE {"sbquo", 0x0201A}, // SINGLE LOW-9 QUOTATION MARK {"sc", 0x0227B}, // SUCCEEDS {"Sc", 0x02ABC}, // DOUBLE SUCCEEDS {"scap", 0x02AB8}, // SUCCEEDS ABOVE ALMOST EQUAL TO {"Scaron", 0x00160}, // LATIN CAPITAL LETTER S WITH CARON {"scaron", 0x00161}, // LATIN SMALL LETTER S WITH CARON {"sccue", 0x0227D}, // SUCCEEDS OR EQUAL TO {"sce", 0x02AB0}, // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN {"scE", 0x02AB4}, // SUCCEEDS ABOVE EQUALS SIGN {"Scedil", 0x0015E}, // LATIN CAPITAL LETTER S WITH CEDILLA {"scedil", 0x0015F}, // LATIN SMALL LETTER S WITH CEDILLA {"Scirc", 0x0015C}, // LATIN CAPITAL LETTER S WITH CIRCUMFLEX {"scirc", 0x0015D}, // LATIN SMALL LETTER S WITH CIRCUMFLEX {"scnap", 0x02ABA}, // SUCCEEDS ABOVE NOT ALMOST EQUAL TO {"scnE", 0x02AB6}, // SUCCEEDS ABOVE NOT EQUAL TO {"scnsim", 0x022E9}, // SUCCEEDS BUT NOT EQUIVALENT TO {"scpolint", 0x02A13}, // LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE {"scsim", 0x0227F}, // SUCCEEDS OR EQUIVALENT TO {"Scy", 0x00421}, // CYRILLIC CAPITAL LETTER ES {"scy", 0x00441}, // CYRILLIC SMALL LETTER ES {"sdot", 0x022C5}, // DOT OPERATOR {"sdotb", 0x022A1}, // SQUARED DOT OPERATOR {"sdote", 0x02A66}, // EQUALS SIGN WITH DOT BELOW {"searhk", 0x02925}, // SOUTH EAST ARROW WITH HOOK {"searr", 0x02198}, // SOUTH EAST ARROW {"seArr", 0x021D8}, // SOUTH EAST DOUBLE ARROW {"searrow", 0x02198}, // SOUTH EAST ARROW {"sect", 0x000A7}, // SECTION SIGN {"semi", 0x0003B}, // SEMICOLON {"seswar", 0x02929}, // SOUTH EAST ARROW AND SOUTH WEST ARROW {"setminus", 0x02216}, // SET MINUS {"setmn", 0x02216}, // SET MINUS {"sext", 0x02736}, // SIX POINTED BLACK STAR {"sfgr", 0x003C2}, // GREEK SMALL LETTER FINAL SIGMA {"Sfr", 0x1D516}, // MATHEMATICAL FRAKTUR CAPITAL S {"sfr", 0x1D530}, // MATHEMATICAL FRAKTUR SMALL S {"sfrown", 0x02322}, // FROWN {"Sgr", 0x003A3}, // GREEK CAPITAL LETTER SIGMA {"sgr", 0x003C3}, // GREEK SMALL LETTER SIGMA {"sharp", 0x0266F}, // MUSIC SHARP SIGN {"SHCHcy", 0x00429}, // CYRILLIC CAPITAL LETTER SHCHA {"shchcy", 0x00449}, // CYRILLIC SMALL LETTER SHCHA {"SHcy", 0x00428}, // CYRILLIC CAPITAL LETTER SHA {"shcy", 0x00448}, // CYRILLIC SMALL LETTER SHA {"ShortDownArrow", 0x02193}, // DOWNWARDS ARROW {"ShortLeftArrow", 0x02190}, // LEFTWARDS ARROW {"shortmid", 0x02223}, // DIVIDES {"shortparallel", 0x02225}, // PARALLEL TO {"ShortRightArrow", 0x02192}, // RIGHTWARDS ARROW {"ShortUpArrow", 0x02191}, // UPWARDS ARROW {"shy", 0x000AD}, // SOFT HYPHEN {"Sigma", 0x003A3}, // GREEK CAPITAL LETTER SIGMA {"sigma", 0x003C3}, // GREEK SMALL LETTER SIGMA {"sigmaf", 0x003C2}, // GREEK SMALL LETTER FINAL SIGMA {"sigmav", 0x003C2}, // GREEK SMALL LETTER FINAL SIGMA {"sim", 0x0223C}, // TILDE OPERATOR {"simdot", 0x02A6A}, // TILDE OPERATOR WITH DOT ABOVE {"sime", 0x02243}, // ASYMPTOTICALLY EQUAL TO {"simeq", 0x02243}, // ASYMPTOTICALLY EQUAL TO {"simg", 0x02A9E}, // SIMILAR OR GREATER-THAN {"simgE", 0x02AA0}, // SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN {"siml", 0x02A9D}, // SIMILAR OR LESS-THAN {"simlE", 0x02A9F}, // SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN {"simne", 0x02246}, // APPROXIMATELY BUT NOT ACTUALLY EQUAL TO {"simplus", 0x02A24}, // PLUS SIGN WITH TILDE ABOVE {"simrarr", 0x02972}, // TILDE OPERATOR ABOVE RIGHTWARDS ARROW {"slarr", 0x02190}, // LEFTWARDS ARROW {"SmallCircle", 0x02218}, // RING OPERATOR {"smallsetminus", 0x02216}, // SET MINUS {"smashp", 0x02A33}, // SMASH PRODUCT {"smeparsl", 0x029E4}, // EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE {"smid", 0x02223}, // DIVIDES {"smile", 0x02323}, // SMILE {"smt", 0x02AAA}, // SMALLER THAN {"smte", 0x02AAC}, // SMALLER THAN OR EQUAL TO {"smtes", 0x02AAC, 0x0FE00}, // SMALLER THAN OR slanted EQUAL {"SOFTcy", 0x0042C}, // CYRILLIC CAPITAL LETTER SOFT SIGN {"softcy", 0x0044C}, // CYRILLIC SMALL LETTER SOFT SIGN {"sol", 0x0002F}, // SOLIDUS {"solb", 0x029C4}, // SQUARED RISING DIAGONAL SLASH {"solbar", 0x0233F}, // APL FUNCTIONAL SYMBOL SLASH BAR {"Sopf", 0x1D54A}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL S {"sopf", 0x1D564}, // MATHEMATICAL DOUBLE-STRUCK SMALL S {"spades", 0x02660}, // BLACK SPADE SUIT {"spadesuit", 0x02660}, // BLACK SPADE SUIT {"spar", 0x02225}, // PARALLEL TO {"sqcap", 0x02293}, // SQUARE CAP {"sqcaps", 0x02293, 0x0FE00}, // SQUARE CAP with serifs {"sqcup", 0x02294}, // SQUARE CUP {"sqcups", 0x02294, 0x0FE00}, // SQUARE CUP with serifs {"Sqrt", 0x0221A}, // SQUARE ROOT {"sqsub", 0x0228F}, // SQUARE IMAGE OF {"sqsube", 0x02291}, // SQUARE IMAGE OF OR EQUAL TO {"sqsubset", 0x0228F}, // SQUARE IMAGE OF {"sqsubseteq", 0x02291}, // SQUARE IMAGE OF OR EQUAL TO {"sqsup", 0x02290}, // SQUARE ORIGINAL OF {"sqsupe", 0x02292}, // SQUARE ORIGINAL OF OR EQUAL TO {"sqsupset", 0x02290}, // SQUARE ORIGINAL OF {"sqsupseteq", 0x02292}, // SQUARE ORIGINAL OF OR EQUAL TO {"squ", 0x025A1}, // WHITE SQUARE {"square", 0x025A1}, // WHITE SQUARE {"Square", 0x025A1}, // WHITE SQUARE {"SquareIntersection", 0x02293}, // SQUARE CAP {"SquareSubset", 0x0228F}, // SQUARE IMAGE OF {"SquareSubsetEqual", 0x02291}, // SQUARE IMAGE OF OR EQUAL TO {"SquareSuperset", 0x02290}, // SQUARE ORIGINAL OF {"SquareSupersetEqual", 0x02292}, // SQUARE ORIGINAL OF OR EQUAL TO {"SquareUnion", 0x02294}, // SQUARE CUP {"squarf", 0x025AA}, // BLACK SMALL SQUARE {"squf", 0x025AA}, // BLACK SMALL SQUARE {"srarr", 0x02192}, // RIGHTWARDS ARROW {"Sscr", 0x1D4AE}, // MATHEMATICAL SCRIPT CAPITAL S {"sscr", 0x1D4C8}, // MATHEMATICAL SCRIPT SMALL S {"ssetmn", 0x02216}, // SET MINUS {"ssmile", 0x02323}, // SMILE {"sstarf", 0x022C6}, // STAR OPERATOR {"Star", 0x022C6}, // STAR OPERATOR {"star", 0x02606}, // WHITE STAR {"starf", 0x02605}, // BLACK STAR {"straightepsilon", 0x003F5}, // GREEK LUNATE EPSILON SYMBOL {"straightphi", 0x003D5}, // GREEK PHI SYMBOL {"strns", 0x000AF}, // MACRON {"sub", 0x02282}, // SUBSET OF {"Sub", 0x022D0}, // DOUBLE SUBSET {"subdot", 0x02ABD}, // SUBSET WITH DOT {"sube", 0x02286}, // SUBSET OF OR EQUAL TO {"subE", 0x02AC5}, // SUBSET OF ABOVE EQUALS SIGN {"subedot", 0x02AC3}, // SUBSET OF OR EQUAL TO WITH DOT ABOVE {"submult", 0x02AC1}, // SUBSET WITH MULTIPLICATION SIGN BELOW {"subne", 0x0228A}, // SUBSET OF WITH NOT EQUAL TO {"subnE", 0x02ACB}, // SUBSET OF ABOVE NOT EQUAL TO {"subplus", 0x02ABF}, // SUBSET WITH PLUS SIGN BELOW {"subrarr", 0x02979}, // SUBSET ABOVE RIGHTWARDS ARROW {"subset", 0x02282}, // SUBSET OF {"Subset", 0x022D0}, // DOUBLE SUBSET {"subseteq", 0x02286}, // SUBSET OF OR EQUAL TO {"subseteqq", 0x02AC5}, // SUBSET OF ABOVE EQUALS SIGN {"SubsetEqual", 0x02286}, // SUBSET OF OR EQUAL TO {"subsetneq", 0x0228A}, // SUBSET OF WITH NOT EQUAL TO {"subsetneqq", 0x02ACB}, // SUBSET OF ABOVE NOT EQUAL TO {"subsim", 0x02AC7}, // SUBSET OF ABOVE TILDE OPERATOR {"subsub", 0x02AD5}, // SUBSET ABOVE SUBSET {"subsup", 0x02AD3}, // SUBSET ABOVE SUPERSET {"succ", 0x0227B}, // SUCCEEDS {"succapprox", 0x02AB8}, // SUCCEEDS ABOVE ALMOST EQUAL TO {"succcurlyeq", 0x0227D}, // SUCCEEDS OR EQUAL TO {"Succeeds", 0x0227B}, // SUCCEEDS {"SucceedsEqual", 0x02AB0}, // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN {"SucceedsSlantEqual", 0x0227D}, // SUCCEEDS OR EQUAL TO {"SucceedsTilde", 0x0227F}, // SUCCEEDS OR EQUIVALENT TO {"succeq", 0x02AB0}, // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN {"succnapprox", 0x02ABA}, // SUCCEEDS ABOVE NOT ALMOST EQUAL TO {"succneqq", 0x02AB6}, // SUCCEEDS ABOVE NOT EQUAL TO {"succnsim", 0x022E9}, // SUCCEEDS BUT NOT EQUIVALENT TO {"succsim", 0x0227F}, // SUCCEEDS OR EQUIVALENT TO {"SuchThat", 0x0220B}, // CONTAINS AS MEMBER {"sum", 0x02211}, // N-ARY SUMMATION {"Sum", 0x02211}, // N-ARY SUMMATION {"sung", 0x0266A}, // EIGHTH NOTE {"sup", 0x02283}, // SUPERSET OF {"Sup", 0x022D1}, // DOUBLE SUPERSET {"sup1", 0x000B9}, // SUPERSCRIPT ONE {"sup2", 0x000B2}, // SUPERSCRIPT TWO {"sup3", 0x000B3}, // SUPERSCRIPT THREE {"supdot", 0x02ABE}, // SUPERSET WITH DOT {"supdsub", 0x02AD8}, // SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET {"supe", 0x02287}, // SUPERSET OF OR EQUAL TO {"supE", 0x02AC6}, // SUPERSET OF ABOVE EQUALS SIGN {"supedot", 0x02AC4}, // SUPERSET OF OR EQUAL TO WITH DOT ABOVE {"Superset", 0x02283}, // SUPERSET OF {"SupersetEqual", 0x02287}, // SUPERSET OF OR EQUAL TO {"suphsol", 0x027C9}, // SUPERSET PRECEDING SOLIDUS {"suphsub", 0x02AD7}, // SUPERSET BESIDE SUBSET {"suplarr", 0x0297B}, // SUPERSET ABOVE LEFTWARDS ARROW {"supmult", 0x02AC2}, // SUPERSET WITH MULTIPLICATION SIGN BELOW {"supne", 0x0228B}, // SUPERSET OF WITH NOT EQUAL TO {"supnE", 0x02ACC}, // SUPERSET OF ABOVE NOT EQUAL TO {"supplus", 0x02AC0}, // SUPERSET WITH PLUS SIGN BELOW {"supset", 0x02283}, // SUPERSET OF {"Supset", 0x022D1}, // DOUBLE SUPERSET {"supseteq", 0x02287}, // SUPERSET OF OR EQUAL TO {"supseteqq", 0x02AC6}, // SUPERSET OF ABOVE EQUALS SIGN {"supsetneq", 0x0228B}, // SUPERSET OF WITH NOT EQUAL TO {"supsetneqq", 0x02ACC}, // SUPERSET OF ABOVE NOT EQUAL TO {"supsim", 0x02AC8}, // SUPERSET OF ABOVE TILDE OPERATOR {"supsub", 0x02AD4}, // SUPERSET ABOVE SUBSET {"supsup", 0x02AD6}, // SUPERSET ABOVE SUPERSET {"swarhk", 0x02926}, // SOUTH WEST ARROW WITH HOOK {"swarr", 0x02199}, // SOUTH WEST ARROW {"swArr", 0x021D9}, // SOUTH WEST DOUBLE ARROW {"swarrow", 0x02199}, // SOUTH WEST ARROW {"swnwar", 0x0292A}, // SOUTH WEST ARROW AND NORTH WEST ARROW {"szlig", 0x000DF}, // LATIN SMALL LETTER SHARP S ]; immutable NameId[] namesT = [ {"Tab", 0x00009}, // CHARACTER TABULATION {"target", 0x02316}, // POSITION INDICATOR {"Tau", 0x003A4}, // GREEK CAPITAL LETTER TAU {"tau", 0x003C4}, // GREEK SMALL LETTER TAU {"tbrk", 0x023B4}, // TOP SQUARE BRACKET {"Tcaron", 0x00164}, // LATIN CAPITAL LETTER T WITH CARON {"tcaron", 0x00165}, // LATIN SMALL LETTER T WITH CARON {"Tcedil", 0x00162}, // LATIN CAPITAL LETTER T WITH CEDILLA {"tcedil", 0x00163}, // LATIN SMALL LETTER T WITH CEDILLA {"Tcy", 0x00422}, // CYRILLIC CAPITAL LETTER TE {"tcy", 0x00442}, // CYRILLIC SMALL LETTER TE {"tdot", 0x020DB}, // COMBINING THREE DOTS ABOVE {"telrec", 0x02315}, // TELEPHONE RECORDER {"Tfr", 0x1D517}, // MATHEMATICAL FRAKTUR CAPITAL T {"tfr", 0x1D531}, // MATHEMATICAL FRAKTUR SMALL T {"Tgr", 0x003A4}, // GREEK CAPITAL LETTER TAU {"tgr", 0x003C4}, // GREEK SMALL LETTER TAU {"there4", 0x02234}, // THEREFORE {"therefore", 0x02234}, // THEREFORE {"Therefore", 0x02234}, // THEREFORE {"Theta", 0x00398}, // GREEK CAPITAL LETTER THETA {"theta", 0x003B8}, // GREEK SMALL LETTER THETA {"thetasym", 0x003D1}, // GREEK THETA SYMBOL {"thetav", 0x003D1}, // GREEK THETA SYMBOL {"THgr", 0x00398}, // GREEK CAPITAL LETTER THETA {"thgr", 0x003B8}, // GREEK SMALL LETTER THETA {"thickapprox", 0x02248}, // ALMOST EQUAL TO {"thicksim", 0x0223C}, // TILDE OPERATOR {"ThickSpace", 0x0205F, 0x0200A}, // space of width 5/18 em {"thinsp", 0x02009}, // THIN SPACE {"ThinSpace", 0x02009}, // THIN SPACE {"thkap", 0x02248}, // ALMOST EQUAL TO {"thksim", 0x0223C}, // TILDE OPERATOR {"THORN", 0x000DE}, // LATIN CAPITAL LETTER THORN {"thorn", 0x000FE}, // LATIN SMALL LETTER THORN {"tilde", 0x002DC}, // SMALL TILDE {"Tilde", 0x0223C}, // TILDE OPERATOR {"TildeEqual", 0x02243}, // ASYMPTOTICALLY EQUAL TO {"TildeFullEqual", 0x02245}, // APPROXIMATELY EQUAL TO {"TildeTilde", 0x02248}, // ALMOST EQUAL TO {"times", 0x000D7}, // MULTIPLICATION SIGN {"timesb", 0x022A0}, // SQUARED TIMES {"timesbar", 0x02A31}, // MULTIPLICATION SIGN WITH UNDERBAR {"timesd", 0x02A30}, // MULTIPLICATION SIGN WITH DOT ABOVE {"tint", 0x0222D}, // TRIPLE INTEGRAL {"toea", 0x02928}, // NORTH EAST ARROW AND SOUTH EAST ARROW {"top", 0x022A4}, // DOWN TACK {"topbot", 0x02336}, // APL FUNCTIONAL SYMBOL I-BEAM {"topcir", 0x02AF1}, // DOWN TACK WITH CIRCLE BELOW {"Topf", 0x1D54B}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL T {"topf", 0x1D565}, // MATHEMATICAL DOUBLE-STRUCK SMALL T {"topfork", 0x02ADA}, // PITCHFORK WITH TEE TOP {"tosa", 0x02929}, // SOUTH EAST ARROW AND SOUTH WEST ARROW {"tprime", 0x02034}, // TRIPLE PRIME {"trade", 0x02122}, // TRADE MARK SIGN {"TRADE", 0x02122}, // TRADE MARK SIGN {"triangle", 0x025B5}, // WHITE UP-POINTING SMALL TRIANGLE {"triangledown", 0x025BF}, // WHITE DOWN-POINTING SMALL TRIANGLE {"triangleleft", 0x025C3}, // WHITE LEFT-POINTING SMALL TRIANGLE {"trianglelefteq", 0x022B4}, // NORMAL SUBGROUP OF OR EQUAL TO {"triangleq", 0x0225C}, // DELTA EQUAL TO {"triangleright", 0x025B9}, // WHITE RIGHT-POINTING SMALL TRIANGLE {"trianglerighteq", 0x022B5}, // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO {"tridot", 0x025EC}, // WHITE UP-POINTING TRIANGLE WITH DOT {"trie", 0x0225C}, // DELTA EQUAL TO {"triminus", 0x02A3A}, // MINUS SIGN IN TRIANGLE {"TripleDot", 0x020DB}, // COMBINING THREE DOTS ABOVE {"triplus", 0x02A39}, // PLUS SIGN IN TRIANGLE {"trisb", 0x029CD}, // TRIANGLE WITH SERIFS AT BOTTOM {"tritime", 0x02A3B}, // MULTIPLICATION SIGN IN TRIANGLE {"trpezium", 0x023E2}, // WHITE TRAPEZIUM {"Tscr", 0x1D4AF}, // MATHEMATICAL SCRIPT CAPITAL T {"tscr", 0x1D4C9}, // MATHEMATICAL SCRIPT SMALL T {"TScy", 0x00426}, // CYRILLIC CAPITAL LETTER TSE {"tscy", 0x00446}, // CYRILLIC SMALL LETTER TSE {"TSHcy", 0x0040B}, // CYRILLIC CAPITAL LETTER TSHE {"tshcy", 0x0045B}, // CYRILLIC SMALL LETTER TSHE {"Tstrok", 0x00166}, // LATIN CAPITAL LETTER T WITH STROKE {"tstrok", 0x00167}, // LATIN SMALL LETTER T WITH STROKE {"twixt", 0x0226C}, // BETWEEN {"twoheadleftarrow", 0x0219E}, // LEFTWARDS TWO HEADED ARROW {"twoheadrightarrow", 0x021A0}, // RIGHTWARDS TWO HEADED ARROW ]; immutable NameId[] namesU = [ {"Uacgr", 0x0038E}, // GREEK CAPITAL LETTER UPSILON WITH TONOS {"uacgr", 0x003CD}, // GREEK SMALL LETTER UPSILON WITH TONOS {"Uacute", 0x000DA}, // LATIN CAPITAL LETTER U WITH ACUTE {"uacute", 0x000FA}, // LATIN SMALL LETTER U WITH ACUTE {"uarr", 0x02191}, // UPWARDS ARROW {"Uarr", 0x0219F}, // UPWARDS TWO HEADED ARROW {"uArr", 0x021D1}, // UPWARDS DOUBLE ARROW {"Uarrocir", 0x02949}, // UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE {"Ubrcy", 0x0040E}, // CYRILLIC CAPITAL LETTER SHORT U {"ubrcy", 0x0045E}, // CYRILLIC SMALL LETTER SHORT U {"Ubreve", 0x0016C}, // LATIN CAPITAL LETTER U WITH BREVE {"ubreve", 0x0016D}, // LATIN SMALL LETTER U WITH BREVE {"Ucirc", 0x000DB}, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX {"ucirc", 0x000FB}, // LATIN SMALL LETTER U WITH CIRCUMFLEX {"Ucy", 0x00423}, // CYRILLIC CAPITAL LETTER U {"ucy", 0x00443}, // CYRILLIC SMALL LETTER U {"udarr", 0x021C5}, // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW {"Udblac", 0x00170}, // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE {"udblac", 0x00171}, // LATIN SMALL LETTER U WITH DOUBLE ACUTE {"udhar", 0x0296E}, // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT {"udiagr", 0x003B0}, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS {"Udigr", 0x003AB}, // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA {"udigr", 0x003CB}, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA {"ufisht", 0x0297E}, // UP FISH TAIL {"Ufr", 0x1D518}, // MATHEMATICAL FRAKTUR CAPITAL U {"ufr", 0x1D532}, // MATHEMATICAL FRAKTUR SMALL U {"Ugr", 0x003A5}, // GREEK CAPITAL LETTER UPSILON {"ugr", 0x003C5}, // GREEK SMALL LETTER UPSILON {"Ugrave", 0x000D9}, // LATIN CAPITAL LETTER U WITH GRAVE {"ugrave", 0x000F9}, // LATIN SMALL LETTER U WITH GRAVE {"uHar", 0x02963}, // UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT {"uharl", 0x021BF}, // UPWARDS HARPOON WITH BARB LEFTWARDS {"uharr", 0x021BE}, // UPWARDS HARPOON WITH BARB RIGHTWARDS {"uhblk", 0x02580}, // UPPER HALF BLOCK {"ulcorn", 0x0231C}, // TOP LEFT CORNER {"ulcorner", 0x0231C}, // TOP LEFT CORNER {"ulcrop", 0x0230F}, // TOP LEFT CROP {"ultri", 0x025F8}, // UPPER LEFT TRIANGLE {"Umacr", 0x0016A}, // LATIN CAPITAL LETTER U WITH MACRON {"umacr", 0x0016B}, // LATIN SMALL LETTER U WITH MACRON {"uml", 0x000A8}, // DIAERESIS {"UnderBar", 0x0005F}, // LOW LINE {"UnderBrace", 0x023DF}, // BOTTOM CURLY BRACKET {"UnderBracket", 0x023B5}, // BOTTOM SQUARE BRACKET {"UnderParenthesis", 0x023DD}, // BOTTOM PARENTHESIS {"Union", 0x022C3}, // N-ARY UNION {"UnionPlus", 0x0228E}, // MULTISET UNION {"Uogon", 0x00172}, // LATIN CAPITAL LETTER U WITH OGONEK {"uogon", 0x00173}, // LATIN SMALL LETTER U WITH OGONEK {"Uopf", 0x1D54C}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL U {"uopf", 0x1D566}, // MATHEMATICAL DOUBLE-STRUCK SMALL U {"uparrow", 0x02191}, // UPWARDS ARROW {"UpArrow", 0x02191}, // UPWARDS ARROW {"Uparrow", 0x021D1}, // UPWARDS DOUBLE ARROW {"UpArrowBar", 0x02912}, // UPWARDS ARROW TO BAR {"UpArrowDownArrow", 0x021C5}, // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW {"updownarrow", 0x02195}, // UP DOWN ARROW {"UpDownArrow", 0x02195}, // UP DOWN ARROW {"Updownarrow", 0x021D5}, // UP DOWN DOUBLE ARROW {"UpEquilibrium", 0x0296E}, // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT {"upharpoonleft", 0x021BF}, // UPWARDS HARPOON WITH BARB LEFTWARDS {"upharpoonright", 0x021BE}, // UPWARDS HARPOON WITH BARB RIGHTWARDS {"uplus", 0x0228E}, // MULTISET UNION {"UpperLeftArrow", 0x02196}, // NORTH WEST ARROW {"UpperRightArrow", 0x02197}, // NORTH EAST ARROW {"upsi", 0x003C5}, // GREEK SMALL LETTER UPSILON {"Upsi", 0x003D2}, // GREEK UPSILON WITH HOOK SYMBOL {"upsih", 0x003D2}, // GREEK UPSILON WITH HOOK SYMBOL {"Upsilon", 0x003A5}, // GREEK CAPITAL LETTER UPSILON {"upsilon", 0x003C5}, // GREEK SMALL LETTER UPSILON {"UpTee", 0x022A5}, // UP TACK {"UpTeeArrow", 0x021A5}, // UPWARDS ARROW FROM BAR {"upuparrows", 0x021C8}, // UPWARDS PAIRED ARROWS {"urcorn", 0x0231D}, // TOP RIGHT CORNER {"urcorner", 0x0231D}, // TOP RIGHT CORNER {"urcrop", 0x0230E}, // TOP RIGHT CROP {"Uring", 0x0016E}, // LATIN CAPITAL LETTER U WITH RING ABOVE {"uring", 0x0016F}, // LATIN SMALL LETTER U WITH RING ABOVE {"urtri", 0x025F9}, // UPPER RIGHT TRIANGLE {"Uscr", 0x1D4B0}, // MATHEMATICAL SCRIPT CAPITAL U {"uscr", 0x1D4CA}, // MATHEMATICAL SCRIPT SMALL U {"utdot", 0x022F0}, // UP RIGHT DIAGONAL ELLIPSIS {"Utilde", 0x00168}, // LATIN CAPITAL LETTER U WITH TILDE {"utilde", 0x00169}, // LATIN SMALL LETTER U WITH TILDE {"utri", 0x025B5}, // WHITE UP-POINTING SMALL TRIANGLE {"utrif", 0x025B4}, // BLACK UP-POINTING SMALL TRIANGLE {"uuarr", 0x021C8}, // UPWARDS PAIRED ARROWS {"Uuml", 0x000DC}, // LATIN CAPITAL LETTER U WITH DIAERESIS {"uuml", 0x000FC}, // LATIN SMALL LETTER U WITH DIAERESIS {"uwangle", 0x029A7}, // OBLIQUE ANGLE OPENING DOWN ]; immutable NameId[] namesV = [ {"vangrt", 0x0299C}, // RIGHT ANGLE VARIANT WITH SQUARE {"varepsilon", 0x003F5}, // GREEK LUNATE EPSILON SYMBOL {"varkappa", 0x003F0}, // GREEK KAPPA SYMBOL {"varnothing", 0x02205}, // EMPTY SET {"varphi", 0x003D5}, // GREEK PHI SYMBOL {"varpi", 0x003D6}, // GREEK PI SYMBOL {"varpropto", 0x0221D}, // PROPORTIONAL TO {"varr", 0x02195}, // UP DOWN ARROW {"vArr", 0x021D5}, // UP DOWN DOUBLE ARROW {"varrho", 0x003F1}, // GREEK RHO SYMBOL {"varsigma", 0x003C2}, // GREEK SMALL LETTER FINAL SIGMA {"varsubsetneq", 0x0228A, 0x0FE00}, // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members {"varsubsetneqq", 0x02ACB, 0x0FE00}, // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members {"varsupsetneq", 0x0228B, 0x0FE00}, // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members {"varsupsetneqq", 0x02ACC, 0x0FE00}, // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members {"vartheta", 0x003D1}, // GREEK THETA SYMBOL {"vartriangleleft", 0x022B2}, // NORMAL SUBGROUP OF {"vartriangleright", 0x022B3}, // CONTAINS AS NORMAL SUBGROUP {"vBar", 0x02AE8}, // SHORT UP TACK WITH UNDERBAR {"Vbar", 0x02AEB}, // DOUBLE UP TACK {"vBarv", 0x02AE9}, // SHORT UP TACK ABOVE SHORT DOWN TACK {"Vcy", 0x00412}, // CYRILLIC CAPITAL LETTER VE {"vcy", 0x00432}, // CYRILLIC SMALL LETTER VE {"vdash", 0x022A2}, // RIGHT TACK {"vDash", 0x022A8}, // TRUE {"Vdash", 0x022A9}, // FORCES {"VDash", 0x022AB}, // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE {"Vdashl", 0x02AE6}, // LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL {"vee", 0x02228}, // LOGICAL OR {"Vee", 0x022C1}, // N-ARY LOGICAL OR {"veebar", 0x022BB}, // XOR {"veeeq", 0x0225A}, // EQUIANGULAR TO {"vellip", 0x022EE}, // VERTICAL ELLIPSIS {"verbar", 0x0007C}, // VERTICAL LINE {"Verbar", 0x02017}, // DOUBLE VERTICAL LINE {"vert", 0x0007C}, // VERTICAL LINE {"Vert", 0x02017}, // DOUBLE VERTICAL LINE {"VerticalBar", 0x02223}, // DIVIDES {"VerticalLine", 0x0007C}, // VERTICAL LINE {"VerticalSeparator", 0x02758}, // LIGHT VERTICAL BAR {"VerticalTilde", 0x02240}, // WREATH PRODUCT {"VeryThinSpace", 0x0200A}, // HAIR SPACE {"Vfr", 0x1D519}, // MATHEMATICAL FRAKTUR CAPITAL V {"vfr", 0x1D533}, // MATHEMATICAL FRAKTUR SMALL V {"vltri", 0x022B2}, // NORMAL SUBGROUP OF {"vnsub", 0x02282, 0x020D2}, // SUBSET OF with vertical line {"vnsup", 0x02283, 0x020D2}, // SUPERSET OF with vertical line {"Vopf", 0x1D54D}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL V {"vopf", 0x1D567}, // MATHEMATICAL DOUBLE-STRUCK SMALL V {"vprop", 0x0221D}, // PROPORTIONAL TO {"vrtri", 0x022B3}, // CONTAINS AS NORMAL SUBGROUP {"Vscr", 0x1D4B1}, // MATHEMATICAL SCRIPT CAPITAL V {"vscr", 0x1D4CB}, // MATHEMATICAL SCRIPT SMALL V {"vsubne", 0x0228A, 0x0FE00}, // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members {"vsubnE", 0x02ACB, 0x0FE00}, // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members {"vsupne", 0x0228B, 0x0FE00}, // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members {"vsupnE", 0x02ACC, 0x0FE00}, // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members {"Vvdash", 0x022AA}, // TRIPLE VERTICAL BAR RIGHT TURNSTILE {"vzigzag", 0x0299A}, // VERTICAL ZIGZAG LINE ]; immutable NameId[] namesW = [ {"Wcirc", 0x00174}, // LATIN CAPITAL LETTER W WITH CIRCUMFLEX {"wcirc", 0x00175}, // LATIN SMALL LETTER W WITH CIRCUMFLEX {"wedbar", 0x02A5F}, // LOGICAL AND WITH UNDERBAR {"wedge", 0x02227}, // LOGICAL AND {"Wedge", 0x022C0}, // N-ARY LOGICAL AND {"wedgeq", 0x02259}, // ESTIMATES {"weierp", 0x02118}, // SCRIPT CAPITAL P {"Wfr", 0x1D51A}, // MATHEMATICAL FRAKTUR CAPITAL W {"wfr", 0x1D534}, // MATHEMATICAL FRAKTUR SMALL W {"Wopf", 0x1D54E}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL W {"wopf", 0x1D568}, // MATHEMATICAL DOUBLE-STRUCK SMALL W {"wp", 0x02118}, // SCRIPT CAPITAL P {"wr", 0x02240}, // WREATH PRODUCT {"wreath", 0x02240}, // WREATH PRODUCT {"Wscr", 0x1D4B2}, // MATHEMATICAL SCRIPT CAPITAL W {"wscr", 0x1D4CC}, // MATHEMATICAL SCRIPT SMALL W ]; immutable NameId[] namesX = [ {"xcap", 0x022C2}, // N-ARY INTERSECTION {"xcirc", 0x025EF}, // LARGE CIRCLE {"xcup", 0x022C3}, // N-ARY UNION {"xdtri", 0x025BD}, // WHITE DOWN-POINTING TRIANGLE {"Xfr", 0x1D51B}, // MATHEMATICAL FRAKTUR CAPITAL X {"xfr", 0x1D535}, // MATHEMATICAL FRAKTUR SMALL X {"Xgr", 0x0039E}, // GREEK CAPITAL LETTER XI {"xgr", 0x003BE}, // GREEK SMALL LETTER XI {"xharr", 0x027F7}, // LONG LEFT RIGHT ARROW {"xhArr", 0x027FA}, // LONG LEFT RIGHT DOUBLE ARROW {"Xi", 0x0039E}, // GREEK CAPITAL LETTER XI {"xi", 0x003BE}, // GREEK SMALL LETTER XI {"xlarr", 0x027F5}, // LONG LEFTWARDS ARROW {"xlArr", 0x027F8}, // LONG LEFTWARDS DOUBLE ARROW {"xmap", 0x027FC}, // LONG RIGHTWARDS ARROW FROM BAR {"xnis", 0x022FB}, // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE {"xodot", 0x02A00}, // N-ARY CIRCLED DOT OPERATOR {"Xopf", 0x1D54F}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL X {"xopf", 0x1D569}, // MATHEMATICAL DOUBLE-STRUCK SMALL X {"xoplus", 0x02A01}, // N-ARY CIRCLED PLUS OPERATOR {"xotime", 0x02A02}, // N-ARY CIRCLED TIMES OPERATOR {"xrarr", 0x027F6}, // LONG RIGHTWARDS ARROW {"xrArr", 0x027F9}, // LONG RIGHTWARDS DOUBLE ARROW {"Xscr", 0x1D4B3}, // MATHEMATICAL SCRIPT CAPITAL X {"xscr", 0x1D4CD}, // MATHEMATICAL SCRIPT SMALL X {"xsqcup", 0x02A06}, // N-ARY SQUARE UNION OPERATOR {"xuplus", 0x02A04}, // N-ARY UNION OPERATOR WITH PLUS {"xutri", 0x025B3}, // WHITE UP-POINTING TRIANGLE {"xvee", 0x022C1}, // N-ARY LOGICAL OR {"xwedge", 0x022C0}, // N-ARY LOGICAL AND ]; immutable NameId[] namesY = [ {"Yacute", 0x000DD}, // LATIN CAPITAL LETTER Y WITH ACUTE {"yacute", 0x000FD}, // LATIN SMALL LETTER Y WITH ACUTE {"YAcy", 0x0042F}, // CYRILLIC CAPITAL LETTER YA {"yacy", 0x0044F}, // CYRILLIC SMALL LETTER YA {"Ycirc", 0x00176}, // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX {"ycirc", 0x00177}, // LATIN SMALL LETTER Y WITH CIRCUMFLEX {"Ycy", 0x0042B}, // CYRILLIC CAPITAL LETTER YERU {"ycy", 0x0044B}, // CYRILLIC SMALL LETTER YERU {"yen", 0x000A5}, // YEN SIGN {"Yfr", 0x1D51C}, // MATHEMATICAL FRAKTUR CAPITAL Y {"yfr", 0x1D536}, // MATHEMATICAL FRAKTUR SMALL Y {"YIcy", 0x00407}, // CYRILLIC CAPITAL LETTER YI {"yicy", 0x00457}, // CYRILLIC SMALL LETTER YI {"Yopf", 0x1D550}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL Y {"yopf", 0x1D56A}, // MATHEMATICAL DOUBLE-STRUCK SMALL Y {"Yscr", 0x1D4B4}, // MATHEMATICAL SCRIPT CAPITAL Y {"yscr", 0x1D4CE}, // MATHEMATICAL SCRIPT SMALL Y {"YUcy", 0x0042E}, // CYRILLIC CAPITAL LETTER YU {"yucy", 0x0044E}, // CYRILLIC SMALL LETTER YU {"yuml", 0x000FF}, // LATIN SMALL LETTER Y WITH DIAERESIS {"Yuml", 0x00178}, // LATIN CAPITAL LETTER Y WITH DIAERESIS ]; immutable NameId[] namesZ = [ {"Zacute", 0x00179}, // LATIN CAPITAL LETTER Z WITH ACUTE {"zacute", 0x0017A}, // LATIN SMALL LETTER Z WITH ACUTE {"Zcaron", 0x0017D}, // LATIN CAPITAL LETTER Z WITH CARON {"zcaron", 0x0017E}, // LATIN SMALL LETTER Z WITH CARON {"Zcy", 0x00417}, // CYRILLIC CAPITAL LETTER ZE {"zcy", 0x00437}, // CYRILLIC SMALL LETTER ZE {"Zdot", 0x0017B}, // LATIN CAPITAL LETTER Z WITH DOT ABOVE {"zdot", 0x0017C}, // LATIN SMALL LETTER Z WITH DOT ABOVE {"zeetrf", 0x02128}, // BLACK-LETTER CAPITAL Z {"ZeroWidthSpace", 0x0200B}, // ZERO WIDTH SPACE {"Zeta", 0x00396}, // GREEK CAPITAL LETTER ZETA {"zeta", 0x003B6}, // GREEK SMALL LETTER ZETA {"Zfr", 0x02128}, // BLACK-LETTER CAPITAL Z {"zfr", 0x1D537}, // MATHEMATICAL FRAKTUR SMALL Z {"Zgr", 0x00396}, // GREEK CAPITAL LETTER ZETA {"zgr", 0x003B6}, // GREEK SMALL LETTER ZETA {"ZHcy", 0x00416}, // CYRILLIC CAPITAL LETTER ZHE {"zhcy", 0x00436}, // CYRILLIC SMALL LETTER ZHE {"zigrarr", 0x021DD}, // RIGHTWARDS SQUIGGLE ARROW {"Zopf", 0x02124}, // DOUBLE-STRUCK CAPITAL Z {"zopf", 0x1D56B}, // MATHEMATICAL DOUBLE-STRUCK SMALL Z {"Zscr", 0x1D4B5}, // MATHEMATICAL SCRIPT CAPITAL Z {"zscr", 0x1D4CF}, // MATHEMATICAL SCRIPT SMALL Z {"zwj", 0x0200D}, // ZERO WIDTH JOINER {"zwnj", 0x0200C}, // ZERO WIDTH NON-JOINER ]; ldc-1.40.0-src/dmd/location.d0000644000000000000000000001402614727557031014360 0ustar rootroot/** * Encapsulates file/line/column locations. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/location.d, _location.d) * Documentation: https://dlang.org/phobos/dmd_location.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/location.d */ module dmd.location; import core.stdc.stdio; import dmd.common.outbuffer; import dmd.root.array; import dmd.root.filename; version (DMDLIB) { version = LocOffset; } /// How code locations are formatted for diagnostic reporting enum MessageStyle : ubyte { digitalmars, /// filename.d(line): message gnu, /// filename.d:line: message, see https://www.gnu.org/prep/standards/html_node/Errors.html } /** A source code location Used for error messages, `__FILE__` and `__LINE__` tokens, `__traits(getLocation, XXX)`, debug info etc. */ struct Loc { private uint _linnum; private uint _charnum; private uint fileIndex; // index into filenames[], starting from 1 (0 means no filename) version (LocOffset) uint fileOffset; /// utf8 code unit index relative to start of file, starting from 0 static immutable Loc initial; /// use for default initialization of const ref Loc's extern (C++) __gshared bool showColumns; extern (C++) __gshared MessageStyle messageStyle; __gshared Array!(const(char)*) filenames; nothrow: /******************************* * Configure how display is done * Params: * showColumns = when to display columns * messageStyle = digitalmars or gnu style messages */ extern (C++) static void set(bool showColumns, MessageStyle messageStyle) { this.showColumns = showColumns; this.messageStyle = messageStyle; } extern (C++) this(const(char)* filename, uint linnum, uint charnum) @safe { this._linnum = linnum; this._charnum = charnum; this.filename = filename; } /// utf8 code unit index relative to start of line, starting from 1 extern (C++) uint charnum() const @nogc @safe { return _charnum; } /// ditto extern (C++) uint charnum(uint num) @nogc @safe { return _charnum = num; } /// line number, starting from 1 extern (C++) uint linnum() const @nogc @safe { return _linnum; } /// ditto extern (C++) uint linnum(uint num) @nogc @safe { return _linnum = num; } /*** * Returns: filename for this location, null if none */ extern (C++) const(char)* filename() const @nogc { return fileIndex ? filenames[fileIndex - 1] : null; } /*** * Set file name for this location * Params: * name = file name for location, null for no file name */ extern (C++) void filename(const(char)* name) @trusted { if (name) { //printf("setting %s\n", name); filenames.push(name); fileIndex = cast(uint)filenames.length; assert(fileIndex, "internal compiler error: file name index overflow"); } else fileIndex = 0; } extern (C++) const(char)* toChars( bool showColumns = Loc.showColumns, MessageStyle messageStyle = Loc.messageStyle) const nothrow { OutBuffer buf; if (fileIndex) { buf.writestring(filename); } if (linnum) { final switch (messageStyle) { case MessageStyle.digitalmars: buf.writeByte('('); buf.print(linnum); if (showColumns && charnum) { buf.writeByte(','); buf.print(charnum); } buf.writeByte(')'); break; case MessageStyle.gnu: // https://www.gnu.org/prep/standards/html_node/Errors.html buf.writeByte(':'); buf.print(linnum); if (showColumns && charnum) { buf.writeByte(':'); buf.print(charnum); } break; } } return buf.extractChars(); } /** * Checks for equivalence by comparing the filename contents (not the pointer) and character location. * * Note: * - Uses case-insensitive comparison on Windows * - Ignores `charnum` if `Columns` is false. */ extern (C++) bool equals(ref const(Loc) loc) const { return (!showColumns || charnum == loc.charnum) && linnum == loc.linnum && FileName.equals(filename, loc.filename); } /** * `opEquals()` / `toHash()` for AA key usage * * Compare filename contents (case-sensitively on Windows too), not * the pointer - a static foreach loop repeatedly mixing in a mixin * may lead to multiple equivalent filenames (`foo.d-mixin-`), * e.g., for test/runnable/test18880.d. */ extern (D) bool opEquals(ref const(Loc) loc) const @trusted nothrow @nogc { import core.stdc.string : strcmp; return charnum == loc.charnum && linnum == loc.linnum && (filename == loc.filename || (filename && loc.filename && strcmp(filename, loc.filename) == 0)); } /// ditto extern (D) size_t toHash() const @trusted nothrow { import dmd.root.string : toDString; auto hash = hashOf(linnum); hash = hashOf(charnum, hash); hash = hashOf(filename.toDString, hash); return hash; } /****************** * Returns: * true if Loc has been set to other than the default initialization */ bool isValid() const pure @safe { return fileIndex != 0; } } ldc-1.40.0-src/dmd/id.d0000644000000000000000000004174614727557031013155 0ustar rootroot/** * Contains the `Id` struct with a list of predefined symbols the compiler knows about. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/id.d, _id.d) * Documentation: https://dlang.org/phobos/dmd_id.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/id.d */ module dmd.id; import dmd.identifier; import dmd.tokens; /** * Represents a list of predefined symbols the compiler knows about. * * All static fields in this struct represents a specific predefined symbol. */ extern (C++) struct Id { static __gshared: mixin(msgtable.generate(&identifier)); /** * Populates the identifier pool with all predefined symbols. * * An identifier that corresponds to each static field in this struct will * be placed in the identifier pool. */ extern(C++) void initialize() { mixin(msgtable.generate(&initializer)); } /** * Deinitializes the global state of the compiler. * * This can be used to restore the state set by `initialize` to its original * state. */ extern (D) void deinitialize() { mixin(msgtable.generate(&deinitializer)); } } private: /** * Each element in this array will generate one static field in the `Id` struct * and a call to `Identifier.idPool` to populate the identifier pool in the * `Id.initialize` method. */ immutable Msgtable[] msgtable = [ { "IUnknown" }, { "Object" }, { "object" }, { "_size_t", "size_t" }, { "_ptrdiff_t", "ptrdiff_t" }, { "string" }, { "wstring" }, { "dstring" }, { "max" }, { "min" }, { "This", "this" }, { "_super", "super" }, { "ctor", "__ctor" }, { "dtor", "__dtor" }, { "__xdtor", "__xdtor" }, { "__fieldDtor", "__fieldDtor" }, { "__aggrDtor", "__aggrDtor" }, { "cppdtor", "__cppdtor" }, { "ticppdtor", "__ticppdtor" }, { "postblit", "__postblit" }, { "__xpostblit", "__xpostblit" }, { "__fieldPostblit", "__fieldPostblit" }, { "__aggrPostblit", "__aggrPostblit" }, { "classInvariant", "__invariant" }, { "unitTest", "__unitTest" }, { "require", "__require" }, { "ensure", "__ensure" }, { "capture", "__capture" }, { "this2", "__this" }, { "_init", "init" }, { "__sizeof", "sizeof" }, { "__xalignof", "alignof" }, { "_mangleof", "mangleof" }, { "stringof" }, { "_tupleof", "tupleof" }, { "length" }, { "remove" }, { "ptr" }, { "array" }, { "funcptr" }, { "dollar", "__dollar" }, { "ctfe", "__ctfe" }, { "offset" }, { "offsetof" }, { "bitoffsetof" }, { "bitwidth" }, { "ModuleInfo" }, { "ClassInfo" }, { "classinfo" }, { "typeinfo" }, { "outer" }, { "Exception" }, { "RTInfo" }, { "Throwable" }, { "Error" }, { "withSym", "__withSym" }, { "result", "__result" }, { "returnLabel", "__returnLabel" }, { "line" }, { "empty", "" }, { "dotdotdot", "..." }, // use for error messages { "p" }, { "__vptr" }, { "__monitor" }, { "gate", "__gate" }, { "__c_long" }, { "__c_ulong" }, { "__c_longlong" }, { "__c_ulonglong" }, { "__c_long_double" }, { "__c_char" }, { "__c_wchar_t" }, { "__c_complex_float" }, { "__c_complex_double" }, { "__c_complex_real" }, { "cpp_type_info_ptr", "__cpp_type_info_ptr" }, { "_assert", "assert" }, { "_unittest", "unittest" }, { "_body", "body" }, { "printf" }, { "scanf" }, { "TypeInfo" }, { "TypeInfo_Class" }, { "TypeInfo_Interface" }, { "TypeInfo_Struct" }, { "TypeInfo_Enum" }, { "TypeInfo_Pointer" }, { "TypeInfo_Vector" }, { "TypeInfo_Array" }, { "TypeInfo_StaticArray" }, { "TypeInfo_AssociativeArray" }, { "TypeInfo_Function" }, { "TypeInfo_Delegate" }, { "TypeInfo_Tuple" }, { "TypeInfo_Const" }, { "TypeInfo_Invariant" }, { "TypeInfo_Shared" }, { "TypeInfo_Wild", "TypeInfo_Inout" }, { "elements" }, { "_arguments_typeinfo" }, { "_arguments" }, { "_argptr" }, { "destroy" }, { "xopEquals", "__xopEquals" }, { "xopCmp", "__xopCmp" }, { "xtoHash", "__xtoHash" }, { "__tmpfordtor" }, { "LINE", "__LINE__" }, { "FILE", "__FILE__" }, { "MODULE", "__MODULE__" }, { "FUNCTION", "__FUNCTION__" }, { "PRETTY_FUNCTION", "__PRETTY_FUNCTION__" }, { "DATE", "__DATE__" }, { "TIME", "__TIME__" }, { "TIMESTAMP", "__TIMESTAMP__" }, { "VENDOR", "__VENDOR__" }, { "VERSIONX", "__VERSION__" }, { "EOFX", "__EOF__" }, { "nan" }, { "infinity" }, { "dig" }, { "epsilon" }, { "mant_dig" }, { "max_10_exp" }, { "max_exp" }, { "min_10_exp" }, { "min_exp" }, { "min_normal" }, { "re" }, { "im" }, { "C" }, { "D" }, { "Windows" }, { "System" }, { "Objective" }, { "exit" }, { "success" }, { "failure" }, { "keys" }, { "values" }, { "rehash" }, { "future", "__future" }, { "property" }, { "nogc" }, { "live" }, { "safe" }, { "trusted" }, { "system" }, { "disable" }, // For inline assembler { "___out", "out" }, { "___in", "in" }, { "__int", "int" }, { "_dollar", "$" }, { "__LOCAL_SIZE" }, // For operator overloads { "uadd", "opPos" }, { "neg", "opNeg" }, { "com", "opCom" }, { "add", "opAdd" }, { "add_r", "opAdd_r" }, { "sub", "opSub" }, { "sub_r", "opSub_r" }, { "mul", "opMul" }, { "mul_r", "opMul_r" }, { "div", "opDiv" }, { "div_r", "opDiv_r" }, { "mod", "opMod" }, { "mod_r", "opMod_r" }, { "eq", "opEquals" }, { "cmp", "opCmp" }, { "iand", "opAnd" }, { "iand_r", "opAnd_r" }, { "ior", "opOr" }, { "ior_r", "opOr_r" }, { "ixor", "opXor" }, { "ixor_r", "opXor_r" }, { "shl", "opShl" }, { "shl_r", "opShl_r" }, { "shr", "opShr" }, { "shr_r", "opShr_r" }, { "ushr", "opUShr" }, { "ushr_r", "opUShr_r" }, { "cat", "opCat" }, { "cat_r", "opCat_r" }, { "assign", "opAssign" }, { "addass", "opAddAssign" }, { "subass", "opSubAssign" }, { "mulass", "opMulAssign" }, { "divass", "opDivAssign" }, { "modass", "opModAssign" }, { "andass", "opAndAssign" }, { "orass", "opOrAssign" }, { "xorass", "opXorAssign" }, { "shlass", "opShlAssign" }, { "shrass", "opShrAssign" }, { "ushrass", "opUShrAssign" }, { "catass", "opCatAssign" }, { "postinc", "opPostInc" }, { "postdec", "opPostDec" }, { "index", "opIndex" }, { "indexass", "opIndexAssign" }, { "slice", "opSlice" }, { "sliceass", "opSliceAssign" }, { "call", "opCall" }, { "_cast", "opCast" }, { "opIn" }, { "opIn_r" }, { "opStar" }, { "opDot" }, { "opDispatch" }, { "opDollar" }, { "opUnary" }, { "opIndexUnary" }, { "opSliceUnary" }, { "opBinary" }, { "opBinaryRight" }, { "opOpAssign" }, { "opIndexOpAssign" }, { "opSliceOpAssign" }, { "pow", "opPow" }, { "pow_r", "opPow_r" }, { "powass", "opPowAssign" }, { "classNew", "new" }, { "classDelete", "delete" }, // For foreach { "apply", "opApply" }, { "applyReverse", "opApplyReverse" }, // Ranges { "Fempty", "empty" }, { "Ffront", "front" }, { "Fback", "back" }, { "FpopFront", "popFront" }, { "FpopBack", "popBack" }, // For internal functions { "aaLen", "_aaLen" }, { "aaKeys", "_aaKeys" }, { "aaValues", "_aaValues" }, { "aaRehash", "_aaRehash" }, { "_aaAsStruct" }, { "monitorenter", "_d_monitorenter" }, { "monitorexit", "_d_monitorexit" }, { "criticalenter", "_d_criticalenter2" }, { "criticalexit", "_d_criticalexit" }, { "__ArrayPostblit" }, { "__ArrayDtor" }, { "_d_delThrowable" }, { "_d_newThrowable" }, { "_d_newclassT" }, { "_d_newclassTTrace" }, { "_d_newitemT" }, { "_d_newitemTTrace" }, { "_d_newarrayT" }, { "_d_newarrayTTrace" }, { "_d_newarraymTX" }, { "_d_newarraymTXTrace" }, { "_d_assert_fail" }, { "dup" }, { "_aaApply" }, { "_aaApply2" }, { "_d_arrayctor" }, { "_d_arraysetctor" }, { "_d_arraysetassign" }, { "_d_arrayassign_l" }, { "_d_arrayassign_r" }, { "imported" }, { "InterpolationHeader" }, { "InterpolationFooter" }, { "InterpolatedLiteral" }, { "InterpolatedExpression" }, // For pragma's { "Pinline", "inline" }, { "lib" }, { "linkerDirective" }, { "mangle" }, { "msg" }, { "startaddress" }, { "crt_constructor" }, { "crt_destructor" }, // For special functions { "tohash", "toHash" }, { "tostring", "toString" }, { "getmembers", "getMembers" }, // Special functions { "__alloca", "alloca" }, { "main" }, { "WinMain" }, { "DllMain" }, { "CMain", "_d_cmain" }, { "rt_init" }, { "__cmp" }, { "__equals"}, { "__switch"}, { "__switch_error"}, { "__ArrayCast"}, { "_d_HookTraceImpl" }, { "_d_arraysetlengthTImpl"}, { "_d_arraysetlengthT"}, { "_d_arraysetlengthTTrace"}, { "_d_arrayappendT" }, { "_d_arrayappendTTrace" }, { "_d_arrayappendcTX" }, { "_d_arrayappendcTXTrace" }, { "_d_arraycatnTX" }, { "_d_arraycatnTXTrace" }, // varargs implementation { "stdc" }, { "stdarg" }, { "va_start" }, // Builtin functions { "std" }, { "core" }, { "internal" }, { "config" }, { "c_complex_float" }, { "c_complex_double" }, { "c_complex_real" }, { "etc" }, { "attribute" }, { "atomic" }, { "atomicOp" }, { "math" }, { "sin" }, { "cos" }, { "tan" }, { "_sqrt", "sqrt" }, { "_pow", "pow" }, { "atan2" }, { "rint" }, { "ldexp" }, { "rndtol" }, { "exp" }, { "expm1" }, { "exp2" }, { "yl2x" }, { "yl2xp1" }, { "log" }, { "log2" }, { "log10" }, { "round" }, { "floor" }, { "trunc" }, { "fmax" }, { "fmin" }, { "fma" }, { "isnan" }, { "isInfinity" }, { "isfinite" }, { "ceil" }, { "copysign" }, { "fabs" }, { "toPrec" }, { "simd" }, { "__prefetch"}, { "__simd_sto"}, { "__simd"}, { "__simd_ib"}, { "bitop" }, { "bsf" }, { "bsr" }, { "btc" }, { "btr" }, { "bts" }, { "bswap" }, { "volatile"}, { "volatileLoad"}, { "volatileStore"}, { "_popcnt"}, { "inp"}, { "inpl"}, { "inpw"}, { "outp"}, { "outpl"}, { "outpw"}, { "builtinsModuleName", "builtins" }, { "ctfeWrite", "__ctfeWrite" }, // Traits { "isAbstractClass" }, { "isArithmetic" }, { "isAssociativeArray" }, { "isBitfield" }, { "isFinalClass" }, { "isTemplate" }, { "isPOD" }, { "isDeprecated" }, { "isDisabled" }, { "isFuture" }, { "isNested" }, { "isFloating" }, { "isIntegral" }, { "isScalar" }, { "isStaticArray" }, { "isUnsigned" }, { "isVirtualFunction" }, { "isVirtualMethod" }, { "isAbstractFunction" }, { "isFinalFunction" }, { "isOverrideFunction" }, { "isStaticFunction" }, { "isModule" }, { "isPackage" }, { "isRef" }, { "isOut" }, { "isLazy" }, { "isCOMClass" }, { "hasMember" }, { "identifier" }, { "fullyQualifiedName" }, { "getProtection" }, { "getVisibility" }, { "parent" }, { "child" }, { "getMember" }, { "getOverloads" }, { "getVirtualFunctions" }, { "getVirtualMethods" }, { "classInstanceSize" }, { "classInstanceAlignment" }, { "allMembers" }, { "derivedMembers" }, { "isSame" }, { "compiles" }, { "getAliasThis" }, { "getAttributes" }, { "getFunctionAttributes" }, { "getFunctionVariadicStyle" }, { "getParameterStorageClasses" }, { "getLinkage" }, { "getUnitTests" }, { "getVirtualIndex" }, { "getPointerBitmap" }, { "initSymbol" }, { "getCppNamespaces" }, { "isReturnOnStack" }, { "isZeroInit" }, { "getTargetInfo" }, { "getLocation" }, { "hasPostblit" }, { "hasCopyConstructor" }, { "isCopyable" }, { "toType" }, { "parameters" }, // For C++ mangling { "allocator" }, { "basic_string" }, { "basic_istream" }, { "basic_ostream" }, { "basic_iostream" }, { "char_traits" }, // Compiler recognized UDA's { "udaGNUAbiTag", "gnuAbiTag" }, { "udaSelector", "selector" }, { "udaOptional", "optional"}, { "udaMustUse", "mustuse" }, { "udaStandalone", "standalone" }, // Editions { "__edition_latest_do_not_use", }, // C names, for undefined identifier error messages { "NULL" }, { "TRUE" }, { "FALSE" }, { "unsigned" }, { "wchar_t" }, // for C compiler { "ImportC", "__C" }, { "__tag" }, { "dllimport" }, { "dllexport" }, { "naked" }, { "thread" }, { "vector_size" }, { "__func__" }, { "always_inline" }, { "noinline" }, { "noreturn" }, { "_nothrow", "nothrow" }, { "_deprecated", "deprecated" }, { "_align", "align" }, { "aligned" }, { "__pragma", "pragma" }, { "importc_builtins", "__importc_builtins" }, { "builtin_va_list", "__builtin_va_list" }, { "builtin_va_arg", "__builtin_va_arg" }, { "va_list_tag", "__va_list_tag" }, { "va_arg" }, { "pack" }, { "show" }, { "push" }, { "pop" }, { "_pure", "pure" }, { "define" }, { "undef" }, { "ident" }, { "packed" }, // IN_LLVM: LDC-specific pragmas { "LDC_intrinsic" }, { "LDC_no_typeinfo" }, { "LDC_no_moduleinfo" }, { "LDC_alloca" }, { "LDC_va_start" }, { "LDC_va_copy" }, { "LDC_va_end" }, { "LDC_va_arg" }, { "LDC_verbose" }, { "LDC_allow_inline" }, { "LDC_never_inline" }, { "LDC_inline_asm" }, { "LDC_inline_ir" }, { "LDC_fence" }, { "LDC_atomic_load" }, { "LDC_atomic_store" }, { "LDC_atomic_cmp_xchg" }, { "LDC_atomic_rmw" }, { "LDC_global_crt_ctor" }, { "LDC_global_crt_dtor" }, { "LDC_extern_weak" }, { "LDC_profile_instr" }, // IN_LLVM: LDC-specific traits { "targetCPU" }, { "targetHasFeature" }, // IN_LLVM: LDC-specific attributes { "ldc" }, { "attributes" }, { "udaAllocSize", "allocSize" }, // fastmath is an AliasSeq of llvmAttr and llvmFastMathFlag { "udaOptStrategy", "optStrategy" }, { "udaLLVMAttr", "llvmAttr" }, { "udaLLVMFastMathFlag", "llvmFastMathFlag" }, { "udaSection", "section" }, { "udaTarget", "target" }, { "udaAssumeUsed", "_assumeUsed" }, { "udaCallingConvention", "callingConvention" }, { "udaWeak", "_weak" }, { "udaCompute", "compute" }, { "udaKernel", "_kernel" }, { "udaDynamicCompile", "_dynamicCompile" }, { "udaDynamicCompileConst", "_dynamicCompileConst" }, { "udaDynamicCompileEmit", "_dynamicCompileEmit" }, { "udaHidden", "_hidden" }, { "udaNoSanitize", "noSanitize" }, { "udaNoSplitStack", "_noSplitStack" }, { "udaSwiftStub", "swift"}, // IN_LLVM: DCompute specific types and functionss { "dcompute" }, { "dcPointer", "Pointer" }, { "dcReflect", "__dcompute_reflect" }, { "RTInfoImpl" }, { "opencl" }, // IN_LLVM { "io" }, ]; /* * Tuple of DMD source code identifier and symbol in the D executable. * * The first element of the tuple is the identifier to use in the DMD source * code and the second element, if present, is the name to use in the D * executable. If second element, `name`, is not present the identifier, * `ident`, will be used instead */ struct Msgtable { // The identifier to use in the DMD source. string ident; // The name to use in the D executable private string name_; /* * Returns: the name to use in the D executable, `name_` if non-empty, * otherwise `ident` */ string name() @safe { return name_ ? name_ : ident; } } /* * Iterates the given Msgtable array, passes each element to the given lambda * and accumulates a string from each return value of calling the lambda. * Appends a newline character after each call to the lambda. */ string generate(immutable(Msgtable)[] msgtable, string function(Msgtable) dg) { string code; foreach (i, m ; msgtable) { if (i != 0) code ~= '\n'; code ~= dg(m); } return code; } // Used to generate the code for each identifier. string identifier(Msgtable m) @safe { return "Identifier " ~ m.ident ~ ";"; } // Used to generate the code for each initializer. string initializer(Msgtable m) @safe { return m.ident ~ ` = Identifier.idPool("` ~ m.name ~ `");`; } // Used to generate the code for each deinitializer. string deinitializer(Msgtable m) @safe { return m.ident ~ " = Identifier.init;"; } ldc-1.40.0-src/dmd/console.d0000644000000000000000000001401314727557031014206 0ustar rootroot/** * Control the various text mode attributes, such as color, when writing text * to the console. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/console.d, _console.d) * Documentation: https://dlang.org/phobos/dmd_console.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/console.d */ module dmd.console; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; version (Windows) { import core.sys.windows.winbase; import core.sys.windows.wincon; import core.sys.windows.windef; private extern (C) int isatty(int) @trusted @nogc nothrow; } else version (Posix) { import core.sys.posix.unistd; } else { static assert(0); } enum Color : int { black = 0, red = 1, green = 2, blue = 4, yellow = red | green, magenta = red | blue, cyan = green | blue, lightGray = red | green | blue, bright = 8, darkGray = bright | black, brightRed = bright | red, brightGreen = bright | green, brightBlue = bright | blue, brightYellow = bright | yellow, brightMagenta = bright | magenta, brightCyan = bright | cyan, white = bright | lightGray, } interface Console { nothrow: @property FILE* fp(); /** * Turn on/off intensity. * Params: * bright = turn it on */ void setColorBright(bool bright); /** * Set color and intensity. * Params: * color = the color */ void setColor(Color color); /** * Reset console attributes to what they were * when create() was called. */ void resetColor(); } version (Windows) private final class WindowsConsole : Console { nothrow: private: CONSOLE_SCREEN_BUFFER_INFO sbi; HANDLE handle; FILE* _fp; static HANDLE getStdHandle(FILE *fp) { /* Determine if stream fp is a console */ version (CRuntime_DigitalMars) { if (!isatty(fp._file)) return null; } else version (CRuntime_Microsoft) { if (!isatty(fileno(fp))) return null; } else { static assert(0, "Unsupported Windows runtime."); } if (fp == stdout) return GetStdHandle(STD_OUTPUT_HANDLE); else if (fp == stderr) return GetStdHandle(STD_ERROR_HANDLE); else return null; } public: @property FILE* fp() { return _fp; } static WindowsConsole create(FILE* fp) { auto h = getStdHandle(fp); if (h is null) return null; CONSOLE_SCREEN_BUFFER_INFO sbi; if (GetConsoleScreenBufferInfo(h, &sbi) == 0) // get initial state of console return null; auto c = new WindowsConsole(); c._fp = fp; c.handle = h; c.sbi = sbi; return c; } void setColorBright(bool bright) { SetConsoleTextAttribute(handle, sbi.wAttributes | (bright ? FOREGROUND_INTENSITY : 0)); } void setColor(Color color) { enum FOREGROUND_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; WORD attr = sbi.wAttributes; attr = (attr & ~(FOREGROUND_WHITE | FOREGROUND_INTENSITY)) | ((color & Color.red) ? FOREGROUND_RED : 0) | ((color & Color.green) ? FOREGROUND_GREEN : 0) | ((color & Color.blue) ? FOREGROUND_BLUE : 0) | ((color & Color.bright) ? FOREGROUND_INTENSITY : 0); SetConsoleTextAttribute(handle, attr); } void resetColor() { SetConsoleTextAttribute(handle, sbi.wAttributes); } } /* Uses the ANSI escape codes. * https://en.wikipedia.org/wiki/ANSI_escape_code * Foreground colors: 30..37 * Background colors: 40..47 * Attributes: * 0: reset all attributes * 1: high intensity * 2: low intensity * 3: italic * 4: single line underscore * 5: slow blink * 6: fast blink * 7: reverse video * 8: hidden */ private final class ANSIConsole : Console { nothrow: private: FILE* _fp; public: this(FILE* fp) @safe { _fp = fp; } @property FILE* fp() { return _fp; } void setColorBright(bool bright) { fprintf(_fp, "\033[%dm", bright); } void setColor(Color color) { fprintf(_fp, "\033[%d;%dm", color & Color.bright ? 1 : 0, 30 + (color & ~Color.bright)); } void resetColor() { fputs("\033[m", _fp); } } /** Tries to detect whether DMD has been invoked from a terminal. Returns: `true` if a terminal has been detected, `false` otherwise */ bool detectTerminal() nothrow { version (Posix) { const(char)* term = getenv("TERM"); return isatty(STDERR_FILENO) && term && term[0] && strcmp(term, "dumb") != 0; } else version (Windows) { auto h = WindowsConsole.getStdHandle(stderr); if (h is null) return false; CONSOLE_SCREEN_BUFFER_INFO sbi; return GetConsoleScreenBufferInfo(h, &sbi) != 0; } else static assert(0); } /** * Tries to detect the preference for colorized console output * based on the `NO_COLOR` environment variable: https://no-color.org/ * * Returns: `true` if colorized console output is preferred */ bool detectColorPreference() nothrow @trusted { const noColor = getenv("NO_COLOR"); return noColor == null || noColor[0] == '\0'; } /** * Creates an instance of Console connected to stream fp. * Params: * fp = io stream * Returns: * reference to created Console */ Console createConsole(FILE* fp) nothrow { version (Windows) { if (auto c = WindowsConsole.create(fp)) return c; } return new ANSIConsole(fp); } ldc-1.40.0-src/dmd/traits.d0000644000000000000000000022200314727557031014052 0ustar rootroot/** * Handle introspection functionality of the `__traits()` construct. * * Specification: $(LINK2 https://dlang.org/spec/traits.html, Traits) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/traits.d, _traits.d) * Documentation: https://dlang.org/phobos/dmd_traits.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/traits.d */ module dmd.traits; import core.stdc.stdio; import dmd.aggregate; import dmd.arraytypes; import dmd.astcodegen; import dmd.astenums; import dmd.attrib; import dmd.attribsem; import dmd.canthrow; import dmd.dclass; import dmd.declaration; import dmd.dimport; import dmd.dinterpret; import dmd.dmangle; import dmd.dmodule; import dmd.dscope; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.errors; import dmd.errorsink; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.nogc; import dmd.optimize; import dmd.parse; import dmd.root.array; import dmd.root.speller; import dmd.root.stringtable; import dmd.target; import dmd.tokens; import dmd.typesem; import dmd.visitor; import dmd.rootobject; import dmd.common.outbuffer; import dmd.root.string; enum LOGSEMANTIC = false; /************************ TraitsExp ************************************/ /************************************** * Convert `Expression` or `Type` to corresponding `Dsymbol`, additionally * stripping off expression contexts. * * Some symbol related `__traits` ignore arguments expression contexts. * For example: * ---- * struct S { void f() {} } * S s; * pragma(msg, __traits(isNested, s.f)); * // s.f is `DotVarExp`, but `__traits(isNested)`` needs a `FuncDeclaration`. * ---- * * This is used for that common `__traits` behavior. * * Input: * oarg object to get the symbol for * Returns: * Dsymbol the corresponding symbol for oarg */ private Dsymbol getDsymbolWithoutExpCtx(RootObject oarg) { if (auto e = isExpression(oarg)) { if (auto dve = e.isDotVarExp()) return dve.var; if (auto dte = e.isDotTemplateExp()) return dte.td; } return getDsymbol(oarg); } /** * Fill an array of target size_t values that indicate possible pointer words in memory * if interpreted as the type given as argument. * One bit in the array per pointer-sized piece of memory * Params: * loc = location for error messages * t = type to generate pointer bitmap from * data = array forming the bitmap * eSink = error message sink * Returns: * size of the type `t` in bytes, ulong.max on error */ ulong getTypePointerBitmap(Loc loc, Type t, ref Array!(ulong) data, ErrorSink eSink) { auto tc = t.isTypeClass(); const ulong sz = (tc && !tc.sym.isInterfaceDeclaration()) ? tc.sym.AggregateDeclaration.size(loc) : t.size(loc); if (sz == SIZE_INVALID) return ulong.max; const sz_size_t = Type.tsize_t.size(loc); // size of target's size_t assert(sz_size_t <= ulong.sizeof); if (sz > sz.max - sz_size_t) { eSink.error(loc, "size overflow for type `%s`", t.toChars()); return ulong.max; } const ulong bitsPerElement = sz_size_t * 8; // bits used in each array element const ulong cntptr = (sz + sz_size_t - 1) / sz_size_t; // pointers have same size as sz_size_t const ulong length = (cntptr + bitsPerElement - 1) / bitsPerElement; // a bit per pointer data.setDim(cast(size_t)length); data.zero(); ulong offset; bool error; // sticky error indicator void visit(Type t) { void setpointer(ulong off) { ulong ptroff = off / sz_size_t; data[cast(size_t)(ptroff / bitsPerElement)] |= 1L << (ptroff % bitsPerElement); } void visitType(Type t) { Type tb = t.toBasetype(); if (tb != t) visit(tb); } void visitError(TypeError t) { visitType(t); } void visitBasic(TypeBasic t) { if (t.ty == Tvoid) setpointer(offset); } void visitVector(TypeVector t) { } void visitSArray(TypeSArray t) { ulong arrayoff = offset; ulong nextsize = t.next.size(); if (nextsize == SIZE_INVALID) error = true; ulong dim = t.dim.toInteger(); for (ulong i = 0; i < dim; i++) { offset = arrayoff + i * nextsize; visit(t.next); } offset = arrayoff; } void visitDArray(TypeDArray t) { setpointer(offset + sz_size_t); } // dynamic array is {length,ptr} void visitAArray(TypeAArray t) { setpointer(offset); } void visitPointer(TypePointer t) { if (t.nextOf().ty != Tfunction) // don't mark function pointers setpointer(offset); } void visitReference(TypeReference t) { setpointer(offset); } void visitClass(TypeClass t) { setpointer(offset); } void visitFunction(TypeFunction t) { } void visitDelegate(TypeDelegate t) { setpointer(offset); } void visitEnum(TypeEnum t) { visitType(t); } void visitTuple(TypeTuple t) { visitType(t); } void visitNull(TypeNull t) { // always a null pointer } void visitNoreturn(TypeNoreturn t) { } void visitStruct(TypeStruct t) { ulong structoff = offset; foreach (v; t.sym.fields) { offset = structoff + v.offset; if (v.type.ty == Tclass) setpointer(offset); else visit(v.type); } offset = structoff; } void visitDefaultCase(Type t) { //printf("ty = %d\n", t.ty); assert(0); } mixin VisitType!void visit; visit.VisitType(t); } if (auto tcx = t.isTypeClass()) { // a "toplevel" class is treated as an instance, while TypeClass fields are treated as references void visitTopLevelClass(TypeClass t) { ulong classoff = offset; // skip vtable-ptr and monitor if (t.sym.baseClass) visitTopLevelClass(t.sym.baseClass.type.isTypeClass()); foreach (v; t.sym.fields) { offset = classoff + v.offset; visit(v.type); } offset = classoff; } visitTopLevelClass(tcx); } else visit(t); return error ? ulong.max : sz; } /** * get an array of size_t values that indicate possible pointer words in memory * if interpreted as the type given as argument * the first array element is the size of the type for independent interpretation * of the array * following elements bits represent one word (4/8 bytes depending on the target * architecture). If set the corresponding memory might contain a pointer/reference. * * Returns: [T.sizeof, pointerbit0-31/63, pointerbit32/64-63/128, ...] */ private Expression pointerBitmap(TraitsExp e, ErrorSink eSink) { if (!e.args || e.args.length != 1) { eSink.error(e.loc, "a single type expected for trait pointerBitmap"); return ErrorExp.get(); } Type t = getType((*e.args)[0]); if (!t) { eSink.error(e.loc, "`%s` is not a type", (*e.args)[0].toChars()); return ErrorExp.get(); } Array!(ulong) data; const ulong sz = getTypePointerBitmap(e.loc, t, data, eSink); if (sz == ulong.max) return ErrorExp.get(); auto exps = new Expressions(data.length + 1); (*exps)[0] = new IntegerExp(e.loc, sz, Type.tsize_t); // [0] is size in bytes of t foreach (size_t i; 1 .. exps.length) (*exps)[i] = new IntegerExp(e.loc, data[cast(size_t) (i - 1)], Type.tsize_t); auto ale = new ArrayLiteralExp(e.loc, Type.tsize_t.sarrayOf(data.length + 1), exps); return ale; } Expression semanticTraits(TraitsExp e, Scope* sc) { static if (LOGSEMANTIC) { printf("TraitsExp::semantic() %s\n", e.toChars()); } if (e.ident != Id.compiles && e.ident != Id.isSame && e.ident != Id.identifier && e.ident != Id.getProtection && e.ident != Id.getVisibility && e.ident != Id.getAttributes) { // Pretend we're in a deprecated scope so that deprecation messages // aren't triggered when checking if a symbol is deprecated const save = sc.stc; if (e.ident == Id.isDeprecated) sc.stc |= STC.deprecated_; if (!TemplateInstance.semanticTiargs(e.loc, sc, e.args, 1)) { sc.stc = save; return ErrorExp.get(); } sc.stc = save; } size_t dim = e.args ? e.args.length : 0; Expression dimError(int expected) { error(e.loc, "expected %d arguments for `%s` but had %d", expected, e.ident.toChars(), cast(int)dim); return ErrorExp.get(); } static IntegerExp True() { return IntegerExp.createBool(true); } static IntegerExp False() { return IntegerExp.createBool(false); } /******** * Gets the function type from a given AST node * if the node is a function of some sort. * Params: * o = an AST node to check for a `TypeFunction` * fdp = if `o` is a FuncDeclaration then fdp is set to that, otherwise `null` * Returns: * a type node if `o` is a declaration of * a delegate, function, function-pointer or a variable of the former. * Otherwise, `null`. */ static TypeFunction toTypeFunction(RootObject o, out FuncDeclaration fdp) { Type t; if (auto s = getDsymbolWithoutExpCtx(o)) { if (auto fd = s.isFuncDeclaration()) { t = fd.type; fdp = fd; } else if (auto vd = s.isVarDeclaration()) t = vd.type; else t = isType(o); } else t = isType(o); if (t) { if (auto tf = t.isFunction_Delegate_PtrToFunction()) return tf; } return null; } IntegerExp isX(T)(bool delegate(T) fp) { if (!dim) return False(); foreach (o; *e.args) { static if (is(T == Type)) auto y = getType(o); static if (is(T : Dsymbol)) { auto s = getDsymbolWithoutExpCtx(o); if (!s) return False(); } static if (is(T == Dsymbol)) alias y = s; static if (is(T == Declaration)) auto y = s.isDeclaration(); static if (is(T == FuncDeclaration)) auto y = s.isFuncDeclaration(); if (!y || !fp(y)) return False(); } return True(); } alias isTypeX = isX!Type; alias isDsymX = isX!Dsymbol; alias isDeclX = isX!Declaration; alias isFuncX = isX!FuncDeclaration; Expression isPkgX(bool function(Package) fp) { return isDsymX((Dsymbol sym) { Package p = resolveIsPackage(sym); return (p !is null) && fp(p); }); } if (e.ident == Id.isArithmetic) { return isTypeX(t => t.isintegral() || t.isfloating()); } if (e.ident == Id.isFloating) { return isTypeX(t => t.isfloating()); } if (e.ident == Id.isIntegral) { return isTypeX(t => t.isintegral()); } if (e.ident == Id.isScalar) { return isTypeX(t => t.isscalar()); } if (e.ident == Id.isUnsigned) { return isTypeX(t => t.isunsigned()); } if (e.ident == Id.isAssociativeArray) { return isTypeX(t => t.toBasetype().ty == Taarray); } if (e.ident == Id.isDeprecated) { if (isTypeX(t => t.iscomplex() || t.isimaginary()).toBool().hasValue(true)) return True(); return isDsymX(t => t.isDeprecated()); } if (e.ident == Id.isFuture) { return isDeclX(t => t.isFuture()); } if (e.ident == Id.isStaticArray) { return isTypeX(t => t.toBasetype().ty == Tsarray); } if (e.ident == Id.isAbstractClass) { return isTypeX(t => t.toBasetype().isTypeClass() && t.toBasetype().isTypeClass().sym.isAbstract()); } if (e.ident == Id.isFinalClass) { return isTypeX(t => t.toBasetype().isTypeClass() && (t.toBasetype().isTypeClass().sym.storage_class & STC.final_) != 0); } if (e.ident == Id.isTemplate) { if (dim != 1) return dimError(1); return isDsymX((s) { if (!s.toAlias().isOverloadable()) return false; return overloadApply(s, sm => sm.isTemplateDeclaration() !is null) != 0; }); } if (e.ident == Id.isBitfield) { if (dim != 1) return dimError(1); return isDsymX((s) { s = s.toAlias(); return s.isBitFieldDeclaration() !is null; }); } if (e.ident == Id.isPOD) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto t = isType(o); if (!t) { error(e.loc, "type expected as second argument of __traits `%s` instead of `%s`", e.ident.toChars(), o.toChars()); return ErrorExp.get(); } Type tb = t.baseElemOf(); auto ts = tb.isTypeStruct(); if (auto sd = ts ? ts.sym : null) { return sd.isPOD() ? True() : False(); } return True(); } if (e.ident == Id.hasCopyConstructor || e.ident == Id.hasPostblit) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto t = isType(o); if (!t) { error(e.loc, "type expected as second argument of __traits `%s` instead of `%s`", e.ident.toChars(), o.toChars()); return ErrorExp.get(); } Type tb = t.baseElemOf(); auto ts = tb.isTypeStruct(); if (auto sd = ts ? ts.sym : null) { return (e.ident == Id.hasPostblit) ? (sd.postblit ? True() : False()) : (sd.hasCopyCtor ? True() : False()); } return False(); } if (e.ident == Id.isCopyable) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto t = isType(o); if (!t) { error(e.loc, "type expected as second argument of __traits `%s` instead of `%s`", e.ident.toChars(), o.toChars()); return ErrorExp.get(); } t = t.toBasetype(); // get the base in case `t` is an `enum` if (auto ts = t.isTypeStruct()) { ts.sym.dsymbolSemantic(sc); } return isCopyable(t) ? True() : False(); } if (e.ident == Id.isNested) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbolWithoutExpCtx(o); if (!s) { } else if (auto ad = s.isAggregateDeclaration()) { return ad.isNested() ? True() : False(); } else if (auto fd = s.isFuncDeclaration()) { return fd.isNested() ? True() : False(); } error(e.loc, "aggregate or function expected instead of `%s`", o.toChars()); return ErrorExp.get(); } if (e.ident == Id.isDisabled) { if (dim != 1) return dimError(1); return isDeclX(f => f.isDisabled()); } if (e.ident == Id.isAbstractFunction) { if (dim != 1) return dimError(1); return isFuncX(f => f.isAbstract()); } if (e.ident == Id.isVirtualFunction) { // @@@DEPRECATED2.121@@@ // Deprecated in 2.101 - Can be removed from 2.121 deprecation(e.loc, "`traits(isVirtualFunction)` is deprecated. Use `traits(isVirtualMethod)` instead"); if (dim != 1) return dimError(1); return isFuncX(f => f.isVirtual()); } if (e.ident == Id.isVirtualMethod) { if (dim != 1) return dimError(1); return isFuncX(f => f.isVirtualMethod()); } if (e.ident == Id.isFinalFunction) { if (dim != 1) return dimError(1); return isFuncX(f => f.isFinalFunc()); } if (e.ident == Id.isOverrideFunction) { if (dim != 1) return dimError(1); return isFuncX(f => f.isOverride()); } if (e.ident == Id.isStaticFunction) { if (dim != 1) return dimError(1); return isFuncX(f => !f.needThis() && !f.isNested()); } if (e.ident == Id.isModule) { if (dim != 1) return dimError(1); return isPkgX(p => p.isModule() || p.isPackageMod()); } if (e.ident == Id.isPackage) { if (dim != 1) return dimError(1); return isPkgX(p => p.isModule() is null); } if (e.ident == Id.isRef) { if (dim != 1) return dimError(1); return isDeclX(d => d.isRef()); } if (e.ident == Id.isOut) { if (dim != 1) return dimError(1); return isDeclX(d => d.isOut()); } if (e.ident == Id.isLazy) { if (dim != 1) return dimError(1); return isDeclX(d => (d.storage_class & STC.lazy_) != 0); } if (e.ident == Id.isCOMClass) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbol(o); AggregateDeclaration agg; if (!s || ((agg = s.isAggregateDeclaration()) is null)) { error(e.loc, "argument to `__traits(isCOMClass, %s)` is not a declaration", o.toChars()); return ErrorExp.get(); } if (ClassDeclaration cd = agg.isClassDeclaration()) return cd.com ? True() : False(); else return False(); } if (e.ident == Id.identifier) { // Get identifier for symbol as a string literal /* Specify 0 for bit 0 of the flags argument to semanticTiargs() so that * a symbol should not be folded to a constant. * Bit 1 means don't convert Parameter to Type if Parameter has an identifier */ if (!TemplateInstance.semanticTiargs(e.loc, sc, e.args, 2)) return ErrorExp.get(); if (dim != 1) return dimError(1); auto o = (*e.args)[0]; Identifier id; if (auto po = isParameter(o)) { if (!po.ident) { error(e.loc, "argument `%s` has no identifier", po.type.toChars()); return ErrorExp.get(); } id = po.ident; } else { Dsymbol s = getDsymbolWithoutExpCtx(o); if (!s || !s.ident) { error(e.loc, "argument `%s` has no identifier", o.toChars()); return ErrorExp.get(); } id = s.ident; } auto se = new StringExp(e.loc, id.toString()); return se.expressionSemantic(sc); } if (e.ident == Id.fullyQualifiedName) // https://dlang.org/spec/traits.html#fullyQualifiedName { if (dim != 1) return dimError(1); Scope* sc2 = sc.push(); sc2.flags = sc.flags | SCOPE.noaccesscheck | SCOPE.ignoresymbolvisibility; bool ok = TemplateInstance.semanticTiargs(e.loc, sc2, e.args, 1); sc2.pop(); if (!ok) return ErrorExp.get(); const(char)[] fqn; auto o = (*e.args)[0]; if (auto s = getDsymbolWithoutExpCtx(o)) { if (s.semanticRun == PASS.initial) s.dsymbolSemantic(null); fqn = s.toPrettyChars().toDString(); } else if (auto t = getType(o)) { fqn = t.toPrettyChars(true).toDString(); } else { if (!isError(o)) error(e.loc, "argument `%s` has no identifier", o.toChars()); return ErrorExp.get(); } assert(fqn); auto se = new StringExp(e.loc, fqn); return se.expressionSemantic(sc); } if (e.ident == Id.getProtection || e.ident == Id.getVisibility) { if (dim != 1) return dimError(1); Scope* sc2 = sc.push(); sc2.flags = sc.flags | SCOPE.noaccesscheck | SCOPE.ignoresymbolvisibility; bool ok = TemplateInstance.semanticTiargs(e.loc, sc2, e.args, 1); sc2.pop(); if (!ok) return ErrorExp.get(); auto o = (*e.args)[0]; auto s = getDsymbolWithoutExpCtx(o); if (!s) { if (!isError(o)) error(e.loc, "argument `%s` has no visibility", o.toChars()); return ErrorExp.get(); } if (s.semanticRun == PASS.initial) s.dsymbolSemantic(null); auto protName = visibilityToString(s.visible().kind); // TODO: How about package(names) assert(protName); auto se = new StringExp(e.loc, protName); return se.expressionSemantic(sc); } if (e.ident == Id.parent) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbolWithoutExpCtx(o); if (s) { // https://issues.dlang.org/show_bug.cgi?id=12496 // Consider: // class T1 // { // class C(uint value) { } // } // __traits(parent, T1.C!2) if (auto ad = s.isAggregateDeclaration()) // `s` is `C` { if (ad.isNested()) // `C` is nested { if (auto p = s.toParent()) // `C`'s parent is `C!2`, believe it or not { if (auto ti = p.isTemplateInstance()) // `C!2` is a template instance { s = p; // `C!2`'s parent is `T1` auto td = ti.tempdecl; if (td) s = td; // get the declaration context just in case there's two contexts } } } } if (auto fd = s.isFuncDeclaration()) // https://issues.dlang.org/show_bug.cgi?id=8943 s = fd.toAliasFunc(); if (!s.isImport()) // https://issues.dlang.org/show_bug.cgi?id=8922 s = s.toParent(); } if (!s || s.isImport()) { error(e.loc, "argument `%s` has no parent", o.toChars()); return ErrorExp.get(); } if (auto f = s.isFuncDeclaration()) { if (auto td = getFuncTemplateDecl(f)) { if (td.overroot) // if not start of overloaded list of TemplateDeclaration's td = td.overroot; // then get the start Expression ex = new TemplateExp(e.loc, td, f); ex = ex.expressionSemantic(sc); return ex; } if (auto fld = f.isFuncLiteralDeclaration()) { // Directly translate to VarExp instead of FuncExp Expression ex = new VarExp(e.loc, fld, true); return ex.expressionSemantic(sc); } } return symbolToExp(s, e.loc, sc, false); } if (e.ident == Id.child) { if (dim != 2) return dimError(2); Expression ex; auto op = (*e.args)[0]; if (auto symp = getDsymbol(op)) ex = new DsymbolExp(e.loc, symp); else if (auto exp = op.isExpression()) ex = exp; else { error(e.loc, "symbol or expression expected as first argument of __traits `child` instead of `%s`", op.toChars()); return ErrorExp.get(); } ex = ex.expressionSemantic(sc); auto oc = (*e.args)[1]; auto symc = getDsymbol(oc); if (!symc) { error(e.loc, "symbol expected as second argument of __traits `child` instead of `%s`", oc.toChars()); return ErrorExp.get(); } if (auto d = symc.isDeclaration()) ex = new DotVarExp(e.loc, ex, d); else if (auto td = symc.isTemplateDeclaration()) ex = new DotExp(e.loc, ex, new TemplateExp(e.loc, td)); else if (auto ti = symc.isScopeDsymbol()) ex = new DotExp(e.loc, ex, new ScopeExp(e.loc, ti)); else assert(0); ex = ex.expressionSemantic(sc); return ex; } if (e.ident == Id.toType) { if (dim != 1) return dimError(1); auto ex = isExpression((*e.args)[0]); if (!ex) { error(e.loc, "expression expected as second argument of __traits `%s`", e.ident.toChars()); return ErrorExp.get(); } ex = ex.ctfeInterpret(); StringExp se = semanticString(sc, ex, "__traits(toType, string)"); if (!se) { return ErrorExp.get(); } Type t = decoToType(se.toUTF8(sc).peekString()); if (!t) { error(e.loc, "cannot determine `%s`", e.toChars()); return ErrorExp.get(); } return (new TypeExp(e.loc, t)).expressionSemantic(sc); } if (e.ident == Id.hasMember || e.ident == Id.getMember || e.ident == Id.getOverloads || e.ident == Id.getVirtualMethods || e.ident == Id.getVirtualFunctions) { if (dim != 2 && !(dim == 3 && e.ident == Id.getOverloads)) return dimError(2); auto o = (*e.args)[0]; auto ex = isExpression((*e.args)[1]); if (!ex) { error(e.loc, "expression expected as second argument of __traits `%s`", e.ident.toChars()); return ErrorExp.get(); } ex = ex.ctfeInterpret(); bool includeTemplates = false; if (dim == 3 && e.ident == Id.getOverloads) { auto b = isExpression((*e.args)[2]); b = b.ctfeInterpret(); if (!b.type.equals(Type.tbool)) { error(e.loc, "`bool` expected as third argument of `__traits(getOverloads)`, not `%s` of type `%s`", b.toChars(), b.type.toChars()); return ErrorExp.get(); } includeTemplates = b.toBool().get(); } StringExp se = ex.toStringExp(); if (!se || se.len == 0) { error(e.loc, "string expected as second argument of __traits `%s` instead of `%s`", e.ident.toChars(), ex.toChars()); return ErrorExp.get(); } se = se.toUTF8(sc); if (se.sz != 1) { error(e.loc, "string must be chars"); return ErrorExp.get(); } auto id = Identifier.idPool(se.peekString()); /* Prefer a Type, because getDsymbol(Type) can lose type modifiers. Then a Dsymbol, because it might need some runtime contexts. */ Dsymbol sym = getDsymbol(o); if (sym && e.ident == Id.hasMember) { if (auto sm = sym.search(e.loc, id)) return True(); // https://issues.dlang.org/show_bug.cgi?id=23951 if (auto decl = sym.isDeclaration()) { ex = typeDotIdExp(e.loc, decl.type, id); goto doSemantic; } } if (auto t = isType(o)) ex = typeDotIdExp(e.loc, t, id); else if (sym) { ex = new DsymbolExp(e.loc, sym); ex = new DotIdExp(e.loc, ex, id); } else if (auto ex2 = isExpression(o)) ex = new DotIdExp(e.loc, ex2, id); else { error(e.loc, "invalid first argument"); return ErrorExp.get(); } doSemantic: // ignore symbol visibility and disable access checks for these traits Scope* scx = sc.push(); scx.flags |= SCOPE.ignoresymbolvisibility | SCOPE.noaccesscheck; scope (exit) scx.pop(); if (e.ident == Id.hasMember) { /* Take any errors as meaning it wasn't found */ ex = ex.trySemantic(scx); return ex ? True() : False(); } else if (e.ident == Id.getMember) { if (auto die = ex.isDotIdExp()) // Prevent semantic() from replacing Symbol with its initializer die.wantsym = true; ex = ex.expressionSemantic(scx); return ex; } else if (e.ident == Id.getVirtualFunctions || e.ident == Id.getVirtualMethods || e.ident == Id.getOverloads) { uint errors = global.errors; Expression eorig = ex; ex = ex.expressionSemantic(scx); if (errors < global.errors) error(e.loc, "`%s` cannot be resolved", eorig.toChars()); if (e.ident == Id.getVirtualFunctions) { // @@@DEPRECATED2.121@@@ // Deprecated in 2.101 - Can be removed from 2.121 deprecation(e.loc, "`traits(getVirtualFunctions)` is deprecated. Use `traits(getVirtualMethods)` instead"); } /* Create tuple of functions of ex */ auto exps = new Expressions(); Dsymbol f; if (auto ve = ex.isVarExp) { if (ve.var.isFuncDeclaration() || ve.var.isOverDeclaration()) f = ve.var; ex = null; } else if (auto dve = ex.isDotVarExp) { if (dve.var.isFuncDeclaration() || dve.var.isOverDeclaration()) f = dve.var; if (dve.e1.op == EXP.dotType || dve.e1.op == EXP.this_) ex = null; else ex = dve.e1; } else if (auto te = ex.isTemplateExp) { auto td = te.td; f = td; if (td && td.funcroot) f = td.funcroot; ex = null; } else if (auto dte = ex.isDotTemplateExp) { auto td = dte.td; f = td; if (td && td.funcroot) f = td.funcroot; ex = null; if (dte.e1.op != EXP.dotType && dte.e1.op != EXP.this_) ex = dte.e1; } bool[string] funcTypeHash; /* Compute the function signature and insert it in the * hashtable, if not present. This is needed so that * traits(getOverlods, F3, "visit") does not count `int visit(int)` * twice in the following example: * * ============================================= * interface F1 { int visit(int);} * interface F2 { int visit(int); void visit(); } * interface F3 : F2, F1 {} *============================================== */ void insertInterfaceInheritedFunction(FuncDeclaration fd, Expression e) { auto signature = fd.type.toString(); //printf("%s - %s\n", fd.toChars, signature); if (signature !in funcTypeHash) { funcTypeHash[signature] = true; exps.push(e); } } int dg(Dsymbol s) { auto fd = s.isFuncDeclaration(); if (!fd) { if (includeTemplates) { if (auto td = s.isTemplateDeclaration()) { // if td is part of an overload set we must take a copy // which shares the same `instances` cache but without // `overroot` and `overnext` set to avoid overload // behaviour in the result. if (td.overnext !is null) { if (td.instances is null) { // create an empty AA just to copy it scope ti = new TemplateInstance(Loc.initial, Id.empty, null); auto tib = TemplateInstanceBox(ti); td.instances[tib] = null; td.instances.clear(); } td = td.syntaxCopy(null); import core.stdc.string : memcpy; memcpy(cast(void*) td, cast(void*) s, __traits(classInstanceSize, TemplateDeclaration)); td.overroot = null; td.overnext = null; } auto e = ex ? new DotTemplateExp(Loc.initial, ex, td) : new DsymbolExp(Loc.initial, td); exps.push(e); } } return 0; } if (e.ident == Id.getVirtualFunctions && !fd.isVirtual()) return 0; if (e.ident == Id.getVirtualMethods && !fd.isVirtualMethod()) return 0; auto fa = new FuncAliasDeclaration(fd.ident, fd, false); fa.visibility = fd.visibility; auto e = ex ? new DotVarExp(Loc.initial, ex, fa, false) : new DsymbolExp(Loc.initial, fa, false); // if the parent is an interface declaration // we must check for functions with the same signature // in different inherited interfaces if (sym && sym.isInterfaceDeclaration()) insertInterfaceInheritedFunction(fd, e); else exps.push(e); return 0; } InterfaceDeclaration ifd = null; if (sym) ifd = sym.isInterfaceDeclaration(); // If the symbol passed as a parameter is an // interface that inherits other interfaces overloadApply(f, &dg); if (ifd && ifd.interfaces && f) { // check the overloads of each inherited interface individually foreach (bc; ifd.interfaces) { if (auto fd = bc.sym.search(e.loc, f.ident)) overloadApply(fd, &dg); } } auto tup = new TupleExp(e.loc, exps); return tup.expressionSemantic(scx); } else assert(0); } if (e.ident == Id.classInstanceSize || e.ident == Id.classInstanceAlignment) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbol(o); auto cd = s ? s.isClassDeclaration() : null; if (!cd) { error(e.loc, "first argument is not a class"); return ErrorExp.get(); } if (cd.sizeok != Sizeok.done) { cd.size(e.loc); } if (cd.sizeok != Sizeok.done) { error(e.loc, "%s `%s` is forward referenced", cd.kind(), cd.toChars()); return ErrorExp.get(); } return new IntegerExp(e.loc, e.ident == Id.classInstanceSize ? cd.structsize : cd.alignsize, Type.tsize_t); } if (e.ident == Id.getAliasThis) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbol(o); auto ad = s ? s.isAggregateDeclaration() : null; auto exps = new Expressions(); if (ad && ad.aliasthis) exps.push(new StringExp(e.loc, ad.aliasthis.ident.toString())); Expression ex = new TupleExp(e.loc, exps); ex = ex.expressionSemantic(sc); return ex; } if (e.ident == Id.getAttributes) { /* Specify 0 for bit 0 of the flags argument to semanticTiargs() so that * a symbol should not be folded to a constant. * Bit 1 means don't convert Parameter to Type if Parameter has an identifier */ if (!TemplateInstance.semanticTiargs(e.loc, sc, e.args, 3)) return ErrorExp.get(); if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto po = isParameter(o); auto s = getDsymbolWithoutExpCtx(o); auto typeOfArg = isType(o); UserAttributeDeclaration udad = null; if (po) { udad = po.userAttribDecl; } else if (s) { // @@@DEPRECATION 2.100.2 if (auto fd = s.isFuncDeclaration()) { if (fd.overnext) { deprecation(e.loc, "`__traits(getAttributes)` may only be used for individual functions, not the overload set `%s`", fd.toChars()); deprecationSupplemental(e.loc, "the result of `__traits(getOverloads)` may be used to select the desired function to extract attributes from"); } } // @@@DEPRECATION 2.100.2 if (auto td = s.isTemplateDeclaration()) { if (td.overnext) { deprecation(e.loc, "`__traits(getAttributes)` may only be used for individual functions, not the overload set `%s`", td.ident.toChars()); deprecationSupplemental(e.loc, "the result of `__traits(getOverloads)` may be used to select the desired function to extract attributes from"); } } if (s.isImport()) { s = s.isImport().mod; } //printf("getAttributes %s, attrs = %p, scope = %p\n", s.toChars(), s.userAttribDecl, s._scope); udad = s.userAttribDecl; } else if (typeOfArg) { // If there is a type but no symbol, do nothing rather than erroring. } else { version (none) { Expression x = isExpression(o); Type t = isType(o); if (x) printf("e = %s %s\n", EXPtoString(x.op).ptr, x.toChars()); if (t) printf("t = %d %s\n", t.ty, t.toChars()); } error(e.loc, "first argument is not a symbol"); return ErrorExp.get(); } auto exps = udad ? udad.getAttributes() : new Expressions(); auto tup = new TupleExp(e.loc, exps); return tup.expressionSemantic(sc); } if (e.ident == Id.getFunctionAttributes) { /* Extract all function attributes as a tuple (const/shared/inout/pure/nothrow/etc) except UDAs. * https://dlang.org/spec/traits.html#getFunctionAttributes */ if (dim != 1) return dimError(1); FuncDeclaration fd; TypeFunction tf = toTypeFunction((*e.args)[0], fd); if (!tf) { error(e.loc, "first argument is not a function"); return ErrorExp.get(); } // https://issues.dlang.org/show_bug.cgi?id=19706 // When getting the attributes of the instance of a // templated member function semantic tiargs does // not perform semantic3 on the instance. // For more information see FuncDeclaration.functionSemantic. // For getFunctionAttributes it is mandatory to do // attribute inference. if (fd && fd.parent && fd.parent.isTemplateInstance) { functionSemantic3(fd); tf = fd.type.isTypeFunction(); } auto mods = new Expressions(); void addToMods(string str) { mods.push(new StringExp(Loc.initial, str)); } tf.modifiersApply(&addToMods); tf.attributesApply(&addToMods, TRUSTformatSystem); auto tup = new TupleExp(e.loc, mods); return tup.expressionSemantic(sc); } if (e.ident == Id.isReturnOnStack) { /* Extract as a boolean if function return value is on the stack * https://dlang.org/spec/traits.html#isReturnOnStack */ if (dim != 1) return dimError(1); RootObject o = (*e.args)[0]; FuncDeclaration fd; TypeFunction tf = toTypeFunction(o, fd); if (!tf) { error(e.loc, "argument to `__traits(isReturnOnStack, %s)` is not a function", o.toChars()); return ErrorExp.get(); } bool value = target.isReturnOnStack(tf, fd && fd.needThis()); return IntegerExp.createBool(value); } if (e.ident == Id.getFunctionVariadicStyle) { /* Accept a symbol or a type. Returns one of the following: * "none" not a variadic function * "argptr" extern(D) void dstyle(...), use `__argptr` and `__arguments` * "stdarg" extern(C) void cstyle(int, ...), use core.stdc.stdarg * "typesafe" void typesafe(T[] ...) * "KR" old K+R style */ // get symbol linkage as a string if (dim != 1) return dimError(1); LINK link; VarArg varargs; auto o = (*e.args)[0]; FuncDeclaration fd; TypeFunction tf = toTypeFunction(o, fd); if (tf) { link = tf.linkage; varargs = tf.parameterList.varargs; } else { if (!fd) { error(e.loc, "argument to `__traits(getFunctionVariadicStyle, %s)` is not a function", o.toChars()); return ErrorExp.get(); } link = fd._linkage; varargs = fd.getParameterList().varargs; } string style; final switch (varargs) { case VarArg.none: style = "none"; break; case VarArg.variadic: style = (link == LINK.d) ? "argptr" : "stdarg"; break; case VarArg.KRvariadic: style = "KR"; break; case VarArg.typesafe: style = "typesafe"; break; } auto se = new StringExp(e.loc, style); return se.expressionSemantic(sc); } if (e.ident == Id.getParameterStorageClasses) { /* Accept a function symbol or a type, followed by a parameter index. * Returns a tuple of strings of the parameter's storage classes. */ // get symbol linkage as a string if (dim != 2) return dimError(2); auto o = (*e.args)[0]; auto o1 = (*e.args)[1]; ParameterList fparams; CallExp ce; if (auto exp = isExpression(o)) ce = exp.isCallExp(); if (ce) { fparams = ce.f.getParameterList(); } else { FuncDeclaration fd; auto tf = toTypeFunction(o, fd); if (tf) fparams = tf.parameterList; else if (fd) fparams = fd.getParameterList(); else { error(e.loc, "first argument to `__traits(getParameterStorageClasses, %s, %s)` is not a function or a function call", o.toChars(), o1.toChars()); return ErrorExp.get(); } } // Avoid further analysis for invalid functions leading to misleading error messages if (!fparams.parameters) return ErrorExp.get(); StorageClass stc; // Set stc to storage class of the ith parameter auto ex = isExpression((*e.args)[1]); if (!ex) { error(e.loc, "expression expected as second argument of `__traits(getParameterStorageClasses, %s, %s)`", o.toChars(), o1.toChars()); return ErrorExp.get(); } ex = ex.ctfeInterpret(); auto ii = ex.toUInteger(); if (ii >= fparams.length) { error(e.loc, "parameter index must be in range 0..%u not %s", cast(uint)fparams.length, ex.toChars()); return ErrorExp.get(); } uint n = cast(uint)ii; Parameter p = fparams[n]; stc = p.storageClass; // This mirrors hdrgen.visit(Parameter p) if (p.type && p.type.mod & MODFlags.shared_) stc &= ~STC.shared_; auto exps = new Expressions; void push(string s) { exps.push(new StringExp(e.loc, s)); } if (stc & STC.auto_) push("auto"); if (stc & STC.return_) push("return"); if (stc & STC.out_) push("out"); else if (stc & STC.in_) push("in"); else if (stc & STC.ref_) push("ref"); else if (stc & STC.lazy_) push("lazy"); else if (stc & STC.alias_) push("alias"); if (stc & STC.const_) push("const"); if (stc & STC.immutable_) push("immutable"); if (stc & STC.wild) push("inout"); if (stc & STC.shared_) push("shared"); if (stc & STC.scope_ && !(stc & STC.scopeinferred)) push("scope"); auto tup = new TupleExp(e.loc, exps); return tup.expressionSemantic(sc); } if (e.ident == Id.getLinkage) { // get symbol linkage as a string if (dim != 1) return dimError(1); LINK link; auto o = (*e.args)[0]; FuncDeclaration fd; TypeFunction tf = toTypeFunction(o, fd); if (tf) { link = fd ? fd.toAliasFunc()._linkage : tf.linkage; } else { auto s = getDsymbol(o); Declaration d; AggregateDeclaration agg; if (!s || ((d = s.isDeclaration()) is null && (agg = s.isAggregateDeclaration()) is null)) { error(e.loc, "argument to `__traits(getLinkage, %s)` is not a declaration", o.toChars()); return ErrorExp.get(); } if (d !is null) link = d._linkage; else { // Resolves forward references if (agg.sizeok != Sizeok.done) { agg.size(e.loc); if (agg.sizeok != Sizeok.done) { error(e.loc, "%s `%s` is forward referenced", agg.kind(), agg.toChars()); return ErrorExp.get(); } } final switch (agg.classKind) { case ClassKind.d: link = LINK.d; break; case ClassKind.cpp: link = LINK.cpp; break; case ClassKind.objc: link = LINK.objc; break; case ClassKind.c: link = LINK.c; break; } } } auto linkage = linkageToChars(link); auto se = new StringExp(e.loc, linkage.toDString()); return se.expressionSemantic(sc); } if (e.ident == Id.allMembers || e.ident == Id.derivedMembers) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbol(o); if (!s) { error(e.loc, "in expression `%s` `%s` can't have members", e.toChars(), o.toChars()); errorSupplemental(e.loc, "`%s` must evaluate to either a module, a struct, an union, a class, an interface or a template instantiation", o.toChars()); return ErrorExp.get(); } if (auto imp = s.isImport()) { // https://issues.dlang.org/show_bug.cgi?id=9692 // https://issues.dlang.org/show_bug.cgi?id=20008 if (imp.pkg) s = imp.pkg; } // https://issues.dlang.org/show_bug.cgi?id=16044 if (auto p = s.isPackage()) { if (auto pm = p.isPackageMod()) s = pm; } auto sds = s.isScopeDsymbol(); if (!sds || sds.isTemplateDeclaration()) { error(e.loc, "in expression `%s` %s `%s` has no members", e.toChars(), s.kind(), s.toChars()); errorSupplemental(e.loc, "`%s` must evaluate to either a module, a struct, an union, a class, an interface or a template instantiation", s.toChars()); return ErrorExp.get(); } auto idents = new Identifiers(); int pushIdentsDg(size_t n, Dsymbol sm) { if (!sm) return 1; // skip local symbols, such as static foreach loop variables if (auto decl = sm.isDeclaration()) { if (decl.storage_class & STC.local) { return 0; } // skip 'this' context pointers else if (decl.isThisDeclaration()) return 0; } // https://issues.dlang.org/show_bug.cgi?id=20915 // skip version and debug identifiers if (sm.isVersionSymbol() || sm.isDebugSymbol()) return 0; //printf("\t[%i] %s %s\n", i, sm.kind(), sm.toChars()); if (sm.ident) { // https://issues.dlang.org/show_bug.cgi?id=10096 // https://issues.dlang.org/show_bug.cgi?id=10100 // Skip over internal members in __traits(allMembers) if ((sm.isCtorDeclaration() && sm.ident != Id.ctor) || (sm.isDtorDeclaration() && sm.ident != Id.dtor) || (sm.isPostBlitDeclaration() && sm.ident != Id.postblit) || sm.isInvariantDeclaration() || sm.isUnitTestDeclaration()) { return 0; } if (sm.ident == Id.empty) { return 0; } if (sm.isTypeInfoDeclaration()) // https://issues.dlang.org/show_bug.cgi?id=15177 return 0; if ((!sds.isModule() && !sds.isPackage()) && sm.isImport()) // https://issues.dlang.org/show_bug.cgi?id=17057 return 0; //printf("\t%s\n", sm.ident.toChars()); /* Skip if already present in idents[] */ foreach (id; *idents) { if (id == sm.ident) return 0; // Avoid using strcmp in the first place due to the performance impact in an O(N^2) loop. debug { import core.stdc.string : strcmp; assert(strcmp(id.toChars(), sm.ident.toChars()) != 0); } } idents.push(sm.ident); } else if (auto ed = sm.isEnumDeclaration()) { _foreach(null, ed.members, &pushIdentsDg); } return 0; } _foreach(sc, sds.members, &pushIdentsDg); auto cd = sds.isClassDeclaration(); if (cd && e.ident == Id.allMembers) { if (cd.semanticRun < PASS.semanticdone) cd.dsymbolSemantic(null); // https://issues.dlang.org/show_bug.cgi?id=13668 // Try to resolve forward reference void pushBaseMembersDg(ClassDeclaration cd) { for (size_t i = 0; i < cd.baseclasses.length; i++) { auto cb = (*cd.baseclasses)[i].sym; assert(cb); _foreach(null, cb.members, &pushIdentsDg); if (cb.baseclasses.length) pushBaseMembersDg(cb); } } pushBaseMembersDg(cd); } // Turn Identifiers into StringExps reusing the allocated array assert(Expressions.sizeof == Identifiers.sizeof); auto exps = cast(Expressions*)idents; foreach (i, id; *idents) { auto se = new StringExp(e.loc, id.toString()); (*exps)[i] = se; } /* Making this a tuple is more flexible, as it can be statically unrolled. * To make an array literal, enclose __traits in [ ]: * [ __traits(allMembers, ...) ] */ Expression ex = new TupleExp(e.loc, exps); ex = ex.expressionSemantic(sc); return ex; } if (e.ident == Id.compiles) { /* Determine if all the objects - types, expressions, or symbols - * compile without error */ if (!dim) return False(); foreach (o; *e.args) { uint errors = global.startGagging(); Scope* sc2 = sc.push(); sc2.tinst = null; sc2.minst = null; // this is why code for these are not emitted to object file sc2.flags = (sc.flags & ~(SCOPE.ctfe | SCOPE.condition)) | SCOPE.compile | SCOPE.fullinst; bool err = false; auto t = isType(o); auto ex = isExpression(o); if (t) { Dsymbol s; t.resolve(e.loc, sc2, ex, t, s); if (t) { t.typeSemantic(e.loc, sc2); if (t.ty == Terror) err = true; } else if (s && s.errors) err = true; } if (ex) { ex = ex.expressionSemantic(sc2); ex = resolvePropertiesOnly(sc2, ex); ex = ex.optimize(WANTvalue); if (sc2.func && sc2.func.type.isTypeFunction()) { const tf = sc2.func.type.isTypeFunction(); err |= tf.isnothrow && canThrow(ex, sc2.func, null); } ex = checkGC(sc2, ex); if (ex.op == EXP.error) err = true; } // Carefully detach the scope from the parent and throw it away as // we only need it to evaluate the expression // https://issues.dlang.org/show_bug.cgi?id=15428 sc2.detach(); if (global.endGagging(errors) || err) { return False(); } } return True(); } if (e.ident == Id.isSame) { /* Determine if two symbols are the same */ if (dim != 2) return dimError(2); // https://issues.dlang.org/show_bug.cgi?id=20761 // tiarg semantic may expand in place the list of arguments, for example: // // before tiarg sema: __traits(isSame, seq!(0,0), seq!(1,1)) // after : __traits(isSame, 0, 0, 1, 1) // // so we split in two lists Objects ob1; ob1.push((*e.args)[0]); Objects ob2; ob2.push((*e.args)[1]); if (!TemplateInstance.semanticTiargs(e.loc, sc, &ob1, 0)) return ErrorExp.get(); if (!TemplateInstance.semanticTiargs(e.loc, sc, &ob2, 0)) return ErrorExp.get(); if (ob1.length != ob2.length) return False(); foreach (immutable i; 0 .. ob1.length) if (!ob1[i].isSame(ob2[i], sc)) return False(); return True(); } if (e.ident == Id.getUnitTests) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbolWithoutExpCtx(o); if (!s) { error(e.loc, "argument `%s` to __traits(getUnitTests) must be a module or aggregate", o.toChars()); return ErrorExp.get(); } if (auto imp = s.isImport()) // https://issues.dlang.org/show_bug.cgi?id=10990 s = imp.mod; auto sds = s.isScopeDsymbol(); if (!sds || sds.isTemplateDeclaration()) { error(e.loc, "argument `%s` to __traits(getUnitTests) must be a module or aggregate, not a %s", s.toChars(), s.kind()); return ErrorExp.get(); } auto exps = new Expressions(); if (global.params.useUnitTests) { bool[void*] uniqueUnitTests; void symbolDg(Dsymbol s) { if (auto ad = s.isAttribDeclaration()) { ad.include(null).foreachDsymbol(&symbolDg); } else if (auto tm = s.isTemplateMixin()) { tm.members.foreachDsymbol(&symbolDg); } else if (auto ud = s.isUnitTestDeclaration()) { if (cast(void*)ud in uniqueUnitTests) return; uniqueUnitTests[cast(void*)ud] = true; auto ad = new FuncAliasDeclaration(ud.ident, ud, false); ad.visibility = ud.visibility; auto e = new DsymbolExp(Loc.initial, ad, false); exps.push(e); } } sds.members.foreachDsymbol(&symbolDg); } auto te = new TupleExp(e.loc, exps); return te.expressionSemantic(sc); } if (e.ident == Id.getVirtualIndex) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; auto s = getDsymbolWithoutExpCtx(o); auto fd = s ? s.isFuncDeclaration() : null; if (!fd) { error(e.loc, "first argument to __traits(getVirtualIndex) must be a function"); return ErrorExp.get(); } fd = fd.toAliasFunc(); // Necessary to support multiple overloads. return new IntegerExp(e.loc, fd.vtblIndex, Type.tptrdiff_t); } if (e.ident == Id.getPointerBitmap) { return pointerBitmap(e, global.errorSink); } if (e.ident == Id.initSymbol) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; ErrorExp badArgument() { error(e.loc, "struct / class type expected as argument to __traits(initSymbol) instead of `%s`", o.toChars()); return ErrorExp.get(); } Type t = isType(o); if (!t || t.isTypeEnum()) return badArgument(); AggregateDeclaration ad = isAggregate(t); // Interfaces don't have an init symbol and hence cause linker errors if (!ad || ad.isInterfaceDeclaration()) return badArgument(); Declaration d = new SymbolDeclaration(ad.loc, ad); d.type = Type.tvoid.arrayOf().constOf(); d.storage_class |= STC.rvalue; return new VarExp(e.loc, d); } version (IN_LLVM) { import dmd.hooks : semanticTraitsHook; if (Expression ret = semanticTraitsHook(e, sc)) { return ret; } } if (e.ident == Id.isZeroInit) { if (dim != 1) return dimError(1); auto o = (*e.args)[0]; Type t = isType(o); if (!t) { error(e.loc, "type expected as second argument of __traits `%s` instead of `%s`", e.ident.toChars(), o.toChars()); return ErrorExp.get(); } // https://issues.dlang.org/show_bug.cgi?id=23534 // // For enums, we need to get the enum initializer // (the first enum member), not the initializer of the // type of the enum members. Type tb = t.isTypeEnum ? t : t.baseElemOf(); return tb.isZeroInit(e.loc) ? True() : False(); } if (e.ident == Id.getTargetInfo) { if (dim != 1) return dimError(1); auto ex = isExpression((*e.args)[0]); StringExp se = ex ? ex.ctfeInterpret().toStringExp() : null; if (!ex || !se || se.len == 0) { error(e.loc, "string expected as argument of __traits `%s` instead of `%s`", e.ident.toChars(), (*e.args)[0].toChars()); return ErrorExp.get(); } se = se.toUTF8(sc); const slice = se.peekString(); Expression r = target.getTargetInfo(slice.ptr, e.loc); // BUG: reliance on terminating 0 if (!r) { error(e.loc, "`getTargetInfo` key `\"%.*s\"` not supported by this implementation", cast(int)slice.length, slice.ptr); return ErrorExp.get(); } return r.expressionSemantic(sc); } if (e.ident == Id.getLocation) { if (dim != 1) return dimError(1); auto arg0 = (*e.args)[0]; Dsymbol s = getDsymbolWithoutExpCtx(arg0); if (!s || !s.loc.isValid()) { error(e.loc, "can only get the location of a symbol, not `%s`", arg0.toChars()); return ErrorExp.get(); } const fd = s.isFuncDeclaration(); // FIXME:td.overnext is always set, even when using an index on it //const td = s.isTemplateDeclaration(); if ((fd && fd.overnext) /*|| (td && td.overnext)*/) { error(e.loc, "cannot get location of an overload set, " ~ "use `__traits(getOverloads, ..., \"%s\"%s)[N]` " ~ "to get the Nth overload", arg0.toChars(), /*td ? ", true".ptr :*/ "".ptr); return ErrorExp.get(); } auto exps = new Expressions(3); (*exps)[0] = new StringExp(e.loc, s.loc.filename.toDString()); (*exps)[1] = new IntegerExp(e.loc, s.loc.linnum,Type.tint32); (*exps)[2] = new IntegerExp(e.loc, s.loc.charnum,Type.tint32); auto tup = new TupleExp(e.loc, exps); return tup.expressionSemantic(sc); } if (e.ident == Id.getCppNamespaces) { auto o = (*e.args)[0]; auto s = getDsymbolWithoutExpCtx(o); auto exps = new Expressions(0); if (auto d = s.isDeclaration()) { if (d.inuse) { .error(d.loc, "%s `%s` circular reference in `__traits(GetCppNamespaces,...)`", d.kind, d.toPrettyChars); return ErrorExp.get(); } d.inuse = 1; } /** Prepend the namespaces in the linked list `ns` to `es`. Returns: true if `ns` contains an `ErrorExp`. */ bool prependNamespaces(Expressions* es, CPPNamespaceDeclaration ns) { // Semantic processing will convert `extern(C++, "a", "b", "c")` // into `extern(C++, "a") extern(C++, "b") extern(C++, "c")`, // creating a linked list what `a`'s `cppnamespace` points to `b`, // and `b`'s points to `c`. Our entry point is `a`. for (; ns !is null; ns = ns.cppnamespace) { ns.dsymbolSemantic(sc); if (ns.exp.isErrorExp()) return true; auto se = ns.exp.toStringExp(); // extern(C++, (emptyTuple)) // struct D {} // will produce a blank ident if (!se.len) continue; es.insert(0, se); } return false; } for (auto p = s; !p.isModule(); p = p.toParent()) { p.dsymbolSemantic(sc); auto pp = p.toParent(); if (pp.isTemplateInstance()) { if (!p.cppnamespace) continue; //if (!p.toParent().cppnamespace) // continue; auto inner = new Expressions(0); auto outer = new Expressions(0); if (prependNamespaces(inner, p.cppnamespace)) return ErrorExp.get(); if (prependNamespaces(outer, pp.cppnamespace)) return ErrorExp.get(); size_t i = 0; while(i < outer.length && ((*inner)[i]) == (*outer)[i]) i++; foreach_reverse (ns; (*inner)[][i .. $]) exps.insert(0, ns); continue; } if (p.isNspace()) exps.insert(0, new StringExp(p.loc, p.ident.toString())); if (prependNamespaces(exps, p.cppnamespace)) return ErrorExp.get(); } if (auto d = s.isDeclaration()) d.inuse = 0; auto tup = new TupleExp(e.loc, exps); return tup.expressionSemantic(sc); } //https://issues.dlang.org/show_bug.cgi?id=22291 if (e.ident == Id.parameters) { //No args are valid if (e.args) { char[] contents = cast(char[]) e.args.toString(); contents = contents[1..$]; contents[$-1] = '\0'; error(e.loc, "`__traits(parameters)` cannot have arguments, but `%s` was supplied", contents.ptr); return ErrorExp.get(); } auto fd = sc.getEnclosingFunction(); if (!fd) { error(e.loc, "`__traits(parameters)` may only be used inside a function"); return ErrorExp.get(); } auto tf = fd.type.isTypeFunction(); assert(tf); auto exps = new Expressions(0); int addParameterDG(size_t idx, Parameter x) { assert(x.ident); exps.push(new IdentifierExp(e.loc, x.ident)); return 0; } /* This is required since not all "parameters" actually have a name until they (tuples) are expanded e.g. an anonymous tuple parameter's contents get given names but not the tuple itself. */ Parameter._foreach(tf.parameterList.parameters, &addParameterDG); auto tup = new TupleExp(e.loc, exps); return tup.expressionSemantic(sc); } /* Can't find the identifier. Try a spell check for a better error message */ traitNotFound(e); return ErrorExp.get(); } /// compare arguments of __traits(isSame) private bool isSame(RootObject o1, RootObject o2, Scope* sc) { static FuncLiteralDeclaration isLambda(RootObject oarg) { if (auto t = isDsymbol(oarg)) { if (auto td = t.isTemplateDeclaration()) { if (td.members && td.members.length == 1) { if (auto fd = (*td.members)[0].isFuncLiteralDeclaration()) return fd; } } } else if (auto ea = isExpression(oarg)) { if (ea.op == EXP.function_) { if (auto fe = ea.isFuncExp()) return fe.fd; } } return null; } auto l1 = isLambda(o1); auto l2 = isLambda(o2); if (l1 && l2) { import dmd.lambdacomp : isSameFuncLiteral; if (isSameFuncLiteral(l1, l2, sc)) return true; } // https://issues.dlang.org/show_bug.cgi?id=12001, allow isSame, , Type t1 = isType(o1); Type t2 = isType(o2); if (t1 && t2 && t1.equals(t2)) return true; auto s1 = getDsymbol(o1); auto s2 = getDsymbol(o2); //printf("isSame: %s, %s\n", o1.toChars(), o2.toChars()); version (none) { printf("o1: %p\n", o1); printf("o2: %p\n", o2); if (!s1) { if (auto ea = isExpression(o1)) printf("%s\n", ea.toChars()); if (auto ta = isType(o1)) printf("%s\n", ta.toChars()); return false; } else printf("%s %s\n", s1.kind(), s1.toChars()); } if (!s1 && !s2) { auto ea1 = isExpression(o1); auto ea2 = isExpression(o2); if (ea1 && ea2) { if (ea1.equals(ea2)) return true; } } if (!s1 || !s2) return false; s1 = s1.toAlias(); s2 = s2.toAlias(); if (auto fa1 = s1.isFuncAliasDeclaration()) s1 = fa1.toAliasFunc(); if (auto fa2 = s2.isFuncAliasDeclaration()) s2 = fa2.toAliasFunc(); // https://issues.dlang.org/show_bug.cgi?id=11259 // compare import symbol to a package symbol static bool cmp(Dsymbol s1, Dsymbol s2) { auto imp = s1.isImport(); return imp && imp.pkg && imp.pkg == s2.isPackage(); } if (cmp(s1,s2) || cmp(s2,s1)) return true; if (s1 == s2) return true; // https://issues.dlang.org/show_bug.cgi?id=18771 // OverloadSets are equal if they contain the same functions auto overSet1 = s1.isOverloadSet(); if (!overSet1) return false; auto overSet2 = s2.isOverloadSet(); if (!overSet2) return false; if (overSet1.a.length != overSet2.a.length) return false; // OverloadSets contain array of Dsymbols => O(n*n) // to compare for equality as the order of overloads // might not be the same Lnext: foreach(overload1; overSet1.a) { foreach(overload2; overSet2.a) { if (overload1 == overload2) continue Lnext; } return false; } return true; } /*********************************** * A trait was not found. Give a decent error message * by trying a spell check. * Params: * e = the offending trait */ private void traitNotFound(TraitsExp e) { __gshared const StringTable!bool traitsStringTable; __gshared bool initialized; if (!initialized) { initialized = true; // lazy initialization // All possible traits __gshared Identifier*[59] idents = [ &Id.allMembers, &Id.child, &Id.classInstanceAlignment, &Id.classInstanceSize, &Id.compiles, &Id.derivedMembers, &Id.fullyQualifiedName, &Id.getAliasThis, &Id.getAttributes, &Id.getFunctionAttributes, &Id.getFunctionVariadicStyle, &Id.getLinkage, &Id.getLocation, &Id.getMember, &Id.getOverloads, &Id.getParameterStorageClasses, &Id.getPointerBitmap, &Id.getProtection, &Id.getTargetInfo, &Id.getUnitTests, &Id.getVirtualFunctions, &Id.getVirtualIndex, &Id.getVirtualMethods, &Id.getVisibility, &Id.hasCopyConstructor, &Id.hasMember, &Id.hasPostblit, &Id.identifier, &Id.isAbstractClass, &Id.isAbstractFunction, &Id.isArithmetic, &Id.isAssociativeArray, &Id.isCopyable, &Id.isDeprecated, &Id.isDisabled, &Id.isFinalClass, &Id.isFinalFunction, &Id.isFloating, &Id.isFuture, &Id.isIntegral, &Id.isLazy, &Id.isModule, &Id.isNested, &Id.isOut, &Id.isOverrideFunction, &Id.isPackage, &Id.isPOD, &Id.isRef, &Id.isReturnOnStack, &Id.isSame, &Id.isScalar, &Id.isStaticArray, &Id.isStaticFunction, &Id.isUnsigned, &Id.isVirtualFunction, &Id.isVirtualMethod, &Id.isZeroInit, &Id.parameters, &Id.parent, ]; StringTable!(bool)* stringTable = cast(StringTable!(bool)*) &traitsStringTable; stringTable._init(idents.length); foreach (id; idents) { auto sv = stringTable.insert((*id).toString(), true); assert(sv); } } static const(char)[] trait_search_fp(const(char)[] seed, out int cost) { //printf("trait_search_fp('%s')\n", seed); if (!seed.length) return null; cost = 0; // all the same cost const sv = traitsStringTable.lookup(seed); return sv ? sv.toString() : null; } if (auto sub = speller!trait_search_fp(e.ident.toString())) error(e.loc, "unrecognized trait `%s`, did you mean `%.*s`?", e.ident.toChars(), cast(int) sub.length, sub.ptr); else error(e.loc, "unrecognized trait `%s`", e.ident.toChars()); } ldc-1.40.0-src/dmd/import.h0000644000000000000000000000265314727557031014071 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/import.h */ #pragma once #include "dsymbol.h" class Identifier; struct Scope; class Module; class Package; class Import final : public Dsymbol { public: /* static import aliasId = pkg1.pkg2.id : alias1 = name1, alias2 = name2; */ DArray packages; // array of Identifier's representing packages Identifier *id; // module Identifier Identifier *aliasId; int isstatic; // !=0 if static import Visibility visibility; // Pairs of alias=name to bind into current namespace Identifiers names; Identifiers aliases; Module *mod; Package *pkg; // leftmost package/module AliasDeclarations aliasdecls; // corresponding AliasDeclarations for alias=name pairs const char *kind() const override; Visibility visible() override; Import *syntaxCopy(Dsymbol *s) override; // copy only syntax trees Dsymbol *toAlias() override; bool overloadInsert(Dsymbol *s) override; Import *isImport() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; ldc-1.40.0-src/dmd/lexer.d0000644000000000000000000036054514727557031013701 0ustar rootroot/** * Implements the lexical analyzer, which converts source code into lexical tokens. * * Specification: $(LINK2 https://dlang.org/spec/lex.html, Lexical) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/lexer.d, _lexer.d) * Documentation: https://dlang.org/phobos/dmd_lexer.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/lexer.d */ module dmd.lexer; import core.stdc.ctype; import core.stdc.stdio; import core.stdc.string; import dmd.entity; import dmd.errorsink; import dmd.id; import dmd.identifier; import dmd.location; import dmd.common.smallbuffer; import dmd.common.outbuffer; import dmd.common.charactertables; import dmd.root.array; import dmd.root.ctfloat; import dmd.root.port; import dmd.root.rmem; import dmd.root.utf; import dmd.tokens; nothrow: version (DMDLIB) { version = LocOffset; } /*********************************************************** * Values to use for various magic identifiers */ struct CompileEnv { import dmd.common.charactertables; uint versionNumber; /// __VERSION__ const(char)[] date; /// __DATE__ const(char)[] time; /// __TIME__ const(char)[] vendor; /// __VENDOR__ const(char)[] timestamp; /// __TIMESTAMP__ bool previewIn; /// `in` means `[ref] scope const`, accepts rvalues bool ddocOutput; /// collect embedded documentation comments bool masm; /// use MASM inline asm syntax // these need a default otherwise tests won't work. IdentifierCharLookup cCharLookupTable; /// C identifier table (set to the lexer by the C parser) IdentifierCharLookup dCharLookupTable; /// D identifier table } /*********************************************************** */ class Lexer { private __gshared OutBuffer stringbuffer; Loc scanloc; // for error messages Loc prevloc; // location of token before current const(char)* p; // current character Token token; IdentifierCharLookup charLookup; /// Character table for identifiers // For ImportC bool Ccompile; /// true if compiling ImportC // The following are valid only if (Ccompile == true) ubyte boolsize; /// size of a C _Bool, default 1 ubyte shortsize; /// size of a C short, default 2 ubyte intsize; /// size of a C int, default 4 ubyte longsize; /// size of C long, 4 or 8 ubyte long_longsize; /// size of a C long long, default 8 ubyte long_doublesize; /// size of C long double, 8 or D real.sizeof ubyte wchar_tsize; /// size of C wchar_t, 2 or 4 ErrorSink eSink; /// send error messages through this interface CompileEnv compileEnv; /// environment private { const(char)* base; // pointer to start of buffer const(char)* end; // pointer to last element of buffer const(char)* line; // start of current line bool doDocComment; // collect doc comment information bool anyToken; // seen at least one token bool commentToken; // comments are TOK.comment's bool tokenizeNewlines; // newlines are turned into TOK.endOfLine's bool whitespaceToken; // tokenize whitespaces (only for DMDLIB) int inTokenStringConstant; // can be larger than 1 when in nested q{} strings int lastDocLine; // last line of previous doc comment Token* tokenFreelist; } nothrow: /********************* * Creates a Lexer for the source code base[begoffset..endoffset+1]. * The last character, base[endoffset], must be null (0) or EOF (0x1A). * * Params: * filename = used for error messages * base = source code, must be terminated by a null (0) or EOF (0x1A) character * begoffset = starting offset into base[] * endoffset = the last offset to read into base[] * doDocComment = handle documentation comments * commentToken = comments become TOK.comment's * errorSink = where error messages go, must not be null * compileEnv = version, vendor, date, time, etc. */ this(const(char)* filename, const(char)* base, size_t begoffset, size_t endoffset, bool doDocComment, bool commentToken, ErrorSink errorSink, const CompileEnv* compileEnv) scope { scanloc = Loc(filename, 1, 1); // debug printf("Lexer::Lexer(%p)\n", base); // debug printf("lexer.filename = %s\n", filename); token = Token.init; this.base = base; this.end = base + endoffset; p = base + begoffset; line = p; this.doDocComment = doDocComment; this.commentToken = commentToken; this.tokenizeNewlines = false; this.inTokenStringConstant = 0; this.lastDocLine = 0; this.eSink = errorSink; assert(errorSink); if (compileEnv) this.compileEnv = *compileEnv; else { this.compileEnv.versionNumber = 1; this.compileEnv.vendor = "DLF"; this.compileEnv.cCharLookupTable = IdentifierCharLookup.forTable(IdentifierTable.LR); this.compileEnv.dCharLookupTable = IdentifierCharLookup.forTable(IdentifierTable.LR); } //initKeywords(); /* If first line starts with '#!', ignore the line */ if (p && p[0] == '#' && p[1] == '!') { p += 2; for (;;p++) { char c = *p; switch (c) { case '\n': p++; goto case; case 0: case 0x1A: break; default: // Note: We do allow malformed UTF-8 on shebang line. // It could have a meaning if the native system // encoding is not Unicode. See test compilable/test13512.d // for example encoded in KOI-8. // We also allow bidirectional control characters. // We do not execute the shebang line, so it can't be used // to conceal code. It is up to the shell to sanitize it. continue; } break; } endOfLine(); } // setup the identifier table lookup functions // C tables are setup in its parser constructor // Due to us not knowing if we're in C at this point in time. charLookup = this.compileEnv.dCharLookupTable; } /*********************** * Alternative entry point for DMDLIB, adds `whitespaceToken` */ this(const(char)* filename, const(char)* base, size_t begoffset, size_t endoffset, bool doDocComment, bool commentToken, bool whitespaceToken, ErrorSink errorSink, const CompileEnv* compileEnv = null ) { this(filename, base, begoffset, endoffset, doDocComment, commentToken, errorSink, compileEnv); this.whitespaceToken = whitespaceToken; } /****************** * Used for unittests for a mock Lexer */ this(ErrorSink errorSink) scope @safe { assert(errorSink); this.eSink = errorSink; } /************************************** * Reset lexer to lex #define's */ final void resetDefineLines(const(char)[] slice) { base = slice.ptr; end = base + slice.length; assert(*end == 0); p = base; line = p; tokenizeNewlines = true; inTokenStringConstant = 0; lastDocLine = 0; scanloc = Loc("#defines", 1, 1); } /********************************** * Set up for next #define line. * p should be at start of next line. */ final void nextDefineLine() { tokenizeNewlines = true; } /*************** * Range interface */ final bool empty() const pure @property @nogc @safe { return front() == TOK.endOfFile; } final TOK front() const pure @property @nogc @safe { return token.value; } final void popFront() { nextToken(); } /// Returns: a newly allocated `Token`. Token* allocateToken() pure nothrow @safe { if (tokenFreelist) { Token* t = tokenFreelist; tokenFreelist = t.next; t.next = null; return t; } return new Token(); } /// Frees the given token by returning it to the freelist. private void releaseToken(Token* token) pure nothrow @nogc @safe { if (mem.isGCEnabled) *token = Token.init; token.next = tokenFreelist; tokenFreelist = token; } final TOK nextToken() { prevloc = token.loc; if (token.next) { Token* t = token.next; memcpy(&token, t, Token.sizeof); releaseToken(t); } else { scan(&token); } //printf(token.toChars()); return token.value; } /*********************** * Look ahead at next token's value. */ final TOK peekNext() { return peek(&token).value; } /*********************** * Look 2 tokens ahead at value. */ final TOK peekNext2() { Token* t = peek(&token); return peek(t).value; } /**************************** * Turn next token in buffer into a token. * Params: * t = the token to set the resulting Token to */ final void scan(Token* t) { const lastLine = scanloc.linnum; Loc startLoc; t.blockComment = null; t.lineComment = null; size_t universalCharacterName4, universalCharacterName8; while (1) { t.ptr = p; //printf("p = %p, *p = '%c'\n",p,*p); t.loc = loc(); switch (*p) { case 0: case 0x1A: t.value = TOK.endOfFile; // end of file // Intentionally not advancing `p`, such that subsequent calls keep returning TOK.endOfFile. return; case ' ': // Skip 4 spaces at a time after aligning 'p' to a 4-byte boundary. while ((cast(size_t)p) % uint.sizeof) { if (*p != ' ') goto LendSkipFourSpaces; p++; } while (*(cast(uint*)p) == 0x20202020) // ' ' == 0x20 p += 4; // Skip over any remaining space on the line. while (*p == ' ') p++; LendSkipFourSpaces: version (DMDLIB) { if (whitespaceToken) { t.value = TOK.whitespace; return; } } continue; // skip white space case '\t': case '\v': case '\f': p++; version (DMDLIB) { if (whitespaceToken) { t.value = TOK.whitespace; return; } } continue; // skip white space case '\r': p++; if (*p != '\n') // if CR stands by itself { endOfLine(); if (tokenizeNewlines) { t.value = TOK.endOfLine; tokenizeNewlines = false; return; } } version (DMDLIB) { if (whitespaceToken) { t.value = TOK.whitespace; return; } } continue; // skip white space case '\n': p++; endOfLine(); if (tokenizeNewlines) { t.value = TOK.endOfLine; tokenizeNewlines = false; return; } version (DMDLIB) { if (whitespaceToken) { t.value = TOK.whitespace; return; } } continue; // skip white space case '\\': if (Ccompile) { if (p[1] == '\r' || p[1] == '\n') { ++p; // ignore \ followed by new line, like VC does continue; } else if (p[1] == 'u') { // Universal Character Name (C) 2 byte // \uXXXX // let the main case handling for identifiers process this // case_indent will always increment, so subtract to prevent branching on the fast path p--; goto case_ident; } else if (p[1] == 'U') { // Universal Character Name (C) 4 byte // \UXXXXXXXX // let the main case handling for identifiers process this // case_indent will always increment, so subtract to prevent branching on the fast path p--; goto case_ident; } } goto default; case '0': if (!isZeroSecond(p[1])) // if numeric literal does not continue { ++p; t.unsvalue = 0; t.value = TOK.int32Literal; return; } goto Lnumber; case '1': .. case '9': if (!isDigitSecond(p[1])) // if numeric literal does not continue { t.unsvalue = *p - '0'; ++p; t.value = TOK.int32Literal; return; } Lnumber: t.value = number(t); return; case '\'': if (issinglechar(p[1]) && p[2] == '\'') { t.unsvalue = p[1]; // simple one character literal t.value = TOK.charLiteral; p += 3; } else if (Ccompile) { clexerCharConstant(*t, 0); } else { t.value = charConstant(t); } return; case 'u': case 'U': case 'L': if (!Ccompile) goto case_ident; if (p[1] == '\'') // C wide character constant { char c = *p; if (c == 'L') // convert L to u or U c = (wchar_tsize == 4) ? 'u' : 'U'; ++p; clexerCharConstant(*t, c); return; } else if (p[1] == '\"') // C wide string literal { const c = *p; ++p; escapeStringConstant(t); t.postfix = c == 'L' ? (wchar_tsize == 2 ? 'w' : 'd') : c == 'u' ? 'w' : 'd'; return; } else if (p[1] == '8' && p[2] == '\"') // C UTF-8 string literal { p += 2; escapeStringConstant(t); return; } goto case_ident; case 'r': if (Ccompile || p[1] != '"') goto case_ident; p++; goto case '`'; case '`': if (Ccompile) goto default; wysiwygStringConstant(t); return; case 'x': if (p[1] != '"') goto case_ident; p++; t.value = hexStringConstant(t); return; case 'q': if (Ccompile) goto case_ident; if (p[1] == '"') { p++; delimitedStringConstant(t); return; } else if (p[1] == '{') { p++; tokenStringConstant(t); return; } else goto case_ident; case 'i': if (Ccompile) goto case_ident; if (p[1] == '"') { p++; // skip the i escapeStringConstant(t, true); return; } else if (p[1] == '`') { p++; // skip the i wysiwygStringConstant(t, true); return; } else if (p[1] == 'q' && p[2] == '{') { p += 2; // skip the i and q tokenStringConstant(t, true); return; } else goto case_ident; case '"': escapeStringConstant(t); return; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': /*case 'i':*/ case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': /*case 'q': case 'r':*/ case 's': case 't': //case 'u': case 'v': case 'w': /*case 'x':*/ case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': //case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': //case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case_ident: { IdentLoop: while (1) { // If this is changed, change the decrement in C's universal character name code above // For syntax \uXXXX and \UXXXXXXXX const c = *++p; // Is this the first character of the identifier // For the universal character name this will line up, // for the main switch it won't since it wasn't the first, // for the default it won't either because a decode increments. const isStartCharacter = t.ptr is p; if (isidchar(c)) continue; else if (c & 0x80) { const s = p; const u = decodeUTF(); if (isStartCharacter) { if (charLookup.isStart(u)) continue; error(t.loc, "character 0x%04x is not allowed as a start character in an identifier", u); } else { if (charLookup.isContinue(u)) continue; error(t.loc, "character 0x%04x is not allowed as a continue character in an identifier", u); } p = s; } else if (Ccompile && c == '\\') { uint times; const s = p; p++; if (*p == 'u') { // Universal Character Name (C) 2 byte // \uXXXX p++; times = 4; } else if (*p == 'U') { // Universal Character Name (C) 4 byte // \UXXXXXXXX p++; times = 8; } else { error(t.loc, "char 0x%x is not allowed to follow '\\' expecting a C universal character name in format \\uXXXX or \\UXXXXXXXX with hex digits instead of X with invalid u/U", *p); p = s; break; } foreach(_; 0 .. times) { const hc = *p; p++; if ((hc >= '0' && hc <= '9') || (hc >= 'a' && hc <= 'f') || (hc >= 'A' && hc <= 'F')) continue; error(t.loc, "char 0x%x is not allowed to follow '\\' expecting a C universal character name in format \\uXXXX or \\UXXXXXXXX with hex digits instead of X with invalid hex digit", hc); p = s; break IdentLoop; } continue; } break; } Identifier id; if (universalCharacterName4 > 0 || universalCharacterName8 > 0) { auto priorValidation = t.ptr[0 .. p - t.ptr]; const(char)* priorVPtr = priorValidation.ptr; const possibleLength = ( priorValidation.length - ( (universalCharacterName4 * 6) + (universalCharacterName8 * 10) )) + ( (universalCharacterName4 * 3) + (universalCharacterName8 * 4) ); char[64] buffer = void; SmallBuffer!char sb = SmallBuffer!char(possibleLength, buffer[]); char[] storage = sb.extent; size_t offset; while(priorVPtr < &priorValidation[$-1] + 1) { if (*priorVPtr == '\\') { dchar tempDchar = 0; uint times; // universal character name (C) if (priorVPtr[1] == 'u') times = 4; else if (priorVPtr[1] == 'U') times = 8; else assert(0, "ICE: Universal character name is 2 or 4 bytes only"); priorVPtr += 2; foreach(_; 0 .. times) { char c = *++priorVPtr; if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'a' && c <= 'f') c -= 'a' - 10; else if (c >= 'A' && c <= 'F') c -= 'A' - 10; tempDchar <<= 4; tempDchar |= c; } utf_encodeChar(&storage[offset], tempDchar); offset += utf_codeLengthChar(tempDchar); // Could be an error instead of a warning, // but hey it was written specifically so why worry? if (priorVPtr is priorValidation.ptr) { if (!charLookup.isStart(tempDchar)) warning(t.loc, "char 0x%x is not allowed start character for an identifier", tempDchar); } else { if (!charLookup.isContinue(tempDchar)) warning(t.loc, "char 0x%x is not allowed continue character for an identifier", tempDchar); } } else storage[offset++] = *++priorVPtr; } id = Identifier.idPool(storage[0 .. offset], false); } else id = Identifier.idPool((cast(char*)t.ptr)[0 .. p - t.ptr], false); t.ident = id; t.value = cast(TOK)id.getValue(); anyToken = 1; /* Different keywords for C and D */ if (Ccompile) { if (t.value != TOK.identifier) { t.value = Ckeywords[t.value]; // filter out D keywords } } else if (t.value >= FirstCKeyword) t.value = TOK.identifier; // filter out C keywords else if (*t.ptr == '_') // if special identifier token { void toToken(const(char)[] s) { t.value = TOK.string_; t.ustring = s.ptr; t.len = cast(uint)s.length; t.postfix = 0; } if (id == Id.DATE) toToken(compileEnv.date); else if (id == Id.TIME) toToken(compileEnv.time); else if (id == Id.VENDOR) toToken(compileEnv.vendor); else if (id == Id.TIMESTAMP) toToken(compileEnv.timestamp); else if (id == Id.VERSIONX) { t.value = TOK.int64Literal; t.unsvalue = compileEnv.versionNumber; } else if (id == Id.EOFX) { t.value = TOK.endOfFile; // Advance scanner to end of file while (!(*p == 0 || *p == 0x1A)) p++; } } //printf("t.value = %d\n",t.value); return; } case '/': p++; switch (*p) { case '=': p++; t.value = TOK.divAssign; return; case '*': p++; startLoc = loc(); while (1) { while (1) { const c = *p; switch (c) { case '/': break; case '\n': endOfLine(); p++; continue; case '\r': p++; if (*p != '\n') endOfLine(); continue; case 0: case 0x1A: error(t.loc, "unterminated /* */ comment"); p = end; t.loc = loc(); t.value = TOK.endOfFile; return; default: if (c & 0x80) { const u = decodeUTF(); if (u == PS || u == LS) endOfLine(); } p++; continue; } break; } p++; if (p[-2] == '*' && p - 3 != t.ptr) break; } if (commentToken) { t.loc = startLoc; t.value = TOK.comment; return; } else if (doDocComment && t.ptr[2] == '*' && p - 4 != t.ptr) { // if /** but not /**/ getDocComment(t, lastLine == startLoc.linnum, startLoc.linnum - lastDocLine > 1); lastDocLine = scanloc.linnum; } continue; case '/': // do // style comments startLoc = loc(); while (1) { const c = *++p; switch (c) { case '\n': break; case '\r': if (p[1] == '\n') p++; break; case 0: case 0x1A: if (commentToken) { p = end; t.loc = startLoc; t.value = TOK.comment; return; } if (doDocComment && t.ptr[2] == '/') { getDocComment(t, lastLine == startLoc.linnum, startLoc.linnum - lastDocLine > 1); lastDocLine = scanloc.linnum; } p = end; t.loc = loc(); t.value = TOK.endOfFile; return; default: if (c & 0x80) { const u = decodeUTF(); if (u == PS || u == LS) break; } continue; } break; } if (commentToken) { version (DMDLIB) {} else { p++; endOfLine(); } t.loc = startLoc; t.value = TOK.comment; return; } if (doDocComment && t.ptr[2] == '/') { getDocComment(t, lastLine == startLoc.linnum, startLoc.linnum - lastDocLine > 1); lastDocLine = scanloc.linnum; } p++; endOfLine(); continue; case '+': if (!Ccompile) { int nest; startLoc = loc(); p++; nest = 1; while (1) { char c = *p; switch (c) { case '/': p++; if (*p == '+') { p++; nest++; } continue; case '+': p++; if (*p == '/') { p++; if (--nest == 0) break; } continue; case '\r': p++; if (*p != '\n') endOfLine(); continue; case '\n': endOfLine(); p++; continue; case 0: case 0x1A: error(t.loc, "unterminated /+ +/ comment"); p = end; t.loc = loc(); t.value = TOK.endOfFile; return; default: if (c & 0x80) { uint u = decodeUTF(); if (u == PS || u == LS) endOfLine(); } p++; continue; } break; } if (commentToken) { t.loc = startLoc; t.value = TOK.comment; return; } if (doDocComment && t.ptr[2] == '+' && p - 4 != t.ptr) { // if /++ but not /++/ getDocComment(t, lastLine == startLoc.linnum, startLoc.linnum - lastDocLine > 1); lastDocLine = scanloc.linnum; } continue; } break; default: break; } t.value = TOK.div; return; case '.': p++; if (isdigit(*p)) { /* Note that we don't allow ._1 and ._ as being * valid floating point numbers. */ p--; t.value = inreal(t); } else if (p[0] == '.') { if (p[1] == '.') { p += 2; t.value = TOK.dotDotDot; } else { p++; t.value = TOK.slice; } } else t.value = TOK.dot; return; case '&': p++; if (*p == '=') { p++; t.value = TOK.andAssign; } else if (*p == '&') { p++; t.value = TOK.andAnd; } else t.value = TOK.and; return; case '|': p++; if (*p == '=') { p++; t.value = TOK.orAssign; } else if (*p == '|') { p++; t.value = TOK.orOr; } else t.value = TOK.or; return; case '-': p++; if (*p == '=') { p++; t.value = TOK.minAssign; } else if (*p == '-') { p++; t.value = TOK.minusMinus; } else if (*p == '>') { ++p; t.value = TOK.arrow; } else t.value = TOK.min; return; case '+': p++; if (*p == '=') { p++; t.value = TOK.addAssign; } else if (*p == '+') { p++; t.value = TOK.plusPlus; } else t.value = TOK.add; return; case '<': p++; if (*p == '=') { p++; t.value = TOK.lessOrEqual; // <= } else if (*p == '<') { p++; if (*p == '=') { p++; t.value = TOK.leftShiftAssign; // <<= } else t.value = TOK.leftShift; // << } else if (*p == ':' && Ccompile) { ++p; t.value = TOK.leftBracket; // <: } else if (*p == '%' && Ccompile) { ++p; t.value = TOK.leftCurly; // <% } else t.value = TOK.lessThan; // < return; case '>': p++; if (*p == '=') { p++; t.value = TOK.greaterOrEqual; // >= } else if (*p == '>') { p++; if (*p == '=') { p++; t.value = TOK.rightShiftAssign; // >>= } else if (*p == '>') { p++; if (*p == '=') { p++; t.value = TOK.unsignedRightShiftAssign; // >>>= } else t.value = TOK.unsignedRightShift; // >>> } else t.value = TOK.rightShift; // >> } else t.value = TOK.greaterThan; // > return; case '!': p++; if (*p == '=') { p++; t.value = TOK.notEqual; // != } else t.value = TOK.not; // ! return; case '=': p++; if (*p == '=') { p++; t.value = TOK.equal; // == } else if (*p == '>') { p++; t.value = TOK.goesTo; // => } else t.value = TOK.assign; // = return; case '~': p++; if (*p == '=') { p++; t.value = TOK.concatenateAssign; // ~= } else t.value = TOK.tilde; // ~ return; case '^': p++; if (*p == '^') { p++; if (*p == '=') { p++; t.value = TOK.powAssign; // ^^= } else t.value = TOK.pow; // ^^ } else if (*p == '=') { p++; t.value = TOK.xorAssign; // ^= } else t.value = TOK.xor; // ^ return; case '(': p++; t.value = TOK.leftParenthesis; return; case ')': p++; t.value = TOK.rightParenthesis; return; case '[': p++; t.value = TOK.leftBracket; return; case ']': p++; t.value = TOK.rightBracket; return; case '{': p++; t.value = TOK.leftCurly; return; case '}': p++; t.value = TOK.rightCurly; return; case '?': p++; t.value = TOK.question; return; case ',': p++; t.value = TOK.comma; return; case ';': p++; t.value = TOK.semicolon; return; case ':': p++; if (*p == ':') { ++p; t.value = TOK.colonColon; } else if (*p == '>' && Ccompile) { ++p; t.value = TOK.rightBracket; } else t.value = TOK.colon; return; case '$': p++; t.value = TOK.dollar; return; case '@': p++; t.value = TOK.at; return; case '*': p++; if (*p == '=') { p++; t.value = TOK.mulAssign; } else t.value = TOK.mul; return; case '%': p++; if (*p == '=') { p++; t.value = TOK.modAssign; } else if (*p == '>' && Ccompile) { ++p; t.value = TOK.rightCurly; } else if (*p == ':' && Ccompile) { goto case '#'; // %: means # } else t.value = TOK.mod; return; case '#': { // https://issues.dlang.org/show_bug.cgi?id=22825 // Special token sequences are terminated by newlines, // and should not be skipped over. this.tokenizeNewlines = true; p++; if (parseSpecialTokenSequence()) continue; t.value = TOK.pound; return; } default: { dchar c = *p; if (c & 0x80) { c = decodeUTF(); // Check for start of an identifier if (charLookup.isStart(c)) goto case_ident; if (c == PS || c == LS) { endOfLine(); p++; if (tokenizeNewlines) { t.value = TOK.endOfLine; tokenizeNewlines = false; return; } continue; } } if (c < 0x80 && isprint(c)) error(t.loc, "character '%c' is not a valid token", c); else error(t.loc, "character 0x%02x is not a valid token", c); p++; continue; // assert(0); } } } } final Token* peek(Token* ct) { Token* t; if (ct.next) t = ct.next; else { t = allocateToken(); scan(t); ct.next = t; } return t; } /********************************* * tk is on the opening (. * Look ahead and return token that is past the closing ). */ final Token* peekPastParen(Token* tk) { //printf("peekPastParen()\n"); int parens = 1; int curlynest = 0; while (1) { tk = peek(tk); //tk.print(); switch (tk.value) { case TOK.leftParenthesis: parens++; continue; case TOK.rightParenthesis: --parens; if (parens) continue; tk = peek(tk); break; case TOK.leftCurly: curlynest++; continue; case TOK.rightCurly: if (--curlynest >= 0) continue; break; case TOK.semicolon: if (curlynest) continue; break; case TOK.endOfFile: break; default: continue; } return tk; } } /******************************************* * Parse escape sequence. */ private uint escapeSequence(out dchar c2) { return Lexer.escapeSequence(token.loc, p, Ccompile, c2); } /******** * Parse the given string literal escape sequence into a single character. * D https://dlang.org/spec/lex.html#escape_sequences * C11 6.4.4.4 * Params: * loc = location to use for error messages * sequence = pointer to string with escape sequence to parse. Updated to * point past the end of the escape sequence * Ccompile = true for compile C11 escape sequences * c2 = returns second `dchar` of html entity with 2 code units, otherwise stays `dchar.init` * Returns: * the escape sequence as a single character */ private dchar escapeSequence(const ref Loc loc, ref const(char)* sequence, bool Ccompile, out dchar c2) { const(char)* p = sequence; // cache sequence reference on stack scope(exit) sequence = p; uint c = *p; int ndigits; switch (c) { case '\'': case '"': case '?': case '\\': Lconsume: p++; break; case 'a': c = 7; goto Lconsume; case 'b': c = 8; goto Lconsume; case 'f': c = 12; goto Lconsume; case 'n': c = 10; goto Lconsume; case 'r': c = 13; goto Lconsume; case 't': c = 9; goto Lconsume; case 'v': c = 11; goto Lconsume; case 'u': ndigits = 4; goto Lhex; case 'U': ndigits = 8; goto Lhex; case 'x': ndigits = 2; Lhex: p++; c = *p; if (ishex(cast(char)c)) { uint v = 0; int n = 0; if (Ccompile && ndigits == 2) { /* C11 6.4.4.4-7 one to infinity hex digits */ do { if (isdigit(cast(char)c)) c -= '0'; else if (islower(c)) c -= 'a' - 10; else c -= 'A' - 10; v = v * 16 + c; c = *++p; } while (ishex(cast(char)c)); } else { while (1) { if (isdigit(cast(char)c)) c -= '0'; else if (islower(c)) c -= 'a' - 10; else c -= 'A' - 10; v = v * 16 + c; c = *++p; if (++n == ndigits) break; if (!ishex(cast(char)c)) { error(loc, "escape hex sequence has %d hex digits instead of %d", n, ndigits); break; } } if (ndigits != 2 && !utf_isValidDchar(v)) { error(loc, "invalid UTF character \\U%08x", v); if (v >= 0xD800 && v <= 0xDFFF) errorSupplemental("The code unit is a UTF-16 surrogate, is the escape UTF-16 not a Unicode code point?"); v = '?'; // recover with valid UTF character } } c = v; } else { error(loc, "undefined escape hex sequence \\%c%c", sequence[0], c); p++; } break; case '&': if (Ccompile) goto default; // named character entity for (const idstart = ++p; 1; p++) { switch (*p) { case ';': auto entity = HtmlNamedEntity(idstart[0 .. p - idstart]); c = entity[0]; if (entity == entity.init) { error(loc, "unnamed character entity &%.*s;", cast(int)(p - idstart), idstart); c = '?'; } if (entity[1] != entity.init[1]) c2 = entity[1]; p++; break; default: if (isalpha(*p) || (p != idstart && isdigit(*p))) continue; error(loc, "unterminated named entity &%.*s;", cast(int)(p - idstart + 1), idstart); c = '?'; break; } break; } break; case 0: case 0x1A: // end of file c = '\\'; break; default: if (isoctal(cast(char)c)) { uint v = 0; int n = 0; do { v = v * 8 + (c - '0'); c = *++p; } while (++n < 3 && isoctal(cast(char)c)); c = v; if (c > 0xFF) error(loc, "escape octal sequence \\%03o is larger than \\377", c); } else { error(loc, "undefined escape sequence \\%c", c); p++; } break; } return c; } /** Lex a wysiwyg string. `p` must be pointing to the first character before the contents of the string literal. The character pointed to by `p` will be used as the terminating character (i.e. backtick or double-quote). Params: result = pointer to the token that accepts the result */ private void wysiwygStringConstant(Token* result, bool supportInterpolation = false) { if (supportInterpolation) { result.value = TOK.interpolated; result.interpolatedSet = null; } else { result.value = TOK.string_; } Loc start = loc(); auto terminator = p[0]; p++; stringbuffer.setsize(0); while (1) { dchar c = p[0]; p++; switch (c) { case '\n': endOfLine(); break; case '\r': if (p[0] == '\n') continue; // ignore c = '\n'; // treat EndOfLine as \n character endOfLine(); break; case '$': if (!supportInterpolation) goto default; if (!handleInterpolatedSegment(result, start)) goto default; continue; case 0: case 0x1A: error("unterminated string constant starting at %s", start.toChars()); result.setString(); // rewind `p` so it points to the EOF character p--; return; default: if (c == terminator) { if (supportInterpolation) result.appendInterpolatedPart(stringbuffer); else result.setString(stringbuffer); stringPostfix(result); return; } else if (c & 0x80) { p--; const u = decodeUTF(); p++; if (u == PS || u == LS) endOfLine(); stringbuffer.writeUTF8(u); continue; } break; } stringbuffer.writeByte(c); } } /************************************** * Lex hex strings: * x"0A ae 34FE BD" */ final TOK hexStringConstant(Token* t) { Loc start = loc(); uint n = 0; uint v = ~0; // dead assignment, needed to suppress warning p++; stringbuffer.setsize(0); while (1) { dchar c = *p++; switch (c) { case ' ': case '\t': case '\v': case '\f': continue; // skip white space case '\r': if (*p == '\n') continue; // ignore '\r' if followed by '\n' // Treat isolated '\r' as if it were a '\n' goto case '\n'; case '\n': endOfLine(); continue; case 0: case 0x1A: error("unterminated string constant starting at %s", start.toChars()); t.setString(); // decrement `p`, because it needs to point to the next token (the 0 or 0x1A character is the TOK.endOfFile token). p--; return TOK.hexadecimalString; case '"': if (n & 1) { error("odd number (%d) of hex characters in hex string", n); stringbuffer.writeByte(v); } t.setString(stringbuffer); stringPostfix(t); return TOK.hexadecimalString; default: if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'a' && c <= 'f') c -= 'a' - 10; else if (c >= 'A' && c <= 'F') c -= 'A' - 10; else if (c & 0x80) { p--; const u = decodeUTF(); p++; if (u == PS || u == LS) endOfLine(); else error("non-hex character \\u%04x in hex string", u); } else error("non-hex character '%c' in hex string", c); if (n & 1) { v = (v << 4) | c; stringbuffer.writeByte(v); } else v = c; n++; break; } } assert(0); // see bug 15731 } /** Lex a delimited string. Some examples of delimited strings are: --- q"(foo(xxx))" // "foo(xxx)" q"[foo$(LPAREN)]" // "foo$(LPAREN)" q"/foo]/" // "foo]" q"HERE foo HERE" // "foo\n" --- It is assumed that `p` points to the opening double-quote '"'. Params: result = pointer to the token that accepts the result */ private void delimitedStringConstant(Token* result) { result.value = TOK.string_; Loc start = loc(); dchar delimleft = 0; dchar delimright = 0; uint nest = 1; uint nestcount = ~0; // dead assignment, needed to suppress warning Identifier hereid = null; uint blankrol = 0; uint startline = 0; p++; stringbuffer.setsize(0); while (1) { const s = p; dchar c = *p++; //printf("c = '%c'\n", c); switch (c) { case '\n': Lnextline: endOfLine(); startline = 1; if (blankrol) { blankrol = 0; continue; } if (hereid) { stringbuffer.writeUTF8(c); continue; } break; case '\r': if (*p == '\n') continue; // ignore c = '\n'; // treat EndOfLine as \n character goto Lnextline; case 0: case 0x1A: error("unterminated delimited string constant starting at %s", start.toChars()); result.setString(); // decrement `p`, because it needs to point to the next token (the 0 or 0x1A character is the TOK.endOfFile token). p--; return; default: if (c & 0x80) { p--; c = decodeUTF(); p++; if (c == PS || c == LS) goto Lnextline; } break; } if (delimleft == 0) { delimleft = c; nest = 1; nestcount = 1; if (c == '(') delimright = ')'; else if (c == '{') delimright = '}'; else if (c == '[') delimright = ']'; else if (c == '<') delimright = '>'; else if (isalpha(c) || c == '_' || (c >= 0x80 && charLookup.isStart(c))) { // Start of identifier; must be a heredoc Token tok; p = s; scan(&tok); // read in heredoc identifier if (tok.value != TOK.identifier) { error("identifier expected for heredoc, not %s", tok.toChars()); delimright = c; } else { hereid = tok.ident; //printf("hereid = '%s'\n", hereid.toChars()); blankrol = 1; } nest = 0; } else { delimright = c; nest = 0; if (isspace(c)) error("delimiter cannot be whitespace"); } } else { if (blankrol) { error("heredoc rest of line should be blank"); blankrol = 0; continue; } if (nest == 1) { if (c == delimleft) nestcount++; else if (c == delimright) { nestcount--; if (nestcount == 0) goto Ldone; } } else if (c == delimright) goto Ldone; // we're looking for a new identifier token if (startline && (isalpha(c) || c == '_' || (c >= 0x80 && charLookup.isStart(c))) && hereid) { Token tok; auto psave = p; p = s; scan(&tok); // read in possible heredoc identifier //printf("endid = '%s'\n", tok.ident.toChars()); if (tok.value == TOK.identifier && tok.ident is hereid) { /* should check that rest of line is blank */ goto Ldone; } p = psave; } stringbuffer.writeUTF8(c); startline = 0; } } Ldone: if (*p == '"') p++; else if (hereid) error("delimited string must end in `%s\"`", hereid.toChars()); else if (isspace(delimright)) error("delimited string must end in `\"`"); else error(token.loc, "delimited string must end in `%c\"`", delimright); result.setString(stringbuffer); stringPostfix(result); } /** Lex a token string. Some examples of token strings are: --- q{ foo(xxx) } // " foo(xxx) " q{foo$(LPAREN)} // "foo$(LPAREN)" q{{foo}"}"} // "{foo}"}"" --- It is assumed that `p` points to the opening curly-brace. Params: result = pointer to the token that accepts the result */ private void tokenStringConstant(Token* result, bool supportInterpolation = false) { if (supportInterpolation) { result.value = TOK.interpolated; result.interpolatedSet = null; } else { result.value = TOK.string_; } uint nest = 1; const start = loc(); auto pstart = ++p; inTokenStringConstant++; scope(exit) inTokenStringConstant--; while (1) { Token tok; scan(&tok); switch (tok.value) { case TOK.leftCurly: nest++; continue; case TOK.rightCurly: if (--nest == 0) { if (supportInterpolation) result.appendInterpolatedPart(pstart, p - 1 - pstart); else result.setString(pstart, p - 1 - pstart); stringPostfix(result); return; } continue; case TOK.dollar: if (!supportInterpolation) goto default; stringbuffer.setsize(0); stringbuffer.write(pstart, p - 1 - pstart); if (!handleInterpolatedSegment(result, start)) goto default; stringbuffer.setsize(0); pstart = p; continue; case TOK.endOfFile: error("unterminated token string constant starting at %s", start.toChars()); result.setString(); return; default: continue; } } } // returns true if it got special treatment as an interpolated segment // otherwise returns false, indicating to treat it as just part of a normal string private bool handleInterpolatedSegment(Token* token, Loc start) { switch(*p) { case '(': // expression, at this level we need to scan until the closing ')' // always put the string part in first token.appendInterpolatedPart(stringbuffer); stringbuffer.setsize(0); int openParenCount = 1; p++; // skip the first open paren auto pstart = p; while (openParenCount > 0) { // need to scan with the lexer to support embedded strings and other complex cases Token tok; scan(&tok); if (tok.value == TOK.leftParenthesis) openParenCount++; if (tok.value == TOK.rightParenthesis) openParenCount--; if (tok.value == TOK.endOfFile) { // FIXME: make this error better, it spams a lot error("unterminated interpolated string constant starting at %s", start.toChars()); return false; } } // then put the interpolated string segment token.appendInterpolatedPart(pstart[0 .. p - 1 - pstart]); stringbuffer.setsize(0); // make sure this is reset from the last token scan // otherwise something like i"$(func("thing")) stuff" can still include it return true; default: // nothing special return false; } } /** Scan a quoted string while building the processed string value by handling escape sequences. The result is returned in the given `t` token. This function assumes that `p` currently points to the opening quote of the string. Params: t = the token to set the resulting string to * References: * D https://dlang.org/spec/lex.html#double_quoted_strings * ImportC C11 6.4.5 */ private void escapeStringConstant(Token* t, bool supportInterpolation = false) { if (supportInterpolation) { t.value = TOK.interpolated; t.interpolatedSet = null; } else { t.value = TOK.string_; } const start = loc(); const tc = *p++; // opening quote stringbuffer.setsize(0); while (1) { dchar c = *p++; dchar c2; switch (c) { case '\\': switch (*p) { case '&': if (Ccompile) goto default; c = escapeSequence(c2); stringbuffer.writeUTF8(c); if (c2 != dchar.init) stringbuffer.writeUTF8(c2); continue; case 'u': case 'U': c = escapeSequence(c2); stringbuffer.writeUTF8(c); continue; case '$': if (supportInterpolation) { p++; // skip escaped $ stringbuffer.writeByte('$'); continue; } else goto default; default: c = escapeSequence(c2); break; } break; case '$': if (!supportInterpolation) goto default; if (!handleInterpolatedSegment(t, start)) goto default; continue; case '\n': endOfLine(); if (Ccompile) goto Lunterminated; break; case '\r': if (*p == '\n') continue; // ignore c = '\n'; // treat EndOfLine as \n character endOfLine(); if (Ccompile) goto Lunterminated; break; case '\'': case '"': if (c != tc) goto default; if (supportInterpolation) t.appendInterpolatedPart(stringbuffer); else t.setString(stringbuffer); if (!Ccompile) stringPostfix(t); return; case 0: case 0x1A: // decrement `p`, because it needs to point to the next token (the 0 or 0x1A character is the TOK.endOfFile token). p--; Lunterminated: error("unterminated string constant starting at %s", start.toChars()); t.setString(); return; default: if (c & 0x80) { p--; c = decodeUTF(); if (c == LS || c == PS) { c = '\n'; endOfLine(); if (Ccompile) goto Lunterminated; } p++; stringbuffer.writeUTF8(c); continue; } break; } stringbuffer.writeByte(c); } } /************************************** * Reference: * https://dlang.org/spec/lex.html#characterliteral */ private TOK charConstant(Token* t) { TOK tk = TOK.charLiteral; //printf("Lexer::charConstant\n"); p++; dchar c = *p++; dchar c2; switch (c) { case '\\': switch (*p) { case 'u': tk = TOK.wcharLiteral; goto default; case 'U': case '&': tk = TOK.dcharLiteral; goto default; default: t.unsvalue = escapeSequence(c2); if (c2 != c2.init) { error("html entity requires 2 code units, use a string instead of a character"); t.unsvalue = '?'; } break; } break; case '\n': L1: endOfLine(); goto case; case '\r': goto case '\''; case 0: case 0x1A: // decrement `p`, because it needs to point to the next token (the 0 or 0x1A character is the TOK.endOfFile token). p--; goto case; case '\'': error("unterminated character constant"); t.unsvalue = '?'; return tk; default: if (c & 0x80) { p--; c = decodeUTF(); p++; if (c == LS || c == PS) goto L1; if (c < 0xD800 || (c >= 0xE000 && c < 0xFFFE)) tk = TOK.wcharLiteral; else tk = TOK.dcharLiteral; } t.unsvalue = c; break; } if (*p != '\'') { while (*p != '\'' && *p != 0x1A && *p != 0 && *p != '\n' && *p != '\r' && *p != ';' && *p != ')' && *p != ']' && *p != '}') { if (*p & 0x80) { const s = p; c = decodeUTF(); if (c == LS || c == PS) { p = s; break; } } p++; } if (*p == '\'') { error("character constant has multiple characters"); p++; } else error("unterminated character constant"); t.unsvalue = '?'; return tk; } p++; return tk; } /*************************************** * Lex C character constant. * Parser is on the opening quote. * Params: * t = token to fill in * prefix = one of `u`, `U` or 0. * Reference: * C11 6.4.4.4 */ private void clexerCharConstant(ref Token t, char prefix) { escapeStringConstant(&t); const(char)[] str = t.ustring[0 .. t.len]; const n = str.length; const loc = t.loc; if (n == 0) { error(loc, "empty character constant"); t.value = TOK.semicolon; return; } uint u; switch (prefix) { case 0: if (n == 1) // fast case { u = str[0]; } else if (n > 4) error(loc, "max number of chars in character literal is 4, had %d", cast(int)n); else { foreach (i, c; str) (cast(char*)&u)[n - 1 - i] = c; } break; case 'u': dchar d1; size_t idx; while (idx < n) { string msg = utf_decodeChar(str, idx, d1); if (msg) error(loc, "%.*s", cast(int)msg.length, msg.ptr); } if (d1 >= 0x1_0000) error(loc, "x%x does not fit in 16 bits", d1); t.unsvalue = d1; t.value = TOK.wcharLiteral; // C11 6.4.4.4-9 return; case 'U': dchar d; size_t idx; auto msg = utf_decodeChar(str, idx, d); if (msg) error(loc, "%.*s", cast(int)msg.length, msg.ptr); else if (idx < n) error(loc, "max number of chars in 32 bit character literal is 1, had %d", cast(int)((n + 3) >> 2)); t.unsvalue = d; t.value = TOK.dcharLiteral; // C11 6.4.4.4-9 return; default: assert(0); } t.value = n == 1 ? TOK.charLiteral : TOK.int32Literal; t.unsvalue = u; } /*************************************** * Get postfix of string literal. */ private void stringPostfix(Token* t) pure @nogc { switch (*p) { case 'c': case 'w': case 'd': t.postfix = *p; p++; break; default: t.postfix = 0; break; } } /************************************** * Read in a number. * If it's an integer, store it in tok.TKutok.Vlong. * integers can be decimal, octal or hex * Handle the suffixes U, UL, LU, L, etc. * If it's double, store it in tok.TKutok.Vdouble. * Returns: * TKnum * TKdouble,... */ private TOK number(Token* t) { int base = 10; const start = p; ulong n = 0; // unsigned >=64 bit integer type int d; bool err = false; bool overflow = false; bool anyBinaryDigitsNoSingleUS = false; bool anyHexDigitsNoSingleUS = false; char errorDigit = 0; dchar c = *p; if (c == '0') { ++p; c = *p; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': base = 8; break; case '8': case '9': errorDigit = cast(char) c; base = 8; break; case 'x': case 'X': ++p; base = 16; break; case 'b': case 'B': ++p; base = 2; break; case '.': if (p[1] == '.') goto Ldone; // if ".." if (isalpha(p[1]) || p[1] == '_' || p[1] & 0x80) { if (Ccompile && (p[1] == 'f' || p[1] == 'F' || p[1] == 'l' || p[1] == 'L')) goto Lreal; // if `0.f` or `0.L` goto Ldone; // if ".identifier" or ".unicode" } goto Lreal; // '.' is part of current token case 'i': case 'f': case 'F': goto Lreal; case '_': if (Ccompile) error("embedded `_` not allowed"); ++p; base = 8; break; case 'L': if (p[1] == 'i') goto Lreal; break; default: break; } } while (1) { c = *p; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ++p; d = c - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': ++p; if (base != 16) { if (c == 'e' || c == 'E' || c == 'f' || c == 'F') goto Lreal; } if (c >= 'a') d = c + 10 - 'a'; else d = c + 10 - 'A'; break; case 'L': if (p[1] == 'i') goto Lreal; goto Ldone; case '.': if (p[1] == '.') goto Ldone; // if ".." if (base <= 10 && n > 0 && (isalpha(p[1]) || p[1] == '_' || p[1] & 0x80)) { if (Ccompile && base == 10 && (p[1] == 'e' || p[1] == 'E' || p[1] == 'f' || p[1] == 'F' || p[1] == 'l' || p[1] == 'L')) goto Lreal; // if `1.e6` or `1.f` or `1.L` goto Ldone; // if ".identifier" or ".unicode" } if (base == 16 && (!ishex(p[1]) || p[1] == '_' || p[1] & 0x80)) goto Ldone; // if ".identifier" or ".unicode" if (base == 2) goto Ldone; // if ".identifier" or ".unicode" goto Lreal; // otherwise as part of a floating point literal case 'i': if (Ccompile) goto Ldone; goto Lreal; case 'p': case 'P': Lreal: p = start; return inreal(t); case '_': if (Ccompile) goto default; ++p; continue; default: goto Ldone; } // got a digit here, set any necessary flags, check for errors anyHexDigitsNoSingleUS = true; anyBinaryDigitsNoSingleUS = true; if (!errorDigit && d >= base) { errorDigit = cast(char) c; } // Avoid expensive overflow check if we aren't at risk of overflow if (n <= 0x0FFF_FFFF_FFFF_FFFFUL) n = n * base + d; else { import core.checkedint : mulu, addu; n = mulu(n, base, overflow); n = addu(n, d, overflow); } } Ldone: if (errorDigit) { error(token.loc, "%s digit expected, not `%c`", base == 2 ? "binary".ptr : base == 8 ? "octal".ptr : "decimal".ptr, errorDigit); err = true; } if (overflow && !err) { error("integer overflow"); err = true; } if ((base == 2 && !anyBinaryDigitsNoSingleUS) || (base == 16 && !anyHexDigitsNoSingleUS)) error(token.loc, "`%.*s` isn't a valid integer literal, use `%.*s0` instead", cast(int)(p - start), start, 2, start); t.unsvalue = n; if (Ccompile) return cnumber(base, n); enum FLAGS : int { none = 0, decimal = 1, // decimal unsigned = 2, // u or U suffix long_ = 4, // L suffix } FLAGS flags = (base == 10) ? FLAGS.decimal : FLAGS.none; // Parse trailing 'u', 'U', 'l' or 'L' in any combination const psuffix = p; while (1) { FLAGS f; switch (*p) { case 'U': case 'u': f = FLAGS.unsigned; goto L1; case 'l': f = FLAGS.long_; error("lower case integer suffix 'l' is not allowed. Please use 'L' instead"); goto L1; case 'L': f = FLAGS.long_; L1: p++; if ((flags & f) && !err) { error("repeated integer suffix `%c`", p[-1]); err = true; } flags = cast(FLAGS)(flags | f); continue; default: break; } break; } if (base == 8 && n >= 8) { if (err) // can't translate invalid octal value, just show a generic message error("octal literals larger than 7 are no longer supported"); else error(token.loc, "octal literals `0%llo%.*s` are no longer supported, use `std.conv.octal!\"%llo%.*s\"` instead", n, cast(int)(p - psuffix), psuffix, n, cast(int)(p - psuffix), psuffix); } TOK result; switch (flags) { case FLAGS.none: /* Octal or Hexadecimal constant. * First that fits: int, uint, long, ulong */ if (n & 0x8000000000000000L) result = TOK.uns64Literal; else if (n & 0xFFFFFFFF00000000L) result = TOK.int64Literal; else if (n & 0x80000000) result = TOK.uns32Literal; else result = TOK.int32Literal; break; case FLAGS.decimal: /* First that fits: int, long, long long */ if (n & 0x8000000000000000L) { result = TOK.uns64Literal; } else if (n & 0xFFFFFFFF80000000L) result = TOK.int64Literal; else result = TOK.int32Literal; break; case FLAGS.unsigned: case FLAGS.decimal | FLAGS.unsigned: /* First that fits: uint, ulong */ if (n & 0xFFFFFFFF00000000L) result = TOK.uns64Literal; else result = TOK.uns32Literal; break; case FLAGS.decimal | FLAGS.long_: if (n & 0x8000000000000000L) { if (!err) { error("signed integer overflow"); err = true; } result = TOK.uns64Literal; } else result = TOK.int64Literal; break; case FLAGS.long_: if (n & 0x8000000000000000L) result = TOK.uns64Literal; else result = TOK.int64Literal; break; case FLAGS.unsigned | FLAGS.long_: case FLAGS.decimal | FLAGS.unsigned | FLAGS.long_: result = TOK.uns64Literal; break; default: debug { printf("%x\n", flags); } assert(0); } return result; } /************************************** * Lex C integer-suffix * Params: * base = number base * n = raw integer value * Returns: * token value */ private TOK cnumber(int base, ulong n) { /* C11 6.4.4.1 * Parse trailing suffixes: * u or U * l or L * ll or LL */ enum FLAGS : uint { octalhex = 1, // octal or hexadecimal decimal = 2, // decimal unsigned = 4, // u or U suffix long_ = 8, // l or L suffix llong = 0x10, // ll or LL // Microsoft extensions i8 = 0x20, i16 = 0x40, i32 = 0x80, i64 = 0x100, } FLAGS flags = (base == 10) ? FLAGS.decimal : FLAGS.octalhex; bool err; Lsuffixes: while (1) { FLAGS f; const cs = *p; switch (cs) { case 'U': case 'u': f = FLAGS.unsigned; break; case 'l': case 'L': f = FLAGS.long_; if (cs == p[1]) { f = FLAGS.long_ | FLAGS.llong; ++p; } break; case 'i': case 'I': if (p[1] == '8') { f = FLAGS.i8; ++p; } else if (p[1] == '1' && p[2] == '6') { f = FLAGS.i16; p += 2; } else if (p[1] == '3' && p[2] == '2') { f = FLAGS.i32; p += 2; } else if (p[1] == '6' && p[2] == '4') { f = FLAGS.i64; p += 2; } else break Lsuffixes; if (p[1] >= '0' && p[1] <= '9' && !err) { error("invalid integer suffix"); err = true; } break; default: break Lsuffixes; } ++p; if ((flags & f) && !err) { error("duplicate integer suffixes"); err = true; } flags = cast(FLAGS)(flags | f); } TOK result = TOK.int32Literal; // default switch (flags) { /* Since D doesn't have a variable sized `long` or `unsigned long` type, * this code deviates from C by picking D int, uint, long, or ulong instead */ case FLAGS.octalhex: /* Octal or Hexadecimal constant. * First that fits: int, unsigned, long, unsigned long, * long long, unsigned long long */ if (n & 0x8000000000000000L) result = TOK.uns64Literal; // unsigned long else if (n & 0xFFFFFFFF00000000L) result = TOK.int64Literal; // long else if (n & 0x80000000) result = TOK.uns32Literal; else result = TOK.int32Literal; break; case FLAGS.decimal: /* First that fits: int, long, long long */ if (n & 0x8000000000000000L) result = TOK.uns64Literal; // unsigned long else if (n & 0xFFFFFFFF80000000L) result = TOK.int64Literal; // long else result = TOK.int32Literal; break; case FLAGS.octalhex | FLAGS.unsigned: case FLAGS.decimal | FLAGS.unsigned: /* First that fits: unsigned, unsigned long, unsigned long long */ if (n & 0xFFFFFFFF00000000L) result = TOK.uns64Literal; // unsigned long else result = TOK.uns32Literal; break; case FLAGS.decimal | FLAGS.long_: /* First that fits: long, long long */ if (longsize == 4 || long_longsize == 4) { if (n & 0xFFFFFFFF_80000000L) result = TOK.int64Literal; else result = TOK.int32Literal; // long } else { result = TOK.int64Literal; // long } break; case FLAGS.octalhex | FLAGS.long_: /* First that fits: long, unsigned long, long long, * unsigned long long */ if (longsize == 4 || long_longsize == 4) { if (n & 0x8000000000000000L) result = TOK.uns64Literal; else if (n & 0xFFFFFFFF00000000L) result = TOK.int64Literal; else if (n & 0x80000000) result = TOK.uns32Literal; // unsigned long else result = TOK.int32Literal; // long } else { if (n & 0x80000000_00000000L) result = TOK.uns64Literal; // unsigned long else result = TOK.int64Literal; // long } break; case FLAGS.octalhex | FLAGS.unsigned | FLAGS.long_: case FLAGS.decimal | FLAGS.unsigned | FLAGS.long_: /* First that fits: unsigned long, unsigned long long */ if (longsize == 4 || long_longsize == 4) { if (n & 0xFFFFFFFF00000000L) result = TOK.uns64Literal; else result = TOK.uns32Literal; // unsigned long } else { result = TOK.uns64Literal; // unsigned long } break; case FLAGS.octalhex | FLAGS.long_ | FLAGS.llong: /* First that fits: long long, unsigned long long */ if (n & 0x8000000000000000L) result = TOK.uns64Literal; else result = TOK.int64Literal; break; case FLAGS.decimal | FLAGS.long_ | FLAGS.llong: /* long long */ result = TOK.int64Literal; break; case FLAGS.octalhex | FLAGS.long_ | FLAGS.unsigned | FLAGS.llong: case FLAGS.decimal | FLAGS.long_ | FLAGS.unsigned | FLAGS.llong: result = TOK.uns64Literal; break; case FLAGS.octalhex | FLAGS.i8: case FLAGS.octalhex | FLAGS.i16: case FLAGS.octalhex | FLAGS.i32: case FLAGS.octalhex | FLAGS.unsigned | FLAGS.i8: case FLAGS.octalhex | FLAGS.unsigned | FLAGS.i16: case FLAGS.octalhex | FLAGS.unsigned | FLAGS.i32: case FLAGS.decimal | FLAGS.unsigned | FLAGS.i8: case FLAGS.decimal | FLAGS.unsigned | FLAGS.i16: case FLAGS.decimal | FLAGS.unsigned | FLAGS.i32: result = TOK.uns32Literal; break; case FLAGS.decimal | FLAGS.i8: case FLAGS.decimal | FLAGS.i16: case FLAGS.decimal | FLAGS.i32: result = TOK.int32Literal; break; case FLAGS.octalhex | FLAGS.i64: case FLAGS.octalhex | FLAGS.unsigned | FLAGS.i64: case FLAGS.decimal | FLAGS.unsigned | FLAGS.i64: result = TOK.uns64Literal; break; case FLAGS.decimal | FLAGS.i64: result = TOK.int64Literal; break; default: debug printf("%x\n",flags); assert(0); } return result; } /************************************** * Read in characters, converting them to real. * Bugs: * Exponent overflow not detected. * Too much requested precision is not detected. */ private TOK inreal(Token* t) { //printf("Lexer::inreal()\n"); debug { assert(*p == '.' || isdigit(*p)); } bool isWellformedString = true; stringbuffer.setsize(0); auto pstart = p; bool hex = false; dchar c = *p++; // Leading '0x' if (c == '0') { c = *p++; if (c == 'x' || c == 'X') { hex = true; c = *p++; } } // Digits to left of '.' while (1) { if (c == '.') { c = *p++; break; } if (isdigit(c) || (hex && isxdigit(c)) || c == '_') { c = *p++; continue; } break; } // Digits to right of '.' while (1) { if (isdigit(c) || (hex && isxdigit(c)) || c == '_') { c = *p++; continue; } break; } if (c == 'e' || c == 'E' || (hex && (c == 'p' || c == 'P'))) { c = *p++; if (c == '-' || c == '+') { c = *p++; } bool anyexp = false; while (1) { if (isdigit(c)) { anyexp = true; c = *p++; continue; } if (c == '_') { if (Ccompile) error("embedded `_` in numeric literals not allowed"); c = *p++; continue; } if (!anyexp) { error("missing exponent"); isWellformedString = false; } break; } } else if (hex) { error("exponent required for hex float"); isWellformedString = false; } --p; while (pstart < p) { if (*pstart != '_') stringbuffer.writeByte(*pstart); ++pstart; } stringbuffer.writeByte(0); auto sbufptr = cast(const(char)*)stringbuffer[].ptr; TOK result; bool isOutOfRange = false; t.floatvalue = (isWellformedString ? CTFloat.parse(sbufptr, isOutOfRange) : CTFloat.zero); bool imaginary = false; if (*p == 'i' && Ccompile) { ++p; imaginary = true; } switch (*p) { case 'F': case 'f': if (isWellformedString && !isOutOfRange) isOutOfRange = Port.isFloat32LiteralOutOfRange(sbufptr); result = TOK.float32Literal; p++; break; default: if (isWellformedString && !isOutOfRange) isOutOfRange = Port.isFloat64LiteralOutOfRange(sbufptr); result = TOK.float64Literal; break; case 'l': if (!Ccompile) error("use 'L' suffix instead of 'l'"); goto case 'L'; case 'L': ++p; version (IN_LLVM) { /* *always* map C `long double` literals to D `real` ones */ } else { if (Ccompile && long_doublesize == 8) goto default; } result = TOK.float80Literal; break; } if ((*p == 'i' || *p == 'I') && !Ccompile) { if (*p == 'I') error("use 'i' suffix instead of 'I'"); p++; imaginary = true; } if (imaginary) { switch (result) { case TOK.float32Literal: result = TOK.imaginary32Literal; break; case TOK.float64Literal: result = TOK.imaginary64Literal; break; case TOK.float80Literal: result = TOK.imaginary80Literal; break; default: break; } } const isLong = (result == TOK.float80Literal || result == TOK.imaginary80Literal); if (isOutOfRange && !isLong && (!Ccompile || hex)) { /* C11 6.4.4.2 doesn't actually care if it is not representable if it is not hex */ const char* suffix = result == TOK.float32Literal ? "f" : result == TOK.float80Literal ? "L" : ""; const char* type = [TOK.float32Literal: "`float`".ptr, TOK.float64Literal: "`double`".ptr, TOK.float80Literal: "`real` for the current target".ptr][result]; error(scanloc, "number `%s%s` is not representable as a %s", sbufptr, suffix, type); const char* extra = result == TOK.float64Literal ? "`real` literals can be written using the `L` suffix. " : ""; eSink.errorSupplemental(scanloc, "%shttps://dlang.org/spec/lex.html#floatliteral", extra); } debug { switch (result) { case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: break; default: assert(0); } } return result; } final Loc loc() @nogc { scanloc.charnum = cast(ushort)(1 + p - line); version (LocOffset) scanloc.fileOffset = cast(uint)(p - base); return scanloc; } void error(T...)(const(char)* format, T args) { eSink.error(token.loc, format, args); } void error(T...)(const ref Loc loc, const(char)* format, T args) { eSink.error(loc, format, args); } void errorSupplemental(T...)(const(char)* format, T args) { eSink.errorSupplemental(token.loc, format, args); } void deprecation(T...)(const ref Loc loc, const(char)* format, T args) { eSink.deprecation(loc, format, args); } void warning(T...)(const ref Loc loc, const(char)* format, T args) { eSink.warning(loc, format, args); } void deprecation(T...)(const(char)* format, T args) { eSink.deprecation(token.loc, format, args); } void deprecationSupplemental(T...)(const(char)* format, T args) { eSink.deprecationSupplemental(token.loc, format, args); } /*************************************** * Parse special token sequence: * Returns: * true if the special token sequence was handled * References: * https://dlang.org/spec/lex.html#special-token-sequence */ bool parseSpecialTokenSequence() { Token n; scan(&n); if (n.value == TOK.identifier) { if (n.ident == Id.line) { poundLine(n, false); return true; } else { const locx = loc(); // @@@DEPRECATED_2.103@@@ // Turn into an error in 2.113 if (inTokenStringConstant) deprecation(locx, "token string requires valid D tokens, not `#%s`", n.ident.toChars()); else error(locx, "C preprocessor directive `#%s` is not supported", n.ident.toChars()); } } else if (n.value == TOK.if_) { const locx = loc(); if (inTokenStringConstant) error(locx, "token string requires valid D tokens, not `#if`"); else error(locx, "C preprocessor directive `#if` is not supported, use `version` or `static if`"); } return false; } /********************************************* * Parse line/file preprocessor directive: * #line linnum [filespec] * Allow __LINE__ for linnum, and __FILE__ for filespec. * Accept linemarker format: * # linnum [filespec] {flags} * There can be zero or more flags, which are one of the digits 1..4, and * must be in ascending order. The flags are ignored. * Params: * tok = token we're on, which is linnum of linemarker * linemarker = true if line marker format and lexer is on linnum * References: * linemarker https://gcc.gnu.org/onlinedocs/gcc-11.1.0/cpp/Preprocessor-Output.html */ final void poundLine(ref Token tok, bool linemarker) { auto linnum = this.scanloc.linnum; const(char)* filespec = null; bool flags; if (!linemarker) scan(&tok); if (tok.value == TOK.int32Literal || tok.value == TOK.int64Literal) { const lin = cast(int)(tok.unsvalue); if (lin != tok.unsvalue) { error(tok.loc, "line number `%lld` out of range", cast(ulong)tok.unsvalue); skipToNextLine(); return; } else linnum = lin; } else if (tok.value == TOK.line) // #line __LINE__ { } else { error(tok.loc, "positive integer argument expected following `#line`"); if (tok.value != TOK.endOfLine) skipToNextLine(); return; } while (1) { scan(&tok); switch (tok.value) { case TOK.endOfFile: case TOK.endOfLine: if (!inTokenStringConstant) { this.scanloc.linnum = linnum; if (filespec) this.scanloc.filename = filespec; } return; case TOK.file: if (filespec || flags) goto Lerr; filespec = mem.xstrdup(scanloc.filename); continue; case TOK.string_: if (filespec || flags) goto Lerr; if (tok.ptr[0] != '"' || tok.postfix != 0) goto Lerr; filespec = tok.ustring; continue; case TOK.int32Literal: if (!filespec) goto Lerr; if (linemarker && tok.unsvalue >= 1 && tok.unsvalue <= 4) { flags = true; // linemarker flags seen continue; } goto Lerr; default: goto Lerr; } } Lerr: if (filespec is null) error(tok.loc, "invalid filename for `#line` directive"); else if (linemarker) error(tok.loc, "invalid flag for line marker directive"); else if (!Ccompile) error(tok.loc, "found `%s` when expecting new line following `#line` directive", tok.toChars()); if (tok.value != TOK.endOfLine) skipToNextLine(); } /*************************************** * Scan forward to start of next line. * Params: * defines = send characters to `defines` */ final void skipToNextLine(OutBuffer* defines = null) { while (1) { switch (*p) { case 0: case 0x1A: return; // do not advance p case '\n': ++p; break; case '\r': ++p; if (p[0] == '\n') ++p; break; default: if (defines) defines.writeByte(*p); // don't care about Unicode line endings for C else if (*p & 0x80) { const u = decodeUTF(); if (u == PS || u == LS) { ++p; break; } } ++p; continue; } break; } endOfLine(); tokenizeNewlines = false; } /******************************************** * Decode UTF character. * Issue error messages for invalid sequences. * Return decoded character, advance p to last character in UTF sequence. */ private uint decodeUTF() { string msg; auto result = decodeUTFpure(msg); if (msg) error(token.loc, "%.*s", cast(int)msg.length, msg.ptr); return result; } /******************************************** * Same as above, but the potential error message is stored to the * msg parameter instead of being issued. */ private pure uint decodeUTFpure(out string msg) { const s = p; assert(*s & 0x80); // Check length of remaining string up to 4 UTF-8 characters size_t len; for (len = 1; len < 4 && s[len]; len++) { } size_t idx = 0; dchar u; msg = utf_decodeChar(s[0 .. len], idx, u); p += idx - 1; if (!msg && isBidiControl(u)) msg = "Bidirectional control characters are disallowed for security reasons."; return u; } /*************************************************** * Parse doc comment embedded between t.ptr and p. * Remove trailing blanks and tabs from lines. * Replace all newlines with \n. * Remove leading comment character from each line. * Decide if it's a lineComment or a blockComment. * Append to previous one for this token. * * If newParagraph is true, an extra newline will be * added between adjoining doc comments. */ private void getDocComment(Token* t, uint lineComment, bool newParagraph) pure { /* ct tells us which kind of comment it is: '/', '*', or '+' */ const ct = t.ptr[2]; /* Start of comment text skips over / * *, / + +, or / / / */ const(char)* q = t.ptr + 3; // start of comment text const(char)* qend = p; if (ct == '*' || ct == '+') qend -= 2; /* Scan over initial row of ****'s or ++++'s or ////'s */ for (; q < qend; q++) { if (*q != ct) break; } /* Remove leading spaces until start of the comment */ int linestart = 0; if (ct == '/') { while (q < qend && (*q == ' ' || *q == '\t')) ++q; } else if (q < qend) { if (*q == '\r') { ++q; if (q < qend && *q == '\n') ++q; linestart = 1; } else if (*q == '\n') { ++q; linestart = 1; } } /* Remove trailing row of ****'s or ++++'s */ if (ct != '/') { for (; q < qend; qend--) { if (qend[-1] != ct) break; } } /* Comment is now [q .. qend]. * Canonicalize it into buf[]. */ OutBuffer buf; void trimTrailingWhitespace() { const s = buf[]; auto len = s.length; while (len && (s[len - 1] == ' ' || s[len - 1] == '\t')) --len; buf.setsize(len); } for (; q < qend; q++) { char c = *q; switch (c) { case '*': case '+': if (linestart && c == ct) { linestart = 0; /* Trim preceding whitespace up to preceding \n */ trimTrailingWhitespace(); continue; } break; case ' ': case '\t': break; case '\r': if (q[1] == '\n') continue; // skip the \r goto Lnewline; default: if (c == 226) { // If LS or PS if (q[1] == 128 && (q[2] == 168 || q[2] == 169)) { q += 2; goto Lnewline; } } linestart = 0; break; Lnewline: c = '\n'; // replace all newlines with \n goto case; case '\n': linestart = 1; /* Trim trailing whitespace */ trimTrailingWhitespace(); break; } buf.writeByte(c); } /* Trim trailing whitespace (if the last line does not have newline) */ trimTrailingWhitespace(); // Always end with a newline const s = buf[]; if (s.length == 0 || s[$ - 1] != '\n') buf.writeByte('\n'); // It's a line comment if the start of the doc comment comes // after other non-whitespace on the same line. auto dc = (lineComment && anyToken) ? &t.lineComment : &t.blockComment; // Combine with previous doc comment, if any if (*dc) { auto p = combineComments(*dc, buf[], newParagraph); *dc = p ? p[0 .. strlen(p)] : null; } else *dc = buf.extractSlice(true); } /******************************************** * Combine two document comments into one, * separated by an extra newline if newParagraph is true. */ static const(char)* combineComments(const(char)[] c1, const(char)[] c2, bool newParagraph) pure { //debug printf("Lexer::combineComments('%*.s', '%*.s', '%i')\n", cast(int) c1.length, c1.ptr, cast(int) c2.length, c2.ptr, newParagraph); const(int) newParagraphSize = newParagraph ? 1 : 0; // Size of the combining '\n' if (!c1) return c2.ptr; if (!c2) return c1.ptr; int insertNewLine = 0; if (c1.length && c1[$ - 1] != '\n') insertNewLine = 1; const retSize = c1.length + insertNewLine + newParagraphSize + c2.length; auto p = cast(char*)mem.xmalloc_noscan(retSize + 1); p[0 .. c1.length] = c1[]; if (insertNewLine) p[c1.length] = '\n'; if (newParagraph) p[c1.length + insertNewLine] = '\n'; p[retSize - c2.length .. retSize] = c2[]; p[retSize] = 0; return p; } /************************** * `p` should be at start of next line */ private void endOfLine() @nogc @safe { scanloc.linnum = scanloc.linnum + 1; line = p; } /**************************** * Print the tokens from the current `token` to the end, * while not advancing the parser forward. * Useful for debugging. */ void printRestOfTokens() { auto tk = &token; while (1) { printf("%s ", (*tk).toChars()); if (tk.value == TOK.endOfFile || tk.value == TOK.endOfLine) break; tk = peek(tk); } printf("\n"); } } /******************************* Unittest *****************************************/ unittest { fprintf(stderr, "Lexer.unittest %d\n", __LINE__); ErrorSink errorSink = new ErrorSinkStderr; void test(T)(string sequence, T expected, bool Ccompile = false) { auto p = cast(const(char)*)sequence.ptr; dchar c2; Lexer lexer = new Lexer(errorSink); assert(expected == lexer.escapeSequence(Loc.initial, p, Ccompile, c2)); assert(p == sequence.ptr + sequence.length); } test(`'`, '\''); test(`"`, '"'); test(`?`, '?'); test(`\`, '\\'); test(`0`, '\0'); test(`a`, '\a'); test(`b`, '\b'); test(`f`, '\f'); test(`n`, '\n'); test(`r`, '\r'); test(`t`, '\t'); test(`v`, '\v'); test(`x00`, 0x00); test(`xff`, 0xff); test(`xFF`, 0xff); test(`xa7`, 0xa7); test(`x3c`, 0x3c); test(`xe2`, 0xe2); test(`1`, '\1'); test(`42`, '\42'); test(`357`, '\357'); test(`u1234`, '\u1234'); test(`uf0e4`, '\uf0e4'); test(`U0001f603`, '\U0001f603'); test(`"`, '"'); test(`<`, '<'); test(`>`, '>'); } unittest { fprintf(stderr, "Lexer.unittest %d\n", __LINE__); static class ErrorSinkTest : ErrorSinkNull { nothrow: extern (C++): override: import core.stdc.stdio; import core.stdc.stdarg; string expected; string expectedSupplemental; bool gotError; void error(const ref Loc loc, const(char)* format, ...) { gotError = true; char[100] buffer = void; va_list ap; va_start(ap, format); auto actual = buffer[0 .. vsnprintf(buffer.ptr, buffer.length, format, ap)]; va_end(ap); assert(expected == actual); } void errorSupplemental(const ref Loc loc, const(char)* format, ...) { gotError = true; char[128] buffer = void; va_list ap; va_start(ap, format); auto actual = buffer[0 .. vsnprintf(buffer.ptr, buffer.length, format, ap)]; va_end(ap); assert(expectedSupplemental == actual); } } ErrorSinkTest errorSink = new ErrorSinkTest; void test2(string sequence, string[2] expectedError, dchar expectedReturnValue, uint expectedScanLength, bool Ccompile = false) { errorSink.expected = expectedError[0]; errorSink.expectedSupplemental = expectedError[1]; errorSink.gotError = false; auto p = cast(const(char)*)sequence.ptr; Lexer lexer = new Lexer(errorSink); dchar c2; auto actualReturnValue = lexer.escapeSequence(Loc.initial, p, Ccompile, c2); assert(errorSink.gotError); assert(expectedReturnValue == actualReturnValue); auto actualScanLength = p - sequence.ptr; assert(expectedScanLength == actualScanLength); } void test(string sequence, string expectedError, dchar expectedReturnValue, uint expectedScanLength, bool Ccompile = false) { test2(sequence, [expectedError, null], expectedReturnValue, expectedScanLength, Ccompile); } test("c", `undefined escape sequence \c`, 'c', 1); test("!", `undefined escape sequence \!`, '!', 1); test(""", `undefined escape sequence \&`, '&', 1, true); test("x1", `escape hex sequence has 1 hex digits instead of 2`, '\x01', 2); test("u1" , `escape hex sequence has 1 hex digits instead of 4`, 0x1, 2); test("u12" , `escape hex sequence has 2 hex digits instead of 4`, 0x12, 3); test("u123", `escape hex sequence has 3 hex digits instead of 4`, 0x123, 4); test("U0" , `escape hex sequence has 1 hex digits instead of 8`, 0x0, 2); test("U00" , `escape hex sequence has 2 hex digits instead of 8`, 0x00, 3); test("U000" , `escape hex sequence has 3 hex digits instead of 8`, 0x000, 4); test("U0000" , `escape hex sequence has 4 hex digits instead of 8`, 0x0000, 5); test("U0001f" , `escape hex sequence has 5 hex digits instead of 8`, 0x0001f, 6); test("U0001f6" , `escape hex sequence has 6 hex digits instead of 8`, 0x0001f6, 7); test("U0001f60", `escape hex sequence has 7 hex digits instead of 8`, 0x0001f60, 8); test("U00110000", `invalid UTF character \U00110000`, '?', 9); test("xg0" , `undefined escape hex sequence \xg`, 'g', 2); test("ug000" , `undefined escape hex sequence \ug`, 'g', 2); test("Ug0000000", `undefined escape hex sequence \Ug`, 'g', 2); test("&BAD;", `unnamed character entity &BAD;` , '?', 5); test(""", `unterminated named entity "`, '?', 5); test(""", `unterminated named entity "`, '?', 5); test("400", `escape octal sequence \400 is larger than \377`, 0x100, 3); test2("uD800", [`invalid UTF character \U0000d800`, `The code unit is a UTF-16 surrogate, is the escape UTF-16 not a Unicode code point?`], '?', 5); test2("uDFFF", [`invalid UTF character \U0000dfff`, `The code unit is a UTF-16 surrogate, is the escape UTF-16 not a Unicode code point?`], '?', 5); } unittest { fprintf(stderr, "Lexer.unittest %d\n", __LINE__); /* Not much here, just trying things out. */ string text = "int"; // We rely on the implicit null-terminator ErrorSink errorSink = new ErrorSinkStderr; scope Lexer lex1 = new Lexer(null, text.ptr, 0, text.length, false, false, errorSink, null); TOK tok; tok = lex1.nextToken(); //printf("tok == %s, %d, %d\n", Token::toChars(tok), tok, TOK.int32); assert(tok == TOK.int32); tok = lex1.nextToken(); assert(tok == TOK.endOfFile); tok = lex1.nextToken(); assert(tok == TOK.endOfFile); tok = lex1.nextToken(); assert(tok == TOK.endOfFile); } unittest { fprintf(stderr, "Lexer.unittest %d\n", __LINE__); // We don't want to see Lexer error output during these tests. ErrorSink errorSink = new ErrorSinkNull; // Test malformed input: even malformed input should end in a TOK.endOfFile. static immutable char[][] testcases = [ // Testcase must end with 0 or 0x1A. [0], // not malformed, but pathological ['\'', 0], ['\'', 0x1A], ['{', '{', 'q', '{', 0], [0xFF, 0], [0xFF, 0x80, 0], [0xFF, 0xFF, 0], [0xFF, 0xFF, 0], ['x', '"', 0x1A], ]; foreach (testcase; testcases) { scope Lexer lex2 = new Lexer(null, testcase.ptr, 0, testcase.length-1, false, false, errorSink, null); TOK tok = lex2.nextToken(); size_t iterations = 1; while ((tok != TOK.endOfFile) && (iterations++ < testcase.length)) { tok = lex2.nextToken(); } assert(tok == TOK.endOfFile); tok = lex2.nextToken(); assert(tok == TOK.endOfFile); } } ldc-1.40.0-src/dmd/declaration.h0000644000000000000000000010060314727557031015036 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/declaration.h */ #pragma once #include "dsymbol.h" #include "mtype.h" #include "objc.h" #include "tokens.h" class Expression; class Statement; class LabelDsymbol; class Initializer; class ForeachStatement; struct Ensure { Identifier *id; Statement *ensure; }; class FuncDeclaration; class StructDeclaration; struct IntRange; struct AttributeViolation; namespace dmd { bool functionSemantic(FuncDeclaration* fd); bool functionSemantic3(FuncDeclaration* fd); MATCH leastAsSpecialized(FuncDeclaration *f, FuncDeclaration *g, Identifiers *names); PURE isPure(FuncDeclaration *f); } //enum STC : ulong from astenums.d: #define STCundefined 0ULL #define STCstatic 1ULL /// `static` #define STCextern 2ULL /// `extern` #define STCconst 4ULL /// `const` #define STCfinal 8ULL /// `final` #define STCabstract 0x10ULL /// `abstract` #define STCparameter 0x20ULL /// is function parameter #define STCfield 0x40ULL /// is field of struct, union or class #define STCoverride 0x80ULL /// `override` #define STCauto 0x100ULL /// `auto` #define STCsynchronized 0x200ULL /// `synchronized` #define STCdeprecated 0x400ULL /// `deprecated` #define STCin 0x800ULL /// `in` parameter #define STCout 0x1000ULL /// `out` parameter #define STClazy 0x2000ULL /// `lazy` parameter #define STCforeach 0x4000ULL /// variable for foreach loop #define STCvariadic 0x8000ULL /// the `variadic` parameter in: T foo(T a, U b, V variadic...) #define STCconstscoperef 0x10000ULL /// when `in` means const|scope|ref #define STCtemplateparameter 0x20000ULL /// template parameter #define STCref 0x40000ULL /// `ref` #define STCscope 0x80000ULL /// `scope` #define STCscopeinferred 0x200000ULL /// `scope` has been inferred and should not be part of mangling, `scope` must also be set #define STCreturn 0x400000ULL /// 'return ref' or 'return scope' for function parameters #define STCreturnScope 0x800000ULL /// if `ref return scope` then resolve to `ref` and `return scope` #define STCreturninferred 0x1000000ULL /// `return` has been inferred and should not be part of mangling, `return` must also be set #define STCimmutable 0x2000000ULL /// `immutable` // 0x4000000ULL #define STCmanifest 0x8000000ULL /// manifest constant #define STCnodtor 0x10000000ULL /// do not run destructor #define STCnothrow 0x20000000ULL /// `nothrow` meaning never throws exceptions #define STCpure 0x40000000ULL /// `pure` function #define STCalias 0x100000000ULL /// `alias` parameter #define STCshared 0x200000000ULL /// accessible from multiple threads #define STCgshared 0x400000000ULL /// accessible from multiple threads, but not typed as `shared` #define STCwild 0x800000000ULL /// for wild type constructor #define STCproperty 0x1000000000ULL /// `@property` #define STCsafe 0x2000000000ULL /// `@safe` #define STCtrusted 0x4000000000ULL /// `@trusted` #define STCsystem 0x8000000000ULL /// `@system` #define STCctfe 0x10000000000ULL /// can be used in CTFE, even if it is static #define STCdisable 0x20000000000ULL /// for functions that are not callable #define STCresult 0x40000000000ULL /// for result variables passed to out contracts #define STCnodefaultctor 0x80000000000ULL /// must be set inside constructor #define STCtemp 0x100000000000ULL /// temporary variable #define STCrvalue 0x200000000000ULL /// force rvalue for variables #define STCnogc 0x400000000000ULL /// `@nogc` #define STCautoref 0x800000000000ULL /// Mark for the already deduced `auto ref` parameter #define STCinference 0x1000000000000ULL /// do attribute inference #define STCexptemp 0x2000000000000ULL /// temporary variable that has lifetime restricted to an expression #define STCfuture 0x4000000000000ULL /// introducing new base class function #define STClocal 0x8000000000000ULL /// do not forward (see dmd.dsymbol.ForwardingScopeDsymbol). #define STClive 0x10000000000000ULL /// function `@live` attribute #define STCregister 0x20000000000000ULL /// `register` storage class (ImportC) #define STCvolatile 0x40000000000000ULL /// destined for volatile in the back end #define STC_TYPECTOR (STCconst | STCimmutable | STCshared | STCwild) #define STC_FUNCATTR (STCref | STCnothrow | STCnogc | STCpure | STCproperty | STCsafe | STCtrusted | STCsystem) /**************************************************************/ class Declaration : public Dsymbol { public: Type *type; Type *originalType; // before semantic analysis StorageClass storage_class; Visibility visibility; LINK _linkage; // may be `LINK::system`; use `resolvedLinkage()` to resolve it short inuse; // used to detect cycles uint8_t adFlags; DString mangleOverride; // overridden symbol with pragma(mangle, "...") const char *kind() const override; uinteger_t size(const Loc &loc) override final; bool isStatic() const { return (storage_class & STCstatic) != 0; } LINK resolvedLinkage() const; // returns the linkage, resolving the target-specific `System` one virtual bool isDelete(); virtual bool isDataseg(); virtual bool isThreadlocal(); virtual bool isCodeseg() const; bool isFinal() const { return (storage_class & STCfinal) != 0; } virtual bool isAbstract() { return (storage_class & STCabstract) != 0; } bool isConst() const { return (storage_class & STCconst) != 0; } bool isImmutable() const { return (storage_class & STCimmutable) != 0; } bool isWild() const { return (storage_class & STCwild) != 0; } bool isAuto() const { return (storage_class & STCauto) != 0; } bool isScope() const { return (storage_class & STCscope) != 0; } bool isReturn() const { return (storage_class & STCreturn) != 0; } bool isSynchronized() const { return (storage_class & STCsynchronized) != 0; } bool isParameter() const { return (storage_class & STCparameter) != 0; } bool isDeprecated() const override final { return (storage_class & STCdeprecated) != 0; } bool isOverride() const { return (storage_class & STCoverride) != 0; } bool isResult() const { return (storage_class & STCresult) != 0; } bool isField() const { return (storage_class & STCfield) != 0; } bool isIn() const { return (storage_class & STCin) != 0; } bool isOut() const { return (storage_class & STCout) != 0; } bool isRef() const { return (storage_class & STCref) != 0; } bool isReference() const { return (storage_class & (STCref | STCout)) != 0; } bool isFuture() const { return (storage_class & STCfuture) != 0; } Visibility visible() override final; Declaration *isDeclaration() override final { return this; } void accept(Visitor *v) override { v->visit(this); } }; /**************************************************************/ class TupleDeclaration final : public Declaration { public: Objects *objects; TypeTuple *tupletype; // !=NULL if this is a type tuple d_bool isexp; // true: expression tuple d_bool building; // it's growing in AliasAssign semantic TupleDeclaration *syntaxCopy(Dsymbol *) override; const char *kind() const override; Type *getType() override; Dsymbol *toAlias2() override; bool needThis() override; TupleDeclaration *isTupleDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } #if IN_LLVM void foreachVar(Visitor *v); #endif }; /**************************************************************/ class AliasDeclaration final : public Declaration { public: Dsymbol *aliassym; Dsymbol *overnext; // next in overload list Dsymbol *_import; // !=NULL if unresolved internal alias for selective import static AliasDeclaration *create(const Loc &loc, Identifier *id, Type *type); AliasDeclaration *syntaxCopy(Dsymbol *) override; bool overloadInsert(Dsymbol *s) override; const char *kind() const override; Type *getType() override; Dsymbol *toAlias() override; Dsymbol *toAlias2() override; bool isOverloadable() const override; AliasDeclaration *isAliasDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; /**************************************************************/ class OverDeclaration final : public Declaration { public: Dsymbol *overnext; // next in overload list Dsymbol *aliassym; const char *kind() const override; bool equals(const RootObject * const o) const override; bool overloadInsert(Dsymbol *s) override; Dsymbol *toAlias() override; Dsymbol *isUnique(); bool isOverloadable() const override; OverDeclaration *isOverDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; /**************************************************************/ class VarDeclaration : public Declaration { public: Initializer *_init; FuncDeclarations nestedrefs; // referenced by these lexically nested functions TupleDeclaration *aliasTuple; // if `this` is really a tuple of declarations VarDeclaration *lastVar; // Linked list of variables for goto-skips-init detection Expression *edtor; // if !=NULL, does the destruction of the variable IntRange *range; // if !NULL, the variable is known to be within the range VarDeclarations *maybes; // STCmaybescope variables that are assigned to this STCmaybescope variable unsigned endlinnum; // line number of end of scope that this var lives in unsigned offset; unsigned sequenceNumber; // order the variables are declared structalign_t alignment; // When interpreting, these point to the value (NULL if value not determinable) // The index of this variable on the CTFE stack, ~0u if not allocated unsigned ctfeAdrOnStack; private: uint32_t bitFields; public: int8_t canassign; // // it can be assigned to uint8_t isdataseg; // private data for isDataseg bool isargptr() const; // if parameter that _argptr points to bool isargptr(bool v); bool ctorinit() const; // it has been initialized in a ctor bool ctorinit(bool v); bool iscatchvar() const; // this is the exception object variable in catch() clause bool iscatchvar(bool v); bool isowner() const; // this is an Owner, despite it being `scope` bool isowner(bool v); bool setInCtorOnly() const; // field can only be set in a constructor, as it is const or immutable bool setInCtorOnly(bool v); bool onstack() const; // it is a class that was allocated on the stack bool onstack(bool v); #if IN_LLVM bool onstackWithMatchingDynType() const; // and dynamic type is equivalent to static type bool onstackWithMatchingDynType(bool v); #endif bool overlapped() const; // if it is a field and has overlapping bool overlapped(bool v); bool overlapUnsafe() const; // if it is an overlapping field and the overlaps are unsafe bool overlapUnsafe(bool v); bool maybeScope() const; // allow inferring 'scope' for this variable bool maybeScope(bool v); bool doNotInferReturn() const; // do not infer 'return' for this variable bool doNotInferReturn(bool v); bool isArgDtorVar() const; // temporary created to handle scope destruction of a function argument bool isArgDtorVar(bool v); bool isCmacro() const; // if a C macro turned into a C variable bool isCmacro(bool v); #if MARS bool inClosure() const; // is inserted into a GC allocated closure bool inClosure(bool v); bool inAlignSection() const; // is inserted into aligned section on stack bool inAlignSection(bool v); #endif bool systemInferred() const; bool systemInferred(bool v); static VarDeclaration *create(const Loc &loc, Type *t, Identifier *id, Initializer *init, StorageClass storage_class = STCundefined); VarDeclaration *syntaxCopy(Dsymbol *) override; const char *kind() const override; AggregateDeclaration *isThis() override final; bool needThis() override final; bool isExport() const override final; bool isImportedSymbol() const override final; bool isCtorinit() const; bool isDataseg() override final; bool isThreadlocal() override final; bool isCTFE(); bool isOverlappedWith(VarDeclaration *v); bool hasPointers() override final; bool canTakeAddressOf(); bool needsScopeDtor(); void checkCtorConstInit() override final; Dsymbol *toAlias() override final; // Eliminate need for dynamic_cast VarDeclaration *isVarDeclaration() override final { return (VarDeclaration *)this; } void accept(Visitor *v) override { v->visit(this); } }; /**************************************************************/ class BitFieldDeclaration : public VarDeclaration { public: Expression *width; unsigned fieldWidth; unsigned bitOffset; BitFieldDeclaration *syntaxCopy(Dsymbol *) override; BitFieldDeclaration *isBitFieldDeclaration() override final { return this; } void accept(Visitor *v) override { v->visit(this); } }; /**************************************************************/ // This is a shell around a back end symbol class SymbolDeclaration final : public Declaration { public: AggregateDeclaration *dsym; // Eliminate need for dynamic_cast SymbolDeclaration *isSymbolDeclaration() override { return (SymbolDeclaration *)this; } void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoDeclaration : public VarDeclaration { public: Type *tinfo; static TypeInfoDeclaration *create(Type *tinfo); TypeInfoDeclaration *syntaxCopy(Dsymbol *) override final; const char *toChars() const override final; TypeInfoDeclaration *isTypeInfoDeclaration() override final { return this; } void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoStructDeclaration final : public TypeInfoDeclaration { public: static TypeInfoStructDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoClassDeclaration final : public TypeInfoDeclaration { public: static TypeInfoClassDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoInterfaceDeclaration final : public TypeInfoDeclaration { public: static TypeInfoInterfaceDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoPointerDeclaration final : public TypeInfoDeclaration { public: static TypeInfoPointerDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoArrayDeclaration final : public TypeInfoDeclaration { public: static TypeInfoArrayDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoStaticArrayDeclaration final : public TypeInfoDeclaration { public: static TypeInfoStaticArrayDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoAssociativeArrayDeclaration final : public TypeInfoDeclaration { public: static TypeInfoAssociativeArrayDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoEnumDeclaration final : public TypeInfoDeclaration { public: static TypeInfoEnumDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoFunctionDeclaration final : public TypeInfoDeclaration { public: static TypeInfoFunctionDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoDelegateDeclaration final : public TypeInfoDeclaration { public: static TypeInfoDelegateDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoTupleDeclaration final : public TypeInfoDeclaration { public: static TypeInfoTupleDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoConstDeclaration final : public TypeInfoDeclaration { public: static TypeInfoConstDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoInvariantDeclaration final : public TypeInfoDeclaration { public: static TypeInfoInvariantDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoSharedDeclaration final : public TypeInfoDeclaration { public: static TypeInfoSharedDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoWildDeclaration final : public TypeInfoDeclaration { public: static TypeInfoWildDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; class TypeInfoVectorDeclaration final : public TypeInfoDeclaration { public: static TypeInfoVectorDeclaration *create(Type *tinfo); void accept(Visitor *v) override { v->visit(this); } }; /**************************************************************/ class ThisDeclaration final : public VarDeclaration { public: ThisDeclaration *syntaxCopy(Dsymbol *) override; ThisDeclaration *isThisDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; enum class ILS : unsigned char { ILSuninitialized, // not computed yet ILSno, // cannot inline ILSyes // can inline }; /**************************************************************/ enum class BUILTIN : unsigned char { unknown = 255, /// not known if this is a builtin unimp = 0, /// this is not a builtin gcc, /// this is a GCC builtin llvm, /// this is an LLVM builtin sin, cos, tan, sqrt, fabs, ldexp, log, log2, log10, exp, expm1, exp2, round, floor, ceil, trunc, copysign, pow, fmin, fmax, fma, isnan, isinfinity, isfinite, bsf, bsr, bswap, popcnt, yl2x, yl2xp1, toPrecFloat, toPrecDouble, toPrecReal // IN_LLVM: more for LDC... }; Expression *eval_builtin(const Loc &loc, FuncDeclaration *fd, Expressions *arguments); BUILTIN isBuiltin(FuncDeclaration *fd); struct ContractInfo; class FuncDeclaration : public Declaration { public: Statement *fbody; FuncDeclarations foverrides; // functions this function overrides private: ContractInfo *contracts; // contract information public: const char *mangleString; // mangled symbol created from mangleExact() #if IN_LLVM uint32_t priority; // true if overridden with the pragma(LDC_allow_inline); statement bool allowInlining; // true if set with the pragma(LDC_never_inline); statement bool neverInline; // Whether to emit instrumentation code if -fprofile-instr-generate is specified, // the value is set with pragma(LDC_profile_instr, true|false) bool emitInstrumentation; #endif VarDeclaration *vresult; // result variable for out contracts LabelDsymbol *returnLabel; // where the return goes void *isTypeIsolatedCache; // An AA on the D side to cache an expensive check result // used to prevent symbols in different // scopes from having the same name DsymbolTable *localsymtab; VarDeclaration *vthis; // 'this' parameter (member and nested) VarDeclaration *v_arguments; // '_arguments' parameter VarDeclaration *v_argptr; // '_argptr' variable VarDeclarations *parameters; // Array of VarDeclaration's for parameters DsymbolTable *labtab; // statement label symbol table Dsymbol *overnext; // next in overload list FuncDeclaration *overnext0; // next in overload list (only used during IFTI) Loc endloc; // location of closing curly bracket int vtblIndex; // for member functions, index into vtbl[] ILS inlineStatusStmt; ILS inlineStatusExp; PINLINE inlining; int inlineNest; // !=0 if nested inline // true if errors in semantic3 this function's frame ptr ForeachStatement *fes; // if foreach body, this is the foreach BaseClass* interfaceVirtual; // if virtual, but only appears in interface vtbl[] // if !=NULL, then this is the type // of the 'introducing' function // this one is overriding Type *tintro; StorageClass storage_class2; // storage class for template onemember's // Things that should really go into Scope // 1 if there's a return exp; statement // 2 if there's a throw statement // 4 if there's an assert(0) // 8 if there's inline asm // 16 if there are multiple return statements // IN_LLVM: 32 if there's DMD-style inline asm int hasReturnExp; VarDeclaration *nrvo_var; // variable to replace with shidden #if !IN_LLVM Symbol *shidden; // hidden pointer passed to function #endif ReturnStatements *returns; GotoStatements *gotos; // Gotos with forward references // set if this is a known, builtin function we can evaluate at compile time BUILTIN builtin; // set if someone took the address of this function int tookAddressOf; d_bool requiresClosure; // this function needs a closure // local variables in this function which are referenced by nested functions VarDeclarations closureVars; /** Outer variables which are referenced by this nested function * (the inverse of closureVars) */ VarDeclarations outerVars; // Sibling nested functions which called this one FuncDeclarations siblingCallers; FuncDeclarations *inlinedNestedCallees; AttributeViolation* safetyViolation; AttributeViolation* nogcViolation; AttributeViolation* pureViolation; AttributeViolation* nothrowViolation; // Formerly FUNCFLAGS uint32_t flags; bool purityInprocess() const; bool purityInprocess(bool v); bool safetyInprocess() const; bool safetyInprocess(bool v); bool nothrowInprocess() const; bool nothrowInprocess(bool v); bool nogcInprocess() const; bool nogcInprocess(bool v); bool returnInprocess() const; bool returnInprocess(bool v); bool inlineScanned() const; bool inlineScanned(bool v); bool inferScope() const; bool inferScope(bool v); bool hasCatches() const; bool hasCatches(bool v); bool skipCodegen() const; bool skipCodegen(bool v); bool printf() const; bool printf(bool v); bool scanf() const; bool scanf(bool v); bool noreturn() const; bool noreturn(bool v); bool isNRVO() const; bool isNRVO(bool v); bool isNaked() const; bool isNaked(bool v); bool isGenerated() const; bool isGenerated(bool v); bool isIntroducing() const; bool isIntroducing(bool v); bool hasSemantic3Errors() const; bool hasSemantic3Errors(bool v); bool hasNoEH() const; bool hasNoEH(bool v); bool inferRetType() const; bool inferRetType(bool v); bool hasDualContext() const; bool hasDualContext(bool v); bool hasAlwaysInlines() const; bool hasAlwaysInlines(bool v); bool isCrtCtor() const; bool isCrtCtor(bool v); bool isCrtDtor() const; bool isCrtDtor(bool v); bool dllImport() const; bool dllImport(bool v); bool dllExport() const; bool dllExport(bool v); // Data for a function declaration that is needed for the Objective-C // integration. ObjcFuncDeclaration objc; static FuncDeclaration *create(const Loc &loc, const Loc &endloc, Identifier *id, StorageClass storage_class, Type *type, bool noreturn = false); FuncDeclaration *syntaxCopy(Dsymbol *) override; Statements *frequires(); Ensures *fensures(); Statement *frequire(); Statement *fensure(); FuncDeclaration *fdrequire(); FuncDeclaration *fdensure(); Expressions *fdrequireParams(); Expressions *fdensureParams(); Statements *frequires(Statements *frs); Ensures *fensures(Statements *fes); Statement *frequire(Statement *fr); Statement *fensure(Statement *fe); FuncDeclaration *fdrequire(FuncDeclaration *fdr); FuncDeclaration *fdensure(FuncDeclaration *fde); Expressions *fdrequireParams(Expressions *fdrp); Expressions *fdensureParams(Expressions *fdep); bool equals(const RootObject * const o) const override final; bool overloadInsert(Dsymbol *s) override; bool inUnittest(); LabelDsymbol *searchLabel(Identifier *ident, const Loc &loc); const char *toPrettyChars(bool QualifyTypes = false) override; const char *toFullSignature(); // for diagnostics, e.g. 'int foo(int x, int y) pure' bool isMain() const; bool isCMain() const; bool isWinMain() const; bool isDllMain() const; bool isExport() const override final; bool isImportedSymbol() const override final; bool isCodeseg() const override final; bool isOverloadable() const override final; bool isAbstract() override final; bool isSafe(); bool isTrusted(); bool isNogc(); virtual bool isNested() const; AggregateDeclaration *isThis() override; bool needThis() override final; bool isVirtualMethod(); virtual bool isVirtual() const; bool isFinalFunc() const; virtual bool addPreInvariant(); virtual bool addPostInvariant(); const char *kind() const override; bool isUnique(); bool needsClosure(); bool checkClosure(); bool hasNestedFrameRefs(); ParameterList getParameterList(); static FuncDeclaration *genCfunc(Parameters *args, Type *treturn, const char *name, StorageClass stc=0); static FuncDeclaration *genCfunc(Parameters *args, Type *treturn, Identifier *id, StorageClass stc=0); FuncDeclaration *isFuncDeclaration() override final { return this; } virtual FuncDeclaration *toAliasFunc() { return this; } void accept(Visitor *v) override { v->visit(this); } }; class FuncAliasDeclaration final : public FuncDeclaration { public: FuncDeclaration *funcalias; d_bool hasOverloads; FuncAliasDeclaration *isFuncAliasDeclaration() override { return this; } const char *kind() const override; FuncDeclaration *toAliasFunc() override; void accept(Visitor *v) override { v->visit(this); } }; class FuncLiteralDeclaration final : public FuncDeclaration { public: TOK tok; // TOKfunction or TOKdelegate Type *treq; // target of return type inference // backend d_bool deferToObj; FuncLiteralDeclaration *syntaxCopy(Dsymbol *) override; bool isNested() const override; AggregateDeclaration *isThis() override; bool isVirtual() const override; bool addPreInvariant() override; bool addPostInvariant() override; FuncLiteralDeclaration *isFuncLiteralDeclaration() override { return this; } const char *kind() const override; const char *toPrettyChars(bool QualifyTypes = false) override; void accept(Visitor *v) override { v->visit(this); } }; class CtorDeclaration final : public FuncDeclaration { public: d_bool isCpCtor; CtorDeclaration *syntaxCopy(Dsymbol *) override; const char *kind() const override; const char *toChars() const override; bool isVirtual() const override; bool addPreInvariant() override; bool addPostInvariant() override; CtorDeclaration *isCtorDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class PostBlitDeclaration final : public FuncDeclaration { public: PostBlitDeclaration *syntaxCopy(Dsymbol *) override; bool isVirtual() const override; bool addPreInvariant() override; bool addPostInvariant() override; bool overloadInsert(Dsymbol *s) override; PostBlitDeclaration *isPostBlitDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class DtorDeclaration final : public FuncDeclaration { public: DtorDeclaration *syntaxCopy(Dsymbol *) override; const char *kind() const override; const char *toChars() const override; bool isVirtual() const override; bool addPreInvariant() override; bool addPostInvariant() override; bool overloadInsert(Dsymbol *s) override; DtorDeclaration *isDtorDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class StaticCtorDeclaration : public FuncDeclaration { public: StaticCtorDeclaration *syntaxCopy(Dsymbol *) override; AggregateDeclaration *isThis() override final; bool isVirtual() const override final; bool addPreInvariant() override final; bool addPostInvariant() override final; bool hasStaticCtorOrDtor() override final; StaticCtorDeclaration *isStaticCtorDeclaration() override final { return this; } void accept(Visitor *v) override { v->visit(this); } }; class SharedStaticCtorDeclaration final : public StaticCtorDeclaration { public: bool standalone; SharedStaticCtorDeclaration *syntaxCopy(Dsymbol *) override; SharedStaticCtorDeclaration *isSharedStaticCtorDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class StaticDtorDeclaration : public FuncDeclaration { public: VarDeclaration *vgate; // 'gate' variable StaticDtorDeclaration *syntaxCopy(Dsymbol *) override; AggregateDeclaration *isThis() override final; bool isVirtual() const override final; bool hasStaticCtorOrDtor() override final; bool addPreInvariant() override final; bool addPostInvariant() override final; StaticDtorDeclaration *isStaticDtorDeclaration() override final { return this; } void accept(Visitor *v) override { v->visit(this); } }; class SharedStaticDtorDeclaration final : public StaticDtorDeclaration { public: SharedStaticDtorDeclaration *syntaxCopy(Dsymbol *) override; SharedStaticDtorDeclaration *isSharedStaticDtorDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class InvariantDeclaration final : public FuncDeclaration { public: InvariantDeclaration *syntaxCopy(Dsymbol *) override; bool isVirtual() const override; bool addPreInvariant() override; bool addPostInvariant() override; InvariantDeclaration *isInvariantDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class UnitTestDeclaration final : public FuncDeclaration { public: char *codedoc; /** For documented unittest. */ // toObjFile() these nested functions after this one FuncDeclarations deferredNested; UnitTestDeclaration *syntaxCopy(Dsymbol *) override; AggregateDeclaration *isThis() override; bool isVirtual() const override; bool addPreInvariant() override; bool addPostInvariant() override; UnitTestDeclaration *isUnitTestDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class NewDeclaration final : public FuncDeclaration { public: NewDeclaration *syntaxCopy(Dsymbol *) override; const char *kind() const override; bool isVirtual() const override; bool addPreInvariant() override; bool addPostInvariant() override; NewDeclaration *isNewDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; ldc-1.40.0-src/dmd/templateparamsem.d0000644000000000000000000001425314727557031016113 0ustar rootroot/** * Semantic analysis of template parameters. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/templateparamsem.d, _templateparamsem.d) * Documentation: https://dlang.org/phobos/dmd_templateparamsem.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/templateparamsem.d */ module dmd.templateparamsem; import dmd.arraytypes; import dmd.dinterpret; import dmd.dsymbol; import dmd.dscope; import dmd.dtemplate; import dmd.globals; import dmd.location; import dmd.expression; import dmd.expressionsem; import dmd.rootobject; import dmd.mtype; import dmd.typesem; import dmd.visitor; /************************************************ * Performs semantic on TemplateParameter AST nodes. * * Params: * tp = element of `parameters` to be semantically analyzed * sc = context * parameters = array of `TemplateParameters` supplied to the `TemplateDeclaration` * Returns: * `true` if no errors */ bool tpsemantic(TemplateParameter tp, Scope* sc, TemplateParameters* parameters) { scope v = new TemplateParameterSemanticVisitor(sc, parameters); tp.accept(v); return v.result; } private extern (C++) final class TemplateParameterSemanticVisitor : Visitor { alias visit = Visitor.visit; Scope* sc; TemplateParameters* parameters; bool result; this(Scope* sc, TemplateParameters* parameters) scope @safe { this.sc = sc; this.parameters = parameters; } override void visit(TemplateTypeParameter ttp) { //printf("TemplateTypeParameter.semantic('%s')\n", ident.toChars()); if (ttp.specType && !reliesOnTident(ttp.specType, parameters)) { ttp.specType = ttp.specType.typeSemantic(ttp.loc, sc); } version (none) { // Don't do semantic() until instantiation if (ttp.defaultType) { ttp.defaultType = ttp.defaultType.typeSemantic(ttp.loc, sc); } } result = !(ttp.specType && isError(ttp.specType)); } override void visit(TemplateThisParameter ttp) { import dmd.errors; if (!sc.getStructClassScope()) error(ttp.loc, "cannot use `this` outside an aggregate type"); visit(cast(TemplateTypeParameter)ttp); } override void visit(TemplateValueParameter tvp) { tvp.valType = tvp.valType.typeSemantic(tvp.loc, sc); version (none) { // defer semantic analysis to arg match if (tvp.specValue) { Expression e = tvp.specValue; sc = sc.startCTFE(); e = e.semantic(sc); sc = sc.endCTFE(); e = e.implicitCastTo(sc, tvp.valType); e = e.ctfeInterpret(); if (e.op == EXP.int64 || e.op == EXP.float64 || e.op == EXP.complex80 || e.op == EXP.null_ || e.op == EXP.string_) tvp.specValue = e; } if (tvp.defaultValue) { Expression e = defaultValue; sc = sc.startCTFE(); e = e.semantic(sc); sc = sc.endCTFE(); e = e.implicitCastTo(sc, tvp.valType); e = e.ctfeInterpret(); if (e.op == EXP.int64) tvp.defaultValue = e; } } result = !isError(tvp.valType); } override void visit(TemplateAliasParameter tap) { if (tap.specType && !reliesOnTident(tap.specType, parameters)) { tap.specType = tap.specType.typeSemantic(tap.loc, sc); } tap.specAlias = aliasParameterSemantic(tap.loc, sc, tap.specAlias, parameters); version (none) { // Don't do semantic() until instantiation if (tap.defaultAlias) tap.defaultAlias = tap.defaultAlias.semantic(tap.loc, sc); } result = !(tap.specType && isError(tap.specType)) && !(tap.specAlias && isError(tap.specAlias)); } override void visit(TemplateTupleParameter ttp) { result = true; } } /*********************************************** * Support function for performing semantic analysis on `TemplateAliasParameter`. * * Params: * loc = location (for error messages) * sc = context * o = object to run semantic() on, the `TemplateAliasParameter`s `specAlias` or `defaultAlias` * parameters = array of `TemplateParameters` supplied to the `TemplateDeclaration` * Returns: * object resulting from running `semantic` on `o` */ RootObject aliasParameterSemantic(Loc loc, Scope* sc, RootObject o, TemplateParameters* parameters) { if (!o) return null; Expression ea = isExpression(o); RootObject eaCTFE() { sc = sc.startCTFE(); ea = ea.expressionSemantic(sc); sc = sc.endCTFE(); return ea.ctfeInterpret(); } Type ta = isType(o); if (ta && (!parameters || !reliesOnTident(ta, parameters))) { Dsymbol s = ta.toDsymbol(sc); if (s) return s; else if (TypeInstance ti = ta.isTypeInstance()) { Type t; const errors = global.errors; ta.resolve(loc, sc, ea, t, s); // if we had an error evaluating the symbol, suppress further errors if (!t && errors != global.errors) return Type.terror; // We might have something that looks like a type // but is actually an expression or a dsymbol // see https://issues.dlang.org/show_bug.cgi?id=16472 if (t) return t.typeSemantic(loc, sc); else if (ea) { return eaCTFE(); } else if (s) return s; else assert(0); } else return ta.typeSemantic(loc, sc); } else if (ea) return eaCTFE(); return o; } ldc-1.40.0-src/dmd/ast_node.d0000644000000000000000000000162614727557031014346 0ustar rootroot/** * Defines the base class for all nodes which are part of the AST. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/ast_node.d, _ast_node.d) * Documentation: https://dlang.org/phobos/dmd_ast_node.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/ast_node.d */ module dmd.ast_node; import dmd.rootobject : RootObject; import dmd.visitor : Visitor; /// The base class of all AST nodes. extern (C++) abstract class ASTNode : RootObject { /** * Visits this AST node using the given visitor. * * Params: * v = the visitor to use when visiting this node */ abstract void accept(Visitor v); } ldc-1.40.0-src/dmd/init.d0000644000000000000000000002612614727557031013517 0ustar rootroot/** * Defines initializers of variables, e.g. the array literal in `int[3] x = [0, 1, 2]`. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/init.d, _init.d) * Documentation: https://dlang.org/phobos/dmd_init.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/init.d */ module dmd.init; import core.stdc.stdio; import dmd.arraytypes; import dmd.astenums; import dmd.ast_node; import dmd.expression; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.rootobject; import dmd.visitor; enum NeedInterpret : int { INITnointerpret, INITinterpret, } alias INITnointerpret = NeedInterpret.INITnointerpret; alias INITinterpret = NeedInterpret.INITinterpret; /*********************************************************** */ extern (C++) class Initializer : ASTNode { Loc loc; InitKind kind; override DYNCAST dyncast() const { return DYNCAST.initializer; } extern (D) this(const ref Loc loc, InitKind kind) @safe { this.loc = loc; this.kind = kind; } final inout(ErrorInitializer) isErrorInitializer() inout @nogc nothrow pure @trusted { // Use void* cast to skip dynamic casting call return kind == InitKind.error ? cast(inout ErrorInitializer)cast(void*)this : null; } final inout(VoidInitializer) isVoidInitializer() inout @nogc nothrow pure @trusted { return kind == InitKind.void_ ? cast(inout VoidInitializer)cast(void*)this : null; } final inout(DefaultInitializer) isDefaultInitializer() inout @nogc nothrow pure @trusted { return kind == InitKind.default_ ? cast(inout DefaultInitializer)cast(void*)this : null; } final inout(StructInitializer) isStructInitializer() inout @nogc nothrow pure @trusted { return kind == InitKind.struct_ ? cast(inout StructInitializer)cast(void*)this : null; } final inout(ArrayInitializer) isArrayInitializer() inout @nogc nothrow pure @trusted { return kind == InitKind.array ? cast(inout ArrayInitializer)cast(void*)this : null; } final inout(ExpInitializer) isExpInitializer() inout @nogc nothrow pure @trusted { return kind == InitKind.exp ? cast(inout ExpInitializer)cast(void*)this : null; } final inout(CInitializer) isCInitializer() inout @nogc nothrow pure @trusted { return kind == InitKind.C_ ? cast(inout CInitializer)cast(void*)this : null; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class VoidInitializer : Initializer { Type type; // type that this will initialize to extern (D) this(const ref Loc loc) @safe { super(loc, InitKind.void_); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * The C23 default initializer `{ }` */ extern (C++) final class DefaultInitializer : Initializer { Type type; // type that this will initialize to extern (D) this(const ref Loc loc) @safe { super(loc, InitKind.default_); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class ErrorInitializer : Initializer { extern (D) this() @safe { super(Loc.initial, InitKind.error); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class StructInitializer : Initializer { Identifiers field; // of Identifier *'s Initializers value; // parallel array of Initializer *'s extern (D) this(const ref Loc loc) { super(loc, InitKind.struct_); } extern (D) void addInit(Identifier field, Initializer value) { //printf("StructInitializer::addInit(field = %p, value = %p)\n", field, value); this.field.push(field); this.value.push(value); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class ArrayInitializer : Initializer { Expressions index; // indices Initializers value; // of Initializer *'s uint dim; // length of array being initialized Type type; // type that array will be used to initialize bool sem; // true if semantic() is run bool isCarray; // C array semantics extern (D) this(const ref Loc loc) { super(loc, InitKind.array); } extern (D) void addInit(Expression index, Initializer value) { this.index.push(index); this.value.push(value); dim = 0; type = null; } bool isAssociativeArray() const pure { foreach (idx; index) { if (idx) return true; } return false; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class ExpInitializer : Initializer { bool expandTuples; Expression exp; extern (D) this(const ref Loc loc, Expression exp) @safe { super(loc, InitKind.exp); this.exp = exp; } override void accept(Visitor v) { v.visit(this); } } /********************************************* * Holds the `designator` for C initializers */ struct Designator { Expression exp; /// [ constant-expression ] Identifier ident; /// . identifier this(Expression exp) @safe { this.exp = exp; } this(Identifier ident) @safe { this.ident = ident; } } /********************************************* * Holds the `designation (opt) initializer` for C initializers */ struct DesigInit { Designators* designatorList; /// designation (opt) Initializer initializer; /// initializer } /******************************** * C11 6.7.9 Initialization * Represents the C initializer-list */ extern (C++) final class CInitializer : Initializer { DesigInits initializerList; /// initializer-list Type type; /// type that array will be used to initialize bool sem; /// true if semantic() is run extern (D) this(const ref Loc loc) { super(loc, InitKind.C_); } override void accept(Visitor v) { v.visit(this); } } /**************************************** * Copy the AST for Initializer. * Params: * inx = Initializer AST to copy * Returns: * the copy */ Initializer syntaxCopy(Initializer inx) { static Initializer visitVoid(VoidInitializer vi) { return new VoidInitializer(vi.loc); } static Initializer visitDefault(DefaultInitializer vi) { return new DefaultInitializer(vi.loc); } static Initializer visitError(ErrorInitializer vi) { return vi; } static Initializer visitExp(ExpInitializer vi) { return new ExpInitializer(vi.loc, vi.exp.syntaxCopy()); } static Initializer visitStruct(StructInitializer vi) { auto si = new StructInitializer(vi.loc); assert(vi.field.length == vi.value.length); si.field.setDim(vi.field.length); si.value.setDim(vi.value.length); foreach (const i; 0 .. vi.field.length) { si.field[i] = vi.field[i]; si.value[i] = vi.value[i].syntaxCopy(); } return si; } static Initializer visitArray(ArrayInitializer vi) { auto ai = new ArrayInitializer(vi.loc); assert(vi.index.length == vi.value.length); ai.index.setDim(vi.index.length); ai.value.setDim(vi.value.length); foreach (const i; 0 .. vi.value.length) { ai.index[i] = vi.index[i] ? vi.index[i].syntaxCopy() : null; ai.value[i] = vi.value[i].syntaxCopy(); } return ai; } static Initializer visitC(CInitializer vi) { auto ci = new CInitializer(vi.loc); ci.initializerList.setDim(vi.initializerList.length); foreach (const i; 0 .. vi.initializerList.length) { DesigInit* cdi = &ci.initializerList[i]; DesigInit* vdi = &ci.initializerList[i]; cdi.initializer = vdi.initializer.syntaxCopy(); if (vdi.designatorList) { cdi.designatorList = new Designators(); cdi.designatorList.setDim(vdi.designatorList.length); foreach (const j; 0 .. vdi.designatorList.length) { Designator* cdid = &(*cdi.designatorList)[j]; Designator* vdid = &(*vdi.designatorList)[j]; cdid.exp = vdid.exp ? vdid.exp.syntaxCopy() : null; cdid.ident = vdid.ident; } } } return ci; } mixin VisitInitializer!Initializer visit; return visit.VisitInitializer(inx); } /*********************************************************** * Visit each Initializer in init. Call a function visit%s(init) for * each node, where %s is the op of the node. Otherwise call visitDefault(init) * for that node. If the visit function returns R.init, continue * visiting each node, otherwise return the value of R. * Params: * Result = return type * init = Initializer tree to traverse * Returns: * Result.init for continue, value of type Result for early exit */ mixin template VisitInitializer(Result) { Result VisitInitializer(Initializer init) { final switch (init.kind) { case InitKind.void_: mixin(visitCase("Void")); break; case InitKind.default_: mixin(visitCase("Default")); break; case InitKind.error: mixin(visitCase("Error")); break; case InitKind.struct_: mixin(visitCase("Struct")); break; case InitKind.array: mixin(visitCase("Array")); break; case InitKind.exp: mixin(visitCase("Exp")); break; case InitKind.C_: mixin(visitCase("C")); break; } static if (is(Result == void)) { } else return Result.init; } } /**************************************** * CTFE-only helper function for VisitInitializer. * Params: * handler = string for the name of the visit handler * Returns: boilerplate code for a case */ string visitCase(string handler) pure @safe { if (__ctfe) { return " auto ix = init.is"~handler~"Initializer(); static if (is(Result == void)) visit"~handler~"(ix); else { Result r = visit"~handler~"(ix); if (r !is Result.init) return r; } "; } assert(0); } ldc-1.40.0-src/dmd/common/0000755000000000000000000000000014727557031013670 5ustar rootrootldc-1.40.0-src/dmd/common/outbuffer.d0000644000000000000000000006271714727557031016053 0ustar rootroot/** * An expandable buffer in which you can write text or binary data. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: Walter Bright, https://www.digitalmars.com * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/outbuffer.d, root/_outbuffer.d) * Documentation: https://dlang.org/phobos/dmd_root_outbuffer.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/outbuffer.d */ module dmd.common.outbuffer; import core.stdc.stdarg; import core.stdc.stdio; import core.stdc.string; import core.stdc.stdlib; nothrow: // In theory these functions should also restore errno, but we don't care because // we abort application on error anyway. extern (C) private pure @system @nogc nothrow { pragma(mangle, "malloc") void* pureMalloc(size_t); pragma(mangle, "realloc") void* pureRealloc(void* ptr, size_t size); pragma(mangle, "free") void pureFree(void* ptr); } debug { debug = stomp; // flush out dangling pointer problems by stomping on unused memory } /** `OutBuffer` is a write-only output stream of untyped data. It is backed up by a contiguous array or a memory-mapped file. */ struct OutBuffer { import dmd.common.file : FileMapping, touchFile, writeFile; // IMPORTANT: PLEASE KEEP STATE AND DESTRUCTOR IN SYNC WITH DEFINITION IN ./outbuffer.h. // state { private ubyte[] data; private size_t offset; private bool notlinehead; /// File mapping, if any. Use a pointer for ABI compatibility with the C++ counterpart. /// If the pointer is non-null the store is a memory-mapped file, otherwise the store is RAM. private FileMapping!ubyte* fileMapping; /// Whether to indent bool doindent; /// Whether to indent by 4 spaces or by tabs; bool spaces; /// Current indent level int level; // state } nothrow: /** Construct given size. */ this(size_t initialSize) nothrow @safe { reserve(initialSize); } /** Construct from filename. Will map the file into memory (or create it anew if necessary) and start writing at the beginning of it. Params: filename = zero-terminated name of file to map into memory */ @trusted this(const(char)* filename) { FileMapping!ubyte model; fileMapping = cast(FileMapping!ubyte*) malloc(model.sizeof); memcpy(fileMapping, &model, model.sizeof); fileMapping.__ctor(filename); //fileMapping = new FileMapping!ubyte(filename); data = (*fileMapping)[]; } /** Frees resources associated. */ extern (C++) void dtor() pure nothrow @trusted { if (fileMapping) { if (fileMapping.active) fileMapping.close(); } else { debug (stomp) memset(data.ptr, 0xFF, data.length); pureFree(data.ptr); } } /** Frees resources associated automatically. */ extern (C++) ~this() pure nothrow @trusted { dtor(); } /// For porting with ease from dmd.backend.outbuf.Outbuffer ubyte* buf() nothrow @system { return data.ptr; } /// For porting with ease from dmd.backend.outbuf.Outbuffer ubyte** bufptr() nothrow @system { static struct Array { size_t length; ubyte* ptr; } auto a = cast(Array*) &data; assert(a.length == data.length && a.ptr == data.ptr); return &a.ptr; } extern (C++) size_t length() const pure @nogc @safe nothrow { return offset; } /********************** * Transfer ownership of the allocated data to the caller. * Returns: * pointer to the allocated data */ extern (C++) char* extractData() pure nothrow @nogc @trusted { char* p = cast(char*)data.ptr; data = null; offset = 0; return p; } /** Releases all resources associated with `this` and resets it as an empty memory buffer. The config variables `notlinehead`, `doindent` etc. are not changed. */ extern (C++) void destroy() pure nothrow { dtor(); fileMapping = null; data = null; offset = 0; } /** Reserves `nbytes` bytes of additional memory (or file space) in advance. The resulting capacity is at least the previous length plus `nbytes`. Params: nbytes = the number of additional bytes to reserve */ extern (C++) void reserve(size_t nbytes) pure nothrow @trusted { //debug (stomp) printf("OutBuffer::reserve: size = %lld, offset = %lld, nbytes = %lld\n", data.length, offset, nbytes); const minSize = offset + nbytes; if (data.length >= minSize) return; /* Increase by factor of 1.5; round up to 16 bytes. * The odd formulation is so it will map onto single x86 LEA instruction. */ const size = ((minSize * 3 + 30) / 2) & ~15; if (fileMapping && fileMapping.active) { fileMapping.resize(size); data = (*fileMapping)[]; } else { debug (stomp) { auto p = cast(ubyte*) pureMalloc(size); p || assert(0, "OutBuffer: out of memory."); memcpy(p, data.ptr, offset); memset(data.ptr, 0xFF, data.length); // stomp old location pureFree(data.ptr); memset(p + offset, 0xff, size - offset); // stomp unused data } else { auto p = cast(ubyte*) pureRealloc(data.ptr, size); p || assert(0, "OutBuffer: out of memory."); memset(p + offset + nbytes, 0xff, size - offset - nbytes); } data = p[0 .. size]; } } /************************ * Shrink the size of the data to `size`. * Params: * size = new size of data, must be <= `.length` */ extern (C++) void setsize(size_t size) pure nothrow @nogc @safe { assert(size <= data.length); offset = size; } extern (C++) void reset() pure nothrow @nogc @safe { offset = 0; } private void indent() pure nothrow @safe { if (level) { const indentLevel = spaces ? level * 4 : level; reserve(indentLevel); data[offset .. offset + indentLevel] = (spaces ? ' ' : '\t'); offset += indentLevel; } notlinehead = true; } // Write an array to the buffer, no reserve check @system nothrow void writen(const void *b, size_t len) { memcpy(data.ptr + offset, b, len); offset += len; } extern (C++) void write(const(void)* data, size_t nbytes) pure nothrow @system { write(data[0 .. nbytes]); } void write(scope const(void)[] buf) pure nothrow @trusted { if (doindent && !notlinehead) indent(); reserve(buf.length); memcpy(this.data.ptr + offset, buf.ptr, buf.length); offset += buf.length; } /** * Writes a 16 bit value, no reserve check. */ nothrow void write16n(int v) { auto x = cast(ushort) v; data[offset] = x & 0x00FF; data[offset + 1] = x >> 8u; offset += 2; } /** * Writes a 16 bit value. */ void write16(int v) nothrow { auto u = cast(ushort) v; write(&u, u.sizeof); } /** * Writes a 32 bit int. */ void write32(int v) nothrow @trusted { write(&v, v.sizeof); } /** * Writes a 64 bit int. */ @trusted void write64(long v) nothrow { write(&v, v.sizeof); } /// Buffer will NOT be zero-terminated extern (C++) void writestring(const(char)* s) pure nothrow @system { if (!s) return; import core.stdc.string : strlen; write(s[0 .. strlen(s)]); } /// ditto void writestring(scope const(char)[] s) pure nothrow @safe { write(s); } /// ditto void writestring(scope string s) pure nothrow @safe { write(s); } /// Buffer will NOT be zero-terminated, followed by newline void writestringln(const(char)[] s) pure nothrow @safe { writestring(s); writenl(); } /** Write C string AND null byte */ void writeStringz(const(char)* s) pure nothrow @system { write(s[0 .. strlen(s)+1]); } /// ditto void writeStringz(const(char)[] s) pure nothrow @safe { write(s); writeByte(0); } /// ditto void writeStringz(string s) pure nothrow @safe { writeStringz(cast(const(char)[])(s)); } extern (C++) void prependstring(const(char)* string) pure nothrow @system { size_t len = strlen(string); reserve(len); memmove(data.ptr + len, data.ptr, offset); memcpy(data.ptr, string, len); offset += len; } /// strip trailing tabs or spaces, write newline extern (C++) void writenl() pure nothrow @safe { while (offset > 0 && (data[offset - 1] == ' ' || data[offset - 1] == '\t')) offset--; version (Windows) { writeword(0x0A0D); // newline is CR,LF on Microsoft OS's } else { writeByte('\n'); } if (doindent) notlinehead = false; } // Write n zeros; return pointer to start of zeros @trusted void *writezeros(size_t n) nothrow { reserve(n); auto result = memset(data.ptr + offset, 0, n); offset += n; return result; } // Position buffer to accept the specified number of bytes at offset void position(size_t where, size_t nbytes) nothrow { if (where + nbytes > data.length) { reserve(where + nbytes - offset); } offset = where; debug assert(offset + nbytes <= data.length); } /** * Writes an 8 bit byte, no reserve check. */ extern (C++) nothrow @safe void writeByten(int b) { this.data[offset++] = cast(ubyte) b; } extern (C++) void writeByte(uint b) pure nothrow @safe { if (doindent && !notlinehead && b != '\n') indent(); reserve(1); this.data[offset] = cast(ubyte)b; offset++; } extern (C++) void writeUTF8(uint b) pure nothrow @safe { reserve(6); if (b <= 0x7F) { this.data[offset] = cast(ubyte)b; offset++; } else if (b <= 0x7FF) { this.data[offset + 0] = cast(ubyte)((b >> 6) | 0xC0); this.data[offset + 1] = cast(ubyte)((b & 0x3F) | 0x80); offset += 2; } else if (b <= 0xFFFF) { this.data[offset + 0] = cast(ubyte)((b >> 12) | 0xE0); this.data[offset + 1] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); this.data[offset + 2] = cast(ubyte)((b & 0x3F) | 0x80); offset += 3; } else if (b <= 0x1FFFFF) { this.data[offset + 0] = cast(ubyte)((b >> 18) | 0xF0); this.data[offset + 1] = cast(ubyte)(((b >> 12) & 0x3F) | 0x80); this.data[offset + 2] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); this.data[offset + 3] = cast(ubyte)((b & 0x3F) | 0x80); offset += 4; } else assert(0); } extern (C++) void prependbyte(uint b) pure nothrow @trusted { reserve(1); memmove(data.ptr + 1, data.ptr, offset); data[0] = cast(ubyte)b; offset++; } extern (C++) void writewchar(uint w) pure nothrow @safe { version (Windows) { writeword(w); } else { write4(w); } } extern (C++) void writeword(uint w) pure nothrow @trusted { version (Windows) { uint newline = 0x0A0D; } else { uint newline = '\n'; } if (doindent && !notlinehead && w != newline) indent(); reserve(2); *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; offset += 2; } extern (C++) void writeUTF16(uint w) pure nothrow @trusted { reserve(4); if (w <= 0xFFFF) { *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; offset += 2; } else if (w <= 0x10FFFF) { *cast(ushort*)(this.data.ptr + offset) = cast(ushort)((w >> 10) + 0xD7C0); *cast(ushort*)(this.data.ptr + offset + 2) = cast(ushort)((w & 0x3FF) | 0xDC00); offset += 4; } else assert(0); } extern (C++) void write4(uint w) pure nothrow @trusted { version (Windows) { bool notnewline = w != 0x000A000D; } else { bool notnewline = true; } if (doindent && !notlinehead && notnewline) indent(); reserve(4); *cast(uint*)(this.data.ptr + offset) = w; offset += 4; } extern (C++) void write(const OutBuffer* buf) pure nothrow @trusted { if (buf) { reserve(buf.offset); memcpy(data.ptr + offset, buf.data.ptr, buf.offset); offset += buf.offset; } } extern (C++) void fill0(size_t nbytes) pure nothrow @trusted { reserve(nbytes); memset(data.ptr + offset, 0, nbytes); offset += nbytes; } /** * Allocate space, but leave it uninitialized. * Params: * nbytes = amount to allocate * Returns: * slice of the allocated space to be filled in */ extern (D) char[] allocate(size_t nbytes) pure nothrow @safe { reserve(nbytes); offset += nbytes; return cast(char[])data[offset - nbytes .. offset]; } extern (C++) void vprintf(const(char)* format, va_list args) nothrow @system { int count; if (doindent && !notlinehead) indent(); uint psize = 128; for (;;) { reserve(psize); va_list va; va_copy(va, args); /* The functions vprintf(), vfprintf(), vsprintf(), vsnprintf() are equivalent to the functions printf(), fprintf(), sprintf(), snprintf(), respectively, except that they are called with a va_list instead of a variable number of arguments. These functions do not call the va_end macro. Consequently, the value of ap is undefined after the call. The application should call va_end(ap) itself afterwards. */ count = vsnprintf(cast(char*)data.ptr + offset, psize, format, va); va_end(va); if (count == -1) // snn.lib and older libcmt.lib return -1 if buffer too small psize *= 2; else if (count >= psize) psize = count + 1; else break; } offset += count; // if (mem.isGCEnabled) memset(data.ptr + offset, 0xff, psize - count); } static if (__VERSION__ < 2092) { extern (C++) void printf(const(char)* format, ...) nothrow @system { va_list ap; va_start(ap, format); vprintf(format, ap); va_end(ap); } } else { pragma(printf) extern (C++) void printf(const(char)* format, ...) nothrow @system { va_list ap; va_start(ap, format); vprintf(format, ap); va_end(ap); } } /************************************** * Convert `u` to a string and append it to the buffer. * Params: * u = integral value to append */ extern (C++) void print(ulong u) pure nothrow @safe { UnsignedStringBuf buf = void; writestring(unsignedToTempString(u, buf)); } extern (C++) void bracket(char left, char right) pure nothrow @trusted { reserve(2); memmove(data.ptr + 1, data.ptr, offset); data[0] = left; data[offset + 1] = right; offset += 2; } /****************** * Insert left at i, and right at j. * Return index just past right. */ extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow @system { size_t leftlen = strlen(left); size_t rightlen = strlen(right); reserve(leftlen + rightlen); insert(i, left, leftlen); insert(j + leftlen, right, rightlen); return j + leftlen + rightlen; } extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow @system { reserve(nbytes); memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset); this.offset += nbytes; } /**************************************** * Returns: offset + nbytes */ extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow @system { spread(offset, nbytes); memmove(data.ptr + offset, p, nbytes); return offset + nbytes; } size_t insert(size_t offset, const(char)[] s) pure nothrow @system { return insert(offset, s.ptr, s.length); } extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc @system { memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes)); this.offset -= nbytes; } /** * Returns: * a non-owning const slice of the buffer contents */ extern (D) const(char)[] opSlice() const pure nothrow @nogc @safe { return cast(const(char)[])data[0 .. offset]; } extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc @safe { return cast(const(char)[])data[lwr .. upr]; } extern (D) char opIndex(size_t i) const pure nothrow @nogc @safe { return cast(char)data[i]; } alias opDollar = length; /*********************************** * Extract the data as a slice and take ownership of it. * * When `true` is passed as an argument, this function behaves * like `dmd.utils.toDString(thisbuffer.extractChars())`. * * Params: * nullTerminate = When `true`, the data will be `null` terminated. * This is useful to call C functions or store * the result in `Strings`. Defaults to `false`. */ extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow { const length = offset; if (!nullTerminate) return extractData()[0 .. length]; // There's already a terminating `'\0'` if (length && data[length - 1] == '\0') return extractData()[0 .. length - 1]; writeByte(0); return extractData()[0 .. length]; } extern (D) byte[] extractUbyteSlice(bool nullTerminate = false) pure nothrow { return cast(byte[]) extractSlice(nullTerminate); } // Append terminating null if necessary and get view of internal buffer extern (C++) char* peekChars() pure nothrow { if (!offset || data[offset - 1] != '\0') { writeByte(0); offset--; // allow appending more } return cast(char*)data.ptr; } // Peek at slice of data without taking ownership extern (D) ubyte[] peekSlice() pure nothrow { return data[0 .. offset]; } // Append terminating null if necessary and take ownership of data extern (C++) char* extractChars() pure nothrow @safe { if (!offset || data[offset - 1] != '\0') writeByte(0); return extractData(); } void writesLEB128(int value) pure nothrow @safe { while (1) { ubyte b = value & 0x7F; value >>= 7; // arithmetic right shift if ((value == 0 && !(b & 0x40)) || (value == -1 && (b & 0x40))) { writeByte(b); break; } writeByte(b | 0x80); } } void writeuLEB128(uint value) pure nothrow @safe { do { ubyte b = value & 0x7F; value >>= 7; if (value) b |= 0x80; writeByte(b); } while (value); } /** * Write an array as a string of hexadecimal digits * Params: * data = bytes to write * upperCase = whether to upper case hex digits A-F */ void writeHexString(scope const(ubyte)[] data, bool upperCase) pure nothrow @safe { auto slice = this.allocate(2 * data.length); const a = upperCase ? 'A' : 'a'; foreach (i, c; data) { char hi = (c >> 4) & 0xF; slice[i * 2] = cast(char)(hi < 10 ? hi + '0' : hi - 10 + a); char lo = c & 0xF; slice[i * 2 + 1] = cast(char)(lo < 10 ? lo + '0' : lo - 10 + a); } } /** Destructively saves the contents of `this` to `filename`. As an optimization, if the file already has identical contents with the buffer, no copying is done. This is because on SSD drives reading is often much faster than writing and because there's a high likelihood an identical file is written during the build process. Params: filename = the name of the file to receive the contents Returns: `true` iff the operation succeeded. */ extern(D) bool moveToFile(const char* filename) @system { bool result = true; const bool identical = this[] == FileMapping!(const ubyte)(filename)[]; if (fileMapping && fileMapping.active) { // Defer to corresponding functions in FileMapping. if (identical) { result = fileMapping.discard(); } else { // Resize to fit to get rid of the slack bytes at the end fileMapping.resize(offset); result = fileMapping.moveToFile(filename); } // Can't call destroy() here because the file mapping is already closed. data = null; offset = 0; } else { if (!identical) writeFile(filename, this[]); destroy(); } return identical ? result && touchFile(filename) : result; } } /****** copied from core.internal.string *************/ private: alias UnsignedStringBuf = char[20]; char[] unsignedToTempString(ulong value, return scope char[] buf, uint radix = 10) @safe pure nothrow @nogc { size_t i = buf.length; do { if (value < radix) { ubyte x = cast(ubyte)value; buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); break; } else { ubyte x = cast(ubyte)(value % radix); value /= radix; buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); } } while (value); return buf[i .. $]; } /************* unit tests **************************************************/ unittest { OutBuffer buf; buf.printf("betty"); buf.insert(1, "xx".ptr, 2); buf.insert(3, "yy"); buf.remove(4, 1); buf.bracket('(', ')'); const char[] s = buf[]; assert(s == "(bxxyetty)"); buf.destroy(); } unittest { OutBuffer buf; buf.writestring("abc".ptr); buf.prependstring("def"); buf.prependbyte('x'); OutBuffer buf2; buf2.writestring("mmm"); buf.write(&buf2); char[] s = buf.extractSlice(); assert(s == "xdefabcmmm"); } unittest { OutBuffer buf; buf.writeByte('a'); char[] s = buf.extractSlice(); assert(s == "a"); buf.writeByte('b'); char[] t = buf.extractSlice(); assert(t == "b"); } unittest { OutBuffer buf; char* p = buf.peekChars(); assert(*p == 0); buf.writeByte('s'); char* q = buf.peekChars(); assert(strcmp(q, "s") == 0); } unittest { char[10] buf; char[] s = unsignedToTempString(278, buf[], 10); assert(s == "278"); s = unsignedToTempString(1, buf[], 10); assert(s == "1"); s = unsignedToTempString(8, buf[], 2); assert(s == "1000"); s = unsignedToTempString(29, buf[], 16); assert(s == "1d"); } unittest { OutBuffer buf; buf.writeUTF8(0x0000_0011); buf.writeUTF8(0x0000_0111); buf.writeUTF8(0x0000_1111); buf.writeUTF8(0x0001_1111); buf.writeUTF8(0x0010_0000); assert(buf[] == "\x11\U00000111\U00001111\U00011111\U00100000"); buf.reset(); buf.writeUTF16(0x0000_0011); buf.writeUTF16(0x0010_FFFF); assert(buf[] == cast(string) "\u0011\U0010FFFF"w); } unittest { OutBuffer buf; buf.doindent = true; const(char)[] s = "abc"; buf.writestring(s); buf.level += 1; buf.indent(); buf.writestring("abs"); assert(buf[] == "abc\tabs"); buf.setsize(4); assert(buf.length == 4); } unittest { OutBuffer buf; buf.writenl(); buf.writestring("abc \t "); buf.writenl(); // strips trailing whitespace buf.writenl(); // doesn't strip previous newline version(Windows) assert(buf[] == "\r\nabc\r\n\r\n"); else assert(buf[] == "\nabc\n\n"); } unittest { OutBuffer buf; buf.writeHexString([0xAA, 0xBB], false); buf.writeHexString([0xCC], true); assert(buf[] == "aabbCC"); } ldc-1.40.0-src/dmd/common/outbuffer.h0000644000000000000000000000441114727557031016042 0ustar rootroot /* Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/common/outbuffer.h */ #pragma once #include "../root/dsystem.h" #include "../root/dcompat.h" #include "../root/rmem.h" class RootObject; struct OutBuffer { // IMPORTANT: PLEASE KEEP STATE AND DESTRUCTOR IN SYNC WITH DEFINITION IN ./outbuffer.d. private: DArray data; d_size_t offset; d_bool notlinehead; void *fileMapping; // pointer to a file mapping object not used on the C++ side public: d_bool doindent; d_bool spaces; int level; OutBuffer() { data = DArray(); offset = 0; doindent = 0; level = 0; notlinehead = 0; fileMapping = 0; } ~OutBuffer() { mem.xfree(data.ptr); } d_size_t length() const { return offset; } char *extractData(); void destroy(); void reserve(d_size_t nbytes); void setsize(d_size_t size); void reset(); void write(const void *data, d_size_t nbytes); void writestring(const char *string); void prependstring(const char *string); void writenl(); // write newline void writeByte(unsigned b); void writeUTF8(unsigned b); void prependbyte(unsigned b); void writewchar(unsigned w); void writeword(unsigned w); void writeUTF16(unsigned w); void write4(unsigned w); void write(const OutBuffer *buf); void write(RootObject *obj); void fill0(d_size_t nbytes); void vprintf(const char *format, va_list args); void printf(const char *format, ...); void bracket(char left, char right); d_size_t bracket(d_size_t i, const char *left, d_size_t j, const char *right); void spread(d_size_t offset, d_size_t nbytes); d_size_t insert(d_size_t offset, const void *data, d_size_t nbytes); void remove(d_size_t offset, d_size_t nbytes); // Append terminating null if necessary and get view of internal buffer char *peekChars(); // Append terminating null if necessary and take ownership of data char *extractChars(); }; ldc-1.40.0-src/dmd/common/file.d0000644000000000000000000006060614727557031014764 0ustar rootroot/** * File utilities. * * Functions and objects dedicated to file I/O and management. TODO: Move here artifacts * from places such as root/ so both the frontend and the backend have access to them. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: Walter Bright, https://www.digitalmars.com * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/file.d, common/_file.d) * Documentation: https://dlang.org/phobos/dmd_common_file.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/file.d */ module dmd.common.file; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; import core.stdc.limits; import core.stdc.errno : errno; import core.stdc.stdio : fprintf, remove, rename, stderr; import core.stdc.stdlib; import core.stdc.string : strerror, strlen, memcpy; import dmd.common.smallbuffer; import dmd.root.filename; import dmd.root.rmem; version (Windows) { import core.stdc.wchar_; import core.sys.windows.winbase; version (IN_LLVM) { import core.sys.windows.winnls : CP_UTF8; import core.sys.windows.winnt; enum CodePage = CP_UTF8; } else { import core.sys.windows.winnls : CP_ACP; import core.sys.windows.winnt; enum CodePage = CP_ACP; // assume filenames encoded in system default Windows ANSI code page } enum invalidHandle = INVALID_HANDLE_VALUE; } else version (Posix) { import core.sys.posix.dirent; import core.sys.posix.fcntl; import core.sys.posix.sys.mman; import core.sys.posix.sys.stat; import core.sys.posix.unistd; import core.sys.posix.utime; enum invalidHandle = -1; } else static assert(0); nothrow: /** Encapsulated management of a memory-mapped file. Params: Datum = the mapped data type: Use a POD of size 1 for read/write mapping and a `const` version thereof for read-only mapping. Other primitive types should work, but have not been yet tested. */ struct FileMapping(Datum) { static assert(__traits(isPOD, Datum) && Datum.sizeof == 1, "Not tested with other data types yet. Add new types with care."); // state { /// Handle of underlying file private auto handle = invalidHandle; /// File mapping object needed on Windows version(Windows) private HANDLE fileMappingObject = invalidHandle; /// Memory-mapped array private Datum[] data; /// Name of underlying file, zero-terminated private const(char)* name; // state } nothrow: /** Open `filename` and map it in memory. If `Datum` is `const`, opens for read-only and maps the content in memory; no error is issued if the file does not exist. This makes it easy to treat a non-existing file as empty. If `Datum` is mutable, opens for read/write (creates file if it does not exist) and fails fatally on any error. Due to quirks in `mmap`, if the file is empty, `handle` is valid but `data` is `null`. This state is valid and accounted for. Params: filename = the name of the file to be mapped in memory */ this(const char* filename) { version (Posix) { handle = open(filename, is(Datum == const) ? O_RDONLY : (O_CREAT | O_RDWR), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (handle == invalidHandle) { static if (is(Datum == const)) { // No error, nonexisting file in read mode behaves like an empty file. return; } else { fprintf(stderr, "open(\"%s\") failed: %s\n", filename, strerror(errno)); exit(1); } } const size = fileSize(handle); if (size > 0 && size != ulong.max && size <= size_t.max) { auto p = mmap(null, cast(size_t) size, is(Datum == const) ? PROT_READ : PROT_WRITE, MAP_SHARED, handle, 0); if (p == MAP_FAILED) { fprintf(stderr, "mmap(null, %zu) for \"%s\" failed: %s\n", cast(size_t) size, filename, strerror(errno)); exit(1); } // The cast below will always work because it's gated by the `size <= size_t.max` condition. data = cast(Datum[]) p[0 .. cast(size_t) size]; } } else version(Windows) { static if (is(Datum == const)) { enum createFileMode = GENERIC_READ; enum openFlags = OPEN_EXISTING; } else { enum createFileMode = GENERIC_READ | GENERIC_WRITE; enum openFlags = CREATE_ALWAYS; } handle = filename[0 .. strlen(filename)]. extendedPathThen!(p => CreateFileW(p.ptr, createFileMode, 0, null, openFlags, FILE_ATTRIBUTE_NORMAL, null)); if (handle == invalidHandle) { static if (is(Datum == const)) { return; } else { fprintf(stderr, "CreateFileW() failed for \"%s\": %d\n", filename, GetLastError()); exit(1); } } createMapping(filename, fileSize(handle)); } else static assert(0); // Save the name for later. Technically there's no need: on Linux one can use readlink on /proc/self/fd/NNN. // On BSD and OSX one can use fcntl with F_GETPATH. On Windows one can use GetFileInformationByHandleEx. // But just saving the name is simplest, fastest, and most portable... const totalNameLength = filename.strlen() + 1; auto namex = cast(char*) malloc(totalNameLength); if (!namex) { fprintf(stderr, "FileMapping: Out of memory."); exit(1); } name = cast(char*) memcpy(namex, filename, totalNameLength); } /** Common code factored opportunistically. Windows only. Assumes `handle` is already pointing to an opened file. Initializes the `fileMappingObject` and `data` members. Params: filename = the file to be mapped size = the size of the file in bytes */ version(Windows) private void createMapping(const char* filename, ulong size) { assert(size <= size_t.max || size == ulong.max); assert(handle != invalidHandle); assert(data is null); assert(fileMappingObject == invalidHandle); if (size == 0 || size == ulong.max) return; static if (is(Datum == const)) { enum fileMappingFlags = PAGE_READONLY; enum mapViewFlags = FILE_MAP_READ; } else { enum fileMappingFlags = PAGE_READWRITE; enum mapViewFlags = FILE_MAP_WRITE; } fileMappingObject = CreateFileMappingW(handle, null, fileMappingFlags, 0, 0, null); if (!fileMappingObject) { fprintf(stderr, "CreateFileMappingW(%p) failed for %llu bytes of \"%s\": %d\n", handle, size, filename, GetLastError()); fileMappingObject = invalidHandle; // by convention always use invalidHandle, not null exit(1); } auto p = MapViewOfFile(fileMappingObject, mapViewFlags, 0, 0, 0); if (!p) { fprintf(stderr, "MapViewOfFile() failed for \"%s\": %d\n", filename, GetLastError()); exit(1); } data = cast(Datum[]) p[0 .. cast(size_t) size]; } // Not copyable or assignable (for now). @disable this(const FileMapping!Datum rhs); @disable void opAssign(const ref FileMapping!Datum rhs); /** Frees resources associated with this mapping. However, it does not deallocate the name. */ ~this() pure nothrow { if (!active) return; fakePure({ version (Posix) { // Cannot call fprintf from inside a destructor, so exiting silently. if (data.ptr && munmap(cast(void*) data.ptr, data.length) != 0) { exit(1); } data = null; if (handle != invalidHandle && .close(handle) != 0) { exit(1); } handle = invalidHandle; } else version(Windows) { if (data.ptr !is null && UnmapViewOfFile(cast(void*) data.ptr) == 0) { exit(1); } data = null; if (fileMappingObject != invalidHandle && CloseHandle(fileMappingObject) == 0) { exit(1); } fileMappingObject = invalidHandle; if (handle != invalidHandle && CloseHandle(handle) == 0) { exit(1); } handle = invalidHandle; } else static assert(0); }); } /** Returns the zero-terminated file name associated with the mapping. Can NOT be saved beyond the lifetime of `this`. */ private const(char)* filename() const pure @nogc @safe nothrow { return name; } /** Frees resources associated with this mapping. However, it does not deallocate the name. Reinitializes `this` as a fresh object that can be reused. */ void close() { __dtor(); handle = invalidHandle; version(Windows) fileMappingObject = invalidHandle; data = null; name = null; } /** Deletes the underlying file and frees all resources associated. Reinitializes `this` as a fresh object that can be reused. This function does not abort if the file cannot be deleted, but does print a message on `stderr` and returns `false` to the caller. The underlying rationale is to give the caller the option to continue execution if deleting the file is not important. Returns: `true` iff the file was successfully deleted. If the file was not deleted, prints a message to `stderr` and returns `false`. */ static if (!is(Datum == const)) bool discard() { // Truncate file to zero so unflushed buffers are not flushed unnecessarily. resize(0); auto deleteme = name; close(); // In-memory resource freed, now get rid of the underlying temp file. version(Posix) { if (unlink(deleteme) != 0) { fprintf(stderr, "unlink(\"%s\") failed: %s\n", filename, strerror(errno)); return false; } } else version(Windows) { if (deleteme[0 .. strlen(deleteme)].extendedPathThen!(p => DeleteFileW(p.ptr)) == 0) { fprintf(stderr, "DeleteFileW error %d\n", GetLastError()); return false; } } else static assert(0); return true; } /** Queries whether `this` is currently associated with a file. Returns: `true` iff there is an active mapping. */ bool active() const pure @nogc nothrow { return handle !is invalidHandle; } /** Queries the length of the file associated with this mapping. If not active, returns 0. Returns: the length of the file, or 0 if no file associated. */ size_t length() const pure @nogc @safe nothrow { return data.length; } /** Get a slice to the contents of the entire file. Returns: the contents of the file. If not active, returns the `null` slice. */ auto opSlice() pure @nogc @safe nothrow { return data; } /** Resizes the file and mapping to the specified `size`. Params: size = new length requested */ static if (!is(Datum == const)) void resize(size_t size) pure { assert(handle != invalidHandle); fakePure({ version(Posix) { if (data.length) { assert(data.ptr, "Corrupt memory mapping"); // assert(0) here because it would indicate an internal error munmap(cast(void*) data.ptr, data.length) == 0 || assert(0); data = null; } if (ftruncate(handle, size) != 0) { fprintf(stderr, "ftruncate() failed for \"%s\": %s\n", filename, strerror(errno)); exit(1); } if (size > 0) { auto p = mmap(null, size, PROT_WRITE, MAP_SHARED, handle, 0); if (cast(ssize_t) p == -1) { fprintf(stderr, "mmap() failed for \"%s\": %s\n", filename, strerror(errno)); exit(1); } data = cast(Datum[]) p[0 .. size]; } } else version(Windows) { // Per documentation, must unmap first. if (data.length > 0 && UnmapViewOfFile(cast(void*) data.ptr) == 0) { fprintf(stderr, "UnmapViewOfFile(%p) failed for memory mapping of \"%s\": %d\n", data.ptr, filename, GetLastError()); exit(1); } data = null; if (fileMappingObject != invalidHandle && CloseHandle(fileMappingObject) == 0) { fprintf(stderr, "CloseHandle() failed for memory mapping of \"%s\": %d\n", filename, GetLastError()); exit(1); } fileMappingObject = invalidHandle; LARGE_INTEGER biggie; biggie.QuadPart = size; if (SetFilePointerEx(handle, biggie, null, FILE_BEGIN) == 0 || SetEndOfFile(handle) == 0) { fprintf(stderr, "SetFilePointer() failed for \"%s\": %d\n", filename, GetLastError()); exit(1); } createMapping(name, size); } else static assert(0); }); } /** Unconditionally and destructively moves the underlying file to `filename`. If the operation succeeds, returns true. Upon failure, prints a message to `stderr` and returns `false`. In all cases it closes the underlying file. Params: filename = zero-terminated name of the file to move to. Returns: `true` iff the operation was successful. */ bool moveToFile(const char* filename) { assert(name !is null); // Fetch the name and then set it to `null` so it doesn't get deallocated auto oldname = name; scope(exit) free(cast(void*) oldname); name = null; close(); // Rename the underlying file to the target, no copy necessary. version(Posix) { if (.rename(oldname, filename) != 0) { fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", oldname, filename, strerror(errno)); return false; } } else version(Windows) { auto r = oldname[0 .. strlen(oldname)].extendedPathThen!( p1 => filename[0 .. strlen(filename)].extendedPathThen!(p2 => MoveFileExW(p1.ptr, p2.ptr, MOVEFILE_REPLACE_EXISTING)) ); if (r == 0) { fprintf(stderr, "MoveFileExW(\"%s\", \"%s\") failed: %d\n", oldname, filename, GetLastError()); return false; } } else static assert(0); return true; } } /// Write a file, returning `true` on success. extern(D) static bool writeFile(const(char)* name, const void[] data) nothrow { version (Posix) { int fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, (6 << 6) | (4 << 3) | 4); if (fd == -1) goto err; if (.write(fd, data.ptr, data.length) != data.length) goto err2; if (close(fd) == -1) goto err; return true; err2: close(fd); .remove(name); err: return false; } else version (Windows) { DWORD numwritten; // here because of the gotos const nameStr = name[0 .. strlen(name)]; // work around Windows file path length limitation // (see documentation for extendedPathThen). HANDLE h = nameStr.extendedPathThen! (p => CreateFileW(p.ptr, GENERIC_WRITE, 0, null, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, null)); if (h == INVALID_HANDLE_VALUE) goto err; if (WriteFile(h, data.ptr, cast(DWORD)data.length, &numwritten, null) != TRUE) goto err2; if (numwritten != data.length) goto err2; if (!CloseHandle(h)) goto err; return true; err2: CloseHandle(h); nameStr.extendedPathThen!(p => DeleteFileW(p.ptr)); err: return false; } else { static assert(0); } } /// Touch a file to current date bool touchFile(const char* namez) { version (Windows) { FILETIME ft = void; SYSTEMTIME st = void; GetSystemTime(&st); SystemTimeToFileTime(&st, &ft); // get handle to file HANDLE h = namez[0 .. namez.strlen()].extendedPathThen!(p => CreateFile(p.ptr, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, null, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, null)); if (h == INVALID_HANDLE_VALUE) return false; const f = SetFileTime(h, null, null, &ft); // set last write time if (!CloseHandle(h)) return false; return f != 0; } else version (Posix) { return utime(namez, null) == 0; } else static assert(0); } // Feel free to make these public if used elsewhere. /** Size of a file in bytes. Params: fd = file handle Returns: file size in bytes, or `ulong.max` on any error. */ version (Posix) { private ulong fileSize(int fd) { stat_t buf; if (fstat(fd, &buf) == 0) return buf.st_size; return ulong.max; } } else version (Windows) { /// Ditto private ulong fileSize(HANDLE fd) { ulong result; if (GetFileSizeEx(fd, cast(LARGE_INTEGER*) &result)) return result; return ulong.max; } } else static assert(0); /** Runs a non-pure function or delegate as pure code. Use with caution. Params: fun = the delegate to run, usually inlined: `fakePure({ ... });` Returns: whatever `fun` returns. */ private auto ref fakePure(F)(scope F fun) pure { mixin("alias PureFun = " ~ F.stringof ~ " pure;"); return (cast(PureFun) fun)(); } /*********************************** * Recursively search all the directories and files under dir_path * for files that match one of the extensions in exts[]. * Pass the matches to sink. * Params: * dir_path = root of directories to search * exts = array of filename extensions to match * recurse = go into subdirectories * filenameSink = accepts the resulting matches * Returns: * true for failed to open the directory */ bool findFiles(const char* dir_path, const char[][] exts, bool recurse, void delegate(const(char)[]) nothrow filenameSink) { enum log = false; if (log) printf("findFiles() dir_path: %s\n", dir_path); version (Windows) { debug enum BufLength = 10; // trigger any reallocation bugs else enum BufLength = 100; char[BufLength + 1] buf = void; char* fullPath = buf.ptr; size_t fullPathLength = BufLength; // fullPath = dir_path \ *.* const dir_pathLength = strlen(dir_path); auto count = dir_pathLength + 1 + 3; if (count > fullPathLength) { fullPathLength = count; fullPath = cast(char*)Mem.xrealloc_noscan(fullPath == buf.ptr ? null : fullPath, fullPathLength + 1); } memcpy(fullPath, dir_path, dir_pathLength); strcpy(fullPath + dir_pathLength, "\\*.*".ptr); if (log) printf("fullPath: %s\n", fullPath); WIN32_FIND_DATAW ffd = void; HANDLE hFind = fullPath[0 .. strlen(fullPath)].extendedPathThen!(p => FindFirstFileW(p.ptr, &ffd)); if (hFind == INVALID_HANDLE_VALUE) return true; do { if (log) wprintf("ffd.cFileName: %s\n", ffd.cFileName.ptr); if (ffd.cFileName[0] == 0) continue; // ignore if (ffd.cFileName[0] == '.') continue; // ignore files that start with a ., also ignore . and .. directories const(char)[] name = toNarrowStringz(ffd.cFileName[0 .. wcslen(ffd.cFileName.ptr)], null); if (log) printf("name: %s\n", name.ptr); // fullPath = dir_path \ name.ptr count = dir_pathLength + 1 + name.length; if (count > fullPathLength) { fullPathLength = count; fullPath = cast(char*)Mem.xrealloc_noscan(fullPath == buf.ptr ? null : fullPath, fullPathLength + 1); } strcpy(fullPath + dir_pathLength + 1, name.ptr); if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (recurse) findFiles(fullPath, exts, recurse, filenameSink); } else { const(char)[] nameExt = FileName.ext(name); foreach (ext; exts[]) { if (nameExt == ext) { if (log) printf("adding %s\n", fullPath); filenameSink(fullPath[0 .. count]); } } } mem.xfree(cast(void*)name.ptr); } while (FindNextFileW(hFind, &ffd) != 0); if (fullPath != buf.ptr) mem.xfree(fullPath); FindClose(hFind); if (log) printf("findFiles() exit\n"); return false; } else version (Posix) { DIR* dir = opendir(dir_path); if (!dir) return true; debug enum BufLength = 10; // trigger any reallocation bugs else enum BufLength = 100; char[BufLength + 1] buf = void; char* fullPath = buf.ptr; size_t fullPathLength = BufLength; dirent* entry; while ((entry = readdir(dir)) != null) { //printf("entry: %s\n", entry.d_name.ptr); if (entry.d_name[0] == '.') continue; // ignore files that start with a . // fullPath = dir_path / entry.d_name.ptr const dir_pathLength = strlen(dir_path); const count = dir_pathLength + 1 + strlen(entry.d_name.ptr); if (count > fullPathLength) { fullPathLength = count; fullPath = cast(char*)Mem.xrealloc_noscan(fullPath == buf.ptr ? null : fullPath, fullPathLength + 1); } memcpy(fullPath, dir_path, dir_pathLength); fullPath[dir_pathLength] = '/'; strcpy(fullPath + dir_pathLength + 1, entry.d_name.ptr); stat_t statbuf; if (lstat(fullPath, &statbuf) == -1) continue; const(char)[] name = entry.d_name.ptr[0 .. strlen(entry.d_name.ptr)]; // convert to D string if (!name.length) continue; // ignore if (S_ISDIR(statbuf.st_mode)) { if (recurse && !(name == "." || name == "..")) findFiles(fullPath, exts, recurse, filenameSink); } else if (S_ISREG(statbuf.st_mode)) { foreach (ext; exts) { if (FileName.ext(name) == ext) { //printf("%s\n", fullPath); filenameSink(fullPath[0 .. count]); } } } } if (fullPath != buf.ptr) mem.xfree(fullPath); closedir(dir); return false; } else static assert(0); } ldc-1.40.0-src/dmd/common/bitfields.d0000644000000000000000000000423014727557031016001 0ustar rootroot/** * A library bitfields utility * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: Dennis Korpel * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/bitfields.d, common/bitfields.d) * Documentation: https://dlang.org/phobos/dmd_common_bitfields.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/bitfields.d */ module dmd.common.bitfields; /** * Generate code for bit fields inside a struct/class body * Params: * S = type of a struct with only boolean fields, which should become bit fields * T = type of bit fields variable, must have enough bits to store all booleans * Returns: D code with a bit fields variable and getter / setter functions */ extern (D) string generateBitFields(S, T)() if (__traits(isUnsigned, T)) { string result = "extern (C++) pure nothrow @nogc @safe final {"; enum structName = __traits(identifier, S); string initialValue = ""; foreach (size_t i, mem; __traits(allMembers, S)) { static assert(is(typeof(__traits(getMember, S, mem)) == bool)); static assert(i < T.sizeof * 8, "too many fields for bit field storage of type `"~T.stringof~"`"); enum mask = "(1 << "~i.stringof~")"; result ~= " /// set or get the corresponding "~structName~" member bool "~mem~"() const scope { return !!(bitFields & "~mask~"); } /// ditto bool "~mem~"(bool v) { v ? (bitFields |= "~mask~") : (bitFields &= ~"~mask~"); return v; }"; initialValue = (__traits(getMember, S.init, mem) ? "1" : "0") ~ initialValue; } return result ~ "}\n private "~T.stringof~" bitFields = 0b" ~ initialValue ~ ";\n"; } /// unittest { static struct B { bool x; bool y; bool z = 1; } static struct S { mixin(generateBitFields!(B, ubyte)); } S s; assert(!s.x); s.x = true; assert(s.x); s.x = false; assert(!s.x); s.y = true; assert(s.y); assert(!s.x); assert(s.z); } ldc-1.40.0-src/dmd/common/charactertables.d0000644000000000000000000001425514727557031017173 0ustar rootroot/** * Character tables related to identifiers. * * Supports UAX31, C99, C11 and least restrictive (All). * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://cattermole.co.nz, Richard (Rikki) Andrew Cattermole) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/charactertables.d, common/charactertables.d) * Documentation: https://dlang.org/phobos/dmd_common_charactertables.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/charactertables.d */ module dmd.common.charactertables; @safe nothrow @nogc pure: extern(C++): /// enum IdentifierTable { UAX31, /// C99, /// C11, /// LR, /// Least Restrictive aka All } /// struct IdentifierCharLookup { @safe nothrow @nogc pure: /// extern(C++) bool function(dchar) isStart; /// extern(C++) bool function(dchar) isContinue; /// Lookup the table given the table name static IdentifierCharLookup forTable(IdentifierTable table) { import dmd.common.identifiertables; // Awful solution to require these lambdas. // However without them the extern(C++) ABI issues crop up for isInRange, // and then it can't access the tables. final switch(table) { case IdentifierTable.UAX31: return IdentifierCharLookup( (c) => isInRange!UAX31_Start(c), (c) => isInRange!UAX31_Continue(c)); case IdentifierTable.C99: return IdentifierCharLookup( (c) => isInRange!FixedTable_C99_Start(c), (c) => isInRange!FixedTable_C99_Continue(c)); case IdentifierTable.C11: return IdentifierCharLookup( (c) => isInRange!FixedTable_C11_Start(c), (c) => isInRange!FixedTable_C11_Continue(c)); case IdentifierTable.LR: return IdentifierCharLookup( (c) => isInRange!LeastRestrictive_Start(c), (c) => isInRange!LeastRestrictive_Continue(c)); } } } /** Convenience function for use in places where we just don't care, what the identifier ranges are, or if it is start/continue. Returns: is character a member of least restrictive of all. */ bool isAnyIdentifierCharacter(dchar c) { import dmd.common.identifiertables; return isInRange!LeastRestrictive_OfAll(c); } /// unittest { assert(isAnyIdentifierCharacter('ğ')); } /** Convenience function for use in places where we just don't care, what the identifier ranges are. Returns: is character a member of restrictive Start */ bool isAnyStart(dchar c) { import dmd.common.identifiertables; return isInRange!LeastRestrictive_Start(c); } /// unittest { assert(isAnyStart('ğ')); } /** Convenience function for use in places where we just don't care, what the identifier ranges are. Returns: is character a member of least restrictive Continue */ bool isAnyContinue(dchar c) { import dmd.common.identifiertables; return isInRange!LeastRestrictive_Continue(c); } /// unittest { assert(isAnyContinue('ğ')); } /// UTF line separator enum LS = 0x2028; /// UTF paragraph separator enum PS = 0x2029; private { enum CMoctal = 0x1; enum CMhex = 0x2; enum CMidchar = 0x4; enum CMzerosecond = 0x8; enum CMdigitsecond = 0x10; enum CMsinglechar = 0x20; } /// bool isoctal(const char c) { return (cmtable[c] & CMoctal) != 0; } /// bool ishex(const char c) { return (cmtable[c] & CMhex) != 0; } /// bool isidchar(const char c) { return (cmtable[c] & CMidchar) != 0; } /// bool isZeroSecond(const char c) { return (cmtable[c] & CMzerosecond) != 0; } /// bool isDigitSecond(const char c) { return (cmtable[c] & CMdigitsecond) != 0; } /// bool issinglechar(const char c) { return (cmtable[c] & CMsinglechar) != 0; } /// bool c_isxdigit(const int c) { return (( c >= '0' && c <= '9') || ( c >= 'a' && c <= 'f') || ( c >= 'A' && c <= 'F')); } /// bool c_isalnum(const int c) { return (( c >= '0' && c <= '9') || ( c >= 'a' && c <= 'z') || ( c >= 'A' && c <= 'Z')); } extern(D) private: // originally from dmd.root.utf bool isInRange(alias Ranges)(dchar c) { size_t high = Ranges.length - 1; // Shortcut search if c is out of range size_t low = (c < Ranges[0][0] || Ranges[high][1] < c) ? high + 1 : 0; // Binary search while (low <= high) { const size_t mid = low + ((high - low) >> 1); if (c < Ranges[mid][0]) high = mid - 1; else if (Ranges[mid][1] < c) low = mid + 1; else { assert(Ranges[mid][0] <= c && c <= Ranges[mid][1]); return true; } } return false; } /******************************************** * Do our own char maps */ // originally from dmd.lexer (was private) static immutable cmtable = () { ubyte[256] table; foreach (const c; 0 .. table.length) { if ('0' <= c && c <= '7') table[c] |= CMoctal; if (c_isxdigit(c)) table[c] |= CMhex; if (c_isalnum(c) || c == '_') table[c] |= CMidchar; switch (c) { case 'x': case 'X': case 'b': case 'B': table[c] |= CMzerosecond; break; case '0': .. case '9': case 'e': case 'E': case 'f': case 'F': case 'l': case 'L': case 'p': case 'P': case 'u': case 'U': case 'i': case '.': case '_': table[c] |= CMzerosecond | CMdigitsecond; break; default: break; } switch (c) { case '\\': case '\n': case '\r': case 0: case 0x1A: case '\'': break; default: if (!(c & 0x80)) table[c] |= CMsinglechar; break; } } return table; }(); ldc-1.40.0-src/dmd/common/blake3.d0000644000000000000000000002541414727557031015204 0ustar rootrootmodule dmd.common.blake3; // based on https://github.com/oconnor663/blake3_reference_impl_c/blob/main/reference_impl.c @safe: nothrow: @nogc: /** * Implementation of Blake 3 hash function with streaming disabled * meaning we hash the whole buffer at once. * Input is split into 1KB Chunks which could be hashed independently. * That said, in the compiler I expect almost all inputs will be 1 chunk. * * Chunks get split into 64B Blocks which get hashed and then mixed together * * Params: * data = byte array to hash * Returns: Blake 3 hash of data **/ public ubyte[32] blake3(scope const ubyte[] data) { ChunkState state; CVStack cvStack; size_t cursor = 0; //greater because if it's == we still need to finalize the last //chunk while (data.length - cursor > ChunkLength) { const ubyte[] chunk = data[cursor .. cursor + ChunkLength]; updateChunkStateFull(state, chunk); //the chainingValue is now used to build up the merkle tree with other chunks addChunkToTree(cvStack, state.chainingValue, state.chunkCounter); //reset chunk, leaving chunkCounter as is state.chainingValue = IV; state.block[] = 0; state.blocksCompressed = 0; cursor += ChunkLength; } //now handle the final chunk which might not be full //handle all but last block while (data.length - cursor > BlockLength) { uint[16] blockWords = bytesToWords(data[cursor .. cursor + BlockLength]); with(state) { //can't be end since we handle the last block separately below uint flag = blocksCompressed == 0 ? ChunkStartFlag : 0; uint[16] compressed = compress(chainingValue, blockWords, 64, //full block chunkCounter, flag); chainingValue = compressed[0 .. 8]; blocksCompressed++; } cursor += BlockLength; } //handle last block, which could be the first block too uint flag = ChunkEndFlag | (state.blocksCompressed == 0 ? ChunkStartFlag : 0); //cast is safe bc this must be <= BlockLength const remainingBytes = cast(uint)(data.length - cursor); ubyte[BlockLength] lastBlock = 0; lastBlock[0 .. remainingBytes] = data[cursor .. $]; uint[16] lastBlockWords = bytesToWords(lastBlock); uint[16] compressed = compress(state.chainingValue, lastBlockWords, remainingBytes, state.chunkCounter, flag | (state.chunkCounter == 0 ? RootFlag : 0)); //merge all the remaining parent chunks in the tree uint[8] cv = compressed[0 .. 8]; while (!cvStack.empty) { const leftSib = cvStack.pop(); uint[16] blockWords; blockWords[0 .. 8] = leftSib[]; blockWords[8 .. $] = cv[]; cv[] = compress(IV, blockWords, 64, 0, ParentFlag | (cvStack.empty ? RootFlag : 0))[0 .. 8]; } //finally finalize the root chunk //convert words to bytes, little endian ubyte[32] ret; foreach (i, word; cv) { ret[i*4] = word & 0xFF; ret[i*4 + 1] = (word >> 8) & 0xFF; ret[i*4 + 2] = (word >> 16) & 0xFF; ret[i*4 + 3] = (word >> 24) & 0xFF; } return ret; } private: const static uint[8] IV = [0x6a09e667 ,0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]; const static uint[16] permutation = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]; enum BlockLength = 64; enum ChunkLength = 1024; enum ChunkStartFlag = 1; enum ChunkEndFlag = 2; enum ParentFlag = 4; enum RootFlag = 8; enum HashLength = 32; struct ChunkState { //initialized for a non-keyed hash //todo reorder for alignment/caching? uint[8] chainingValue = IV; ulong chunkCounter = 0; ubyte[32] block = 0; //up to 32 bytes ubyte blocksCompressed = 0; } uint rotateRight(uint x, uint n) { return (x >> n ) | ( x << (32 - n)); } //round function //a,b,c,d are elements of the state, m1, m2 are 2 words of the message void g(ref uint a, ref uint b, ref uint c, ref uint d, uint m1, uint m2) { a = a + b + m1; d = rotateRight(d ^ a, 16); c = c + d; b = rotateRight(b ^ c, 12); a = a + b + m2; d = rotateRight(d ^ a, 8); c = c + d; b = rotateRight(b ^ c, 7); } void roundFunction(ref uint[16] state, const ref uint[16] message) { //columns of 4x4 state matrix g(state[0], state[4], state[8], state[12], message[0], message[1]); g(state[1], state[5], state[9], state[13], message[2], message[3]); g(state[2], state[6], state[10], state[14], message[4], message[5]); g(state[3], state[7], state[11], state[15], message[6], message[7]); //diagonals g(state[0], state[5], state[10], state[15], message[8], message[9]); g(state[1], state[6], state[11], state[12], message[10], message[11]); g(state[2], state[7], state[8], state[13], message[12], message[13]); g(state[3], state[4], state[9], state[14], message[14], message[15]); } void permute(ref uint[16] block) { uint[16] permuted; foreach (i; 0 .. 16) { permuted[i] = block[permutation[i]]; } block = permuted; } //Note, for our implementation, I think only the first 8 words are ever used uint[16] compress(const ref uint[8] chainingValue, const ref uint[16] blockWords, uint blockLength, //in case the block isn't full ulong chunkCounter, uint flags) { uint[16] state; state[0 .. 8] = chainingValue[]; state[8 .. 12] = IV[0 .. 4]; state[12] = cast(uint)chunkCounter; state[13] = cast(uint)(chunkCounter >> 32); state[14] = blockLength; state[15] = flags; uint[16] block = blockWords; foreach (i; 0..6) { roundFunction(state, block); permute(block); } roundFunction(state, block); //skip permuation the last time foreach (i; 0 .. 8) { state[i] ^= state[i + 8]; state[i + 8] ^= chainingValue[i]; } return state; } //if block isn't full, only the first blockLength/4 words //will be filled in uint[16] bytesToWords(scope const ubyte[] block) { uint[16] ret = 0; foreach(i; 0 .. (block.length/4)) { ret[i] = block[4*i]; ret[i] |= (cast(uint)block[4*i + 1]) << 8; ret[i] |= (cast(uint)block[4*i + 2]) << 16; ret[i] |= (cast(uint)block[4*i + 3]) << 24; } return ret; } //full sized chunks, so no need to check for partial blocks, etc void updateChunkStateFull(ref ChunkState chunkState, scope const ubyte[] chunk) { for (size_t cursor = 0; cursor < ChunkLength; cursor += BlockLength) { uint[16] blockWords = bytesToWords(chunk[cursor .. cursor + BlockLength]); with(chunkState) { //first block gets ChunkStart, last gets ChunkEnd uint flag = blocksCompressed == 0 ? ChunkStartFlag : (blocksCompressed == (ChunkLength/BlockLength -1) ? ChunkEndFlag : 0); uint[16] compressed = compress(chainingValue, blockWords, 64, //full blocks chunkCounter, flag); //start flag //use the first 8 bytes of this chainingValue = compressed[0..8]; blocksCompressed++; } } chunkState.chunkCounter++; //Need to handle one more block if this one is partial } struct CVStack { uint[8][54] data; //enough space for a really deep tree uint size = 0; nothrow: @safe: @nogc: bool empty() const { return size == 0; } void push(uint[8] cv) { data[size] = cv; size++; } uint[8] pop() { return data[--size]; } } void addChunkToTree(ref CVStack cvStack, uint[8] cv, ulong totalChunks) { //if the total number of chunks ends in 0 bits, then this completed //a subtree, so we'll add as many parent nodes as we can up the tree //this method won't be called on the final chunk, so we never set the //ROOT flag while ( (totalChunks & 1) == 0) { const top = cvStack.pop(); uint[16] blockWords; blockWords[0 .. 8] = top[]; blockWords[8 .. $] = cv[]; cv = compress(IV, blockWords, 64, 0, ParentFlag)[0 .. 8]; totalChunks >>= 1; } cvStack.push(cv); } unittest { //test vectors from the spec. Run it on inputs of //[0, 1, 2, ... 249, 250, 0, 1, 2, ...] of various lengths //available here: //https://github.com/oconnor663/blake3_reference_impl_c/blob/main/test_vectors.json ubyte[N] testVector(size_t N)() { ubyte[N] ret; foreach (i, ref x; ret) { x = i % 251; } return ret; } const oneByte = testVector!1; //todo use hex literals once DMD bootstrap version gets big enough static const expectedOneByte = [0x2d,0x3a,0xde,0xdf,0xf1,0x1b,0x61,0xf1, 0x4c,0x88,0x6e,0x35,0xaf,0xa0,0x36,0x73, 0x6d,0xcd,0x87,0xa7,0x4d,0x27,0xb5,0xc1, 0x51,0x02,0x25,0xd0,0xf5,0x92,0xe2,0x13]; assert(blake3(oneByte) == expectedOneByte); static const expectedTwoBlocks = [0xde,0x1e,0x5f,0xa0,0xbe,0x70,0xdf,0x6d, 0x2b,0xe8,0xff,0xfd,0x0e,0x99,0xce,0xaa, 0x8e,0xb6,0xe8,0xc9,0x3a,0x63,0xf2,0xd8, 0xd1,0xc3,0x0e,0xcb,0x6b,0x26,0x3d,0xee]; const twoBlockInput = testVector!65; assert(blake3(twoBlockInput) == expectedTwoBlocks); static const expectedOneChunk = [0x42,0x21,0x47,0x39,0xf0,0x95,0xa4,0x06, 0xf3,0xfc,0x83,0xde,0xb8,0x89,0x74,0x4a, 0xc0,0x0d,0xf8,0x31,0xc1,0x0d,0xaa,0x55, 0x18,0x9b,0x5d,0x12,0x1c,0x85,0x5a,0xf7]; const barelyOneChunk = testVector!1024; assert(blake3(barelyOneChunk) == expectedOneChunk); static const expectedTwoChunks = [0xd0,0x02,0x78,0xae,0x47,0xeb,0x27,0xb3, 0x4f,0xae,0xcf,0x67,0xb4,0xfe,0x26,0x3f, 0x82,0xd5,0x41,0x29,0x16,0xc1,0xff,0xd9, 0x7c,0x8c,0xb7,0xfb,0x81,0x4b,0x84,0x44]; const barelyTwoChunks = testVector!1025; assert(blake3(barelyTwoChunks) == expectedTwoChunks); static const expectedBig = [0x62,0xb6,0x96,0x0e,0x1a,0x44,0xbc,0xc1, 0xeb,0x1a,0x61,0x1a,0x8d,0x62,0x35,0xb6, 0xb4,0xb7,0x8f,0x32,0xe7,0xab,0xc4,0xfb, 0x4c,0x6c,0xdc,0xce,0x94,0x89,0x5c,0x47]; const bigInput = testVector!31_744; assert(blake3(bigInput) == expectedBig); } ldc-1.40.0-src/dmd/common/charactertables.h0000644000000000000000000000120514727557031017166 0ustar rootroot/** * Character tables related to identifiers. * * Supports UAX31, C99, C11 and least restrictive (All). * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://cattermole.co.nz, Richard (Rikki) Andrew Cattermole) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/charactertables.d, common/charactertables.d) */ #pragma once struct IdentifierCharLookup final { bool(*isStart)(char32_t); bool(*isContinue)(char32_t); // constructor not provided here. }; ldc-1.40.0-src/dmd/common/int128.d0000644000000000000000000004777314727557031015104 0ustar rootroot/* 128 bit integer arithmetic. * * Not optimized for speed. * * Copyright: Copyright (C) 2022-2024 by The D Language Foundation, All Rights Reserved * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Authors: Walter Bright * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/int128.d, root/_int128.d) * Documentation: https://dlang.org/phobos/dmd_common_int128.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/int128.d */ module dmd.common.int128; nothrow: @safe: @nogc: alias I = long; alias U = ulong; enum Ubits = uint(U.sizeof * 8); align(16) struct Cent { U lo; // low 64 bits U hi; // high 64 bits } enum One = Cent(1); enum Zero = Cent(); enum MinusOne = neg(One); /***************************** * Test against 0 * Params: * c = Cent to test * Returns: * true if != 0 */ pure bool tst(Cent c) { return c.hi || c.lo; } /***************************** * Complement * Params: * c = Cent to complement * Returns: * complemented value */ pure Cent com(Cent c) { c.lo = ~c.lo; c.hi = ~c.hi; return c; } /***************************** * Negate * Params: * c = Cent to negate * Returns: * negated value */ pure Cent neg(Cent c) { if (c.lo == 0) c.hi = -c.hi; else { c.lo = -c.lo; c.hi = ~c.hi; } return c; } /***************************** * Increment * Params: * c = Cent to increment * Returns: * incremented value */ pure Cent inc(Cent c) { return add(c, One); } /***************************** * Decrement * Params: * c = Cent to decrement * Returns: * incremented value */ pure Cent dec(Cent c) { return sub(c, One); } /***************************** * Shift left one bit * Params: * c = Cent to shift * Returns: * shifted value */ pure Cent shl1(Cent c) { c.hi = (c.hi << 1) | (cast(I)c.lo < 0); c.lo <<= 1; return c; } /***************************** * Unsigned shift right one bit * Params: * c = Cent to shift * Returns: * shifted value */ pure Cent shr1(Cent c) { c.lo = (c.lo >> 1) | ((c.hi & 1) << (Ubits - 1)); c.hi >>= 1; return c; } /***************************** * Arithmetic shift right one bit * Params: * c = Cent to shift * Returns: * shifted value */ pure Cent sar1(Cent c) { c.lo = (c.lo >> 1) | ((c.hi & 1) << (Ubits - 1)); c.hi = cast(I)c.hi >> 1; return c; } /***************************** * Shift left n bits * Params: * c = Cent to shift * n = number of bits to shift * Returns: * shifted value */ pure Cent shl(Cent c, uint n) { if (n >= Ubits * 2) return Zero; if (n >= Ubits) { c.hi = c.lo << (n - Ubits); c.lo = 0; } else { c.hi = ((c.hi << n) | (c.lo >> (Ubits - n - 1) >> 1)); c.lo = c.lo << n; } return c; } /***************************** * Unsigned shift right n bits * Params: * c = Cent to shift * n = number of bits to shift * Returns: * shifted value */ pure Cent shr(Cent c, uint n) { if (n >= Ubits * 2) return Zero; if (n >= Ubits) { c.lo = c.hi >> (n - Ubits); c.hi = 0; } else { c.lo = ((c.lo >> n) | (c.hi << (Ubits - n - 1) << 1)); c.hi = c.hi >> n; } return c; } /***************************** * Arithmetic shift right n bits * Params: * c = Cent to shift * n = number of bits to shift * Returns: * shifted value */ pure Cent sar(Cent c, uint n) { const signmask = -(c.hi >> (Ubits - 1)); const signshift = (Ubits * 2) - n; c = shr(c, n); // Sign extend all bits beyond the precision of Cent. if (n >= Ubits * 2) { c.hi = signmask; c.lo = signmask; } else if (signshift >= Ubits * 2) { } else if (signshift >= Ubits) { c.hi &= ~(U.max << (signshift - Ubits)); c.hi |= signmask << (signshift - Ubits); } else { c.hi = signmask; c.lo &= ~(U.max << signshift); c.lo |= signmask << signshift; } return c; } /***************************** * Rotate left one bit * Params: * c = Cent to rotate * Returns: * rotated value */ pure Cent rol1(Cent c) { int carry = cast(I)c.hi < 0; c.hi = (c.hi << 1) | (cast(I)c.lo < 0); c.lo = (c.lo << 1) | carry; return c; } /***************************** * Rotate right one bit * Params: * c = Cent to rotate * Returns: * rotated value */ pure Cent ror1(Cent c) { int carry = c.lo & 1; c.lo = (c.lo >> 1) | (cast(U)(c.hi & 1) << (Ubits - 1)); c.hi = (c.hi >> 1) | (cast(U)carry << (Ubits - 1)); return c; } /***************************** * Rotate left n bits * Params: * c = Cent to rotate * n = number of bits to rotate * Returns: * rotated value */ pure Cent rol(Cent c, uint n) { n &= Ubits * 2 - 1; Cent l = shl(c, n); Cent r = shr(c, Ubits * 2 - n); return or(l, r); } /***************************** * Rotate right n bits * Params: * c = Cent to rotate * n = number of bits to rotate * Returns: * rotated value */ pure Cent ror(Cent c, uint n) { n &= Ubits * 2 - 1; Cent r = shr(c, n); Cent l = shl(c, Ubits * 2 - n); return or(r, l); } /**************************** * And c1 & c2. * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * c1 & c2 */ pure Cent and(Cent c1, Cent c2) { return Cent(c1.lo & c2.lo, c1.hi & c2.hi); } /**************************** * Or c1 | c2. * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * c1 | c2 */ pure Cent or(Cent c1, Cent c2) { return Cent(c1.lo | c2.lo, c1.hi | c2.hi); } /**************************** * Xor c1 ^ c2. * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * c1 ^ c2 */ pure Cent xor(Cent c1, Cent c2) { return Cent(c1.lo ^ c2.lo, c1.hi ^ c2.hi); } /**************************** * Add c1 to c2. * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * c1 + c2 */ pure Cent add(Cent c1, Cent c2) { U r = cast(U)(c1.lo + c2.lo); return Cent(r, cast(U)(c1.hi + c2.hi + (r < c1.lo))); } /**************************** * Subtract c2 from c1. * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * c1 - c2 */ pure Cent sub(Cent c1, Cent c2) { return add(c1, neg(c2)); } /**************************** * Multiply c1 * c2. * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * c1 * c2 */ pure Cent mul(Cent c1, Cent c2) { enum mulmask = (1UL << (Ubits / 2)) - 1; enum mulshift = Ubits / 2; // This algorithm splits the operands into 4 words, then computes and sums // the partial products of each part. const c2l0 = c2.lo & mulmask; const c2l1 = c2.lo >> mulshift; const c2h0 = c2.hi & mulmask; const c2h1 = c2.hi >> mulshift; const c1l0 = c1.lo & mulmask; U r0 = c1l0 * c2l0; U r1 = c1l0 * c2l1 + (r0 >> mulshift); U r2 = c1l0 * c2h0 + (r1 >> mulshift); U r3 = c1l0 * c2h1 + (r2 >> mulshift); const c1l1 = c1.lo >> mulshift; r1 = c1l1 * c2l0 + (r1 & mulmask); r2 = c1l1 * c2l1 + (r2 & mulmask) + (r1 >> mulshift); r3 = c1l1 * c2h0 + (r3 & mulmask) + (r2 >> mulshift); const c1h0 = c1.hi & mulmask; r2 = c1h0 * c2l0 + (r2 & mulmask); r3 = c1h0 * c2l1 + (r3 & mulmask) + (r2 >> mulshift); const c1h1 = c1.hi >> mulshift; r3 = c1h1 * c2l0 + (r3 & mulmask); return Cent((r0 & mulmask) + (r1 & mulmask) * (mulmask + 1), (r2 & mulmask) + (r3 & mulmask) * (mulmask + 1)); } /**************************** * Unsigned divide c1 / c2. * Params: * c1 = dividend * c2 = divisor * Returns: * quotient c1 / c2 */ pure Cent udiv(Cent c1, Cent c2) { Cent modulus; return udivmod(c1, c2, modulus); } /**************************** * Unsigned divide c1 / c2. The remainder after division is stored to modulus. * Params: * c1 = dividend * c2 = divisor * modulus = set to c1 % c2 * Returns: * quotient c1 / c2 */ pure Cent udivmod(Cent c1, Cent c2, out Cent modulus) { //printf("udiv c1(%llx,%llx) c2(%llx,%llx)\n", c1.lo, c1.hi, c2.lo, c2.hi); // Based on "Unsigned Doubleword Division" in Hacker's Delight import core.bitop; // Divides a 128-bit dividend by a 64-bit divisor. // The result must fit in 64 bits. static U udivmod128_64(Cent c1, U c2, out U modulus) { // We work in base 2^^32 enum base = 1UL << 32; enum divmask = (1UL << (Ubits / 2)) - 1; enum divshift = Ubits / 2; // Check for overflow and divide by 0 if (c1.hi >= c2) { modulus = 0UL; return ~0UL; } // Computes [num1 num0] / den static uint udiv96_64(U num1, uint num0, U den) { // Extract both digits of the denominator const den1 = cast(uint)(den >> divshift); const den0 = cast(uint)(den & divmask); // Estimate ret as num1 / den1, and then correct it U ret = num1 / den1; const t2 = (num1 % den1) * base + num0; const t1 = ret * den0; if (t1 > t2) ret -= (t1 - t2 > den) ? 2 : 1; return cast(uint)ret; } // Determine the normalization factor. We multiply c2 by this, so that its leading // digit is at least half base. In binary this means just shifting left by the number // of leading zeros, so that there's a 1 in the MSB. // We also shift number by the same amount. This cannot overflow because c1.hi < c2. const shift = (Ubits - 1) - bsr(c2); c2 <<= shift; U num2 = c1.hi; num2 <<= shift; num2 |= (c1.lo >> (-shift & 63)) & (-cast(I)shift >> 63); c1.lo <<= shift; // Extract the low digits of the numerator (after normalizing) const num1 = cast(uint)(c1.lo >> divshift); const num0 = cast(uint)(c1.lo & divmask); // Compute q1 = [num2 num1] / c2 const q1 = udiv96_64(num2, num1, c2); // Compute the true (partial) remainder const rem = num2 * base + num1 - q1 * c2; // Compute q0 = [rem num0] / c2 const q0 = udiv96_64(rem, num0, c2); modulus = (rem * base + num0 - q0 * c2) >> shift; return (cast(U)q1 << divshift) | q0; } // Special cases if (!tst(c2)) { // Divide by zero modulus = Zero; return com(modulus); } if (c1.hi == 0 && c2.hi == 0) { // Single precision divide modulus = Cent(c1.lo % c2.lo); return Cent(c1.lo / c2.lo); } if (c1.hi == 0) { // Numerator is smaller than the divisor modulus = c1; return Zero; } if (c2.hi == 0) { // Divisor is a 64-bit value, so we just need one 128/64 division. // If c1 / c2 would overflow, break c1 up into two halves. const q1 = (c1.hi < c2.lo) ? 0 : (c1.hi / c2.lo); if (q1) c1.hi = c1.hi % c2.lo; U rem; const q0 = udivmod128_64(c1, c2.lo, rem); modulus = Cent(rem); return Cent(q0, q1); } // Full cent precision division. // Here c2 >= 2^^64 // We know that c2.hi != 0, so count leading zeros is OK // We have 0 <= shift <= 63 const shift = (Ubits - 1) - bsr(c2.hi); // Normalize the divisor so its MSB is 1 // v1 = (c2 << shift) >> 64 U v1 = shl(c2, shift).hi; // To ensure no overflow. Cent u1 = shr1(c1); // Get quotient from divide unsigned operation. U rem_ignored; const q1 = udivmod128_64(u1, v1, rem_ignored); // Undo normalization and division of c1 by 2. Cent quotient = shr(shl(Cent(q1), shift), 63); // Make quotient correct or too small by 1 if (tst(quotient)) quotient = dec(quotient); // Now quotient is correct. // Compute rem = c1 - (quotient * c2); Cent rem = sub(c1, mul(quotient, c2)); // Check if remainder is larger than the divisor if (uge(rem, c2)) { // Increment quotient quotient = inc(quotient); // Subtract c2 from remainder rem = sub(rem, c2); } modulus = rem; //printf("quotient "); print(quotient); //printf("modulus "); print(modulus); return quotient; } /**************************** * Signed divide c1 / c2. * Params: * c1 = dividend * c2 = divisor * Returns: * quotient c1 / c2 */ pure Cent div(Cent c1, Cent c2) { Cent modulus; return divmod(c1, c2, modulus); } /**************************** * Signed divide c1 / c2. The remainder after division is stored to modulus. * Params: * c1 = dividend * c2 = divisor * modulus = set to c1 % c2 * Returns: * quotient c1 / c2 */ pure Cent divmod(Cent c1, Cent c2, out Cent modulus) { /* Muck about with the signs so we can use the unsigned divide */ if (cast(I)c1.hi < 0) { if (cast(I)c2.hi < 0) { Cent r = udivmod(neg(c1), neg(c2), modulus); modulus = neg(modulus); return r; } Cent r = neg(udivmod(neg(c1), c2, modulus)); modulus = neg(modulus); return r; } else if (cast(I)c2.hi < 0) { return neg(udivmod(c1, neg(c2), modulus)); } else return udivmod(c1, c2, modulus); } /**************************** * If c1 > c2 unsigned * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 > c2 */ pure bool ugt(Cent c1, Cent c2) { return (c1.hi == c2.hi) ? (c1.lo > c2.lo) : (c1.hi > c2.hi); } /**************************** * If c1 >= c2 unsigned * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 >= c2 */ pure bool uge(Cent c1, Cent c2) { return !ugt(c2, c1); } /**************************** * If c1 < c2 unsigned * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 < c2 */ pure bool ult(Cent c1, Cent c2) { return ugt(c2, c1); } /**************************** * If c1 <= c2 unsigned * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 <= c2 */ pure bool ule(Cent c1, Cent c2) { return !ugt(c1, c2); } /**************************** * If c1 > c2 signed * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 > c2 */ pure bool gt(Cent c1, Cent c2) { return (c1.hi == c2.hi) ? (c1.lo > c2.lo) : (cast(I)c1.hi > cast(I)c2.hi); } /**************************** * If c1 >= c2 signed * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 >= c2 */ pure bool ge(Cent c1, Cent c2) { return !gt(c2, c1); } /**************************** * If c1 < c2 signed * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 < c2 */ pure bool lt(Cent c1, Cent c2) { return gt(c2, c1); } /**************************** * If c1 <= c2 signed * Params: * c1 = operand 1 * c2 = operand 2 * Returns: * true if c1 <= c2 */ pure bool le(Cent c1, Cent c2) { return !gt(c1, c2); } /*******************************************************/ version (unittest) { version (none) { import core.stdc.stdio; @trusted void print(Cent c) { printf("%lld, %lld\n", cast(ulong)c.lo, cast(ulong)c.hi); printf("x%llx, x%llx\n", cast(ulong)c.lo, cast(ulong)c.hi); } } } unittest { const C0 = Zero; const C1 = One; const C2 = Cent(2); const C3 = Cent(3); const C5 = Cent(5); const C10 = Cent(10); const C20 = Cent(20); const C30 = Cent(30); const C100 = Cent(100); const Cm1 = neg(One); const Cm3 = neg(C3); const Cm10 = neg(C10); const C3_1 = Cent(1,3); const C3_2 = Cent(2,3); const C4_8 = Cent(8, 4); const C5_0 = Cent(0, 5); const C7_1 = Cent(1,7); const C7_9 = Cent(9,7); const C9_3 = Cent(3,9); const C10_0 = Cent(0,10); const C10_1 = Cent(1,10); const C10_3 = Cent(3,10); const C11_3 = Cent(3,11); const C20_0 = Cent(0,20); const C90_30 = Cent(30,90); const Cm10_0 = inc(com(C10_0)); // Cent(0, -10); const Cm10_1 = inc(com(C10_1)); // Cent(-1, -11); const Cm10_3 = inc(com(C10_3)); // Cent(-3, -11); const Cm20_0 = inc(com(C20_0)); // Cent(0, -20); enum Cs_3 = Cent(3, I.min); const Cbig_1 = Cent(0xa3ccac1832952398, 0xc3ac542864f652f8); const Cbig_2 = Cent(0x5267b85f8a42fc20, 0); const Cbig_3 = Cent(0xf0000000ffffffff, 0); /************************/ assert( ugt(C1, C0) ); assert( ult(C1, C2) ); assert( uge(C1, C0) ); assert( ule(C1, C2) ); assert( !ugt(C0, C1) ); assert( !ult(C2, C1) ); assert( !uge(C0, C1) ); assert( !ule(C2, C1) ); assert( !ugt(C1, C1) ); assert( !ult(C1, C1) ); assert( uge(C1, C1) ); assert( ule(C2, C2) ); assert( ugt(C10_3, C10_1) ); assert( ugt(C11_3, C10_3) ); assert( !ugt(C9_3, C10_3) ); assert( !ugt(C9_3, C9_3) ); assert( gt(C2, C1) ); assert( !gt(C1, C2) ); assert( !gt(C1, C1) ); assert( gt(C0, Cm1) ); assert( gt(Cm1, neg(C10))); assert( !gt(Cm1, Cm1) ); assert( !gt(Cm1, C0) ); assert( !lt(C2, C1) ); assert( !le(C2, C1) ); assert( ge(C2, C1) ); assert(neg(C10_0) == Cm10_0); assert(neg(C10_1) == Cm10_1); assert(neg(C10_3) == Cm10_3); assert(add(C7_1,C3_2) == C10_3); assert(sub(C1,C2) == Cm1); assert(inc(C3_1) == C3_2); assert(dec(C3_2) == C3_1); assert(shl(C10,0) == C10); assert(shl(C10,Ubits) == C10_0); assert(shl(C10,1) == C20); assert(shl(C10,Ubits * 2) == C0); assert(shr(C10_0,0) == C10_0); assert(shr(C10_0,Ubits) == C10); assert(shr(C10_0,Ubits - 1) == C20); assert(shr(C10_0,Ubits + 1) == C5); assert(shr(C10_0,Ubits * 2) == C0); assert(sar(C10_0,0) == C10_0); assert(sar(C10_0,Ubits) == C10); assert(sar(C10_0,Ubits - 1) == C20); assert(sar(C10_0,Ubits + 1) == C5); assert(sar(C10_0,Ubits * 2) == C0); assert(sar(Cm1,Ubits * 2) == Cm1); assert(shl1(C10) == C20); assert(shr1(C10_0) == C5_0); assert(sar1(C10_0) == C5_0); assert(sar1(Cm1) == Cm1); Cent modulus; assert(udiv(C10,C2) == C5); assert(udivmod(C10,C2, modulus) == C5); assert(modulus == C0); assert(udivmod(C10,C3, modulus) == C3); assert(modulus == C1); assert(udivmod(C10,C0, modulus) == Cm1); assert(modulus == C0); assert(udivmod(C2,C90_30, modulus) == C0); assert(modulus == C2); assert(udiv(mul(C90_30, C2), C2) == C90_30); assert(udiv(mul(C90_30, C2), C90_30) == C2); assert(div(C10,C3) == C3); assert(divmod( C10, C3, modulus) == C3); assert(modulus == C1); assert(divmod(Cm10, C3, modulus) == Cm3); assert(modulus == Cm1); assert(divmod( C10, Cm3, modulus) == Cm3); assert(modulus == C1); assert(divmod(Cm10, Cm3, modulus) == C3); assert(modulus == Cm1); assert(divmod(C2, C90_30, modulus) == C0); assert(modulus == C2); assert(div(mul(C90_30, C2), C2) == C90_30); assert(div(mul(C90_30, C2), C90_30) == C2); assert(divmod(Cbig_1, Cbig_2, modulus) == Cent(0x4496aa309d4d4a2f, U.max)); assert(modulus == Cent(0xd83203d0fdc799b8, U.max)); assert(udivmod(Cbig_1, Cbig_2, modulus) == Cent(0x5fe0e9bace2bedad, 2)); assert(modulus == Cent(0x2c923125a68721f8, 0)); assert(div(Cbig_1, Cbig_3) == Cent(0xbfa6c02b5aff8b86, U.max)); assert(udiv(Cbig_1, Cbig_3) == Cent(0xd0b7d13b48cb350f, 0)); assert(mul(Cm10, C1) == Cm10); assert(mul(C1, Cm10) == Cm10); assert(mul(C9_3, C10) == C90_30); assert(mul(Cs_3, C10) == C30); assert(mul(Cm10, Cm10) == C100); assert(mul(C20_0, Cm1) == Cm20_0); assert( or(C4_8, C3_1) == C7_9); assert(and(C4_8, C7_9) == C4_8); assert(xor(C4_8, C7_9) == C3_1); assert(rol(Cm1, 1) == Cm1); assert(ror(Cm1, 45) == Cm1); assert(rol(ror(C7_9, 5), 5) == C7_9); assert(rol(C7_9, 1) == rol1(C7_9)); assert(ror(C7_9, 1) == ror1(C7_9)); } ldc-1.40.0-src/dmd/common/smallbuffer.d0000644000000000000000000001477414727557031016354 0ustar rootroot/** * Common string functions including filename manipulation. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: Walter Bright, https://www.digitalmars.com * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/smallbuffer.d, common/_smallbuffer.d) * Documentation: https://dlang.org/phobos/dmd_common_smallbuffer.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/smallbuffer */ module dmd.common.smallbuffer; nothrow: /** Defines a temporary array of `Element`s using a fixed-length buffer as back store. If the length of the buffer suffices, it is readily used. Otherwise, `malloc` is used to allocate memory for the array and `free` is used for deallocation in the destructor. This type is meant to use exclusively as an automatic variable. It is not default constructible or copyable. */ struct SmallBuffer(Element) { import core.stdc.stdlib : malloc, free; private Element[] _extent; private bool needsFree; nothrow: @nogc: @disable this(); // no default ctor @disable this(ref const SmallBuffer!Element); // noncopyable, nonassignable /*********** * Construct a SmallBuffer * Params: * len = number of elements in array * buffer = slice to use as backing-store, if len will fit in it */ scope this(size_t len, return scope Element[] buffer) { if (len <= buffer.length) { _extent = buffer[0 .. len]; } else { assert(len < size_t.max / (2 * Element.sizeof)); _extent = (cast(typeof(_extent.ptr)) malloc(len * Element.sizeof))[0 .. len]; _extent.ptr || assert(0, "Out of memory."); needsFree = true; } assert(this.length == len); } ~this() { if (needsFree) free(_extent.ptr); } /****** * Resize existing SmallBuffer. * Params: * len = number of elements after resize */ scope void create(size_t len) { if (len <= _extent.length) { _extent = _extent[0 .. len]; // reuse existing storage } else { __dtor(); assert(len < size_t.max / Element.sizeof); _extent = (cast(typeof(_extent.ptr)) malloc(len * Element.sizeof))[0 .. len]; _extent.ptr || assert(0, "Out of memory."); needsFree = true; } assert(this.length == len); } // Force accesses to extent to be scoped. scope inout extent() { return _extent; } alias extent this; } /// ditto unittest { char[230] buf = void; auto a = SmallBuffer!char(10, buf); assert(a[] is buf[0 .. 10]); auto b = SmallBuffer!char(1000, buf); assert(b[] !is buf[]); b.create(1000); assert(b.length == 1000); assert(b[] !is buf[]); } /** * (Windows only) Converts a narrow string to a wide string using `buffer` as strorage. * Params: * narrow = string to be converted * buffer = where to place the converted string * Returns: a slice of `buffer` containing the converted string. A zero follows the slice. */ version(Windows) wchar[] toWStringz(scope const(char)[] narrow, ref SmallBuffer!wchar buffer) nothrow { if (narrow is null) return null; size_t charsToWchars(scope const(char)[] narrow, scope wchar[] buffer) { // https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar import core.sys.windows.winnls : MultiByteToWideChar, CP_ACP; version (IN_LLVM) { import dmd.common.file : CodePage; return MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length); } else { return MultiByteToWideChar(CP_ACP, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length); } } size_t length = charsToWchars(narrow, buffer[]); if (length >= buffer.length) // not enough room in buffer[] { buffer.create(length + 1); // extend buffer length length = charsToWchars(narrow, buffer[]); // try again assert(length < buffer.length); } buffer[length] = 0; return buffer[0 .. length]; } /************************************** * Converts a path to one suitable to be passed to Win32 API * functions that can deal with paths longer than 248 * characters then calls the supplied function on it. * * Params: * path = The Path to call F on. * * Returns: * The result of calling F on path. * * References: * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx */ version(Windows) auto extendedPathThen(alias F)(const(char)[] path) { import core.sys.windows.winbase; import core.sys.windows.winnt; if (!path.length) return F((wchar[]).init); wchar[1024] buf = void; auto store = SmallBuffer!wchar(buf.length, buf); auto wpath = toWStringz(path, store); // GetFullPathNameW expects a sized buffer to store the result in. Since we don't // know how large it has to be, we pass in null and get the needed buffer length // as the return code. const pathLength = GetFullPathNameW(&wpath[0], 0 /*length8*/, null /*output buffer*/, null /*filePartBuffer*/); if (pathLength == 0) { return F((wchar[]).init); } // wpath is the UTF16 version of path, but to be able to use // extended paths, we need to prefix with `\\?\` and the absolute // path. static immutable prefix = `\\?\`w; // prefix only needed for long names and non-UNC names const needsPrefix = pathLength >= MAX_PATH && (wpath[0] != '\\' || wpath[1] != '\\'); const prefixLength = needsPrefix ? prefix.length : 0; // +1 for the null terminator const bufferLength = pathLength + prefixLength + 1; wchar[1024] absBuf = void; auto absPath = SmallBuffer!wchar(bufferLength, absBuf); absPath[0 .. prefixLength] = prefix[0 .. prefixLength]; const absPathRet = GetFullPathNameW(&wpath[0], cast(uint)(absPath.length - prefixLength - 1), &absPath[prefixLength], null /*filePartBuffer*/); if (absPathRet == 0 || absPathRet > absPath.length - prefixLength) { return F((wchar[]).init); } absPath[$ - 1] = '\0'; // Strip null terminator from the slice return F(absPath[0 .. $ - 1]); } ldc-1.40.0-src/dmd/common/README.md0000644000000000000000000000171714727557031015155 0ustar rootroot# Table of contents | File | Purpose | |------------------------------------------------------------------------------------|-----------------------------------------------------------------| | [bitfields.d](https://github.com/dlang/dmd/blob/master/compiler/src/dmd/common/bitfields.d) | Pack multiple boolean fields into bit fields | | [file.d](https://github.com/dlang/dmd/blob/master/compiler/src/dmd/common/file.d) | Functions and objects dedicated to file I/O and management | | [outbuffer.d](https://github.com/dlang/dmd/blob/master/compiler/src/dmd/common/outbuffer.d) | An expandable buffer in which you can write text or binary data | | [string.d](https://github.com/dlang/dmd/blob/master/compiler/src/dmd/common/smallbuffer.d) | Common string functions including filename manipulation | ldc-1.40.0-src/dmd/common/identifiertables.d0000644000000000000000000026642114727557031017365 0ustar rootroot// Generated by compiler/tools/unicode_tables.d DO NOT MODIFY!!! module dmd.common.identifiertables; /** UAX31 profile Start Entries: 136892 */ static immutable dchar[2][] UAX31_Start = [ [0xAA, 0xAA], [0xB5, 0xB5], [0xBA, 0xBA], [0xC0, 0xD6], [0xD8, 0xF6], [0xF8, 0x2C1], [0x2C6, 0x2D1], [0x2E0, 0x2E4], [0x2EC, 0x2EC], [0x2EE, 0x2EE], [0x370, 0x374], [0x376, 0x377], [0x37B, 0x37D], [0x37F, 0x37F], [0x386, 0x386], [0x388, 0x38A], [0x38C, 0x38C], [0x38E, 0x3A1], [0x3A3, 0x3F5], [0x3F7, 0x481], [0x48A, 0x52F], [0x531, 0x556], [0x559, 0x559], [0x560, 0x588], [0x5D0, 0x5EA], [0x5EF, 0x5F2], [0x620, 0x64A], [0x66E, 0x66F], [0x671, 0x6D3], [0x6D5, 0x6D5], [0x6E5, 0x6E6], [0x6EE, 0x6EF], [0x6FA, 0x6FC], [0x6FF, 0x6FF], [0x710, 0x710], [0x712, 0x72F], [0x74D, 0x7A5], [0x7B1, 0x7B1], [0x7CA, 0x7EA], [0x7F4, 0x7F5], [0x7FA, 0x7FA], [0x800, 0x815], [0x81A, 0x81A], [0x824, 0x824], [0x828, 0x828], [0x840, 0x858], [0x860, 0x86A], [0x870, 0x887], [0x889, 0x88E], [0x8A0, 0x8C9], [0x904, 0x939], [0x93D, 0x93D], [0x950, 0x950], [0x958, 0x961], [0x971, 0x980], [0x985, 0x98C], [0x98F, 0x990], [0x993, 0x9A8], [0x9AA, 0x9B0], [0x9B2, 0x9B2], [0x9B6, 0x9B9], [0x9BD, 0x9BD], [0x9CE, 0x9CE], [0x9DC, 0x9DD], [0x9DF, 0x9E1], [0x9F0, 0x9F1], [0x9FC, 0x9FC], [0xA05, 0xA0A], [0xA0F, 0xA10], [0xA13, 0xA28], [0xA2A, 0xA30], [0xA32, 0xA33], [0xA35, 0xA36], [0xA38, 0xA39], [0xA59, 0xA5C], [0xA5E, 0xA5E], [0xA72, 0xA74], [0xA85, 0xA8D], [0xA8F, 0xA91], [0xA93, 0xAA8], [0xAAA, 0xAB0], [0xAB2, 0xAB3], [0xAB5, 0xAB9], [0xABD, 0xABD], [0xAD0, 0xAD0], [0xAE0, 0xAE1], [0xAF9, 0xAF9], [0xB05, 0xB0C], [0xB0F, 0xB10], [0xB13, 0xB28], [0xB2A, 0xB30], [0xB32, 0xB33], [0xB35, 0xB39], [0xB3D, 0xB3D], [0xB5C, 0xB5D], [0xB5F, 0xB61], [0xB71, 0xB71], [0xB83, 0xB83], [0xB85, 0xB8A], [0xB8E, 0xB90], [0xB92, 0xB95], [0xB99, 0xB9A], [0xB9C, 0xB9C], [0xB9E, 0xB9F], [0xBA3, 0xBA4], [0xBA8, 0xBAA], [0xBAE, 0xBB9], [0xBD0, 0xBD0], [0xC05, 0xC0C], [0xC0E, 0xC10], [0xC12, 0xC28], [0xC2A, 0xC39], [0xC3D, 0xC3D], [0xC58, 0xC5A], [0xC5D, 0xC5D], [0xC60, 0xC61], [0xC80, 0xC80], [0xC85, 0xC8C], [0xC8E, 0xC90], [0xC92, 0xCA8], [0xCAA, 0xCB3], [0xCB5, 0xCB9], [0xCBD, 0xCBD], [0xCDD, 0xCDE], [0xCE0, 0xCE1], [0xCF1, 0xCF2], [0xD04, 0xD0C], [0xD0E, 0xD10], [0xD12, 0xD3A], [0xD3D, 0xD3D], [0xD4E, 0xD4E], [0xD54, 0xD56], [0xD5F, 0xD61], [0xD7A, 0xD7F], [0xD85, 0xD96], [0xD9A, 0xDB1], [0xDB3, 0xDBB], [0xDBD, 0xDBD], [0xDC0, 0xDC6], [0xE01, 0xE30], [0xE32, 0xE32], [0xE40, 0xE46], [0xE81, 0xE82], [0xE84, 0xE84], [0xE86, 0xE8A], [0xE8C, 0xEA3], [0xEA5, 0xEA5], [0xEA7, 0xEB0], [0xEB2, 0xEB2], [0xEBD, 0xEBD], [0xEC0, 0xEC4], [0xEC6, 0xEC6], [0xEDC, 0xEDF], [0xF00, 0xF00], [0xF40, 0xF47], [0xF49, 0xF6C], [0xF88, 0xF8C], [0x1000, 0x102A], [0x103F, 0x103F], [0x1050, 0x1055], [0x105A, 0x105D], [0x1061, 0x1061], [0x1065, 0x1066], [0x106E, 0x1070], [0x1075, 0x1081], [0x108E, 0x108E], [0x10A0, 0x10C5], [0x10C7, 0x10C7], [0x10CD, 0x10CD], [0x10D0, 0x10FA], [0x10FC, 0x1248], [0x124A, 0x124D], [0x1250, 0x1256], [0x1258, 0x1258], [0x125A, 0x125D], [0x1260, 0x1288], [0x128A, 0x128D], [0x1290, 0x12B0], [0x12B2, 0x12B5], [0x12B8, 0x12BE], [0x12C0, 0x12C0], [0x12C2, 0x12C5], [0x12C8, 0x12D6], [0x12D8, 0x1310], [0x1312, 0x1315], [0x1318, 0x135A], [0x1380, 0x138F], [0x13A0, 0x13F5], [0x13F8, 0x13FD], [0x1401, 0x166C], [0x166F, 0x167F], [0x1681, 0x169A], [0x16A0, 0x16EA], [0x16EE, 0x16F8], [0x1700, 0x1711], [0x171F, 0x1731], [0x1740, 0x1751], [0x1760, 0x176C], [0x176E, 0x1770], [0x1780, 0x17B3], [0x17D7, 0x17D7], [0x17DC, 0x17DC], [0x1820, 0x1878], [0x1880, 0x18A8], [0x18AA, 0x18AA], [0x18B0, 0x18F5], [0x1900, 0x191E], [0x1950, 0x196D], [0x1970, 0x1974], [0x1980, 0x19AB], [0x19B0, 0x19C9], [0x1A00, 0x1A16], [0x1A20, 0x1A54], [0x1AA7, 0x1AA7], [0x1B05, 0x1B33], [0x1B45, 0x1B4C], [0x1B83, 0x1BA0], [0x1BAE, 0x1BAF], [0x1BBA, 0x1BE5], [0x1C00, 0x1C23], [0x1C4D, 0x1C4F], [0x1C5A, 0x1C7D], [0x1C80, 0x1C88], [0x1C90, 0x1CBA], [0x1CBD, 0x1CBF], [0x1CE9, 0x1CEC], [0x1CEE, 0x1CF3], [0x1CF5, 0x1CF6], [0x1CFA, 0x1CFA], [0x1D00, 0x1DBF], [0x1E00, 0x1F15], [0x1F18, 0x1F1D], [0x1F20, 0x1F45], [0x1F48, 0x1F4D], [0x1F50, 0x1F57], [0x1F59, 0x1F59], [0x1F5B, 0x1F5B], [0x1F5D, 0x1F5D], [0x1F5F, 0x1F7D], [0x1F80, 0x1FB4], [0x1FB6, 0x1FBC], [0x1FBE, 0x1FBE], [0x1FC2, 0x1FC4], [0x1FC6, 0x1FCC], [0x1FD0, 0x1FD3], [0x1FD6, 0x1FDB], [0x1FE0, 0x1FEC], [0x1FF2, 0x1FF4], [0x1FF6, 0x1FFC], [0x2071, 0x2071], [0x207F, 0x207F], [0x2090, 0x209C], [0x2102, 0x2102], [0x2107, 0x2107], [0x210A, 0x2113], [0x2115, 0x2115], [0x2118, 0x211D], [0x2124, 0x2124], [0x2126, 0x2126], [0x2128, 0x2128], [0x212A, 0x2139], [0x213C, 0x213F], [0x2145, 0x2149], [0x214E, 0x214E], [0x2160, 0x2188], [0x2C00, 0x2CE4], [0x2CEB, 0x2CEE], [0x2CF2, 0x2CF3], [0x2D00, 0x2D25], [0x2D27, 0x2D27], [0x2D2D, 0x2D2D], [0x2D30, 0x2D67], [0x2D6F, 0x2D6F], [0x2D80, 0x2D96], [0x2DA0, 0x2DA6], [0x2DA8, 0x2DAE], [0x2DB0, 0x2DB6], [0x2DB8, 0x2DBE], [0x2DC0, 0x2DC6], [0x2DC8, 0x2DCE], [0x2DD0, 0x2DD6], [0x2DD8, 0x2DDE], [0x3005, 0x3007], [0x3021, 0x3029], [0x3031, 0x3035], [0x3038, 0x303C], [0x3041, 0x3096], [0x309D, 0x309F], [0x30A1, 0x30FA], [0x30FC, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], [0x31A0, 0x31BF], [0x31F0, 0x31FF], [0x3400, 0x4DBF], [0x4E00, 0xA48C], [0xA4D0, 0xA4FD], [0xA500, 0xA60C], [0xA610, 0xA61F], [0xA62A, 0xA62B], [0xA640, 0xA66E], [0xA67F, 0xA69D], [0xA6A0, 0xA6EF], [0xA717, 0xA71F], [0xA722, 0xA788], [0xA78B, 0xA7CA], [0xA7D0, 0xA7D1], [0xA7D3, 0xA7D3], [0xA7D5, 0xA7D9], [0xA7F2, 0xA801], [0xA803, 0xA805], [0xA807, 0xA80A], [0xA80C, 0xA822], [0xA840, 0xA873], [0xA882, 0xA8B3], [0xA8F2, 0xA8F7], [0xA8FB, 0xA8FB], [0xA8FD, 0xA8FE], [0xA90A, 0xA925], [0xA930, 0xA946], [0xA960, 0xA97C], [0xA984, 0xA9B2], [0xA9CF, 0xA9CF], [0xA9E0, 0xA9E4], [0xA9E6, 0xA9EF], [0xA9FA, 0xA9FE], [0xAA00, 0xAA28], [0xAA40, 0xAA42], [0xAA44, 0xAA4B], [0xAA60, 0xAA76], [0xAA7A, 0xAA7A], [0xAA7E, 0xAAAF], [0xAAB1, 0xAAB1], [0xAAB5, 0xAAB6], [0xAAB9, 0xAABD], [0xAAC0, 0xAAC0], [0xAAC2, 0xAAC2], [0xAADB, 0xAADD], [0xAAE0, 0xAAEA], [0xAAF2, 0xAAF4], [0xAB01, 0xAB06], [0xAB09, 0xAB0E], [0xAB11, 0xAB16], [0xAB20, 0xAB26], [0xAB28, 0xAB2E], [0xAB30, 0xAB5A], [0xAB5C, 0xAB69], [0xAB70, 0xABE2], [0xAC00, 0xD7A3], [0xD7B0, 0xD7C6], [0xD7CB, 0xD7FB], [0xF900, 0xFA6D], [0xFA70, 0xFAD9], [0xFB00, 0xFB06], [0xFB13, 0xFB17], [0xFB1D, 0xFB1D], [0xFB1F, 0xFB28], [0xFB2A, 0xFB36], [0xFB38, 0xFB3C], [0xFB3E, 0xFB3E], [0xFB40, 0xFB41], [0xFB43, 0xFB44], [0xFB46, 0xFBB1], [0xFBD3, 0xFC5D], [0xFC64, 0xFD3D], [0xFD50, 0xFD8F], [0xFD92, 0xFDC7], [0xFDF0, 0xFDF9], [0xFE71, 0xFE71], [0xFE73, 0xFE73], [0xFE77, 0xFE77], [0xFE79, 0xFE79], [0xFE7B, 0xFE7B], [0xFE7D, 0xFE7D], [0xFE7F, 0xFEFC], [0xFF21, 0xFF3A], [0xFF41, 0xFF5A], [0xFF66, 0xFF9D], [0xFFA0, 0xFFBE], [0xFFC2, 0xFFC7], [0xFFCA, 0xFFCF], [0xFFD2, 0xFFD7], [0xFFDA, 0xFFDC], [0x10000, 0x1000B], [0x1000D, 0x10026], [0x10028, 0x1003A], [0x1003C, 0x1003D], [0x1003F, 0x1004D], [0x10050, 0x1005D], [0x10080, 0x100FA], [0x10140, 0x10174], [0x10280, 0x1029C], [0x102A0, 0x102D0], [0x10300, 0x1031F], [0x1032D, 0x1034A], [0x10350, 0x10375], [0x10380, 0x1039D], [0x103A0, 0x103C3], [0x103C8, 0x103CF], [0x103D1, 0x103D5], [0x10400, 0x1049D], [0x104B0, 0x104D3], [0x104D8, 0x104FB], [0x10500, 0x10527], [0x10530, 0x10563], [0x10570, 0x1057A], [0x1057C, 0x1058A], [0x1058C, 0x10592], [0x10594, 0x10595], [0x10597, 0x105A1], [0x105A3, 0x105B1], [0x105B3, 0x105B9], [0x105BB, 0x105BC], [0x10600, 0x10736], [0x10740, 0x10755], [0x10760, 0x10767], [0x10780, 0x10785], [0x10787, 0x107B0], [0x107B2, 0x107BA], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080A, 0x10835], [0x10837, 0x10838], [0x1083C, 0x1083C], [0x1083F, 0x10855], [0x10860, 0x10876], [0x10880, 0x1089E], [0x108E0, 0x108F2], [0x108F4, 0x108F5], [0x10900, 0x10915], [0x10920, 0x10939], [0x10980, 0x109B7], [0x109BE, 0x109BF], [0x10A00, 0x10A00], [0x10A10, 0x10A13], [0x10A15, 0x10A17], [0x10A19, 0x10A35], [0x10A60, 0x10A7C], [0x10A80, 0x10A9C], [0x10AC0, 0x10AC7], [0x10AC9, 0x10AE4], [0x10B00, 0x10B35], [0x10B40, 0x10B55], [0x10B60, 0x10B72], [0x10B80, 0x10B91], [0x10C00, 0x10C48], [0x10C80, 0x10CB2], [0x10CC0, 0x10CF2], [0x10D00, 0x10D23], [0x10E80, 0x10EA9], [0x10EB0, 0x10EB1], [0x10F00, 0x10F1C], [0x10F27, 0x10F27], [0x10F30, 0x10F45], [0x10F70, 0x10F81], [0x10FB0, 0x10FC4], [0x10FE0, 0x10FF6], [0x11003, 0x11037], [0x11071, 0x11072], [0x11075, 0x11075], [0x11083, 0x110AF], [0x110D0, 0x110E8], [0x11103, 0x11126], [0x11144, 0x11144], [0x11147, 0x11147], [0x11150, 0x11172], [0x11176, 0x11176], [0x11183, 0x111B2], [0x111C1, 0x111C4], [0x111DA, 0x111DA], [0x111DC, 0x111DC], [0x11200, 0x11211], [0x11213, 0x1122B], [0x1123F, 0x11240], [0x11280, 0x11286], [0x11288, 0x11288], [0x1128A, 0x1128D], [0x1128F, 0x1129D], [0x1129F, 0x112A8], [0x112B0, 0x112DE], [0x11305, 0x1130C], [0x1130F, 0x11310], [0x11313, 0x11328], [0x1132A, 0x11330], [0x11332, 0x11333], [0x11335, 0x11339], [0x1133D, 0x1133D], [0x11350, 0x11350], [0x1135D, 0x11361], [0x11400, 0x11434], [0x11447, 0x1144A], [0x1145F, 0x11461], [0x11480, 0x114AF], [0x114C4, 0x114C5], [0x114C7, 0x114C7], [0x11580, 0x115AE], [0x115D8, 0x115DB], [0x11600, 0x1162F], [0x11644, 0x11644], [0x11680, 0x116AA], [0x116B8, 0x116B8], [0x11700, 0x1171A], [0x11740, 0x11746], [0x11800, 0x1182B], [0x118A0, 0x118DF], [0x118FF, 0x11906], [0x11909, 0x11909], [0x1190C, 0x11913], [0x11915, 0x11916], [0x11918, 0x1192F], [0x1193F, 0x1193F], [0x11941, 0x11941], [0x119A0, 0x119A7], [0x119AA, 0x119D0], [0x119E1, 0x119E1], [0x119E3, 0x119E3], [0x11A00, 0x11A00], [0x11A0B, 0x11A32], [0x11A3A, 0x11A3A], [0x11A50, 0x11A50], [0x11A5C, 0x11A89], [0x11A9D, 0x11A9D], [0x11AB0, 0x11AF8], [0x11C00, 0x11C08], [0x11C0A, 0x11C2E], [0x11C40, 0x11C40], [0x11C72, 0x11C8F], [0x11D00, 0x11D06], [0x11D08, 0x11D09], [0x11D0B, 0x11D30], [0x11D46, 0x11D46], [0x11D60, 0x11D65], [0x11D67, 0x11D68], [0x11D6A, 0x11D89], [0x11D98, 0x11D98], [0x11EE0, 0x11EF2], [0x11F02, 0x11F02], [0x11F04, 0x11F10], [0x11F12, 0x11F33], [0x11FB0, 0x11FB0], [0x12000, 0x12399], [0x12400, 0x1246E], [0x12480, 0x12543], [0x12F90, 0x12FF0], [0x13000, 0x1342F], [0x13441, 0x13446], [0x14400, 0x14646], [0x16800, 0x16A38], [0x16A40, 0x16A5E], [0x16A70, 0x16ABE], [0x16AD0, 0x16AED], [0x16B00, 0x16B2F], [0x16B40, 0x16B43], [0x16B63, 0x16B77], [0x16B7D, 0x16B8F], [0x16E40, 0x16E7F], [0x16F00, 0x16F4A], [0x16F50, 0x16F50], [0x16F93, 0x16F9F], [0x16FE0, 0x16FE1], [0x16FE3, 0x16FE3], [0x17000, 0x187F7], [0x18800, 0x18CD5], [0x18D00, 0x18D08], [0x1AFF0, 0x1AFF3], [0x1AFF5, 0x1AFFB], [0x1AFFD, 0x1AFFE], [0x1B000, 0x1B122], [0x1B132, 0x1B132], [0x1B150, 0x1B152], [0x1B155, 0x1B155], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1BC00, 0x1BC6A], [0x1BC70, 0x1BC7C], [0x1BC80, 0x1BC88], [0x1BC90, 0x1BC99], [0x1D400, 0x1D454], [0x1D456, 0x1D49C], [0x1D49E, 0x1D49F], [0x1D4A2, 0x1D4A2], [0x1D4A5, 0x1D4A6], [0x1D4A9, 0x1D4AC], [0x1D4AE, 0x1D4B9], [0x1D4BB, 0x1D4BB], [0x1D4BD, 0x1D4C3], [0x1D4C5, 0x1D505], [0x1D507, 0x1D50A], [0x1D50D, 0x1D514], [0x1D516, 0x1D51C], [0x1D51E, 0x1D539], [0x1D53B, 0x1D53E], [0x1D540, 0x1D544], [0x1D546, 0x1D546], [0x1D54A, 0x1D550], [0x1D552, 0x1D6A5], [0x1D6A8, 0x1D6C0], [0x1D6C2, 0x1D6DA], [0x1D6DC, 0x1D6FA], [0x1D6FC, 0x1D714], [0x1D716, 0x1D734], [0x1D736, 0x1D74E], [0x1D750, 0x1D76E], [0x1D770, 0x1D788], [0x1D78A, 0x1D7A8], [0x1D7AA, 0x1D7C2], [0x1D7C4, 0x1D7CB], [0x1DF00, 0x1DF1E], [0x1DF25, 0x1DF2A], [0x1E030, 0x1E06D], [0x1E100, 0x1E12C], [0x1E137, 0x1E13D], [0x1E14E, 0x1E14E], [0x1E290, 0x1E2AD], [0x1E2C0, 0x1E2EB], [0x1E4D0, 0x1E4EB], [0x1E7E0, 0x1E7E6], [0x1E7E8, 0x1E7EB], [0x1E7ED, 0x1E7EE], [0x1E7F0, 0x1E7FE], [0x1E800, 0x1E8C4], [0x1E900, 0x1E943], [0x1E94B, 0x1E94B], [0x1EE00, 0x1EE03], [0x1EE05, 0x1EE1F], [0x1EE21, 0x1EE22], [0x1EE24, 0x1EE24], [0x1EE27, 0x1EE27], [0x1EE29, 0x1EE32], [0x1EE34, 0x1EE37], [0x1EE39, 0x1EE39], [0x1EE3B, 0x1EE3B], [0x1EE42, 0x1EE42], [0x1EE47, 0x1EE47], [0x1EE49, 0x1EE49], [0x1EE4B, 0x1EE4B], [0x1EE4D, 0x1EE4F], [0x1EE51, 0x1EE52], [0x1EE54, 0x1EE54], [0x1EE57, 0x1EE57], [0x1EE59, 0x1EE59], [0x1EE5B, 0x1EE5B], [0x1EE5D, 0x1EE5D], [0x1EE5F, 0x1EE5F], [0x1EE61, 0x1EE62], [0x1EE64, 0x1EE64], [0x1EE67, 0x1EE6A], [0x1EE6C, 0x1EE72], [0x1EE74, 0x1EE77], [0x1EE79, 0x1EE7C], [0x1EE7E, 0x1EE7E], [0x1EE80, 0x1EE89], [0x1EE8B, 0x1EE9B], [0x1EEA1, 0x1EEA3], [0x1EEA5, 0x1EEA9], [0x1EEAB, 0x1EEBB], [0x20000, 0x2A6DF], [0x2A700, 0x2B739], [0x2B740, 0x2B81D], [0x2B820, 0x2CEA1], [0x2CEB0, 0x2EBE0], [0x2EBF0, 0x2EE5D], [0x2F800, 0x2FA1D], [0x30000, 0x3134A], [0x31350, 0x323AF], ]; /** UAX31 profile Continue Entries: 140026 */ static immutable dchar[2][] UAX31_Continue = [ [0xAA, 0xAA], [0xB5, 0xB5], [0xB7, 0xB7], [0xBA, 0xBA], [0xC0, 0xD6], [0xD8, 0xF6], [0xF8, 0x2C1], [0x2C6, 0x2D1], [0x2E0, 0x2E4], [0x2EC, 0x2EC], [0x2EE, 0x2EE], [0x300, 0x374], [0x376, 0x377], [0x37B, 0x37D], [0x37F, 0x37F], [0x386, 0x38A], [0x38C, 0x38C], [0x38E, 0x3A1], [0x3A3, 0x3F5], [0x3F7, 0x481], [0x483, 0x487], [0x48A, 0x52F], [0x531, 0x556], [0x559, 0x559], [0x560, 0x588], [0x591, 0x5BD], [0x5BF, 0x5BF], [0x5C1, 0x5C2], [0x5C4, 0x5C5], [0x5C7, 0x5C7], [0x5D0, 0x5EA], [0x5EF, 0x5F2], [0x610, 0x61A], [0x620, 0x669], [0x66E, 0x6D3], [0x6D5, 0x6DC], [0x6DF, 0x6E8], [0x6EA, 0x6FC], [0x6FF, 0x6FF], [0x710, 0x74A], [0x74D, 0x7B1], [0x7C0, 0x7F5], [0x7FA, 0x7FA], [0x7FD, 0x7FD], [0x800, 0x82D], [0x840, 0x85B], [0x860, 0x86A], [0x870, 0x887], [0x889, 0x88E], [0x898, 0x8E1], [0x8E3, 0x963], [0x966, 0x96F], [0x971, 0x983], [0x985, 0x98C], [0x98F, 0x990], [0x993, 0x9A8], [0x9AA, 0x9B0], [0x9B2, 0x9B2], [0x9B6, 0x9B9], [0x9BC, 0x9C4], [0x9C7, 0x9C8], [0x9CB, 0x9CE], [0x9D7, 0x9D7], [0x9DC, 0x9DD], [0x9DF, 0x9E3], [0x9E6, 0x9F1], [0x9FC, 0x9FC], [0x9FE, 0x9FE], [0xA01, 0xA03], [0xA05, 0xA0A], [0xA0F, 0xA10], [0xA13, 0xA28], [0xA2A, 0xA30], [0xA32, 0xA33], [0xA35, 0xA36], [0xA38, 0xA39], [0xA3C, 0xA3C], [0xA3E, 0xA42], [0xA47, 0xA48], [0xA4B, 0xA4D], [0xA51, 0xA51], [0xA59, 0xA5C], [0xA5E, 0xA5E], [0xA66, 0xA75], [0xA81, 0xA83], [0xA85, 0xA8D], [0xA8F, 0xA91], [0xA93, 0xAA8], [0xAAA, 0xAB0], [0xAB2, 0xAB3], [0xAB5, 0xAB9], [0xABC, 0xAC5], [0xAC7, 0xAC9], [0xACB, 0xACD], [0xAD0, 0xAD0], [0xAE0, 0xAE3], [0xAE6, 0xAEF], [0xAF9, 0xAFF], [0xB01, 0xB03], [0xB05, 0xB0C], [0xB0F, 0xB10], [0xB13, 0xB28], [0xB2A, 0xB30], [0xB32, 0xB33], [0xB35, 0xB39], [0xB3C, 0xB44], [0xB47, 0xB48], [0xB4B, 0xB4D], [0xB55, 0xB57], [0xB5C, 0xB5D], [0xB5F, 0xB63], [0xB66, 0xB6F], [0xB71, 0xB71], [0xB82, 0xB83], [0xB85, 0xB8A], [0xB8E, 0xB90], [0xB92, 0xB95], [0xB99, 0xB9A], [0xB9C, 0xB9C], [0xB9E, 0xB9F], [0xBA3, 0xBA4], [0xBA8, 0xBAA], [0xBAE, 0xBB9], [0xBBE, 0xBC2], [0xBC6, 0xBC8], [0xBCA, 0xBCD], [0xBD0, 0xBD0], [0xBD7, 0xBD7], [0xBE6, 0xBEF], [0xC00, 0xC0C], [0xC0E, 0xC10], [0xC12, 0xC28], [0xC2A, 0xC39], [0xC3C, 0xC44], [0xC46, 0xC48], [0xC4A, 0xC4D], [0xC55, 0xC56], [0xC58, 0xC5A], [0xC5D, 0xC5D], [0xC60, 0xC63], [0xC66, 0xC6F], [0xC80, 0xC83], [0xC85, 0xC8C], [0xC8E, 0xC90], [0xC92, 0xCA8], [0xCAA, 0xCB3], [0xCB5, 0xCB9], [0xCBC, 0xCC4], [0xCC6, 0xCC8], [0xCCA, 0xCCD], [0xCD5, 0xCD6], [0xCDD, 0xCDE], [0xCE0, 0xCE3], [0xCE6, 0xCEF], [0xCF1, 0xCF3], [0xD00, 0xD0C], [0xD0E, 0xD10], [0xD12, 0xD44], [0xD46, 0xD48], [0xD4A, 0xD4E], [0xD54, 0xD57], [0xD5F, 0xD63], [0xD66, 0xD6F], [0xD7A, 0xD7F], [0xD81, 0xD83], [0xD85, 0xD96], [0xD9A, 0xDB1], [0xDB3, 0xDBB], [0xDBD, 0xDBD], [0xDC0, 0xDC6], [0xDCA, 0xDCA], [0xDCF, 0xDD4], [0xDD6, 0xDD6], [0xDD8, 0xDDF], [0xDE6, 0xDEF], [0xDF2, 0xDF3], [0xE01, 0xE3A], [0xE40, 0xE4E], [0xE50, 0xE59], [0xE81, 0xE82], [0xE84, 0xE84], [0xE86, 0xE8A], [0xE8C, 0xEA3], [0xEA5, 0xEA5], [0xEA7, 0xEBD], [0xEC0, 0xEC4], [0xEC6, 0xEC6], [0xEC8, 0xECE], [0xED0, 0xED9], [0xEDC, 0xEDF], [0xF00, 0xF00], [0xF18, 0xF19], [0xF20, 0xF29], [0xF35, 0xF35], [0xF37, 0xF37], [0xF39, 0xF39], [0xF3E, 0xF47], [0xF49, 0xF6C], [0xF71, 0xF84], [0xF86, 0xF97], [0xF99, 0xFBC], [0xFC6, 0xFC6], [0x1000, 0x1049], [0x1050, 0x109D], [0x10A0, 0x10C5], [0x10C7, 0x10C7], [0x10CD, 0x10CD], [0x10D0, 0x10FA], [0x10FC, 0x1248], [0x124A, 0x124D], [0x1250, 0x1256], [0x1258, 0x1258], [0x125A, 0x125D], [0x1260, 0x1288], [0x128A, 0x128D], [0x1290, 0x12B0], [0x12B2, 0x12B5], [0x12B8, 0x12BE], [0x12C0, 0x12C0], [0x12C2, 0x12C5], [0x12C8, 0x12D6], [0x12D8, 0x1310], [0x1312, 0x1315], [0x1318, 0x135A], [0x135D, 0x135F], [0x1369, 0x1371], [0x1380, 0x138F], [0x13A0, 0x13F5], [0x13F8, 0x13FD], [0x1401, 0x166C], [0x166F, 0x167F], [0x1681, 0x169A], [0x16A0, 0x16EA], [0x16EE, 0x16F8], [0x1700, 0x1715], [0x171F, 0x1734], [0x1740, 0x1753], [0x1760, 0x176C], [0x176E, 0x1770], [0x1772, 0x1773], [0x1780, 0x17D3], [0x17D7, 0x17D7], [0x17DC, 0x17DD], [0x17E0, 0x17E9], [0x180B, 0x180D], [0x180F, 0x1819], [0x1820, 0x1878], [0x1880, 0x18AA], [0x18B0, 0x18F5], [0x1900, 0x191E], [0x1920, 0x192B], [0x1930, 0x193B], [0x1946, 0x196D], [0x1970, 0x1974], [0x1980, 0x19AB], [0x19B0, 0x19C9], [0x19D0, 0x19DA], [0x1A00, 0x1A1B], [0x1A20, 0x1A5E], [0x1A60, 0x1A7C], [0x1A7F, 0x1A89], [0x1A90, 0x1A99], [0x1AA7, 0x1AA7], [0x1AB0, 0x1ABD], [0x1ABF, 0x1ACE], [0x1B00, 0x1B4C], [0x1B50, 0x1B59], [0x1B6B, 0x1B73], [0x1B80, 0x1BF3], [0x1C00, 0x1C37], [0x1C40, 0x1C49], [0x1C4D, 0x1C7D], [0x1C80, 0x1C88], [0x1C90, 0x1CBA], [0x1CBD, 0x1CBF], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CFA], [0x1D00, 0x1F15], [0x1F18, 0x1F1D], [0x1F20, 0x1F45], [0x1F48, 0x1F4D], [0x1F50, 0x1F57], [0x1F59, 0x1F59], [0x1F5B, 0x1F5B], [0x1F5D, 0x1F5D], [0x1F5F, 0x1F7D], [0x1F80, 0x1FB4], [0x1FB6, 0x1FBC], [0x1FBE, 0x1FBE], [0x1FC2, 0x1FC4], [0x1FC6, 0x1FCC], [0x1FD0, 0x1FD3], [0x1FD6, 0x1FDB], [0x1FE0, 0x1FEC], [0x1FF2, 0x1FF4], [0x1FF6, 0x1FFC], [0x200C, 0x200D], [0x203F, 0x2040], [0x2054, 0x2054], [0x2071, 0x2071], [0x207F, 0x207F], [0x2090, 0x209C], [0x20D0, 0x20DC], [0x20E1, 0x20E1], [0x20E5, 0x20F0], [0x2102, 0x2102], [0x2107, 0x2107], [0x210A, 0x2113], [0x2115, 0x2115], [0x2118, 0x211D], [0x2124, 0x2124], [0x2126, 0x2126], [0x2128, 0x2128], [0x212A, 0x2139], [0x213C, 0x213F], [0x2145, 0x2149], [0x214E, 0x214E], [0x2160, 0x2188], [0x2C00, 0x2CE4], [0x2CEB, 0x2CF3], [0x2D00, 0x2D25], [0x2D27, 0x2D27], [0x2D2D, 0x2D2D], [0x2D30, 0x2D67], [0x2D6F, 0x2D6F], [0x2D7F, 0x2D96], [0x2DA0, 0x2DA6], [0x2DA8, 0x2DAE], [0x2DB0, 0x2DB6], [0x2DB8, 0x2DBE], [0x2DC0, 0x2DC6], [0x2DC8, 0x2DCE], [0x2DD0, 0x2DD6], [0x2DD8, 0x2DDE], [0x2DE0, 0x2DFF], [0x3005, 0x3007], [0x3021, 0x302F], [0x3031, 0x3035], [0x3038, 0x303C], [0x3041, 0x3096], [0x3099, 0x309A], [0x309D, 0x309F], [0x30A1, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], [0x31A0, 0x31BF], [0x31F0, 0x31FF], [0x3400, 0x4DBF], [0x4E00, 0xA48C], [0xA4D0, 0xA4FD], [0xA500, 0xA60C], [0xA610, 0xA62B], [0xA640, 0xA66F], [0xA674, 0xA67D], [0xA67F, 0xA6F1], [0xA717, 0xA71F], [0xA722, 0xA788], [0xA78B, 0xA7CA], [0xA7D0, 0xA7D1], [0xA7D3, 0xA7D3], [0xA7D5, 0xA7D9], [0xA7F2, 0xA827], [0xA82C, 0xA82C], [0xA840, 0xA873], [0xA880, 0xA8C5], [0xA8D0, 0xA8D9], [0xA8E0, 0xA8F7], [0xA8FB, 0xA8FB], [0xA8FD, 0xA92D], [0xA930, 0xA953], [0xA960, 0xA97C], [0xA980, 0xA9C0], [0xA9CF, 0xA9D9], [0xA9E0, 0xA9FE], [0xAA00, 0xAA36], [0xAA40, 0xAA4D], [0xAA50, 0xAA59], [0xAA60, 0xAA76], [0xAA7A, 0xAAC2], [0xAADB, 0xAADD], [0xAAE0, 0xAAEF], [0xAAF2, 0xAAF6], [0xAB01, 0xAB06], [0xAB09, 0xAB0E], [0xAB11, 0xAB16], [0xAB20, 0xAB26], [0xAB28, 0xAB2E], [0xAB30, 0xAB5A], [0xAB5C, 0xAB69], [0xAB70, 0xABEA], [0xABEC, 0xABED], [0xABF0, 0xABF9], [0xAC00, 0xD7A3], [0xD7B0, 0xD7C6], [0xD7CB, 0xD7FB], [0xF900, 0xFA6D], [0xFA70, 0xFAD9], [0xFB00, 0xFB06], [0xFB13, 0xFB17], [0xFB1D, 0xFB28], [0xFB2A, 0xFB36], [0xFB38, 0xFB3C], [0xFB3E, 0xFB3E], [0xFB40, 0xFB41], [0xFB43, 0xFB44], [0xFB46, 0xFBB1], [0xFBD3, 0xFC5D], [0xFC64, 0xFD3D], [0xFD50, 0xFD8F], [0xFD92, 0xFDC7], [0xFDF0, 0xFDF9], [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFE33, 0xFE34], [0xFE4D, 0xFE4F], [0xFE71, 0xFE71], [0xFE73, 0xFE73], [0xFE77, 0xFE77], [0xFE79, 0xFE79], [0xFE7B, 0xFE7B], [0xFE7D, 0xFE7D], [0xFE7F, 0xFEFC], [0xFF10, 0xFF19], [0xFF21, 0xFF3A], [0xFF3F, 0xFF3F], [0xFF41, 0xFF5A], [0xFF65, 0xFFBE], [0xFFC2, 0xFFC7], [0xFFCA, 0xFFCF], [0xFFD2, 0xFFD7], [0xFFDA, 0xFFDC], [0x10000, 0x1000B], [0x1000D, 0x10026], [0x10028, 0x1003A], [0x1003C, 0x1003D], [0x1003F, 0x1004D], [0x10050, 0x1005D], [0x10080, 0x100FA], [0x10140, 0x10174], [0x101FD, 0x101FD], [0x10280, 0x1029C], [0x102A0, 0x102D0], [0x102E0, 0x102E0], [0x10300, 0x1031F], [0x1032D, 0x1034A], [0x10350, 0x1037A], [0x10380, 0x1039D], [0x103A0, 0x103C3], [0x103C8, 0x103CF], [0x103D1, 0x103D5], [0x10400, 0x1049D], [0x104A0, 0x104A9], [0x104B0, 0x104D3], [0x104D8, 0x104FB], [0x10500, 0x10527], [0x10530, 0x10563], [0x10570, 0x1057A], [0x1057C, 0x1058A], [0x1058C, 0x10592], [0x10594, 0x10595], [0x10597, 0x105A1], [0x105A3, 0x105B1], [0x105B3, 0x105B9], [0x105BB, 0x105BC], [0x10600, 0x10736], [0x10740, 0x10755], [0x10760, 0x10767], [0x10780, 0x10785], [0x10787, 0x107B0], [0x107B2, 0x107BA], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080A, 0x10835], [0x10837, 0x10838], [0x1083C, 0x1083C], [0x1083F, 0x10855], [0x10860, 0x10876], [0x10880, 0x1089E], [0x108E0, 0x108F2], [0x108F4, 0x108F5], [0x10900, 0x10915], [0x10920, 0x10939], [0x10980, 0x109B7], [0x109BE, 0x109BF], [0x10A00, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A13], [0x10A15, 0x10A17], [0x10A19, 0x10A35], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x10A60, 0x10A7C], [0x10A80, 0x10A9C], [0x10AC0, 0x10AC7], [0x10AC9, 0x10AE6], [0x10B00, 0x10B35], [0x10B40, 0x10B55], [0x10B60, 0x10B72], [0x10B80, 0x10B91], [0x10C00, 0x10C48], [0x10C80, 0x10CB2], [0x10CC0, 0x10CF2], [0x10D00, 0x10D27], [0x10D30, 0x10D39], [0x10E80, 0x10EA9], [0x10EAB, 0x10EAC], [0x10EB0, 0x10EB1], [0x10EFD, 0x10F1C], [0x10F27, 0x10F27], [0x10F30, 0x10F50], [0x10F70, 0x10F85], [0x10FB0, 0x10FC4], [0x10FE0, 0x10FF6], [0x11000, 0x11046], [0x11066, 0x11075], [0x1107F, 0x110BA], [0x110C2, 0x110C2], [0x110D0, 0x110E8], [0x110F0, 0x110F9], [0x11100, 0x11134], [0x11136, 0x1113F], [0x11144, 0x11147], [0x11150, 0x11173], [0x11176, 0x11176], [0x11180, 0x111C4], [0x111C9, 0x111CC], [0x111CE, 0x111DA], [0x111DC, 0x111DC], [0x11200, 0x11211], [0x11213, 0x11237], [0x1123E, 0x11241], [0x11280, 0x11286], [0x11288, 0x11288], [0x1128A, 0x1128D], [0x1128F, 0x1129D], [0x1129F, 0x112A8], [0x112B0, 0x112EA], [0x112F0, 0x112F9], [0x11300, 0x11303], [0x11305, 0x1130C], [0x1130F, 0x11310], [0x11313, 0x11328], [0x1132A, 0x11330], [0x11332, 0x11333], [0x11335, 0x11339], [0x1133B, 0x11344], [0x11347, 0x11348], [0x1134B, 0x1134D], [0x11350, 0x11350], [0x11357, 0x11357], [0x1135D, 0x11363], [0x11366, 0x1136C], [0x11370, 0x11374], [0x11400, 0x1144A], [0x11450, 0x11459], [0x1145E, 0x11461], [0x11480, 0x114C5], [0x114C7, 0x114C7], [0x114D0, 0x114D9], [0x11580, 0x115B5], [0x115B8, 0x115C0], [0x115D8, 0x115DD], [0x11600, 0x11640], [0x11644, 0x11644], [0x11650, 0x11659], [0x11680, 0x116B8], [0x116C0, 0x116C9], [0x11700, 0x1171A], [0x1171D, 0x1172B], [0x11730, 0x11739], [0x11740, 0x11746], [0x11800, 0x1183A], [0x118A0, 0x118E9], [0x118FF, 0x11906], [0x11909, 0x11909], [0x1190C, 0x11913], [0x11915, 0x11916], [0x11918, 0x11935], [0x11937, 0x11938], [0x1193B, 0x11943], [0x11950, 0x11959], [0x119A0, 0x119A7], [0x119AA, 0x119D7], [0x119DA, 0x119E1], [0x119E3, 0x119E4], [0x11A00, 0x11A3E], [0x11A47, 0x11A47], [0x11A50, 0x11A99], [0x11A9D, 0x11A9D], [0x11AB0, 0x11AF8], [0x11C00, 0x11C08], [0x11C0A, 0x11C36], [0x11C38, 0x11C40], [0x11C50, 0x11C59], [0x11C72, 0x11C8F], [0x11C92, 0x11CA7], [0x11CA9, 0x11CB6], [0x11D00, 0x11D06], [0x11D08, 0x11D09], [0x11D0B, 0x11D36], [0x11D3A, 0x11D3A], [0x11D3C, 0x11D3D], [0x11D3F, 0x11D47], [0x11D50, 0x11D59], [0x11D60, 0x11D65], [0x11D67, 0x11D68], [0x11D6A, 0x11D8E], [0x11D90, 0x11D91], [0x11D93, 0x11D98], [0x11DA0, 0x11DA9], [0x11EE0, 0x11EF6], [0x11F00, 0x11F10], [0x11F12, 0x11F3A], [0x11F3E, 0x11F42], [0x11F50, 0x11F59], [0x11FB0, 0x11FB0], [0x12000, 0x12399], [0x12400, 0x1246E], [0x12480, 0x12543], [0x12F90, 0x12FF0], [0x13000, 0x1342F], [0x13440, 0x13455], [0x14400, 0x14646], [0x16800, 0x16A38], [0x16A40, 0x16A5E], [0x16A60, 0x16A69], [0x16A70, 0x16ABE], [0x16AC0, 0x16AC9], [0x16AD0, 0x16AED], [0x16AF0, 0x16AF4], [0x16B00, 0x16B36], [0x16B40, 0x16B43], [0x16B50, 0x16B59], [0x16B63, 0x16B77], [0x16B7D, 0x16B8F], [0x16E40, 0x16E7F], [0x16F00, 0x16F4A], [0x16F4F, 0x16F87], [0x16F8F, 0x16F9F], [0x16FE0, 0x16FE1], [0x16FE3, 0x16FE4], [0x16FF0, 0x16FF1], [0x17000, 0x187F7], [0x18800, 0x18CD5], [0x18D00, 0x18D08], [0x1AFF0, 0x1AFF3], [0x1AFF5, 0x1AFFB], [0x1AFFD, 0x1AFFE], [0x1B000, 0x1B122], [0x1B132, 0x1B132], [0x1B150, 0x1B152], [0x1B155, 0x1B155], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1BC00, 0x1BC6A], [0x1BC70, 0x1BC7C], [0x1BC80, 0x1BC88], [0x1BC90, 0x1BC99], [0x1BC9D, 0x1BC9E], [0x1CF00, 0x1CF2D], [0x1CF30, 0x1CF46], [0x1D165, 0x1D169], [0x1D16D, 0x1D172], [0x1D17B, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], [0x1D242, 0x1D244], [0x1D400, 0x1D454], [0x1D456, 0x1D49C], [0x1D49E, 0x1D49F], [0x1D4A2, 0x1D4A2], [0x1D4A5, 0x1D4A6], [0x1D4A9, 0x1D4AC], [0x1D4AE, 0x1D4B9], [0x1D4BB, 0x1D4BB], [0x1D4BD, 0x1D4C3], [0x1D4C5, 0x1D505], [0x1D507, 0x1D50A], [0x1D50D, 0x1D514], [0x1D516, 0x1D51C], [0x1D51E, 0x1D539], [0x1D53B, 0x1D53E], [0x1D540, 0x1D544], [0x1D546, 0x1D546], [0x1D54A, 0x1D550], [0x1D552, 0x1D6A5], [0x1D6A8, 0x1D6C0], [0x1D6C2, 0x1D6DA], [0x1D6DC, 0x1D6FA], [0x1D6FC, 0x1D714], [0x1D716, 0x1D734], [0x1D736, 0x1D74E], [0x1D750, 0x1D76E], [0x1D770, 0x1D788], [0x1D78A, 0x1D7A8], [0x1D7AA, 0x1D7C2], [0x1D7C4, 0x1D7CB], [0x1D7CE, 0x1D7FF], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], [0x1DAA1, 0x1DAAF], [0x1DF00, 0x1DF1E], [0x1DF25, 0x1DF2A], [0x1E000, 0x1E006], [0x1E008, 0x1E018], [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], [0x1E030, 0x1E06D], [0x1E08F, 0x1E08F], [0x1E100, 0x1E12C], [0x1E130, 0x1E13D], [0x1E140, 0x1E149], [0x1E14E, 0x1E14E], [0x1E290, 0x1E2AE], [0x1E2C0, 0x1E2F9], [0x1E4D0, 0x1E4F9], [0x1E7E0, 0x1E7E6], [0x1E7E8, 0x1E7EB], [0x1E7ED, 0x1E7EE], [0x1E7F0, 0x1E7FE], [0x1E800, 0x1E8C4], [0x1E8D0, 0x1E8D6], [0x1E900, 0x1E94B], [0x1E950, 0x1E959], [0x1EE00, 0x1EE03], [0x1EE05, 0x1EE1F], [0x1EE21, 0x1EE22], [0x1EE24, 0x1EE24], [0x1EE27, 0x1EE27], [0x1EE29, 0x1EE32], [0x1EE34, 0x1EE37], [0x1EE39, 0x1EE39], [0x1EE3B, 0x1EE3B], [0x1EE42, 0x1EE42], [0x1EE47, 0x1EE47], [0x1EE49, 0x1EE49], [0x1EE4B, 0x1EE4B], [0x1EE4D, 0x1EE4F], [0x1EE51, 0x1EE52], [0x1EE54, 0x1EE54], [0x1EE57, 0x1EE57], [0x1EE59, 0x1EE59], [0x1EE5B, 0x1EE5B], [0x1EE5D, 0x1EE5D], [0x1EE5F, 0x1EE5F], [0x1EE61, 0x1EE62], [0x1EE64, 0x1EE64], [0x1EE67, 0x1EE6A], [0x1EE6C, 0x1EE72], [0x1EE74, 0x1EE77], [0x1EE79, 0x1EE7C], [0x1EE7E, 0x1EE7E], [0x1EE80, 0x1EE89], [0x1EE8B, 0x1EE9B], [0x1EEA1, 0x1EEA3], [0x1EEA5, 0x1EEA9], [0x1EEAB, 0x1EEBB], [0x1FBF0, 0x1FBF9], [0x20000, 0x2A6DF], [0x2A700, 0x2B739], [0x2B740, 0x2B81D], [0x2B820, 0x2CEA1], [0x2CEB0, 0x2EBE0], [0x2EBF0, 0x2EE5D], [0x2F800, 0x2FA1D], [0x30000, 0x3134A], [0x31350, 0x323AF], [0xE0100, 0xE01EF], ]; /** C99 Start Entries: 34958 */ alias FixedTable_C99_Start = FixedTable_C99_Continue; /** C99 Continue Entries: 34958 */ static immutable dchar[2][] FixedTable_C99_Continue = [ [0xAA, 0xAA], [0xB5, 0xB5], [0xB7, 0xB7], [0xBA, 0xBA], [0xC0, 0xD6], [0xD8, 0xF6], [0xF8, 0x1F5], [0x1FA, 0x217], [0x250, 0x2A8], [0x2B0, 0x2B8], [0x2BB, 0x2BB], [0x2BD, 0x2C1], [0x2D0, 0x2D1], [0x2E0, 0x2E4], [0x37A, 0x37A], [0x386, 0x386], [0x388, 0x38A], [0x38C, 0x38C], [0x38E, 0x3A1], [0x3A3, 0x3CE], [0x3D0, 0x3D6], [0x3DA, 0x3DA], [0x3DC, 0x3DC], [0x3DE, 0x3DE], [0x3E0, 0x3E0], [0x3E2, 0x3F3], [0x401, 0x40C], [0x40E, 0x44F], [0x451, 0x45C], [0x45E, 0x481], [0x490, 0x4C4], [0x4C7, 0x4C8], [0x4CB, 0x4CC], [0x4D0, 0x4EB], [0x4EE, 0x4F5], [0x4F8, 0x4F9], [0x531, 0x556], [0x559, 0x559], [0x561, 0x587], [0x5B0, 0x5B9], [0x5BB, 0x5BD], [0x5BF, 0x5BF], [0x5C1, 0x5C2], [0x5D0, 0x5EA], [0x5F0, 0x5F2], [0x621, 0x63A], [0x640, 0x652], [0x660, 0x669], [0x670, 0x6B7], [0x6BA, 0x6BE], [0x6C0, 0x6CE], [0x6D0, 0x6DC], [0x6E5, 0x6E8], [0x6EA, 0x6ED], [0x6F0, 0x6F9], [0x901, 0x903], [0x905, 0x939], [0x93D, 0x94D], [0x950, 0x952], [0x958, 0x963], [0x966, 0x96F], [0x981, 0x983], [0x985, 0x98C], [0x98F, 0x990], [0x993, 0x9A8], [0x9AA, 0x9B0], [0x9B2, 0x9B2], [0x9B6, 0x9B9], [0x9BE, 0x9C4], [0x9C7, 0x9C8], [0x9CB, 0x9CD], [0x9DC, 0x9DD], [0x9DF, 0x9E3], [0x9E6, 0x9F1], [0xA02, 0xA02], [0xA05, 0xA0A], [0xA0F, 0xA10], [0xA13, 0xA28], [0xA2A, 0xA30], [0xA32, 0xA33], [0xA35, 0xA36], [0xA38, 0xA39], [0xA3E, 0xA42], [0xA47, 0xA48], [0xA4B, 0xA4D], [0xA59, 0xA5C], [0xA5E, 0xA5E], [0xA66, 0xA6F], [0xA74, 0xA74], [0xA81, 0xA83], [0xA85, 0xA8B], [0xA8D, 0xA8D], [0xA8F, 0xA91], [0xA93, 0xAA8], [0xAAA, 0xAB0], [0xAB2, 0xAB3], [0xAB5, 0xAB9], [0xABD, 0xAC5], [0xAC7, 0xAC9], [0xACB, 0xACD], [0xAD0, 0xAD0], [0xAE0, 0xAE0], [0xAE6, 0xAEF], [0xB01, 0xB03], [0xB05, 0xB0C], [0xB0F, 0xB10], [0xB13, 0xB28], [0xB2A, 0xB30], [0xB32, 0xB33], [0xB36, 0xB39], [0xB3D, 0xB43], [0xB47, 0xB48], [0xB4B, 0xB4D], [0xB5C, 0xB5D], [0xB5F, 0xB61], [0xB66, 0xB6F], [0xB82, 0xB83], [0xB85, 0xB8A], [0xB8E, 0xB90], [0xB92, 0xB95], [0xB99, 0xB9A], [0xB9C, 0xB9C], [0xB9E, 0xB9F], [0xBA3, 0xBA4], [0xBA8, 0xBAA], [0xBAE, 0xBB5], [0xBB7, 0xBB9], [0xBBE, 0xBC2], [0xBC6, 0xBC8], [0xBCA, 0xBCD], [0xBE7, 0xBEF], [0xC01, 0xC03], [0xC05, 0xC0C], [0xC0E, 0xC10], [0xC12, 0xC28], [0xC2A, 0xC33], [0xC35, 0xC39], [0xC3E, 0xC44], [0xC46, 0xC48], [0xC4A, 0xC4D], [0xC60, 0xC61], [0xC66, 0xC6F], [0xC82, 0xC83], [0xC85, 0xC8C], [0xC8E, 0xC90], [0xC92, 0xCA8], [0xCAA, 0xCB3], [0xCB5, 0xCB9], [0xCBE, 0xCC4], [0xCC6, 0xCC8], [0xCCA, 0xCCD], [0xCDE, 0xCDE], [0xCE0, 0xCE1], [0xCE6, 0xCEF], [0xD02, 0xD03], [0xD05, 0xD0C], [0xD0E, 0xD10], [0xD12, 0xD28], [0xD2A, 0xD39], [0xD3E, 0xD43], [0xD46, 0xD48], [0xD4A, 0xD4D], [0xD60, 0xD61], [0xD66, 0xD6F], [0xE01, 0xE3A], [0xE40, 0xE5B], [0xE81, 0xE82], [0xE84, 0xE84], [0xE87, 0xE88], [0xE8A, 0xE8A], [0xE8D, 0xE8D], [0xE94, 0xE97], [0xE99, 0xE9F], [0xEA1, 0xEA3], [0xEA5, 0xEA5], [0xEA7, 0xEA7], [0xEAA, 0xEAB], [0xEAD, 0xEAE], [0xEB0, 0xEB9], [0xEBB, 0xEBD], [0xEC0, 0xEC4], [0xEC6, 0xEC6], [0xEC8, 0xECD], [0xED0, 0xED9], [0xEDC, 0xEDD], [0xF00, 0xF00], [0xF18, 0xF19], [0xF20, 0xF33], [0xF35, 0xF35], [0xF37, 0xF37], [0xF39, 0xF39], [0xF3E, 0xF47], [0xF49, 0xF69], [0xF71, 0xF84], [0xF86, 0xF8B], [0xF90, 0xF95], [0xF97, 0xF97], [0xF99, 0xFAD], [0xFB1, 0xFB7], [0xFB9, 0xFB9], [0x10A0, 0x10C5], [0x10D0, 0x10F6], [0x1E00, 0x1E9B], [0x1EA0, 0x1EF9], [0x1F00, 0x1F15], [0x1F18, 0x1F1D], [0x1F20, 0x1F45], [0x1F48, 0x1F4D], [0x1F50, 0x1F57], [0x1F59, 0x1F59], [0x1F5B, 0x1F5B], [0x1F5D, 0x1F5D], [0x1F5F, 0x1F7D], [0x1F80, 0x1FB4], [0x1FB6, 0x1FBC], [0x1FBE, 0x1FBE], [0x1FC2, 0x1FC4], [0x1FC6, 0x1FCC], [0x1FD0, 0x1FD3], [0x1FD6, 0x1FDB], [0x1FE0, 0x1FEC], [0x1FF2, 0x1FF4], [0x1FF6, 0x1FFC], [0x203F, 0x2040], [0x207F, 0x207F], [0x2102, 0x2102], [0x2107, 0x2107], [0x210A, 0x2113], [0x2115, 0x2115], [0x2118, 0x211D], [0x2124, 0x2124], [0x2126, 0x2126], [0x2128, 0x2128], [0x212A, 0x2131], [0x2133, 0x2138], [0x2160, 0x2182], [0x3005, 0x3007], [0x3021, 0x3029], [0x3041, 0x3093], [0x309B, 0x309C], [0x30A1, 0x30F6], [0x30FB, 0x30FC], [0x3105, 0x312C], [0x4E00, 0x9FA5], [0xAC00, 0xD7A3], ]; /** C11 Start Entries: 971620 */ alias FixedTable_C11_Start = FixedTable_C11_Continue; /** C11 Continue Entries: 971620 */ static immutable dchar[2][] FixedTable_C11_Continue = [ [0xA8, 0xA8], [0xAA, 0xAA], [0xAD, 0xAD], [0xAF, 0xAF], [0xB2, 0xB5], [0xB7, 0xBA], [0xBC, 0xBE], [0xC0, 0xD6], [0xD8, 0xF6], [0xF8, 0xFF], [0x100, 0x167F], [0x1681, 0x180D], [0x180F, 0x1FFF], [0x200B, 0x200D], [0x202A, 0x202E], [0x203F, 0x2040], [0x2054, 0x2054], [0x2060, 0x206F], [0x2070, 0x218F], [0x2460, 0x24FF], [0x2776, 0x2793], [0x2C00, 0x2DFF], [0x2E80, 0x2FFF], [0x3004, 0x3007], [0x3021, 0x302F], [0x3031, 0x303F], [0x3040, 0xD7FF], [0xF900, 0xFD3D], [0xFD40, 0xFDCF], [0xFDF0, 0xFE44], [0xFE47, 0xFFFD], [0x10000, 0x1FFFD], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD], [0x40000, 0x4FFFD], [0x50000, 0x5FFFD], [0x60000, 0x6FFFD], [0x70000, 0x7FFFD], [0x80000, 0x8FFFD], [0x90000, 0x9FFFD], [0xA0000, 0xAFFFD], [0xB0000, 0xBFFFD], [0xC0000, 0xCFFFD], [0xD0000, 0xDFFFD], [0xE0000, 0xEFFFD], ]; /** Least restrictive with both Start and Continue Entries: 860486 */ static immutable dchar[2][] LeastRestrictive_OfAll = [ [0xA8, 0xA8], [0xAA, 0xAA], [0xAD, 0xAD], [0xAF, 0xAF], [0xB2, 0xB5], [0xB7, 0xBA], [0xBC, 0xBE], [0xC0, 0xD6], [0xD8, 0xF6], [0xF8, 0x217], [0x250, 0x2A8], [0x2B0, 0x2B8], [0x2BB, 0x2BB], [0x2BD, 0x2C1], [0x2C6, 0x2D1], [0x2E0, 0x2E4], [0x2EC, 0x2EC], [0x2EE, 0x2EE], [0x300, 0x374], [0x376, 0x377], [0x37A, 0x37D], [0x37F, 0x37F], [0x386, 0x386], [0x388, 0x38A], [0x38C, 0x38C], [0x38E, 0x3A1], [0x3A3, 0x3D6], [0x3DA, 0x3DA], [0x3DC, 0x3DC], [0x3DE, 0x3DE], [0x3E0, 0x3E0], [0x3E2, 0x3F3], [0x3F7, 0x40C], [0x40E, 0x44F], [0x451, 0x45C], [0x45E, 0x481], [0x483, 0x487], [0x48A, 0x4C4], [0x4C7, 0x4C8], [0x4CB, 0x4CC], [0x4D0, 0x4EB], [0x4EE, 0x4F5], [0x4F8, 0x4F9], [0x531, 0x556], [0x559, 0x559], [0x560, 0x587], [0x591, 0x5B9], [0x5BB, 0x5BD], [0x5BF, 0x5BF], [0x5C1, 0x5C2], [0x5C4, 0x5C5], [0x5C7, 0x5C7], [0x5D0, 0x5EA], [0x5EF, 0x5F2], [0x610, 0x61A], [0x620, 0x63A], [0x640, 0x652], [0x660, 0x669], [0x66E, 0x6BE], [0x6C0, 0x6CE], [0x6D0, 0x6D5], [0x6DF, 0x6E8], [0x6EA, 0x6F9], [0x6FF, 0x6FF], [0x710, 0x710], [0x712, 0x72F], [0x74D, 0x7A5], [0x7B1, 0x7B1], [0x7C0, 0x7EA], [0x7F4, 0x7F5], [0x7FA, 0x7FA], [0x7FD, 0x7FD], [0x800, 0x815], [0x81A, 0x81A], [0x824, 0x824], [0x828, 0x828], [0x840, 0x858], [0x860, 0x86A], [0x870, 0x887], [0x889, 0x88E], [0x898, 0x8C9], [0x8E3, 0x939], [0x93D, 0x94D], [0x950, 0x952], [0x958, 0x963], [0x966, 0x96F], [0x971, 0x983], [0x985, 0x98C], [0x98F, 0x990], [0x993, 0x9A8], [0x9AA, 0x9B0], [0x9B2, 0x9B2], [0x9B6, 0x9B9], [0x9BC, 0x9C4], [0x9C7, 0x9C8], [0x9CB, 0x9CE], [0x9D7, 0x9D7], [0x9DC, 0x9DD], [0x9DF, 0x9E3], [0x9E6, 0x9F1], [0x9FC, 0x9FC], [0x9FE, 0x9FE], [0xA01, 0xA02], [0xA05, 0xA0A], [0xA0F, 0xA10], [0xA13, 0xA28], [0xA2A, 0xA30], [0xA32, 0xA33], [0xA35, 0xA36], [0xA38, 0xA39], [0xA3C, 0xA3C], [0xA3E, 0xA42], [0xA47, 0xA48], [0xA4B, 0xA4D], [0xA51, 0xA51], [0xA59, 0xA5C], [0xA5E, 0xA5E], [0xA66, 0xA6F], [0xA72, 0xA74], [0xA81, 0xA83], [0xA85, 0xA8B], [0xA8D, 0xA8D], [0xA8F, 0xA91], [0xA93, 0xAA8], [0xAAA, 0xAB0], [0xAB2, 0xAB3], [0xAB5, 0xAB9], [0xABC, 0xABD], [0xAC7, 0xAC9], [0xACB, 0xACD], [0xAD0, 0xAD0], [0xAE0, 0xAE0], [0xAE6, 0xAEF], [0xAF9, 0xAFF], [0xB01, 0xB03], [0xB05, 0xB0C], [0xB0F, 0xB10], [0xB13, 0xB28], [0xB2A, 0xB30], [0xB32, 0xB33], [0xB35, 0xB39], [0xB3C, 0xB43], [0xB47, 0xB48], [0xB4B, 0xB4D], [0xB55, 0xB57], [0xB5C, 0xB5D], [0xB5F, 0xB61], [0xB66, 0xB6F], [0xB71, 0xB71], [0xB82, 0xB83], [0xB85, 0xB8A], [0xB8E, 0xB90], [0xB92, 0xB95], [0xB99, 0xB9A], [0xB9C, 0xB9C], [0xB9E, 0xB9F], [0xBA3, 0xBA4], [0xBA8, 0xBAA], [0xBAE, 0xBB5], [0xBB7, 0xBB9], [0xBBE, 0xBC2], [0xBC6, 0xBC8], [0xBCA, 0xBCD], [0xBD0, 0xBD0], [0xBD7, 0xBD7], [0xBE6, 0xBEF], [0xC00, 0xC03], [0xC05, 0xC0C], [0xC0E, 0xC10], [0xC12, 0xC28], [0xC2A, 0xC33], [0xC35, 0xC39], [0xC3C, 0xC44], [0xC46, 0xC48], [0xC4A, 0xC4D], [0xC55, 0xC56], [0xC58, 0xC5A], [0xC5D, 0xC5D], [0xC60, 0xC61], [0xC66, 0xC6F], [0xC80, 0xC83], [0xC85, 0xC8C], [0xC8E, 0xC90], [0xC92, 0xCA8], [0xCAA, 0xCB3], [0xCB5, 0xCB9], [0xCBC, 0xCC4], [0xCC6, 0xCC8], [0xCCA, 0xCCD], [0xCD5, 0xCD6], [0xCDD, 0xCDE], [0xCE0, 0xCE1], [0xCE6, 0xCEF], [0xCF1, 0xCF2], [0xD00, 0xD0C], [0xD0E, 0xD10], [0xD12, 0xD39], [0xD3D, 0xD43], [0xD46, 0xD48], [0xD4A, 0xD4E], [0xD54, 0xD57], [0xD5F, 0xD61], [0xD66, 0xD6F], [0xD7A, 0xD7F], [0xD81, 0xD83], [0xD85, 0xD96], [0xD9A, 0xDB1], [0xDB3, 0xDBB], [0xDBD, 0xDBD], [0xDC0, 0xDC6], [0xDCA, 0xDCA], [0xDCF, 0xDD4], [0xDD6, 0xDD6], [0xDD8, 0xDDF], [0xDE6, 0xDEF], [0xDF2, 0xDF3], [0xE01, 0xE32], [0xE40, 0xE4E], [0xE50, 0xE59], [0xE81, 0xE82], [0xE84, 0xE84], [0xE86, 0xE88], [0xE8A, 0xE8A], [0xE8C, 0xE8D], [0xE94, 0xE97], [0xE99, 0xE9F], [0xEA1, 0xEA3], [0xEA5, 0xEA5], [0xEA7, 0xEAB], [0xEAD, 0xEAE], [0xEB0, 0xEB9], [0xEBB, 0xEBD], [0xEC0, 0xEC4], [0xEC6, 0xEC6], [0xEC8, 0xECE], [0xED0, 0xED9], [0xEDC, 0xEDF], [0xF00, 0xF00], [0xF18, 0xF19], [0xF20, 0xF29], [0xF35, 0xF35], [0xF37, 0xF37], [0xF39, 0xF39], [0xF3E, 0xF47], [0xF49, 0xF6C], [0xF71, 0xF84], [0xF86, 0xF95], [0xF97, 0xF97], [0xF99, 0xFB7], [0xFB9, 0xFB9], [0xFC6, 0xFC6], [0x1000, 0x103F], [0x1050, 0x105D], [0x1061, 0x1061], [0x1065, 0x1066], [0x106E, 0x1070], [0x1075, 0x1081], [0x108E, 0x108E], [0x10A0, 0x10C5], [0x10C7, 0x10C7], [0x10CD, 0x10CD], [0x10D0, 0x10FA], [0x10FC, 0x1248], [0x124A, 0x124D], [0x1250, 0x1256], [0x1258, 0x1258], [0x125A, 0x125D], [0x1260, 0x1288], [0x128A, 0x128D], [0x1290, 0x12B0], [0x12B2, 0x12B5], [0x12B8, 0x12BE], [0x12C0, 0x12C0], [0x12C2, 0x12C5], [0x12C8, 0x12D6], [0x12D8, 0x1310], [0x1312, 0x1315], [0x1318, 0x135A], [0x135D, 0x135F], [0x1369, 0x1371], [0x1380, 0x138F], [0x13A0, 0x13F5], [0x13F8, 0x13FD], [0x1401, 0x166C], [0x166F, 0x167F], [0x1681, 0x169A], [0x16A0, 0x16EA], [0x16EE, 0x16F8], [0x1700, 0x1711], [0x171F, 0x1731], [0x1740, 0x1751], [0x1760, 0x176C], [0x176E, 0x1770], [0x1772, 0x1773], [0x1780, 0x17B3], [0x17D7, 0x17D7], [0x17DC, 0x17DC], [0x17E0, 0x17E9], [0x180B, 0x180D], [0x180F, 0x1878], [0x1880, 0x18A8], [0x18AA, 0x18AA], [0x18B0, 0x18F5], [0x1900, 0x191E], [0x1920, 0x192B], [0x1930, 0x193B], [0x1946, 0x196D], [0x1970, 0x1974], [0x1980, 0x19AB], [0x19B0, 0x19C9], [0x19D0, 0x19DA], [0x1A00, 0x1A16], [0x1A20, 0x1A54], [0x1A60, 0x1A7C], [0x1A7F, 0x1A89], [0x1A90, 0x1A99], [0x1AA7, 0x1AA7], [0x1AB0, 0x1ABD], [0x1ABF, 0x1ACE], [0x1B00, 0x1B33], [0x1B45, 0x1B4C], [0x1B50, 0x1B59], [0x1B6B, 0x1B73], [0x1B80, 0x1BA0], [0x1BAE, 0x1BAF], [0x1BBA, 0x1BE5], [0x1C00, 0x1C37], [0x1C40, 0x1C49], [0x1C4D, 0x1C7D], [0x1C80, 0x1C88], [0x1C90, 0x1CBA], [0x1CBD, 0x1CBF], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CEC], [0x1CEE, 0x1CF3], [0x1CF5, 0x1CF6], [0x1CFA, 0x1CFA], [0x1D00, 0x1EF9], [0x1F00, 0x1F15], [0x1F18, 0x1F1D], [0x1F20, 0x1F45], [0x1F48, 0x1F4D], [0x1F50, 0x1F57], [0x1F59, 0x1F59], [0x1F5B, 0x1F5B], [0x1F5D, 0x1F5D], [0x1F5F, 0x1F7D], [0x1F80, 0x1FB4], [0x1FB6, 0x1FBC], [0x1FBE, 0x1FBE], [0x1FC2, 0x1FC4], [0x1FC6, 0x1FCC], [0x1FD0, 0x1FD3], [0x1FD6, 0x1FDB], [0x1FE0, 0x1FEC], [0x1FF2, 0x1FF4], [0x1FF6, 0x1FFC], [0x200B, 0x200D], [0x202A, 0x202E], [0x203F, 0x2040], [0x2054, 0x2054], [0x2060, 0x2071], [0x207F, 0x207F], [0x2090, 0x209C], [0x20D0, 0x20DC], [0x20E1, 0x20E1], [0x20E5, 0x20F0], [0x2102, 0x2102], [0x2107, 0x2107], [0x210A, 0x2113], [0x2115, 0x2115], [0x2118, 0x211D], [0x2124, 0x2124], [0x2126, 0x2126], [0x2128, 0x2128], [0x212A, 0x2138], [0x213C, 0x213F], [0x2145, 0x2149], [0x214E, 0x214E], [0x2160, 0x2188], [0x2460, 0x24FF], [0x2776, 0x2793], [0x2C00, 0x2CE4], [0x2CEB, 0x2CF3], [0x2D00, 0x2D25], [0x2D27, 0x2D27], [0x2D2D, 0x2D2D], [0x2D30, 0x2D67], [0x2D6F, 0x2D6F], [0x2D7F, 0x2D96], [0x2DA0, 0x2DA6], [0x2DA8, 0x2DAE], [0x2DB0, 0x2DB6], [0x2DB8, 0x2DBE], [0x2DC0, 0x2DC6], [0x2DC8, 0x2DCE], [0x2DD0, 0x2DD6], [0x2DD8, 0x2DDE], [0x2DE0, 0x2DFF], [0x2E80, 0x2FFF], [0x3004, 0x3007], [0x3021, 0x302F], [0x3031, 0x303C], [0x3041, 0x3096], [0x3099, 0x309F], [0x30A1, 0x30FC], [0x3105, 0x312F], [0x3131, 0x318E], [0x31A0, 0x31BF], [0x31F0, 0x31FF], [0x3400, 0x4DBF], [0x4E00, 0xA48C], [0xA4D0, 0xA4FD], [0xA500, 0xA60C], [0xA610, 0xA61F], [0xA62A, 0xA62B], [0xA640, 0xA66F], [0xA674, 0xA67D], [0xA67F, 0xA6EF], [0xA717, 0xA71F], [0xA722, 0xA788], [0xA78B, 0xA7CA], [0xA7D0, 0xA7D1], [0xA7D3, 0xA7D3], [0xA7D5, 0xA7D9], [0xA7F2, 0xA805], [0xA807, 0xA80A], [0xA80C, 0xA822], [0xA82C, 0xA82C], [0xA840, 0xA873], [0xA880, 0xA8B3], [0xA8D0, 0xA8D9], [0xA8E0, 0xA8F7], [0xA8FB, 0xA8FB], [0xA8FD, 0xA8FE], [0xA90A, 0xA925], [0xA930, 0xA946], [0xA960, 0xA97C], [0xA980, 0xA9B2], [0xA9CF, 0xA9CF], [0xA9E0, 0xA9E4], [0xA9E6, 0xA9EF], [0xA9FA, 0xA9FE], [0xAA00, 0xAA28], [0xAA40, 0xAA42], [0xAA44, 0xAA4B], [0xAA50, 0xAA59], [0xAA60, 0xAA76], [0xAA7A, 0xAA7A], [0xAA7E, 0xAAAF], [0xAAB1, 0xAAB1], [0xAAB5, 0xAAB6], [0xAAB9, 0xAABD], [0xAAC0, 0xAAC0], [0xAAC2, 0xAAC2], [0xAADB, 0xAADD], [0xAAE0, 0xAAEA], [0xAAF2, 0xAAF4], [0xAB01, 0xAB06], [0xAB09, 0xAB0E], [0xAB11, 0xAB16], [0xAB20, 0xAB26], [0xAB28, 0xAB2E], [0xAB30, 0xAB5A], [0xAB5C, 0xAB69], [0xAB70, 0xABE2], [0xABEC, 0xABED], [0xABF0, 0xABF9], [0xAC00, 0xD7A3], [0xD7B0, 0xD7C6], [0xD7CB, 0xD7FB], [0xF900, 0xFA6D], [0xFA70, 0xFAD9], [0xFB00, 0xFB06], [0xFB13, 0xFB17], [0xFB1D, 0xFB1D], [0xFB1F, 0xFB28], [0xFB2A, 0xFB36], [0xFB38, 0xFB3C], [0xFB3E, 0xFB3E], [0xFB40, 0xFB41], [0xFB43, 0xFB44], [0xFB46, 0xFBB1], [0xFBD3, 0xFC5D], [0xFC64, 0xFD3D], [0xFD40, 0xFD8F], [0xFD92, 0xFDC7], [0xFDF0, 0xFDF9], [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFE33, 0xFE34], [0xFE47, 0xFE71], [0xFE73, 0xFE73], [0xFE77, 0xFE77], [0xFE79, 0xFE79], [0xFE7B, 0xFE7B], [0xFE7D, 0xFE7D], [0xFE7F, 0xFEFC], [0xFF10, 0xFF19], [0xFF21, 0xFF3A], [0xFF3F, 0xFF3F], [0xFF41, 0xFF5A], [0xFF65, 0xFF9D], [0xFFA0, 0xFFBE], [0xFFC2, 0xFFC7], [0xFFCA, 0xFFCF], [0xFFD2, 0xFFD7], [0xFFDA, 0xFFDC], [0x10000, 0x1000B], [0x1000D, 0x10026], [0x10028, 0x1003A], [0x1003C, 0x1003D], [0x1003F, 0x1004D], [0x10050, 0x1005D], [0x10080, 0x100FA], [0x10140, 0x10174], [0x101FD, 0x101FD], [0x10280, 0x1029C], [0x102A0, 0x102D0], [0x102E0, 0x102E0], [0x10300, 0x1031F], [0x1032D, 0x1034A], [0x10350, 0x10375], [0x10380, 0x1039D], [0x103A0, 0x103C3], [0x103C8, 0x103CF], [0x103D1, 0x103D5], [0x10400, 0x1049D], [0x104A0, 0x104A9], [0x104B0, 0x104D3], [0x104D8, 0x104FB], [0x10500, 0x10527], [0x10530, 0x10563], [0x10570, 0x1057A], [0x1057C, 0x1058A], [0x1058C, 0x10592], [0x10594, 0x10595], [0x10597, 0x105A1], [0x105A3, 0x105B1], [0x105B3, 0x105B9], [0x105BB, 0x105BC], [0x10600, 0x10736], [0x10740, 0x10755], [0x10760, 0x10767], [0x10780, 0x10785], [0x10787, 0x107B0], [0x107B2, 0x107BA], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080A, 0x10835], [0x10837, 0x10838], [0x1083C, 0x1083C], [0x1083F, 0x10855], [0x10860, 0x10876], [0x10880, 0x1089E], [0x108E0, 0x108F2], [0x108F4, 0x108F5], [0x10900, 0x10915], [0x10920, 0x10939], [0x10980, 0x109B7], [0x109BE, 0x109BF], [0x10A00, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A13], [0x10A15, 0x10A17], [0x10A19, 0x10A35], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x10A60, 0x10A7C], [0x10A80, 0x10A9C], [0x10AC0, 0x10AC7], [0x10AC9, 0x10AE6], [0x10B00, 0x10B35], [0x10B40, 0x10B55], [0x10B60, 0x10B72], [0x10B80, 0x10B91], [0x10C00, 0x10C48], [0x10C80, 0x10CB2], [0x10CC0, 0x10CF2], [0x10D00, 0x10D23], [0x10D30, 0x10D39], [0x10E80, 0x10EA9], [0x10EAB, 0x10EAC], [0x10EB0, 0x10EB1], [0x10EFD, 0x10F1C], [0x10F27, 0x10F27], [0x10F30, 0x10F45], [0x10F70, 0x10F81], [0x10FB0, 0x10FC4], [0x10FE0, 0x10FF6], [0x11000, 0x11037], [0x11066, 0x11072], [0x11075, 0x11075], [0x1107F, 0x110AF], [0x110C2, 0x110C2], [0x110D0, 0x110E8], [0x110F0, 0x110F9], [0x11100, 0x11126], [0x11136, 0x1113F], [0x11144, 0x11147], [0x11150, 0x11173], [0x11176, 0x11176], [0x11180, 0x111B2], [0x111C1, 0x111C4], [0x111C9, 0x111CC], [0x111CE, 0x111DA], [0x111DC, 0x111DC], [0x11200, 0x11211], [0x11213, 0x11237], [0x1123E, 0x11240], [0x11280, 0x11286], [0x11288, 0x11288], [0x1128A, 0x1128D], [0x1128F, 0x1129D], [0x1129F, 0x112A8], [0x112B0, 0x112EA], [0x112F0, 0x112F9], [0x11300, 0x11303], [0x11305, 0x1130C], [0x1130F, 0x11310], [0x11313, 0x11328], [0x1132A, 0x11330], [0x11332, 0x11333], [0x11335, 0x11339], [0x1133B, 0x1133D], [0x11347, 0x11348], [0x1134B, 0x1134D], [0x11350, 0x11350], [0x11357, 0x11357], [0x1135D, 0x11363], [0x11366, 0x1136C], [0x11370, 0x11374], [0x11400, 0x1144A], [0x11450, 0x11459], [0x1145E, 0x11461], [0x11480, 0x114C5], [0x114C7, 0x114C7], [0x114D0, 0x114D9], [0x11580, 0x115B5], [0x115B8, 0x115C0], [0x115D8, 0x115DD], [0x11600, 0x11640], [0x11644, 0x11644], [0x11650, 0x11659], [0x11680, 0x116B8], [0x116C0, 0x116C9], [0x11700, 0x1171A], [0x1171D, 0x1172B], [0x11730, 0x11739], [0x11740, 0x11746], [0x11800, 0x1183A], [0x118A0, 0x118E9], [0x118FF, 0x11906], [0x11909, 0x11909], [0x1190C, 0x11913], [0x11915, 0x11916], [0x11918, 0x11935], [0x11937, 0x11938], [0x1193B, 0x1193F], [0x11941, 0x11941], [0x11950, 0x11959], [0x119A0, 0x119A7], [0x119AA, 0x119D7], [0x119DA, 0x119E1], [0x119E3, 0x119E3], [0x11A00, 0x11A32], [0x11A3A, 0x11A3A], [0x11A47, 0x11A47], [0x11A50, 0x11A89], [0x11A9D, 0x11A9D], [0x11AB0, 0x11AF8], [0x11C00, 0x11C08], [0x11C0A, 0x11C36], [0x11C38, 0x11C40], [0x11C50, 0x11C59], [0x11C72, 0x11C8F], [0x11C92, 0x11CA7], [0x11CA9, 0x11CB6], [0x11D00, 0x11D06], [0x11D08, 0x11D09], [0x11D0B, 0x11D36], [0x11D3A, 0x11D3A], [0x11D3C, 0x11D3D], [0x11D3F, 0x11D46], [0x11D50, 0x11D59], [0x11D60, 0x11D65], [0x11D67, 0x11D68], [0x11D6A, 0x11D89], [0x11D90, 0x11D91], [0x11D93, 0x11D98], [0x11DA0, 0x11DA9], [0x11EE0, 0x11EF2], [0x11F00, 0x11F02], [0x11F04, 0x11F10], [0x11F12, 0x11F33], [0x11F3E, 0x11F42], [0x11F50, 0x11F59], [0x11FB0, 0x11FB0], [0x12000, 0x12399], [0x12400, 0x1246E], [0x12480, 0x12543], [0x12F90, 0x12FF0], [0x13000, 0x1342F], [0x13440, 0x13446], [0x14400, 0x14646], [0x16800, 0x16A38], [0x16A40, 0x16A5E], [0x16A60, 0x16A69], [0x16A70, 0x16ABE], [0x16AC0, 0x16AC9], [0x16AD0, 0x16AED], [0x16AF0, 0x16AF4], [0x16B00, 0x16B2F], [0x16B40, 0x16B43], [0x16B50, 0x16B59], [0x16B63, 0x16B77], [0x16B7D, 0x16B8F], [0x16E40, 0x16E7F], [0x16F00, 0x16F4A], [0x16F4F, 0x16F50], [0x16F8F, 0x16F9F], [0x16FE0, 0x16FE1], [0x16FE3, 0x16FE3], [0x16FF0, 0x16FF1], [0x17000, 0x187F7], [0x18800, 0x18CD5], [0x18D00, 0x18D08], [0x1AFF0, 0x1AFF3], [0x1AFF5, 0x1AFFB], [0x1AFFD, 0x1AFFE], [0x1B000, 0x1B122], [0x1B132, 0x1B132], [0x1B150, 0x1B152], [0x1B155, 0x1B155], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1BC00, 0x1BC6A], [0x1BC70, 0x1BC7C], [0x1BC80, 0x1BC88], [0x1BC90, 0x1BC99], [0x1BC9D, 0x1BC9E], [0x1CF00, 0x1CF2D], [0x1CF30, 0x1CF46], [0x1D165, 0x1D169], [0x1D16D, 0x1D172], [0x1D17B, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], [0x1D242, 0x1D244], [0x1D400, 0x1D454], [0x1D456, 0x1D49C], [0x1D49E, 0x1D49F], [0x1D4A2, 0x1D4A2], [0x1D4A5, 0x1D4A6], [0x1D4A9, 0x1D4AC], [0x1D4AE, 0x1D4B9], [0x1D4BB, 0x1D4BB], [0x1D4BD, 0x1D4C3], [0x1D4C5, 0x1D505], [0x1D507, 0x1D50A], [0x1D50D, 0x1D514], [0x1D516, 0x1D51C], [0x1D51E, 0x1D539], [0x1D53B, 0x1D53E], [0x1D540, 0x1D544], [0x1D546, 0x1D546], [0x1D54A, 0x1D550], [0x1D552, 0x1D6A5], [0x1D6A8, 0x1D6C0], [0x1D6C2, 0x1D6DA], [0x1D6DC, 0x1D6FA], [0x1D6FC, 0x1D714], [0x1D716, 0x1D734], [0x1D736, 0x1D74E], [0x1D750, 0x1D76E], [0x1D770, 0x1D788], [0x1D78A, 0x1D7A8], [0x1D7AA, 0x1D7C2], [0x1D7C4, 0x1D7CB], [0x1D7CE, 0x1D7FF], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], [0x1DAA1, 0x1DAAF], [0x1DF00, 0x1DF1E], [0x1DF25, 0x1DF2A], [0x1E000, 0x1E006], [0x1E008, 0x1E018], [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], [0x1E030, 0x1E06D], [0x1E08F, 0x1E08F], [0x1E100, 0x1E12C], [0x1E130, 0x1E13D], [0x1E140, 0x1E149], [0x1E14E, 0x1E14E], [0x1E290, 0x1E2AE], [0x1E2C0, 0x1E2F9], [0x1E4D0, 0x1E4F9], [0x1E7E0, 0x1E7E6], [0x1E7E8, 0x1E7EB], [0x1E7ED, 0x1E7EE], [0x1E7F0, 0x1E7FE], [0x1E800, 0x1E8C4], [0x1E8D0, 0x1E8D6], [0x1E900, 0x1E94B], [0x1E950, 0x1E959], [0x1EE00, 0x1EE03], [0x1EE05, 0x1EE1F], [0x1EE21, 0x1EE22], [0x1EE24, 0x1EE24], [0x1EE27, 0x1EE27], [0x1EE29, 0x1EE32], [0x1EE34, 0x1EE37], [0x1EE39, 0x1EE39], [0x1EE3B, 0x1EE3B], [0x1EE42, 0x1EE42], [0x1EE47, 0x1EE47], [0x1EE49, 0x1EE49], [0x1EE4B, 0x1EE4B], [0x1EE4D, 0x1EE4F], [0x1EE51, 0x1EE52], [0x1EE54, 0x1EE54], [0x1EE57, 0x1EE57], [0x1EE59, 0x1EE59], [0x1EE5B, 0x1EE5B], [0x1EE5D, 0x1EE5D], [0x1EE5F, 0x1EE5F], [0x1EE61, 0x1EE62], [0x1EE64, 0x1EE64], [0x1EE67, 0x1EE6A], [0x1EE6C, 0x1EE72], [0x1EE74, 0x1EE77], [0x1EE79, 0x1EE7C], [0x1EE7E, 0x1EE7E], [0x1EE80, 0x1EE89], [0x1EE8B, 0x1EE9B], [0x1EEA1, 0x1EEA3], [0x1EEA5, 0x1EEA9], [0x1EEAB, 0x1EEBB], [0x1FBF0, 0x1FBF9], [0x20000, 0x2B739], [0x2B740, 0x2B81D], [0x2B820, 0x2CEA1], [0x2CEB0, 0x2EBE0], [0x2EBF0, 0x2EE5D], [0x2F800, 0x2FA1D], [0x30000, 0x323AF], [0x40000, 0x4FFFD], [0x50000, 0x5FFFD], [0x60000, 0x6FFFD], [0x70000, 0x7FFFD], [0x80000, 0x8FFFD], [0x90000, 0x9FFFD], [0xA0000, 0xAFFFD], [0xB0000, 0xBFFFD], [0xC0000, 0xCFFFD], [0xD0000, 0xDFFFD], [0xE0000, 0xEFFFD], ]; /** Least restrictive Start Entries: 858717 */ static immutable dchar[2][] LeastRestrictive_Start = [ [0xA8, 0xA8], [0xAA, 0xAA], [0xAD, 0xAD], [0xAF, 0xAF], [0xB2, 0xB5], [0xB7, 0xBA], [0xBC, 0xBE], [0xC0, 0xD6], [0xD8, 0xF6], [0xF8, 0x217], [0x250, 0x2A8], [0x2B0, 0x2B8], [0x2BB, 0x2BB], [0x2BD, 0x2C1], [0x2C6, 0x2D1], [0x2E0, 0x2E4], [0x2EC, 0x2EC], [0x2EE, 0x2EE], [0x370, 0x374], [0x376, 0x377], [0x37A, 0x37D], [0x37F, 0x37F], [0x386, 0x386], [0x388, 0x38A], [0x38C, 0x38C], [0x38E, 0x3A1], [0x3A3, 0x3CE], [0x3D0, 0x3D6], [0x3DA, 0x3DA], [0x3DC, 0x3DC], [0x3DE, 0x3DE], [0x3E0, 0x3E0], [0x3E2, 0x3F3], [0x3F7, 0x40C], [0x40E, 0x44F], [0x451, 0x45C], [0x45E, 0x481], [0x48A, 0x4C4], [0x4C7, 0x4C8], [0x4CB, 0x4CC], [0x4D0, 0x4EB], [0x4EE, 0x4F5], [0x4F8, 0x4F9], [0x531, 0x556], [0x559, 0x559], [0x560, 0x587], [0x5B0, 0x5B9], [0x5BB, 0x5BD], [0x5BF, 0x5BF], [0x5C1, 0x5C2], [0x5D0, 0x5EA], [0x5EF, 0x5F2], [0x620, 0x63A], [0x640, 0x652], [0x660, 0x669], [0x66E, 0x6BE], [0x6C0, 0x6CE], [0x6D0, 0x6D5], [0x6E5, 0x6E8], [0x6EA, 0x6FC], [0x6FF, 0x6FF], [0x710, 0x710], [0x712, 0x72F], [0x74D, 0x7A5], [0x7B1, 0x7B1], [0x7CA, 0x7EA], [0x7F4, 0x7F5], [0x7FA, 0x7FA], [0x800, 0x815], [0x81A, 0x81A], [0x824, 0x824], [0x828, 0x828], [0x840, 0x858], [0x860, 0x86A], [0x870, 0x887], [0x889, 0x88E], [0x8A0, 0x8C9], [0x901, 0x939], [0x93D, 0x94D], [0x950, 0x952], [0x958, 0x963], [0x966, 0x96F], [0x971, 0x983], [0x985, 0x98C], [0x98F, 0x990], [0x993, 0x9A8], [0x9AA, 0x9B0], [0x9B2, 0x9B2], [0x9B6, 0x9B9], [0x9BD, 0x9C4], [0x9C7, 0x9C8], [0x9CB, 0x9CE], [0x9DC, 0x9DD], [0x9DF, 0x9E3], [0x9E6, 0x9F1], [0x9FC, 0x9FC], [0xA02, 0xA02], [0xA05, 0xA0A], [0xA0F, 0xA10], [0xA13, 0xA28], [0xA2A, 0xA30], [0xA32, 0xA33], [0xA35, 0xA36], [0xA38, 0xA39], [0xA3E, 0xA42], [0xA47, 0xA48], [0xA4B, 0xA4D], [0xA59, 0xA5C], [0xA5E, 0xA5E], [0xA66, 0xA6F], [0xA72, 0xA74], [0xA81, 0xA83], [0xA85, 0xA8B], [0xA8D, 0xA8D], [0xA8F, 0xA91], [0xA93, 0xAA8], [0xAAA, 0xAB0], [0xAB2, 0xAB3], [0xAB5, 0xAB9], [0xABD, 0xABD], [0xAC7, 0xAC9], [0xACB, 0xACD], [0xAD0, 0xAD0], [0xAE0, 0xAE0], [0xAE6, 0xAEF], [0xAF9, 0xAF9], [0xB01, 0xB03], [0xB05, 0xB0C], [0xB0F, 0xB10], [0xB13, 0xB28], [0xB2A, 0xB30], [0xB32, 0xB33], [0xB35, 0xB39], [0xB3D, 0xB43], [0xB47, 0xB48], [0xB4B, 0xB4D], [0xB5C, 0xB5D], [0xB5F, 0xB61], [0xB66, 0xB6F], [0xB71, 0xB71], [0xB82, 0xB83], [0xB85, 0xB8A], [0xB8E, 0xB90], [0xB92, 0xB95], [0xB99, 0xB9A], [0xB9C, 0xB9C], [0xB9E, 0xB9F], [0xBA3, 0xBA4], [0xBA8, 0xBAA], [0xBAE, 0xBB5], [0xBB7, 0xBB9], [0xBBE, 0xBC2], [0xBC6, 0xBC8], [0xBCA, 0xBCD], [0xBD0, 0xBD0], [0xBE7, 0xBEF], [0xC01, 0xC03], [0xC05, 0xC0C], [0xC0E, 0xC10], [0xC12, 0xC28], [0xC2A, 0xC33], [0xC35, 0xC39], [0xC3D, 0xC44], [0xC46, 0xC48], [0xC4A, 0xC4D], [0xC58, 0xC5A], [0xC5D, 0xC5D], [0xC60, 0xC61], [0xC66, 0xC6F], [0xC80, 0xC80], [0xC82, 0xC83], [0xC85, 0xC8C], [0xC8E, 0xC90], [0xC92, 0xCA8], [0xCAA, 0xCB3], [0xCB5, 0xCB9], [0xCBD, 0xCC4], [0xCC6, 0xCC8], [0xCCA, 0xCCD], [0xCDD, 0xCDE], [0xCE0, 0xCE1], [0xCE6, 0xCEF], [0xCF1, 0xCF2], [0xD02, 0xD0C], [0xD0E, 0xD10], [0xD12, 0xD28], [0xD2A, 0xD39], [0xD3D, 0xD43], [0xD46, 0xD48], [0xD4A, 0xD4E], [0xD54, 0xD56], [0xD5F, 0xD61], [0xD66, 0xD6F], [0xD7A, 0xD7F], [0xD85, 0xD96], [0xD9A, 0xDB1], [0xDB3, 0xDBB], [0xDBD, 0xDBD], [0xDC0, 0xDC6], [0xE01, 0xE30], [0xE32, 0xE32], [0xE40, 0xE5B], [0xE81, 0xE82], [0xE84, 0xE84], [0xE86, 0xE88], [0xE8A, 0xE8A], [0xE8C, 0xE8D], [0xE94, 0xE97], [0xE99, 0xE9F], [0xEA1, 0xEA3], [0xEA5, 0xEA5], [0xEA7, 0xEAB], [0xEAD, 0xEAE], [0xEB0, 0xEB2], [0xEBB, 0xEBD], [0xEC0, 0xEC4], [0xEC6, 0xEC6], [0xEC8, 0xECD], [0xED0, 0xED9], [0xEDC, 0xEDD], [0xF00, 0xF00], [0xF18, 0xF19], [0xF20, 0xF33], [0xF35, 0xF35], [0xF37, 0xF37], [0xF39, 0xF39], [0xF3E, 0xF47], [0xF49, 0xF69], [0xF71, 0xF84], [0xF86, 0xF8C], [0xF90, 0xF95], [0xF97, 0xF97], [0xF99, 0xFAD], [0xFB1, 0xFB7], [0xFB9, 0xFB9], [0x1000, 0x102A], [0x103F, 0x103F], [0x1050, 0x1055], [0x105A, 0x105D], [0x1061, 0x1061], [0x1065, 0x1066], [0x106E, 0x1070], [0x1075, 0x1081], [0x108E, 0x108E], [0x10A0, 0x10C5], [0x10C7, 0x10C7], [0x10CD, 0x10CD], [0x10D0, 0x10FA], [0x10FC, 0x1248], [0x124A, 0x124D], [0x1250, 0x1256], [0x1258, 0x1258], [0x125A, 0x125D], [0x1260, 0x1288], [0x128A, 0x128D], [0x1290, 0x12B0], [0x12B2, 0x12B5], [0x12B8, 0x12BE], [0x12C0, 0x12C0], [0x12C2, 0x12C5], [0x12C8, 0x12D6], [0x12D8, 0x1310], [0x1312, 0x1315], [0x1318, 0x135A], [0x1380, 0x138F], [0x13A0, 0x13F5], [0x13F8, 0x13FD], [0x1401, 0x166C], [0x166F, 0x167F], [0x1681, 0x169A], [0x16A0, 0x16EA], [0x16EE, 0x16F8], [0x1700, 0x1711], [0x171F, 0x1731], [0x1740, 0x1751], [0x1760, 0x176C], [0x176E, 0x1770], [0x1780, 0x17B3], [0x17D7, 0x17D7], [0x17DC, 0x17DC], [0x180F, 0x1878], [0x1880, 0x18A8], [0x18AA, 0x18AA], [0x18B0, 0x18F5], [0x1900, 0x191E], [0x1950, 0x196D], [0x1970, 0x1974], [0x1980, 0x19AB], [0x19B0, 0x19C9], [0x1A00, 0x1A16], [0x1A20, 0x1A54], [0x1AA7, 0x1AA7], [0x1B05, 0x1B33], [0x1B45, 0x1B4C], [0x1B83, 0x1BA0], [0x1BAE, 0x1BAF], [0x1BBA, 0x1BE5], [0x1C00, 0x1C23], [0x1C4D, 0x1C4F], [0x1C5A, 0x1C7D], [0x1C80, 0x1C88], [0x1C90, 0x1CBA], [0x1CBD, 0x1CBF], [0x1CE9, 0x1CEC], [0x1CEE, 0x1CF3], [0x1CF5, 0x1CF6], [0x1CFA, 0x1CFA], [0x1D00, 0x1DBF], [0x1E00, 0x1EF9], [0x1F00, 0x1F15], [0x1F18, 0x1F1D], [0x1F20, 0x1F45], [0x1F48, 0x1F4D], [0x1F50, 0x1F57], [0x1F59, 0x1F59], [0x1F5B, 0x1F5B], [0x1F5D, 0x1F5D], [0x1F5F, 0x1F7D], [0x1F80, 0x1FB4], [0x1FB6, 0x1FBC], [0x1FBE, 0x1FBE], [0x1FC2, 0x1FC4], [0x1FC6, 0x1FCC], [0x1FD0, 0x1FD3], [0x1FD6, 0x1FDB], [0x1FE0, 0x1FEC], [0x1FF2, 0x1FF4], [0x1FF6, 0x1FFC], [0x200B, 0x200D], [0x202A, 0x202E], [0x203F, 0x2040], [0x2054, 0x2054], [0x2060, 0x2071], [0x207F, 0x207F], [0x2090, 0x209C], [0x2102, 0x2102], [0x2107, 0x2107], [0x210A, 0x2113], [0x2115, 0x2115], [0x2118, 0x211D], [0x2124, 0x2124], [0x2126, 0x2126], [0x2128, 0x2128], [0x212A, 0x2138], [0x213C, 0x213F], [0x2145, 0x2149], [0x214E, 0x214E], [0x2160, 0x2188], [0x2460, 0x24FF], [0x2776, 0x2793], [0x2C00, 0x2CE4], [0x2CEB, 0x2CEE], [0x2CF2, 0x2CF3], [0x2D00, 0x2D25], [0x2D27, 0x2D27], [0x2D2D, 0x2D2D], [0x2D30, 0x2D67], [0x2D6F, 0x2D6F], [0x2D80, 0x2D96], [0x2DA0, 0x2DA6], [0x2DA8, 0x2DAE], [0x2DB0, 0x2DB6], [0x2DB8, 0x2DBE], [0x2DC0, 0x2DC6], [0x2DC8, 0x2DCE], [0x2DD0, 0x2DD6], [0x2DD8, 0x2DDE], [0x2E80, 0x2FFF], [0x3004, 0x3007], [0x3021, 0x3029], [0x3031, 0x3035], [0x3038, 0x303C], [0x3041, 0x3096], [0x309B, 0x309F], [0x30A1, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E], [0x31A0, 0x31BF], [0x31F0, 0x31FF], [0x3400, 0x4DBF], [0x4E00, 0xA48C], [0xA4D0, 0xA4FD], [0xA500, 0xA60C], [0xA610, 0xA61F], [0xA62A, 0xA62B], [0xA640, 0xA66E], [0xA67F, 0xA69D], [0xA6A0, 0xA6EF], [0xA717, 0xA71F], [0xA722, 0xA788], [0xA78B, 0xA7CA], [0xA7D0, 0xA7D1], [0xA7D3, 0xA7D3], [0xA7D5, 0xA7D9], [0xA7F2, 0xA801], [0xA803, 0xA805], [0xA807, 0xA80A], [0xA80C, 0xA822], [0xA840, 0xA873], [0xA882, 0xA8B3], [0xA8F2, 0xA8F7], [0xA8FB, 0xA8FB], [0xA8FD, 0xA8FE], [0xA90A, 0xA925], [0xA930, 0xA946], [0xA960, 0xA97C], [0xA984, 0xA9B2], [0xA9CF, 0xA9CF], [0xA9E0, 0xA9E4], [0xA9E6, 0xA9EF], [0xA9FA, 0xA9FE], [0xAA00, 0xAA28], [0xAA40, 0xAA42], [0xAA44, 0xAA4B], [0xAA60, 0xAA76], [0xAA7A, 0xAA7A], [0xAA7E, 0xAAAF], [0xAAB1, 0xAAB1], [0xAAB5, 0xAAB6], [0xAAB9, 0xAABD], [0xAAC0, 0xAAC0], [0xAAC2, 0xAAC2], [0xAADB, 0xAADD], [0xAAE0, 0xAAEA], [0xAAF2, 0xAAF4], [0xAB01, 0xAB06], [0xAB09, 0xAB0E], [0xAB11, 0xAB16], [0xAB20, 0xAB26], [0xAB28, 0xAB2E], [0xAB30, 0xAB5A], [0xAB5C, 0xAB69], [0xAB70, 0xABE2], [0xAC00, 0xD7A3], [0xD7B0, 0xD7C6], [0xD7CB, 0xD7FB], [0xF900, 0xFA6D], [0xFA70, 0xFAD9], [0xFB00, 0xFB06], [0xFB13, 0xFB17], [0xFB1D, 0xFB1D], [0xFB1F, 0xFB28], [0xFB2A, 0xFB36], [0xFB38, 0xFB3C], [0xFB3E, 0xFB3E], [0xFB40, 0xFB41], [0xFB43, 0xFB44], [0xFB46, 0xFBB1], [0xFBD3, 0xFC5D], [0xFC64, 0xFD3D], [0xFD40, 0xFD8F], [0xFD92, 0xFDC7], [0xFDF0, 0xFDF9], [0xFE47, 0xFE71], [0xFE73, 0xFE73], [0xFE77, 0xFE77], [0xFE79, 0xFE79], [0xFE7B, 0xFE7B], [0xFE7D, 0xFE7D], [0xFE7F, 0xFEFC], [0xFF21, 0xFF3A], [0xFF41, 0xFF5A], [0xFF66, 0xFF9D], [0xFFA0, 0xFFBE], [0xFFC2, 0xFFC7], [0xFFCA, 0xFFCF], [0xFFD2, 0xFFD7], [0xFFDA, 0xFFDC], [0x10000, 0x1000B], [0x1000D, 0x10026], [0x10028, 0x1003A], [0x1003C, 0x1003D], [0x1003F, 0x1004D], [0x10050, 0x1005D], [0x10080, 0x100FA], [0x10140, 0x10174], [0x10280, 0x1029C], [0x102A0, 0x102D0], [0x10300, 0x1031F], [0x1032D, 0x1034A], [0x10350, 0x10375], [0x10380, 0x1039D], [0x103A0, 0x103C3], [0x103C8, 0x103CF], [0x103D1, 0x103D5], [0x10400, 0x1049D], [0x104B0, 0x104D3], [0x104D8, 0x104FB], [0x10500, 0x10527], [0x10530, 0x10563], [0x10570, 0x1057A], [0x1057C, 0x1058A], [0x1058C, 0x10592], [0x10594, 0x10595], [0x10597, 0x105A1], [0x105A3, 0x105B1], [0x105B3, 0x105B9], [0x105BB, 0x105BC], [0x10600, 0x10736], [0x10740, 0x10755], [0x10760, 0x10767], [0x10780, 0x10785], [0x10787, 0x107B0], [0x107B2, 0x107BA], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080A, 0x10835], [0x10837, 0x10838], [0x1083C, 0x1083C], [0x1083F, 0x10855], [0x10860, 0x10876], [0x10880, 0x1089E], [0x108E0, 0x108F2], [0x108F4, 0x108F5], [0x10900, 0x10915], [0x10920, 0x10939], [0x10980, 0x109B7], [0x109BE, 0x109BF], [0x10A00, 0x10A00], [0x10A10, 0x10A13], [0x10A15, 0x10A17], [0x10A19, 0x10A35], [0x10A60, 0x10A7C], [0x10A80, 0x10A9C], [0x10AC0, 0x10AC7], [0x10AC9, 0x10AE4], [0x10B00, 0x10B35], [0x10B40, 0x10B55], [0x10B60, 0x10B72], [0x10B80, 0x10B91], [0x10C00, 0x10C48], [0x10C80, 0x10CB2], [0x10CC0, 0x10CF2], [0x10D00, 0x10D23], [0x10E80, 0x10EA9], [0x10EB0, 0x10EB1], [0x10F00, 0x10F1C], [0x10F27, 0x10F27], [0x10F30, 0x10F45], [0x10F70, 0x10F81], [0x10FB0, 0x10FC4], [0x10FE0, 0x10FF6], [0x11003, 0x11037], [0x11071, 0x11072], [0x11075, 0x11075], [0x11083, 0x110AF], [0x110D0, 0x110E8], [0x11103, 0x11126], [0x11144, 0x11144], [0x11147, 0x11147], [0x11150, 0x11172], [0x11176, 0x11176], [0x11183, 0x111B2], [0x111C1, 0x111C4], [0x111DA, 0x111DA], [0x111DC, 0x111DC], [0x11200, 0x11211], [0x11213, 0x1122B], [0x1123F, 0x11240], [0x11280, 0x11286], [0x11288, 0x11288], [0x1128A, 0x1128D], [0x1128F, 0x1129D], [0x1129F, 0x112A8], [0x112B0, 0x112DE], [0x11305, 0x1130C], [0x1130F, 0x11310], [0x11313, 0x11328], [0x1132A, 0x11330], [0x11332, 0x11333], [0x11335, 0x11339], [0x1133D, 0x1133D], [0x11350, 0x11350], [0x1135D, 0x11361], [0x11400, 0x11434], [0x11447, 0x1144A], [0x1145F, 0x11461], [0x11480, 0x114AF], [0x114C4, 0x114C5], [0x114C7, 0x114C7], [0x11580, 0x115AE], [0x115D8, 0x115DB], [0x11600, 0x1162F], [0x11644, 0x11644], [0x11680, 0x116AA], [0x116B8, 0x116B8], [0x11700, 0x1171A], [0x11740, 0x11746], [0x11800, 0x1182B], [0x118A0, 0x118DF], [0x118FF, 0x11906], [0x11909, 0x11909], [0x1190C, 0x11913], [0x11915, 0x11916], [0x11918, 0x1192F], [0x1193F, 0x1193F], [0x11941, 0x11941], [0x119A0, 0x119A7], [0x119AA, 0x119D0], [0x119E1, 0x119E1], [0x119E3, 0x119E3], [0x11A00, 0x11A00], [0x11A0B, 0x11A32], [0x11A3A, 0x11A3A], [0x11A50, 0x11A50], [0x11A5C, 0x11A89], [0x11A9D, 0x11A9D], [0x11AB0, 0x11AF8], [0x11C00, 0x11C08], [0x11C0A, 0x11C2E], [0x11C40, 0x11C40], [0x11C72, 0x11C8F], [0x11D00, 0x11D06], [0x11D08, 0x11D09], [0x11D0B, 0x11D30], [0x11D46, 0x11D46], [0x11D60, 0x11D65], [0x11D67, 0x11D68], [0x11D6A, 0x11D89], [0x11D98, 0x11D98], [0x11EE0, 0x11EF2], [0x11F02, 0x11F02], [0x11F04, 0x11F10], [0x11F12, 0x11F33], [0x11FB0, 0x11FB0], [0x12000, 0x12399], [0x12400, 0x1246E], [0x12480, 0x12543], [0x12F90, 0x12FF0], [0x13000, 0x1342F], [0x13441, 0x13446], [0x14400, 0x14646], [0x16800, 0x16A38], [0x16A40, 0x16A5E], [0x16A70, 0x16ABE], [0x16AD0, 0x16AED], [0x16B00, 0x16B2F], [0x16B40, 0x16B43], [0x16B63, 0x16B77], [0x16B7D, 0x16B8F], [0x16E40, 0x16E7F], [0x16F00, 0x16F4A], [0x16F50, 0x16F50], [0x16F93, 0x16F9F], [0x16FE0, 0x16FE1], [0x16FE3, 0x16FE3], [0x17000, 0x187F7], [0x18800, 0x18CD5], [0x18D00, 0x18D08], [0x1AFF0, 0x1AFF3], [0x1AFF5, 0x1AFFB], [0x1AFFD, 0x1AFFE], [0x1B000, 0x1B122], [0x1B132, 0x1B132], [0x1B150, 0x1B152], [0x1B155, 0x1B155], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1BC00, 0x1BC6A], [0x1BC70, 0x1BC7C], [0x1BC80, 0x1BC88], [0x1BC90, 0x1BC99], [0x1D400, 0x1D454], [0x1D456, 0x1D49C], [0x1D49E, 0x1D49F], [0x1D4A2, 0x1D4A2], [0x1D4A5, 0x1D4A6], [0x1D4A9, 0x1D4AC], [0x1D4AE, 0x1D4B9], [0x1D4BB, 0x1D4BB], [0x1D4BD, 0x1D4C3], [0x1D4C5, 0x1D505], [0x1D507, 0x1D50A], [0x1D50D, 0x1D514], [0x1D516, 0x1D51C], [0x1D51E, 0x1D539], [0x1D53B, 0x1D53E], [0x1D540, 0x1D544], [0x1D546, 0x1D546], [0x1D54A, 0x1D550], [0x1D552, 0x1D6A5], [0x1D6A8, 0x1D6C0], [0x1D6C2, 0x1D6DA], [0x1D6DC, 0x1D6FA], [0x1D6FC, 0x1D714], [0x1D716, 0x1D734], [0x1D736, 0x1D74E], [0x1D750, 0x1D76E], [0x1D770, 0x1D788], [0x1D78A, 0x1D7A8], [0x1D7AA, 0x1D7C2], [0x1D7C4, 0x1D7CB], [0x1DF00, 0x1DF1E], [0x1DF25, 0x1DF2A], [0x1E030, 0x1E06D], [0x1E100, 0x1E12C], [0x1E137, 0x1E13D], [0x1E14E, 0x1E14E], [0x1E290, 0x1E2AD], [0x1E2C0, 0x1E2EB], [0x1E4D0, 0x1E4EB], [0x1E7E0, 0x1E7E6], [0x1E7E8, 0x1E7EB], [0x1E7ED, 0x1E7EE], [0x1E7F0, 0x1E7FE], [0x1E800, 0x1E8C4], [0x1E900, 0x1E943], [0x1E94B, 0x1E94B], [0x1EE00, 0x1EE03], [0x1EE05, 0x1EE1F], [0x1EE21, 0x1EE22], [0x1EE24, 0x1EE24], [0x1EE27, 0x1EE27], [0x1EE29, 0x1EE32], [0x1EE34, 0x1EE37], [0x1EE39, 0x1EE39], [0x1EE3B, 0x1EE3B], [0x1EE42, 0x1EE42], [0x1EE47, 0x1EE47], [0x1EE49, 0x1EE49], [0x1EE4B, 0x1EE4B], [0x1EE4D, 0x1EE4F], [0x1EE51, 0x1EE52], [0x1EE54, 0x1EE54], [0x1EE57, 0x1EE57], [0x1EE59, 0x1EE59], [0x1EE5B, 0x1EE5B], [0x1EE5D, 0x1EE5D], [0x1EE5F, 0x1EE5F], [0x1EE61, 0x1EE62], [0x1EE64, 0x1EE64], [0x1EE67, 0x1EE6A], [0x1EE6C, 0x1EE72], [0x1EE74, 0x1EE77], [0x1EE79, 0x1EE7C], [0x1EE7E, 0x1EE7E], [0x1EE80, 0x1EE89], [0x1EE8B, 0x1EE9B], [0x1EEA1, 0x1EEA3], [0x1EEA5, 0x1EEA9], [0x1EEAB, 0x1EEBB], [0x20000, 0x2B739], [0x2B740, 0x2B81D], [0x2B820, 0x2CEA1], [0x2CEB0, 0x2EBE0], [0x2EBF0, 0x2EE5D], [0x2F800, 0x2FA1D], [0x30000, 0x323AF], [0x40000, 0x4FFFD], [0x50000, 0x5FFFD], [0x60000, 0x6FFFD], [0x70000, 0x7FFFD], [0x80000, 0x8FFFD], [0x90000, 0x9FFFD], [0xA0000, 0xAFFFD], [0xB0000, 0xBFFFD], [0xC0000, 0xCFFFD], [0xD0000, 0xDFFFD], [0xE0000, 0xEFFFD], ]; /** Least restrictive Continue Entries: 796056 */ static immutable dchar[2][] LeastRestrictive_Continue = [ [0xA8, 0xA8], [0xAA, 0xAA], [0xAD, 0xAD], [0xAF, 0xAF], [0xB2, 0xB5], [0xB7, 0xBA], [0xBC, 0xBE], [0xC0, 0xD6], [0xD8, 0xF6], [0xF8, 0x217], [0x250, 0x2A8], [0x2B0, 0x2B8], [0x2BB, 0x2BB], [0x2BD, 0x2C1], [0x2C6, 0x2D1], [0x2E0, 0x2E4], [0x2EC, 0x2EC], [0x2EE, 0x2EE], [0x300, 0x374], [0x376, 0x377], [0x37A, 0x37D], [0x37F, 0x37F], [0x386, 0x386], [0x388, 0x38A], [0x38C, 0x38C], [0x38E, 0x3A1], [0x3A3, 0x3D6], [0x3DA, 0x3DA], [0x3DC, 0x3DC], [0x3DE, 0x3DE], [0x3E0, 0x3E0], [0x3E2, 0x3F3], [0x3F7, 0x40C], [0x40E, 0x44F], [0x451, 0x45C], [0x45E, 0x481], [0x483, 0x487], [0x48A, 0x4C4], [0x4C7, 0x4C8], [0x4CB, 0x4CC], [0x4D0, 0x4EB], [0x4EE, 0x4F5], [0x4F8, 0x4F9], [0x531, 0x556], [0x559, 0x559], [0x560, 0x587], [0x591, 0x5B9], [0x5BB, 0x5BD], [0x5BF, 0x5BF], [0x5C1, 0x5C2], [0x5C4, 0x5C5], [0x5C7, 0x5C7], [0x5D0, 0x5EA], [0x5EF, 0x5F2], [0x610, 0x61A], [0x620, 0x63A], [0x640, 0x652], [0x660, 0x669], [0x66E, 0x6B7], [0x6BA, 0x6BE], [0x6C0, 0x6CE], [0x6D0, 0x6DC], [0x6DF, 0x6E8], [0x6EA, 0x6ED], [0x6F0, 0x6F9], [0x6FF, 0x6FF], [0x710, 0x74A], [0x74D, 0x7B1], [0x7C0, 0x7F5], [0x7FA, 0x7FA], [0x7FD, 0x7FD], [0x800, 0x82D], [0x840, 0x85B], [0x860, 0x86A], [0x870, 0x887], [0x889, 0x88E], [0x898, 0x8E1], [0x8E3, 0x903], [0x905, 0x939], [0x93D, 0x94D], [0x950, 0x952], [0x958, 0x963], [0x966, 0x96F], [0x971, 0x983], [0x985, 0x98C], [0x98F, 0x990], [0x993, 0x9A8], [0x9AA, 0x9B0], [0x9B2, 0x9B2], [0x9B6, 0x9B9], [0x9BC, 0x9C4], [0x9C7, 0x9C8], [0x9CB, 0x9CD], [0x9D7, 0x9D7], [0x9DC, 0x9DD], [0x9DF, 0x9E3], [0x9E6, 0x9F1], [0x9FC, 0x9FC], [0x9FE, 0x9FE], [0xA01, 0xA02], [0xA05, 0xA0A], [0xA0F, 0xA10], [0xA13, 0xA28], [0xA2A, 0xA30], [0xA32, 0xA33], [0xA35, 0xA36], [0xA38, 0xA39], [0xA3C, 0xA3C], [0xA3E, 0xA42], [0xA47, 0xA48], [0xA4B, 0xA4D], [0xA51, 0xA51], [0xA59, 0xA5C], [0xA5E, 0xA5E], [0xA66, 0xA6F], [0xA74, 0xA74], [0xA81, 0xA83], [0xA85, 0xA8B], [0xA8D, 0xA8D], [0xA8F, 0xA91], [0xA93, 0xAA8], [0xAAA, 0xAB0], [0xAB2, 0xAB3], [0xAB5, 0xAB9], [0xABC, 0xAC5], [0xAC7, 0xAC9], [0xACB, 0xACD], [0xAD0, 0xAD0], [0xAE0, 0xAE0], [0xAE6, 0xAEF], [0xAF9, 0xAFF], [0xB01, 0xB03], [0xB05, 0xB0C], [0xB0F, 0xB10], [0xB13, 0xB28], [0xB2A, 0xB30], [0xB32, 0xB33], [0xB35, 0xB39], [0xB3C, 0xB43], [0xB47, 0xB48], [0xB4B, 0xB4D], [0xB55, 0xB57], [0xB5C, 0xB5D], [0xB5F, 0xB61], [0xB66, 0xB6F], [0xB71, 0xB71], [0xB82, 0xB83], [0xB85, 0xB8A], [0xB8E, 0xB90], [0xB92, 0xB95], [0xB99, 0xB9A], [0xB9C, 0xB9C], [0xB9E, 0xB9F], [0xBA3, 0xBA4], [0xBA8, 0xBAA], [0xBAE, 0xBB5], [0xBB7, 0xBB9], [0xBBE, 0xBC2], [0xBC6, 0xBC8], [0xBCA, 0xBCD], [0xBD0, 0xBD0], [0xBD7, 0xBD7], [0xBE6, 0xBEF], [0xC00, 0xC03], [0xC05, 0xC0C], [0xC0E, 0xC10], [0xC12, 0xC28], [0xC2A, 0xC33], [0xC35, 0xC39], [0xC3C, 0xC44], [0xC46, 0xC48], [0xC4A, 0xC4D], [0xC55, 0xC56], [0xC58, 0xC5A], [0xC5D, 0xC5D], [0xC60, 0xC61], [0xC66, 0xC6F], [0xC80, 0xC83], [0xC85, 0xC8C], [0xC8E, 0xC90], [0xC92, 0xCA8], [0xCAA, 0xCB3], [0xCB5, 0xCB9], [0xCBC, 0xCC4], [0xCC6, 0xCC8], [0xCCA, 0xCCD], [0xCD5, 0xCD6], [0xCDD, 0xCDE], [0xCE0, 0xCE1], [0xCE6, 0xCEF], [0xCF1, 0xCF3], [0xD00, 0xD03], [0xD05, 0xD0C], [0xD0E, 0xD10], [0xD12, 0xD39], [0xD3E, 0xD43], [0xD46, 0xD48], [0xD4A, 0xD4E], [0xD54, 0xD57], [0xD5F, 0xD61], [0xD66, 0xD6F], [0xD7A, 0xD7F], [0xD81, 0xD83], [0xD85, 0xD96], [0xD9A, 0xDB1], [0xDB3, 0xDBB], [0xDBD, 0xDBD], [0xDC0, 0xDC6], [0xDCA, 0xDCA], [0xDCF, 0xDD4], [0xDD6, 0xDD6], [0xDD8, 0xDDF], [0xDE6, 0xDEF], [0xDF2, 0xDF3], [0xE01, 0xE3A], [0xE40, 0xE4E], [0xE50, 0xE59], [0xE81, 0xE82], [0xE84, 0xE84], [0xE86, 0xE88], [0xE8A, 0xE8A], [0xE8C, 0xE8D], [0xE94, 0xE97], [0xE99, 0xE9F], [0xEA1, 0xEA3], [0xEA5, 0xEA5], [0xEA7, 0xEAB], [0xEAD, 0xEAE], [0xEB0, 0xEB9], [0xEBB, 0xEBD], [0xEC0, 0xEC4], [0xEC6, 0xEC6], [0xEC8, 0xECE], [0xED0, 0xED9], [0xEDC, 0xEDF], [0xF00, 0xF00], [0xF18, 0xF19], [0xF20, 0xF29], [0xF35, 0xF35], [0xF37, 0xF37], [0xF39, 0xF39], [0xF3E, 0xF47], [0xF49, 0xF6C], [0xF71, 0xF84], [0xF86, 0xF95], [0xF97, 0xF97], [0xF99, 0xFB7], [0xFB9, 0xFB9], [0xFC6, 0xFC6], [0x1000, 0x1049], [0x1050, 0x109D], [0x10A0, 0x10C5], [0x10C7, 0x10C7], [0x10CD, 0x10CD], [0x10D0, 0x10FA], [0x10FC, 0x1248], [0x124A, 0x124D], [0x1250, 0x1256], [0x1258, 0x1258], [0x125A, 0x125D], [0x1260, 0x1288], [0x128A, 0x128D], [0x1290, 0x12B0], [0x12B2, 0x12B5], [0x12B8, 0x12BE], [0x12C0, 0x12C0], [0x12C2, 0x12C5], [0x12C8, 0x12D6], [0x12D8, 0x1310], [0x1312, 0x1315], [0x1318, 0x135A], [0x135D, 0x135F], [0x1369, 0x1371], [0x1380, 0x138F], [0x13A0, 0x13F5], [0x13F8, 0x13FD], [0x1401, 0x166C], [0x166F, 0x167F], [0x1681, 0x169A], [0x16A0, 0x16EA], [0x16EE, 0x16F8], [0x1700, 0x1715], [0x171F, 0x1734], [0x1740, 0x1753], [0x1760, 0x176C], [0x176E, 0x1770], [0x1772, 0x1773], [0x1780, 0x17D3], [0x17D7, 0x17D7], [0x17DC, 0x17DD], [0x17E0, 0x17E9], [0x180B, 0x180D], [0x180F, 0x1819], [0x1820, 0x1878], [0x1880, 0x18AA], [0x18B0, 0x18F5], [0x1900, 0x191E], [0x1920, 0x192B], [0x1930, 0x193B], [0x1946, 0x196D], [0x1970, 0x1974], [0x1980, 0x19AB], [0x19B0, 0x19C9], [0x19D0, 0x19DA], [0x1A00, 0x1A1B], [0x1A20, 0x1A5E], [0x1A60, 0x1A7C], [0x1A7F, 0x1A89], [0x1A90, 0x1A99], [0x1AA7, 0x1AA7], [0x1AB0, 0x1ABD], [0x1ABF, 0x1ACE], [0x1B00, 0x1B4C], [0x1B50, 0x1B59], [0x1B6B, 0x1B73], [0x1B80, 0x1BF3], [0x1C00, 0x1C37], [0x1C40, 0x1C49], [0x1C4D, 0x1C7D], [0x1C80, 0x1C88], [0x1C90, 0x1CBA], [0x1CBD, 0x1CBF], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CFA], [0x1D00, 0x1EF9], [0x1F00, 0x1F15], [0x1F18, 0x1F1D], [0x1F20, 0x1F45], [0x1F48, 0x1F4D], [0x1F50, 0x1F57], [0x1F59, 0x1F59], [0x1F5B, 0x1F5B], [0x1F5D, 0x1F5D], [0x1F5F, 0x1F7D], [0x1F80, 0x1FB4], [0x1FB6, 0x1FBC], [0x1FBE, 0x1FBE], [0x1FC2, 0x1FC4], [0x1FC6, 0x1FCC], [0x1FD0, 0x1FD3], [0x1FD6, 0x1FDB], [0x1FE0, 0x1FEC], [0x1FF2, 0x1FF4], [0x1FF6, 0x1FFC], [0x200B, 0x200D], [0x202A, 0x202E], [0x203F, 0x2040], [0x2054, 0x2054], [0x2060, 0x2071], [0x207F, 0x207F], [0x2090, 0x209C], [0x20D0, 0x20DC], [0x20E1, 0x20E1], [0x20E5, 0x20F0], [0x2102, 0x2102], [0x2107, 0x2107], [0x210A, 0x2113], [0x2115, 0x2115], [0x2118, 0x211D], [0x2124, 0x2124], [0x2126, 0x2126], [0x2128, 0x2128], [0x212A, 0x2138], [0x213C, 0x213F], [0x2145, 0x2149], [0x214E, 0x214E], [0x2160, 0x2188], [0x2460, 0x24FF], [0x2776, 0x2793], [0x2C00, 0x2CE4], [0x2CEB, 0x2CF3], [0x2D00, 0x2D25], [0x2D27, 0x2D27], [0x2D2D, 0x2D2D], [0x2D30, 0x2D67], [0x2D6F, 0x2D6F], [0x2D7F, 0x2D96], [0x2DA0, 0x2DA6], [0x2DA8, 0x2DAE], [0x2DB0, 0x2DB6], [0x2DB8, 0x2DBE], [0x2DC0, 0x2DC6], [0x2DC8, 0x2DCE], [0x2DD0, 0x2DD6], [0x2DD8, 0x2DDE], [0x2DE0, 0x2DFF], [0x2E80, 0x2FFF], [0x3004, 0x3007], [0x3021, 0x302F], [0x3031, 0x303C], [0x3041, 0x3096], [0x3099, 0x309F], [0x30A1, 0x30FC], [0x3105, 0x312F], [0x3131, 0x318E], [0x31A0, 0x31BF], [0x31F0, 0x31FF], [0x3400, 0x4DBF], [0x4E00, 0xA48C], [0xA4D0, 0xA4FD], [0xA500, 0xA60C], [0xA610, 0xA62B], [0xA640, 0xA66F], [0xA674, 0xA67D], [0xA67F, 0xA6F1], [0xA717, 0xA71F], [0xA722, 0xA788], [0xA78B, 0xA7CA], [0xA7D0, 0xA7D1], [0xA7D3, 0xA7D3], [0xA7D5, 0xA7D9], [0xA7F2, 0xA827], [0xA82C, 0xA82C], [0xA840, 0xA873], [0xA880, 0xA8C5], [0xA8D0, 0xA8D9], [0xA8E0, 0xA8F7], [0xA8FB, 0xA8FB], [0xA8FD, 0xA92D], [0xA930, 0xA953], [0xA960, 0xA97C], [0xA980, 0xA9C0], [0xA9CF, 0xA9D9], [0xA9E0, 0xA9FE], [0xAA00, 0xAA36], [0xAA40, 0xAA4D], [0xAA50, 0xAA59], [0xAA60, 0xAA76], [0xAA7A, 0xAAC2], [0xAADB, 0xAADD], [0xAAE0, 0xAAEF], [0xAAF2, 0xAAF6], [0xAB01, 0xAB06], [0xAB09, 0xAB0E], [0xAB11, 0xAB16], [0xAB20, 0xAB26], [0xAB28, 0xAB2E], [0xAB30, 0xAB5A], [0xAB5C, 0xAB69], [0xAB70, 0xABEA], [0xABEC, 0xABED], [0xABF0, 0xABF9], [0xAC00, 0xD7A3], [0xD7B0, 0xD7C6], [0xD7CB, 0xD7FB], [0xF900, 0xFA6D], [0xFA70, 0xFAD9], [0xFB00, 0xFB06], [0xFB13, 0xFB17], [0xFB1D, 0xFB28], [0xFB2A, 0xFB36], [0xFB38, 0xFB3C], [0xFB3E, 0xFB3E], [0xFB40, 0xFB41], [0xFB43, 0xFB44], [0xFB46, 0xFBB1], [0xFBD3, 0xFC5D], [0xFC64, 0xFD3D], [0xFD40, 0xFD8F], [0xFD92, 0xFDC7], [0xFDF0, 0xFDF9], [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFE33, 0xFE34], [0xFE47, 0xFE4F], [0xFE71, 0xFE71], [0xFE73, 0xFE73], [0xFE77, 0xFE77], [0xFE79, 0xFE79], [0xFE7B, 0xFE7B], [0xFE7D, 0xFE7D], [0xFE7F, 0xFEFC], [0xFF10, 0xFF19], [0xFF21, 0xFF3A], [0xFF3F, 0xFF3F], [0xFF41, 0xFF5A], [0xFF65, 0xFFBE], [0xFFC2, 0xFFC7], [0xFFCA, 0xFFCF], [0xFFD2, 0xFFD7], [0xFFDA, 0xFFDC], [0x10000, 0x1000B], [0x1000D, 0x10026], [0x10028, 0x1003A], [0x1003C, 0x1003D], [0x1003F, 0x1004D], [0x10050, 0x1005D], [0x10080, 0x100FA], [0x10140, 0x10174], [0x101FD, 0x101FD], [0x10280, 0x1029C], [0x102A0, 0x102D0], [0x102E0, 0x102E0], [0x10300, 0x1031F], [0x1032D, 0x1034A], [0x10350, 0x1037A], [0x10380, 0x1039D], [0x103A0, 0x103C3], [0x103C8, 0x103CF], [0x103D1, 0x103D5], [0x10400, 0x1049D], [0x104A0, 0x104A9], [0x104B0, 0x104D3], [0x104D8, 0x104FB], [0x10500, 0x10527], [0x10530, 0x10563], [0x10570, 0x1057A], [0x1057C, 0x1058A], [0x1058C, 0x10592], [0x10594, 0x10595], [0x10597, 0x105A1], [0x105A3, 0x105B1], [0x105B3, 0x105B9], [0x105BB, 0x105BC], [0x10600, 0x10736], [0x10740, 0x10755], [0x10760, 0x10767], [0x10780, 0x10785], [0x10787, 0x107B0], [0x107B2, 0x107BA], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080A, 0x10835], [0x10837, 0x10838], [0x1083C, 0x1083C], [0x1083F, 0x10855], [0x10860, 0x10876], [0x10880, 0x1089E], [0x108E0, 0x108F2], [0x108F4, 0x108F5], [0x10900, 0x10915], [0x10920, 0x10939], [0x10980, 0x109B7], [0x109BE, 0x109BF], [0x10A00, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A13], [0x10A15, 0x10A17], [0x10A19, 0x10A35], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x10A60, 0x10A7C], [0x10A80, 0x10A9C], [0x10AC0, 0x10AC7], [0x10AC9, 0x10AE6], [0x10B00, 0x10B35], [0x10B40, 0x10B55], [0x10B60, 0x10B72], [0x10B80, 0x10B91], [0x10C00, 0x10C48], [0x10C80, 0x10CB2], [0x10CC0, 0x10CF2], [0x10D00, 0x10D27], [0x10D30, 0x10D39], [0x10E80, 0x10EA9], [0x10EAB, 0x10EAC], [0x10EB0, 0x10EB1], [0x10EFD, 0x10F1C], [0x10F27, 0x10F27], [0x10F30, 0x10F50], [0x10F70, 0x10F85], [0x10FB0, 0x10FC4], [0x10FE0, 0x10FF6], [0x11000, 0x11046], [0x11066, 0x11075], [0x1107F, 0x110BA], [0x110C2, 0x110C2], [0x110D0, 0x110E8], [0x110F0, 0x110F9], [0x11100, 0x11134], [0x11136, 0x1113F], [0x11144, 0x11147], [0x11150, 0x11173], [0x11176, 0x11176], [0x11180, 0x111C4], [0x111C9, 0x111CC], [0x111CE, 0x111DA], [0x111DC, 0x111DC], [0x11200, 0x11211], [0x11213, 0x11237], [0x1123E, 0x11241], [0x11280, 0x11286], [0x11288, 0x11288], [0x1128A, 0x1128D], [0x1128F, 0x1129D], [0x1129F, 0x112A8], [0x112B0, 0x112EA], [0x112F0, 0x112F9], [0x11300, 0x11303], [0x11305, 0x1130C], [0x1130F, 0x11310], [0x11313, 0x11328], [0x1132A, 0x11330], [0x11332, 0x11333], [0x11335, 0x11339], [0x1133B, 0x11344], [0x11347, 0x11348], [0x1134B, 0x1134D], [0x11350, 0x11350], [0x11357, 0x11357], [0x1135D, 0x11363], [0x11366, 0x1136C], [0x11370, 0x11374], [0x11400, 0x1144A], [0x11450, 0x11459], [0x1145E, 0x11461], [0x11480, 0x114C5], [0x114C7, 0x114C7], [0x114D0, 0x114D9], [0x11580, 0x115B5], [0x115B8, 0x115C0], [0x115D8, 0x115DD], [0x11600, 0x11640], [0x11644, 0x11644], [0x11650, 0x11659], [0x11680, 0x116B8], [0x116C0, 0x116C9], [0x11700, 0x1171A], [0x1171D, 0x1172B], [0x11730, 0x11739], [0x11740, 0x11746], [0x11800, 0x1183A], [0x118A0, 0x118E9], [0x118FF, 0x11906], [0x11909, 0x11909], [0x1190C, 0x11913], [0x11915, 0x11916], [0x11918, 0x11935], [0x11937, 0x11938], [0x1193B, 0x11943], [0x11950, 0x11959], [0x119A0, 0x119A7], [0x119AA, 0x119D7], [0x119DA, 0x119E1], [0x119E3, 0x119E4], [0x11A00, 0x11A3E], [0x11A47, 0x11A47], [0x11A50, 0x11A99], [0x11A9D, 0x11A9D], [0x11AB0, 0x11AF8], [0x11C00, 0x11C08], [0x11C0A, 0x11C36], [0x11C38, 0x11C40], [0x11C50, 0x11C59], [0x11C72, 0x11C8F], [0x11C92, 0x11CA7], [0x11CA9, 0x11CB6], [0x11D00, 0x11D06], [0x11D08, 0x11D09], [0x11D0B, 0x11D36], [0x11D3A, 0x11D3A], [0x11D3C, 0x11D3D], [0x11D3F, 0x11D47], [0x11D50, 0x11D59], [0x11D60, 0x11D65], [0x11D67, 0x11D68], [0x11D6A, 0x11D8E], [0x11D90, 0x11D91], [0x11D93, 0x11D98], [0x11DA0, 0x11DA9], [0x11EE0, 0x11EF6], [0x11F00, 0x11F10], [0x11F12, 0x11F3A], [0x11F3E, 0x11F42], [0x11F50, 0x11F59], [0x11FB0, 0x11FB0], [0x12000, 0x12399], [0x12400, 0x1246E], [0x12480, 0x12543], [0x12F90, 0x12FF0], [0x13000, 0x1342F], [0x13440, 0x13455], [0x14400, 0x14646], [0x16800, 0x16A38], [0x16A40, 0x16A5E], [0x16A60, 0x16A69], [0x16A70, 0x16ABE], [0x16AC0, 0x16AC9], [0x16AD0, 0x16AED], [0x16AF0, 0x16AF4], [0x16B00, 0x16B36], [0x16B40, 0x16B43], [0x16B50, 0x16B59], [0x16B63, 0x16B77], [0x16B7D, 0x16B8F], [0x16E40, 0x16E7F], [0x16F00, 0x16F4A], [0x16F4F, 0x16F87], [0x16F8F, 0x16F9F], [0x16FE0, 0x16FE1], [0x16FE3, 0x16FE4], [0x16FF0, 0x16FF1], [0x17000, 0x187F7], [0x18800, 0x18CD5], [0x18D00, 0x18D08], [0x1AFF0, 0x1AFF3], [0x1AFF5, 0x1AFFB], [0x1AFFD, 0x1AFFE], [0x1B000, 0x1B122], [0x1B132, 0x1B132], [0x1B150, 0x1B152], [0x1B155, 0x1B155], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1BC00, 0x1BC6A], [0x1BC70, 0x1BC7C], [0x1BC80, 0x1BC88], [0x1BC90, 0x1BC99], [0x1BC9D, 0x1BC9E], [0x1CF00, 0x1CF2D], [0x1CF30, 0x1CF46], [0x1D165, 0x1D169], [0x1D16D, 0x1D172], [0x1D17B, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], [0x1D242, 0x1D244], [0x1D400, 0x1D454], [0x1D456, 0x1D49C], [0x1D49E, 0x1D49F], [0x1D4A2, 0x1D4A2], [0x1D4A5, 0x1D4A6], [0x1D4A9, 0x1D4AC], [0x1D4AE, 0x1D4B9], [0x1D4BB, 0x1D4BB], [0x1D4BD, 0x1D4C3], [0x1D4C5, 0x1D505], [0x1D507, 0x1D50A], [0x1D50D, 0x1D514], [0x1D516, 0x1D51C], [0x1D51E, 0x1D539], [0x1D53B, 0x1D53E], [0x1D540, 0x1D544], [0x1D546, 0x1D546], [0x1D54A, 0x1D550], [0x1D552, 0x1D6A5], [0x1D6A8, 0x1D6C0], [0x1D6C2, 0x1D6DA], [0x1D6DC, 0x1D6FA], [0x1D6FC, 0x1D714], [0x1D716, 0x1D734], [0x1D736, 0x1D74E], [0x1D750, 0x1D76E], [0x1D770, 0x1D788], [0x1D78A, 0x1D7A8], [0x1D7AA, 0x1D7C2], [0x1D7C4, 0x1D7CB], [0x1D7CE, 0x1D7FF], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C], [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F], [0x1DAA1, 0x1DAAF], [0x1DF00, 0x1DF1E], [0x1DF25, 0x1DF2A], [0x1E000, 0x1E006], [0x1E008, 0x1E018], [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A], [0x1E030, 0x1E06D], [0x1E08F, 0x1E08F], [0x1E100, 0x1E12C], [0x1E130, 0x1E13D], [0x1E140, 0x1E149], [0x1E14E, 0x1E14E], [0x1E290, 0x1E2AE], [0x1E2C0, 0x1E2F9], [0x1E4D0, 0x1E4F9], [0x1E7E0, 0x1E7E6], [0x1E7E8, 0x1E7EB], [0x1E7ED, 0x1E7EE], [0x1E7F0, 0x1E7FE], [0x1E800, 0x1E8C4], [0x1E8D0, 0x1E8D6], [0x1E900, 0x1E94B], [0x1E950, 0x1E959], [0x1EE00, 0x1EE03], [0x1EE05, 0x1EE1F], [0x1EE21, 0x1EE22], [0x1EE24, 0x1EE24], [0x1EE27, 0x1EE27], [0x1EE29, 0x1EE32], [0x1EE34, 0x1EE37], [0x1EE39, 0x1EE39], [0x1EE3B, 0x1EE3B], [0x1EE42, 0x1EE42], [0x1EE47, 0x1EE47], [0x1EE49, 0x1EE49], [0x1EE4B, 0x1EE4B], [0x1EE4D, 0x1EE4F], [0x1EE51, 0x1EE52], [0x1EE54, 0x1EE54], [0x1EE57, 0x1EE57], [0x1EE59, 0x1EE59], [0x1EE5B, 0x1EE5B], [0x1EE5D, 0x1EE5D], [0x1EE5F, 0x1EE5F], [0x1EE61, 0x1EE62], [0x1EE64, 0x1EE64], [0x1EE67, 0x1EE6A], [0x1EE6C, 0x1EE72], [0x1EE74, 0x1EE77], [0x1EE79, 0x1EE7C], [0x1EE7E, 0x1EE7E], [0x1EE80, 0x1EE89], [0x1EE8B, 0x1EE9B], [0x1EEA1, 0x1EEA3], [0x1EEA5, 0x1EEA9], [0x1EEAB, 0x1EEBB], [0x1FBF0, 0x1FBF9], [0x20000, 0x2B739], [0x2B740, 0x2B81D], [0x2B820, 0x2CEA1], [0x2CEB0, 0x2EBE0], [0x2EBF0, 0x2EE5D], [0x2F800, 0x2FA1D], [0x30000, 0x323AF], [0x40000, 0x4FFFD], [0x50000, 0x5FFFD], [0x60000, 0x6FFFD], [0x70000, 0x7FFFD], [0x80000, 0x8FFFD], [0x90000, 0x9FFFD], [0xA0000, 0xAFFFD], [0xB0000, 0xBFFFD], [0xC0000, 0xCFFFD], [0xD0000, 0xDFFFD], [0xE0000, 0xE01EF], ]; ldc-1.40.0-src/dmd/iasmgcc.d0000644000000000000000000003765514727557031014173 0ustar rootroot/** * Inline assembler for the GCC D compiler. * * Copyright (C) 2018-2024 by The D Language Foundation, All Rights Reserved * Authors: Iain Buclaw * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/iasmgcc.d, _iasmgcc.d) * Documentation: https://dlang.org/phobos/dmd_iasmgcc.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/iasmgcc.d */ module dmd.iasmgcc; import core.stdc.string; import dmd.arraytypes; import dmd.astcodegen; import dmd.dscope; import dmd.dsymbol; import dmd.errors; import dmd.errorsink; import dmd.expression; import dmd.expressionsem; import dmd.identifier; import dmd.globals; import dmd.location; import dmd.parse; import dmd.tokens; import dmd.statement; import dmd.statementsem; private: /*********************************** * Parse list of extended asm input or output operands. * Grammar: * | Operands: * | SymbolicName(opt) StringLiteral ( AssignExpression ) * | SymbolicName(opt) StringLiteral ( AssignExpression ), Operands * | * | SymbolicName: * | [ Identifier ] * Params: * p = parser state * s = asm statement to parse * Returns: * number of operands added to the gcc asm statement */ int parseExtAsmOperands(Parser)(Parser p, GccAsmStatement s) { int numargs = 0; while (1) { Expression arg; Identifier name; Expression constraint; switch (p.token.value) { case TOK.semicolon: case TOK.colon: case TOK.endOfFile: return numargs; case TOK.leftBracket: if (p.peekNext() == TOK.identifier) { // Skip over opening `[` p.nextToken(); // Store the symbolic name name = p.token.ident; p.nextToken(); } else { p.eSink.error(s.loc, "expected identifier after `[`"); goto Lerror; } // Look for closing `]` p.check(TOK.rightBracket); // Look for the string literal and fall through if (p.token.value == TOK.string_) goto case; else goto default; case TOK.string_: constraint = p.parsePrimaryExp(); if (p.token.value != TOK.leftParenthesis) { arg = p.parseAssignExp(); error(arg.loc, "`%s` must be surrounded by parentheses", arg.toChars()); } else { // Look for the opening `(` p.check(TOK.leftParenthesis); // Parse the assign expression arg = p.parseAssignExp(); // Look for the closing `)` p.check(TOK.rightParenthesis); } if (!s.args) { s.names = new Identifiers(); s.constraints = new Expressions(); s.args = new Expressions(); } s.names.push(name); s.args.push(arg); s.constraints.push(constraint); numargs++; if (p.token.value == TOK.comma) p.nextToken(); break; default: p.eSink.error(p.token.loc, "expected constant string constraint for operand, not `%s`", p.token.toChars()); goto Lerror; } } Lerror: while (p.token.value != TOK.rightCurly && p.token.value != TOK.semicolon && p.token.value != TOK.endOfFile) p.nextToken(); return numargs; } /*********************************** * Parse list of extended asm clobbers. * Grammar: * | Clobbers: * | StringLiteral * | StringLiteral , Clobbers * Params: * p = parser state * Returns: * array of parsed clobber expressions */ Expressions *parseExtAsmClobbers(Parser)(Parser p) { Expressions *clobbers; while (1) { Expression clobber; switch (p.token.value) { case TOK.semicolon: case TOK.colon: case TOK.endOfFile: return clobbers; case TOK.string_: clobber = p.parsePrimaryExp(); if (!clobbers) clobbers = new Expressions(); clobbers.push(clobber); if (p.token.value == TOK.comma) p.nextToken(); break; default: p.eSink.error(p.token.loc, "expected constant string constraint for clobber name, not `%s`", p.token.toChars()); goto Lerror; } } Lerror: while (p.token.value != TOK.rightCurly && p.token.value != TOK.semicolon && p.token.value != TOK.endOfFile) p.nextToken(); return clobbers; } /*********************************** * Parse list of extended asm goto labels. * Grammar: * | GotoLabels: * | Identifier * | Identifier , GotoLabels * Params: * p = parser state * Returns: * array of parsed goto labels */ Identifiers *parseExtAsmGotoLabels(Parser)(Parser p) { Identifiers *labels; while (1) { switch (p.token.value) { case TOK.semicolon: case TOK.endOfFile: return labels; case TOK.identifier: if (!labels) labels = new Identifiers(); labels.push(p.token.ident); if (p.nextToken() == TOK.comma) p.nextToken(); break; default: p.eSink.error(p.token.loc, "expected identifier for goto label name, not `%s`", p.token.toChars()); goto Lerror; } } Lerror: while (p.token.value != TOK.rightCurly && p.token.value != TOK.semicolon && p.token.value != TOK.endOfFile) p.nextToken(); return labels; } /*********************************** * Parse a gcc asm statement. * There are three forms of inline asm statements, basic, extended, and goto. * Grammar: * | AsmInstruction: * | BasicAsmInstruction * | ExtAsmInstruction * | GotoAsmInstruction * | * | BasicAsmInstruction: * | AssignExpression * | * | ExtAsmInstruction: * | AssignExpression : Operands(opt) : Operands(opt) : Clobbers(opt) * | * | GotoAsmInstruction: * | AssignExpression : : Operands(opt) : Clobbers(opt) : GotoLabels(opt) * Params: * p = parser state * s = asm statement to parse * Returns: * the parsed gcc asm statement */ GccAsmStatement parseGccAsm(Parser)(Parser p, GccAsmStatement s) { s.insn = p.parseAssignExp(); if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile) goto Ldone; // No semicolon followed after instruction template, treat as extended asm. foreach (section; 0 .. 4) { p.check(TOK.colon); final switch (section) { case 0: s.outputargs = p.parseExtAsmOperands(s); break; case 1: p.parseExtAsmOperands(s); break; case 2: s.clobbers = p.parseExtAsmClobbers(); break; case 3: s.labels = p.parseExtAsmGotoLabels(); break; } if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile) goto Ldone; } Ldone: p.check(TOK.semicolon); return s; } /*********************************** * Parse and run semantic analysis on a GccAsmStatement. * Params: * s = gcc asm statement being parsed * sc = the scope where the asm statement is located * Returns: * the completed gcc asm statement, or null if errors occurred */ public Statement gccAsmSemantic(GccAsmStatement s, Scope *sc) { //printf("GccAsmStatement.semantic()\n"); const bool doUnittests = global.params.parsingUnittestsRequired(); scope p = new Parser!ASTCodegen(sc._module, ";", false, global.errorSink, &global.compileEnv, doUnittests); // Make a safe copy of the token list before parsing. Token *toklist = null; Token **ptoklist = &toklist; for (Token *token = s.tokens; token; token = token.next) { *ptoklist = p.allocateToken(); memcpy(*ptoklist, token, Token.sizeof); ptoklist = &(*ptoklist).next; *ptoklist = null; } p.token = *toklist; p.scanloc = s.loc; // Parse the gcc asm statement. const errors = global.errors; s = p.parseGccAsm(s); if (errors != global.errors) return null; s.stc = sc.stc; // Fold the instruction template string. s.insn = semanticString(sc, s.insn, "asm instruction template"); if (s.labels && s.outputargs) error(s.loc, "extended asm statements with labels cannot have output constraints"); // Analyse all input and output operands. if (s.args) { foreach (i; 0 .. s.args.length) { Expression e = (*s.args)[i]; e = e.expressionSemantic(sc); // Check argument is a valid lvalue/rvalue. if (i < s.outputargs) e = e.modifiableLvalue(sc); else if (e.checkValue()) e = ErrorExp.get(); (*s.args)[i] = e; e = (*s.constraints)[i]; e = e.expressionSemantic(sc); assert(e.op == EXP.string_ && (cast(StringExp) e).sz == 1); (*s.constraints)[i] = e; } } // Analyse all clobbers. if (s.clobbers) { foreach (i; 0 .. s.clobbers.length) { Expression e = (*s.clobbers)[i]; e = e.expressionSemantic(sc); assert(e.op == EXP.string_ && (cast(StringExp) e).sz == 1); (*s.clobbers)[i] = e; } } // Analyse all goto labels. if (s.labels) { foreach (i; 0 .. s.labels.length) { Identifier ident = (*s.labels)[i]; GotoStatement gs = new GotoStatement(s.loc, ident); if (!s.gotos) s.gotos = new GotoStatements(); s.gotos.push(gs); gs.statementSemantic(sc); } } return s; } /*********************************** * Run semantic analysis on an CAsmDeclaration. * Params: * ad = asm declaration * sc = the scope where the asm declaration is located */ public void gccAsmSemantic(CAsmDeclaration ad, Scope *sc) { import dmd.typesem : pointerTo; ad.code = semanticString(sc, ad.code, "asm definition"); ad.code.type = ad.code.type.nextOf().pointerTo(); // Asm definition always needs emitting into the root module. import dmd.dmodule : Module; if (sc._module && sc._module.isRoot()) return; if (Module m = Module.rootModule) m.members.push(ad); } unittest { import dmd.mtype : TypeBasic; if (!global.errorSink) global.errorSink = new ErrorSinkCompiler; uint errors = global.startGagging(); scope(exit) global.endGagging(errors); // If this check fails, then Type._init() was called before reaching here, // and the entire chunk of code that follows can be removed. assert(ASTCodegen.Type.tint32 is null); // Minimally initialize the cached types in ASTCodegen.Type, as they are // dependencies for some fail asm tests to succeed. ASTCodegen.Type.stringtable._init(); scope(exit) { ASTCodegen.Type.deinitialize(); ASTCodegen.Type.tint32 = null; } scope tint32 = new TypeBasic(ASTCodegen.Tint32); ASTCodegen.Type.tint32 = tint32; // Imitates asmSemantic if version = IN_GCC. static int semanticAsm(Token* tokens) { const errors = global.errors; scope gas = new GccAsmStatement(Loc.initial, tokens); const bool doUnittests = false; scope p = new Parser!ASTCodegen(null, ";", false, global.errorSink, &global.compileEnv, doUnittests); p.token = *tokens; p.parseGccAsm(gas); return global.errors - errors; } // Imitates parseStatement for asm statements. static void parseAsm(string input, bool expectError) { // Generate tokens from input test. const bool doUnittests = false; scope p = new Parser!ASTCodegen(null, input, false, global.errorSink, &global.compileEnv, doUnittests); p.nextToken(); Token* toklist = null; Token** ptoklist = &toklist; p.check(TOK.asm_); p.check(TOK.leftCurly); while (1) { if (p.token.value == TOK.rightCurly || p.token.value == TOK.endOfFile) break; if (p.token.value == TOK.colonColon) { *ptoklist = p.allocateToken(); memcpy(*ptoklist, &p.token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; *ptoklist = p.allocateToken(); memcpy(*ptoklist, &p.token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; } else { *ptoklist = p.allocateToken(); memcpy(*ptoklist, &p.token, Token.sizeof); ptoklist = &(*ptoklist).next; } *ptoklist = null; p.nextToken(); } p.check(TOK.rightCurly); auto res = semanticAsm(toklist); // Checks for both unexpected passes and failures. assert((res == 0) != expectError); } /// Assembly Tests, all should pass. /// Note: Frontend is not initialized, use only strings and identifiers. immutable string[] passAsmTests = [ // Basic asm statement q{ asm { "nop"; } }, // Extended asm statement q{ asm { "cpuid" : "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (input); } }, // Assembly with symbolic names q{ asm { "bts %[base], %[offset]" : [base] "+rm" (*ptr), : [offset] "Ir" (bitnum); } }, // Assembly with clobbers q{ asm { "cpuid" : "=a" (a) : "a" (input) : "ebx", "ecx", "edx"; } }, // Goto asm statement q{ asm { "jmp %l0" : : : : Ljmplabel; } }, // Any CTFE-able string allowed as instruction template. q{ asm { generateAsm(); } }, // Likewise mixins, permissible so long as the result is a string. q{ asm { mixin(`"repne"`, `~ "scasb"`); } }, // :: token tests q{ asm { "" : : : "memory"; } }, q{ asm { "" :: : "memory"; } }, q{ asm { "" : :: "memory"; } }, q{ asm { "" ::: "memory"; } }, ]; immutable string[] failAsmTests = [ // Found 'h' when expecting ';' q{ asm { ""h; } }, // https://issues.dlang.org/show_bug.cgi?id=20592 q{ asm { "nop" : [name] string (expr); } }, // Expression expected, not ';' q{ asm { ""[; } }, // Expression expected, not ':' q{ asm { "" : : "g" (a ? b : : c); } }, // Found ',' when expecting ':' q{ asm { "", ""; } }, // https://issues.dlang.org/show_bug.cgi?id=20593 q{ asm { "instruction" : : "operand" 123; } }, ]; foreach (test; passAsmTests) parseAsm(test, false); foreach (test; failAsmTests) parseAsm(test, true); } ldc-1.40.0-src/dmd/hdrgen.h0000644000000000000000000000164714727557031014030 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Dave Fladebo * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/hdrgen.h */ #pragma once #include "globals.h" #include "mtype.h" class Expression; class Initializer; class Module; class Statement; namespace dmd { void genhdrfile(Module *m, bool doFuncBodies, OutBuffer &buf); void genCppHdrFiles(Modules &ms); void moduleToBuffer(OutBuffer& buf, bool vcg_ast, Module *m); const char *parametersTypeToChars(ParameterList pl); const char* toChars(const Expression* const e); const char* toChars(const Initializer* const i); const char* toChars(const Statement* const s); const char* toChars(const Type* const t); } ldc-1.40.0-src/dmd/builtin.d0000644000000000000000000006447114727557031014227 0ustar rootroot/** * Implement CTFE for intrinsic (builtin) functions. * * Currently includes functions from `std.math`, `core.math` and `core.bitop`. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/builtin.d, _builtin.d) * Documentation: https://dlang.org/phobos/dmd_builtin.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/builtin.d */ module dmd.builtin; import dmd.arraytypes; import dmd.astenums; import dmd.dmangle; import dmd.errors; import dmd.expression; import dmd.func; import dmd.globals; import dmd.location; import dmd.mtype; import dmd.root.ctfloat; import dmd.tokens; import dmd.id; static import core.bitop; /********************************** * Determine if function is a builtin one that we can * evaluate at compile time. */ public extern (C++) BUILTIN isBuiltin(FuncDeclaration fd) { if (fd.builtin == BUILTIN.unknown) { fd.builtin = determine_builtin(fd); } return fd.builtin; } /************************************** * Evaluate builtin function. * Return result; NULL if cannot evaluate it. */ public extern (C++) Expression eval_builtin(const ref Loc loc, FuncDeclaration fd, Expressions* arguments) { if (fd.builtin == BUILTIN.unimp) return null; switch (fd.builtin) { foreach(e; __traits(allMembers, BUILTIN)) { static if (e == "unknown") case BUILTIN.unknown: assert(false); else static if (IN_LLVM && e.length > 5 && e[0..5] == "llvm_") mixin("case BUILTIN."~e~": return eval_llvm"~e[5..$]~"(loc, fd, (*arguments)[]);"); else mixin("case BUILTIN."~e~": return eval_"~e~"(loc, fd, (*arguments)[]);"); } default: assert(0); } } private: /** * Handler for evaluating builtins during CTFE. * * Params: * loc = The call location, for error reporting. * fd = The callee declaration, e.g. to disambiguate between different overloads * in a single handler (LDC). * arguments = The function call arguments. * Returns: * An Expression containing the return value of the call. */ BUILTIN determine_builtin(FuncDeclaration func) { auto fd = func.toAliasFunc(); if (fd.isDeprecated()) return BUILTIN.unimp; version (IN_LLVM) { import dmd.root.string : toDString; import gen.dpragma : LDCPragma; if (func.llvmInternal == LDCPragma.LLVMintrinsic) { const(char)[] name = func.mangleOverride; if (name.length < 7 || name[0..5] != "llvm.") return BUILTIN.unimp; // find next "." after "llvm." size_t end = 0; foreach (i; 6 .. name.length) { if (name[i] == '.') { end = i; break; } } if (end == 0) return BUILTIN.unimp; name = name[5 .. end]; // e.g., "llvm.sin.f32" => "sin" switch (name) { foreach (e; __traits(allMembers, BUILTIN)) { static if (e.length > 5 && e[0..5] == "llvm_") mixin(`case "`~e[5..$]~`": return BUILTIN.`~e~";"); } default: return BUILTIN.unimp; } } } auto m = fd.getModule(); if (!m || !m.md) return BUILTIN.unimp; const md = m.md; // Look for core.math, core.bitop, std.math, and std.math. const id2 = (md.packages.length == 2) ? md.packages[1] : md.id; if (id2 != Id.math && id2 != Id.bitop && id2 != Id.builtinsModuleName) return BUILTIN.unimp; if (md.packages.length != 1 && !(md.packages.length == 2 && id2 == Id.math)) return BUILTIN.unimp; const id1 = md.packages[0]; if (id1 != Id.core && id1 != Id.std) return BUILTIN.unimp; const id3 = fd.ident; if (id1 == Id.core && id2 == Id.bitop) { if (id3 == Id.bsf) return BUILTIN.bsf; if (id3 == Id.bsr) return BUILTIN.bsr; if (id3 == Id.bswap) return BUILTIN.bswap; if (id3 == Id._popcnt) return BUILTIN.popcnt; return BUILTIN.unimp; } if (id1 == Id.core && id2 == Id.builtinsModuleName) { if (id3 == Id.ctfeWrite) return BUILTIN.ctfeWrite; return BUILTIN.unimp; } // Math if (id3 == Id.sin) return BUILTIN.sin; if (id3 == Id.cos) return BUILTIN.cos; if (id3 == Id.tan) return BUILTIN.tan; if (id3 == Id.atan2) return BUILTIN.unimp; // N.B unimplmeneted if (id3 == Id._sqrt) return BUILTIN.sqrt; if (id3 == Id.fabs) return BUILTIN.fabs; if (id3 == Id.exp) return BUILTIN.exp; if (id3 == Id.expm1) return BUILTIN.expm1; if (id3 == Id.exp2) return BUILTIN.exp2; version (IN_LLVM) { // Our implementations in CTFloat fall back to a generic version in case // host compiler's druntime doesn't provide core.math.yl2x[p1] (GDC, // non-x86 hosts). Not providing yl2x[p1] for CTFE would significantly // limit CTFE-ability of std.math for x86 targets. if (id3 == Id.yl2x) return BUILTIN.yl2x; if (id3 == Id.yl2xp1) return BUILTIN.yl2xp1; } else { if (id3 == Id.yl2x) return CTFloat.yl2x_supported ? BUILTIN.yl2x : BUILTIN.unimp; if (id3 == Id.yl2xp1) return CTFloat.yl2xp1_supported ? BUILTIN.yl2xp1 : BUILTIN.unimp; } if (id3 == Id.log) return BUILTIN.log; if (id3 == Id.log2) return BUILTIN.log2; if (id3 == Id.log10) return BUILTIN.log10; if (id3 == Id.ldexp) return BUILTIN.ldexp; if (id3 == Id.round) return BUILTIN.round; if (id3 == Id.floor) return BUILTIN.floor; if (id3 == Id.ceil) return BUILTIN.ceil; if (id3 == Id.trunc) return BUILTIN.trunc; if (id3 == Id.fmin) return BUILTIN.fmin; if (id3 == Id.fmax) return BUILTIN.fmax; if (id3 == Id.fma) return BUILTIN.fma; if (id3 == Id.copysign) return BUILTIN.copysign; if (id3 == Id.isnan) return BUILTIN.isnan; if (id3 == Id.isInfinity) return BUILTIN.isinfinity; if (id3 == Id.isfinite) return BUILTIN.isfinite; // Only match pow(fp,fp) where fp is a floating point type if (id3 == Id._pow) { if ((*fd.parameters)[0].type.isfloating() && (*fd.parameters)[1].type.isfloating()) return BUILTIN.pow; return BUILTIN.unimp; } if (id3 != Id.toPrec) return BUILTIN.unimp; const(char)* me = mangleExact(fd); final switch (me["_D4core4math__T6toPrecHT".length]) { case 'd': return BUILTIN.toPrecDouble; case 'e': return BUILTIN.toPrecReal; case 'f': return BUILTIN.toPrecFloat; } } Expression eval_unimp(Loc loc, FuncDeclaration fd, Expression[] arguments) { return null; } Expression eval_ctfeWrite(Loc loc, FuncDeclaration fd, Expression[] arguments) { import core.stdc.stdio: fprintf, stderr; import dmd.expression: CTFEExp; import dmd.ctfeexpr: resolveSlice; Expression e = arguments[0]; const se = resolveSlice(e).toStringExp(); assert(se); const slice = se.peekString(); fprintf(stderr, "%.*s", cast(int)slice.length, slice.ptr); return CTFEExp.voidexp; } Expression eval_sin(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.sin(arg0.toReal()), arg0.type); } Expression eval_cos(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.cos(arg0.toReal()), arg0.type); } Expression eval_tan(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.tan(arg0.toReal()), arg0.type); } Expression eval_sqrt(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.sqrt(arg0.toReal()), arg0.type); } Expression eval_fabs(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.fabs(arg0.toReal()), arg0.type); } Expression eval_ldexp(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.int64); return new RealExp(loc, CTFloat.ldexp(arg0.toReal(), cast(int) arg1.toInteger()), arg0.type); } Expression eval_log(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.log(arg0.toReal()), arg0.type); } Expression eval_log2(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.log2(arg0.toReal()), arg0.type); } Expression eval_log10(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.log10(arg0.toReal()), arg0.type); } Expression eval_exp(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.exp(arg0.toReal()), arg0.type); } Expression eval_expm1(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.expm1(arg0.toReal()), arg0.type); } Expression eval_exp2(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.exp2(arg0.toReal()), arg0.type); } Expression eval_round(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.round(arg0.toReal()), arg0.type); } Expression eval_floor(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.floor(arg0.toReal()), arg0.type); } Expression eval_ceil(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.ceil(arg0.toReal()), arg0.type); } Expression eval_trunc(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.trunc(arg0.toReal()), arg0.type); } Expression eval_copysign(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); return new RealExp(loc, CTFloat.copysign(arg0.toReal(), arg1.toReal()), arg0.type); } Expression eval_pow(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); return new RealExp(loc, CTFloat.pow(arg0.toReal(), arg1.toReal()), arg0.type); } Expression eval_fmin(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); return new RealExp(loc, CTFloat.fmin(arg0.toReal(), arg1.toReal()), arg0.type); } Expression eval_fmax(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); return new RealExp(loc, CTFloat.fmax(arg0.toReal(), arg1.toReal()), arg0.type); } Expression eval_fma(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); Expression arg2 = arguments[2]; assert(arg2.op == EXP.float64); return new RealExp(loc, CTFloat.fma(arg0.toReal(), arg1.toReal(), arg2.toReal()), arg0.type); } Expression eval_isnan(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return IntegerExp.createBool(CTFloat.isNaN(arg0.toReal())); } Expression eval_isinfinity(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return IntegerExp.createBool(CTFloat.isInfinity(arg0.toReal())); } Expression eval_isfinite(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); const value = !CTFloat.isNaN(arg0.toReal()) && !CTFloat.isInfinity(arg0.toReal()); return IntegerExp.createBool(value); } Expression eval_bsf(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t n = arg0.toInteger(); if (n == 0) error(loc, "`bsf(0)` is undefined"); return new IntegerExp(loc, core.bitop.bsf(n), Type.tint32); } Expression eval_bsr(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t n = arg0.toInteger(); if (n == 0) error(loc, "`bsr(0)` is undefined"); return new IntegerExp(loc, core.bitop.bsr(n), Type.tint32); } Expression eval_bswap(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t n = arg0.toInteger(); TY ty = arg0.type.toBasetype().ty; if (ty == Tint64 || ty == Tuns64) return new IntegerExp(loc, core.bitop.bswap(cast(ulong) n), arg0.type); else return new IntegerExp(loc, core.bitop.bswap(cast(uint) n), arg0.type); } Expression eval_popcnt(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t n = arg0.toInteger(); return new IntegerExp(loc, core.bitop.popcnt(n), Type.tint32); } Expression eval_yl2x(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); const x = arg0.toReal(); const y = arg1.toReal(); real_t result = CTFloat.zero; CTFloat.yl2x(&x, &y, &result); return new RealExp(loc, result, arg0.type); } Expression eval_yl2xp1(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); const x = arg0.toReal(); const y = arg1.toReal(); real_t result = CTFloat.zero; CTFloat.yl2xp1(&x, &y, &result); return new RealExp(loc, result, arg0.type); } Expression eval_toPrecFloat(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; float f = cast(real)arg0.toReal(); return new RealExp(loc, real_t(f), Type.tfloat32); } Expression eval_toPrecDouble(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; double d = cast(real)arg0.toReal(); return new RealExp(loc, real_t(d), Type.tfloat64); } Expression eval_toPrecReal(Loc loc, FuncDeclaration fd, Expression[] arguments) { Expression arg0 = arguments[0]; return new RealExp(loc, arg0.toReal(), Type.tfloat80); } // These built-ins are reserved for GDC and LDC. Expression eval_gcc(Loc, FuncDeclaration, Expression[]) { assert(0); } Expression eval_llvm(Loc, FuncDeclaration, Expression[]) { assert(0); } version (IN_LLVM) { private Type getTypeOfOverloadedIntrinsic(FuncDeclaration fd) { import dmd.dtemplate : TemplateInstance; // Depending on the state of the code generation we have to look at // the template instance or the function declaration. assert(fd.parent && "function declaration requires parent"); TemplateInstance tinst = fd.parent.isTemplateInstance(); if (tinst) { // See DtoOverloadedIntrinsicName assert(tinst.tdtypes.length == 1); return cast(Type) tinst.tdtypes[0]; } else { assert(fd.type.ty == Tfunction); TypeFunction tf = cast(TypeFunction) fd.type; assert(tf.parameterList.length >= 1); return (*tf.parameterList.parameters)[0].type; } } private int getBitsizeOfType(Loc loc, Type type) { switch (type.toBasetype().ty) { case Tint64: case Tuns64: return 64; case Tint32: case Tuns32: return 32; case Tint16: case Tuns16: return 16; case Tint128: case Tuns128: error(loc, "cent/ucent not supported"); break; default: error(loc, "unsupported type"); break; } return 32; // in case of error } Expression eval_llvmsin(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.sin(arg0.toReal()), type); } Expression eval_llvmcos(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.cos(arg0.toReal()), type); } Expression eval_llvmsqrt(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.sqrt(arg0.toReal()), type); } Expression eval_llvmexp(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.exp(arg0.toReal()), type); } Expression eval_llvmexp2(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.exp2(arg0.toReal()), type); } Expression eval_llvmlog(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.log(arg0.toReal()), type); } Expression eval_llvmlog2(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.log2(arg0.toReal()), type); } Expression eval_llvmlog10(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.log10(arg0.toReal()), type); } Expression eval_llvmfabs(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.fabs(arg0.toReal()), type); } Expression eval_llvmminnum(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); return new RealExp(loc, CTFloat.fmin(arg0.toReal(), arg1.toReal()), type); } Expression eval_llvmmaxnum(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); return new RealExp(loc, CTFloat.fmax(arg0.toReal(), arg1.toReal()), type); } Expression eval_llvmfloor(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.floor(arg0.toReal()), type); } Expression eval_llvmceil(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.ceil(arg0.toReal()), type); } Expression eval_llvmtrunc(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.trunc(arg0.toReal()), type); } Expression eval_llvmrint(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.rint(arg0.toReal()), type); } Expression eval_llvmnearbyint(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.nearbyint(arg0.toReal()), type); } Expression eval_llvmround(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new RealExp(loc, CTFloat.round(arg0.toReal()), type); } alias eval_llvmlrint = eval_llvmllrint; // difference: int return type, not long Expression eval_llvmllrint(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = fd.type.isTypeFunction().next; // int or long Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new IntegerExp(loc, CTFloat.llrint(arg0.toReal()), type); } alias eval_llvmlround = eval_llvmllround; // difference: int return type, not long Expression eval_llvmllround(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = fd.type.isTypeFunction().next; // int or long Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); return new IntegerExp(loc, CTFloat.llround(arg0.toReal()), type); } Expression eval_llvmfma(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); Expression arg2 = arguments[2]; assert(arg2.op == EXP.float64); return new RealExp(loc, CTFloat.fma(arg0.toReal(), arg1.toReal(), arg2.toReal()), type); } Expression eval_llvmcopysign(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.float64); Expression arg1 = arguments[1]; assert(arg1.op == EXP.float64); return new RealExp(loc, CTFloat.copysign(arg0.toReal(), arg1.toReal()), type); } Expression eval_llvmbswap(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t n = arg0.toInteger(); enum ulong BYTEMASK = 0x00FF00FF00FF00FF; enum ulong SHORTMASK = 0x0000FFFF0000FFFF; enum ulong INTMASK = 0x00000000FFFFFFFF; switch (type.toBasetype().ty) { case Tint64: case Tuns64: // swap high and low uints n = ((n >> 32) & INTMASK) | ((n & INTMASK) << 32); goto case Tuns32; case Tint32: case Tuns32: // swap adjacent ushorts n = ((n >> 16) & SHORTMASK) | ((n & SHORTMASK) << 16); goto case Tuns16; case Tint16: case Tuns16: // swap adjacent ubytes n = ((n >> 8 ) & BYTEMASK) | ((n & BYTEMASK) << 8 ); break; case Tint128: case Tuns128: error(loc, "cent/ucent not supported"); break; default: error(loc, "unsupported type"); break; } return new IntegerExp(loc, n, type); } Expression eval_llvmcttz(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t x = arg0.toInteger(); int n = getBitsizeOfType(loc, type); if (x == 0) { if (arguments[1].toInteger()) error(loc, "llvm.cttz.i#(0) is undefined"); } else { int c = n >> 1; n -= 1; const uinteger_t mask = (uinteger_t(1L) << n) | (uinteger_t(1L) << n)-1; do { uinteger_t y = (x << c) & mask; if (y != 0) { n -= c; x = y; } c = c >> 1; } while (c != 0); } return new IntegerExp(loc, n, type); } Expression eval_llvmctlz(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t x = arg0.toInteger(); if (x == 0 && arguments[1].toInteger()) error(loc, "llvm.ctlz.i#(0) is undefined"); int n = getBitsizeOfType(loc, type); int c = n >> 1; do { uinteger_t y = x >> c; if (y != 0) { n -= c; x = y; } c = c >> 1; } while (c != 0); return new IntegerExp(loc, n - x, type); } Expression eval_llvmctpop(Loc loc, FuncDeclaration fd, Expression[] arguments) { // FIXME Does not work for cent/ucent Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); uinteger_t n = arg0.toInteger(); int cnt = 0; while (n) { cnt += (n & 1); n >>= 1; } return new IntegerExp(loc, cnt, type); } Expression eval_llvmexpect(Loc loc, FuncDeclaration fd, Expression[] arguments) { Type type = getTypeOfOverloadedIntrinsic(fd); Expression arg0 = arguments[0]; assert(arg0.op == EXP.int64); return new IntegerExp(loc, arg0.toInteger(), type); } } // IN_LLVM ldc-1.40.0-src/dmd/optimize.d0000644000000000000000000014353014727557031014413 0ustar rootroot/** * Perform constant folding. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/optimize.d, _optimize.d) * Documentation: https://dlang.org/phobos/dmd_optimize.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/optimize.d */ module dmd.optimize; import core.stdc.stdio; import dmd.astenums; import dmd.constfold; import dmd.ctfeexpr; import dmd.dcast; import dmd.dclass; import dmd.declaration; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.errors; import dmd.expression; import dmd.expressionsem; import dmd.globals; import dmd.hdrgen; import dmd.init; import dmd.location; import dmd.mtype; import dmd.printast; import dmd.root.ctfloat; import dmd.sideeffect; import dmd.tokens; import dmd.typesem; import dmd.visitor; /************************************* * If variable has a const initializer, * return that initializer. * Returns: * initializer if there is one, * null if not, * ErrorExp if error */ Expression expandVar(int result, VarDeclaration v) { //printf("expandVar(result = %d, v = %p, %s)\n", result, v, v ? v.toChars() : "null"); /******** * Params: * e = initializer expression */ Expression initializerReturn(Expression e) { if (e.type != v.type) { e = e.castTo(null, v.type); } v.inuse++; e = e.optimize(result); v.inuse--; //if (e) printf("\te = %p, %s, e.type = %d, %s\n", e, e.toChars(), e.type.ty, e.type.toChars()); return e; } static Expression nullReturn() { return null; } static Expression errorReturn() { return ErrorExp.get(); } if (!v) return nullReturn(); if (!v.originalType && v.semanticRun < PASS.semanticdone) // semantic() not yet run v.dsymbolSemantic(null); if (v.type && (v.isConst() || v.isImmutable() || v.storage_class & STC.manifest)) { Type tb = v.type.toBasetype(); if (v.storage_class & STC.manifest || tb.isscalar() || ((result & WANTexpand) && (tb.ty != Tsarray && tb.ty != Tstruct))) { if (v._init) { if (v.inuse) { if (v.storage_class & STC.manifest) { .error(v.loc, "%s `%s` recursive initialization of constant", v.kind, v.toPrettyChars); return errorReturn(); } return nullReturn(); } Expression ei = v.getConstInitializer(); if (!ei) { if (v.storage_class & STC.manifest) { .error(v.loc, "%s `%s` enum cannot be initialized with `%s`", v.kind, v.toPrettyChars, dmd.hdrgen.toChars(v._init)); return errorReturn(); } return nullReturn(); } if (ei.op == EXP.construct || ei.op == EXP.blit) { auto ae = cast(AssignExp)ei; ei = ae.e2; if (ei.isConst() == 1) { } else if (ei.op == EXP.string_) { // https://issues.dlang.org/show_bug.cgi?id=14459 // Do not constfold the string literal // if it's typed as a C string, because the value expansion // will drop the pointer identity. if (!(result & WANTexpand) && ei.type.toBasetype().ty == Tpointer) return nullReturn(); } else return nullReturn(); if (ei.type == v.type) { // const variable initialized with const expression } else if (ei.implicitConvTo(v.type) >= MATCH.constant) { // const var initialized with non-const expression ei = ei.implicitCastTo(null, v.type); ei = ei.expressionSemantic(null); } else return nullReturn(); } else if (!(v.storage_class & STC.manifest) && ei.isConst() != 1 && ei.op != EXP.string_ && ei.op != EXP.address) { return nullReturn(); } if (!ei.type) { return nullReturn(); } else { // Should remove the copy() operation by // making all mods to expressions copy-on-write return initializerReturn(ei.copy()); } } else { // v does not have an initializer version (all) { return nullReturn(); } else { // BUG: what if const is initialized in constructor? auto e = v.type.defaultInit(); e.loc = e1.loc; return initializerReturn(e); } } assert(0); } } return nullReturn(); } private Expression fromConstInitializer(int result, Expression e1) { //printf("fromConstInitializer(result = %x, %s)\n", result, e1.toChars()); //static int xx; if (xx++ == 10) assert(0); Expression e = e1; if (auto ve = e1.isVarExp()) { VarDeclaration v = ve.var.isVarDeclaration(); e = expandVar(result, v); if (e) { // If it is a comma expression involving a declaration, we mustn't // perform a copy -- we'd get two declarations of the same variable. // See https://issues.dlang.org/show_bug.cgi?id=4465. if (e.op == EXP.comma && e.isCommaExp().e1.isDeclarationExp()) e = e1; else if (e.type != e1.type && e1.type && e1.type.ty != Tident) { // Type 'paint' operation e = e.copy(); e.type = e1.type; } e.loc = e1.loc; } else { e = e1; } } return e; } /*** * It is possible for constant folding to change an array expression of * unknown length, into one where the length is known. * If the expression 'arr' is a literal, set lengthVar to be its length. * Params: * lengthVar = variable declaration for the `.length` property * arr = String, ArrayLiteral, or of TypeSArray */ package void setLengthVarIfKnown(VarDeclaration lengthVar, Expression arr) { if (!lengthVar) return; if (lengthVar._init && !lengthVar._init.isVoidInitializer()) return; // we have previously calculated the length dinteger_t len; if (auto se = arr.isStringExp()) len = se.len; else if (auto ale = arr.isArrayLiteralExp()) len = ale.elements.length; else { auto tsa = arr.type.toBasetype().isTypeSArray(); if (!tsa) return; // we don't know the length yet len = tsa.dim.toInteger(); } Expression dollar = new IntegerExp(Loc.initial, len, Type.tsize_t); lengthVar._init = new ExpInitializer(Loc.initial, dollar); lengthVar.storage_class |= STC.static_ | STC.const_; } /*** * Same as above, but determines the length from 'type'. * Params: * lengthVar = variable declaration for the `.length` property * type = TypeSArray */ package void setLengthVarIfKnown(VarDeclaration lengthVar, Type type) { if (!lengthVar) return; if (lengthVar._init && !lengthVar._init.isVoidInitializer()) return; // we have previously calculated the length auto tsa = type.toBasetype().isTypeSArray(); if (!tsa) return; // we don't know the length yet const len = tsa.dim.toInteger(); Expression dollar = new IntegerExp(Loc.initial, len, Type.tsize_t); lengthVar._init = new ExpInitializer(Loc.initial, dollar); lengthVar.storage_class |= STC.static_ | STC.const_; } /********************************* * Constant fold an Expression. * Params: * e = expression to const fold; this may get modified in-place * result = WANTvalue, WANTexpand, or both * keepLvalue = `e` is an lvalue, and keep it as an lvalue since it is * an argument to a `ref` or `out` parameter, or the operand of `&` operator * Returns: * Constant folded version of `e` */ Expression optimize(Expression e, int result, bool keepLvalue = false) { //printf("optimize() e: %s result: %d keepLvalue %d\n", e.toChars(), result, keepLvalue); Expression ret = e; void errorReturn() { ret = ErrorExp.get(); } /* Returns: true if error */ bool expOptimize(ref Expression e, int flags, bool keepLvalue = false) { if (!e) return false; Expression ex = optimize(e, flags, keepLvalue); if (ex.op == EXP.error) { ret = ex; // store error result return true; } else { e = ex; // modify original return false; } } bool unaOptimize(UnaExp e, int flags) { return expOptimize(e.e1, flags); } bool binOptimize(BinExp e, int flags, bool keepLhsLvalue = false) { return expOptimize(e.e1, flags, keepLhsLvalue) | expOptimize(e.e2, flags); } void visitExp(Expression e) { //printf("Expression::optimize(result = x%x) %s\n", result, e.toChars()); } void visitVar(VarExp e) { VarDeclaration v = e.var.isVarDeclaration(); if (!(keepLvalue && v && !(v.storage_class & STC.manifest))) ret = fromConstInitializer(result, e); // if unoptimized, try to optimize the dtor expression // (e.g., might be a LogicalExp with constant lhs) if (ret == e && v && v.edtor) { // prevent infinite recursion (`.~this()`) if (!v.inuse) { v.inuse++; expOptimize(v.edtor, WANTvalue); v.inuse--; } } } void visitTuple(TupleExp e) { expOptimize(e.e0, WANTvalue); foreach (ref ex; (*e.exps)[]) { expOptimize(ex, WANTvalue); } } void visitArrayLiteral(ArrayLiteralExp e) { if (e.elements) { expOptimize(e.basis, result & WANTexpand); foreach (ref ex; (*e.elements)[]) { expOptimize(ex, result & WANTexpand); } } } void visitAssocArrayLiteral(AssocArrayLiteralExp e) { assert(e.keys.length == e.values.length); foreach (i, ref ekey; (*e.keys)[]) { expOptimize(ekey, result & WANTexpand); expOptimize((*e.values)[i], result & WANTexpand); } } void visitStructLiteral(StructLiteralExp e) { if (e.stageflags & stageOptimize) return; const old = e.stageflags; e.stageflags |= stageOptimize; if (e.elements) { foreach (ref ex; (*e.elements)[]) { expOptimize(ex, result & WANTexpand); } } e.stageflags = old; } void visitUna(UnaExp e) { //printf("UnaExp::optimize() %s\n", e.toChars()); if (unaOptimize(e, result)) return; } void visitNeg(NegExp e) { if (unaOptimize(e, result)) return; if (e.e1.isConst() == 1) { ret = Neg(e.type, e.e1).copy(); } } void visitCom(ComExp e) { if (unaOptimize(e, result)) return; if (e.e1.isConst() == 1) { ret = Com(e.type, e.e1).copy(); } } void visitNop(NotExp e) { if (unaOptimize(e, result)) return; if (e.e1.isConst() == 1) { ret = Not(e.type, e.e1).copy(); } } void visitSymOff(SymOffExp e) { assert(e.var); } void visitAddr(AddrExp e) { //printf("AddrExp::optimize(result = %d, keepLvalue = %d) %s\n", result, keepLvalue, e.toChars()); /* Rewrite &(a,b) as (a,&b) */ if (auto ce = e.e1.isCommaExp()) { auto ae = new AddrExp(e.loc, ce.e2, e.type); ret = new CommaExp(ce.loc, ce.e1, ae); ret.type = e.type; return; } // Keep lvalue-ness if (expOptimize(e.e1, result, true)) return; // error return // Convert &*ex to ex if (auto pe = e.e1.isPtrExp()) { Expression ex = pe.e1; if (e.type.equals(ex.type)) ret = ex; else if (e.type.toBasetype().equivalent(ex.type.toBasetype())) { ret = ex.copy(); ret.type = e.type; } return; } if (auto ve = e.e1.isVarExp()) { if (!ve.var.isReference() && !ve.var.isImportedSymbol()) { ret = new SymOffExp(e.loc, ve.var, 0, ve.hasOverloads); ret.type = e.type; return; } } if (e.e1.isDotVarExp()) { /****************************** * Run down the left side of the a.b.c expression to determine the * leftmost variable being addressed (`a`), and accumulate the offsets of the `.b` and `.c`. * Params: * e = the DotVarExp or VarExp * var = set to the VarExp at the end, or null if doesn't end in VarExp * eint = set to the IntegerExp at the end, or null if doesn't end in IntegerExp * offset = accumulation of all the .var offsets encountered * Returns: true on error */ static bool getVarAndOffset(Expression e, out VarDeclaration var, out IntegerExp eint, ref uint offset) { if (e.type.size() == SIZE_INVALID) // trigger computation of v.offset return true; if (auto dve = e.isDotVarExp()) { auto v = dve.var.isVarDeclaration(); if (!v || !v.isField() || v.isBitFieldDeclaration()) return false; if (getVarAndOffset(dve.e1, var, eint, offset)) return true; offset += v.offset; } else if (auto ve = e.isVarExp()) { if (!ve.var.isReference() && !ve.var.isImportedSymbol() && ve.var.isDataseg() && ve.var.isCsymbol()) { var = ve.var.isVarDeclaration(); } } else if (auto ep = e.isPtrExp()) { if (auto ei = ep.e1.isIntegerExp()) { eint = ei; } else if (auto se = ep.e1.isSymOffExp()) { if (!se.var.isReference() && !se.var.isImportedSymbol() && se.var.isDataseg()) { var = se.var.isVarDeclaration(); offset += se.offset; } } } else if (auto ei = e.isIndexExp()) { if (auto ve = ei.e1.isVarExp()) { if (!ve.var.isReference() && !ve.var.isImportedSymbol() && ve.var.isDataseg() && ve.var.isCsymbol()) { if (auto ie = ei.e2.isIntegerExp()) { var = ve.var.isVarDeclaration(); offset += ie.toInteger() * ve.type.toBasetype().nextOf().size(); } } } } return false; } uint offset; VarDeclaration var; IntegerExp eint; if (getVarAndOffset(e.e1, var, eint, offset)) { ret = ErrorExp.get(); return; } if (var) { ret = new SymOffExp(e.loc, var, offset, false); ret.type = e.type; return; } if (eint) { ret = new IntegerExp(e.loc, eint.toInteger() + offset, e.type); return; } } else if (auto ae = e.e1.isIndexExp()) { if (ae.e2.isIntegerExp() && ae.e1.isIndexExp()) { /* Rewrite `(a[i])[index]` to `(&a[i]) + index*size` */ sinteger_t index = ae.e2.toInteger(); auto ae1 = ae.e1.isIndexExp(); // ae1 is a[i] if (auto ts = ae1.type.isTypeSArray()) { sinteger_t dim = ts.dim.toInteger(); if (index < 0 || index > dim) { error(e.loc, "array index %lld is out of bounds `[0..%lld]`", index, dim); return errorReturn(); } import core.checkedint : mulu; bool overflow; const offset = mulu(index, ts.nextOf().size(e.loc), overflow); // offset = index*size if (overflow) { error(e.loc, "array offset overflow"); return errorReturn(); } Expression ex = new AddrExp(ae1.loc, ae1); // &a[i] ex.type = ae1.type.pointerTo(); Expression add = new AddExp(ae.loc, ex, new IntegerExp(ae.e2.loc, offset, ae.e2.type)); add.type = e.type; ret = optimize(add, result, keepLvalue); return; } } // Convert &array[n] to &array+n if (ae.e2.isIntegerExp() && ae.e1.isVarExp()) { sinteger_t index = ae.e2.toInteger(); VarExp ve = ae.e1.isVarExp(); if (ve.type.isTypeSArray() && !ve.var.isImportedSymbol()) { TypeSArray ts = ve.type.isTypeSArray(); sinteger_t dim = ts.dim.toInteger(); if (index < 0 || index >= dim) { /* 0 for C static arrays means size is unknown, no need to check, * and address one past the end is OK, too */ if (!((dim == 0 || dim == index) && ve.var.isCsymbol())) { error(e.loc, "array index %lld is out of bounds `[0..%lld]`", index, dim); return errorReturn(); } } import core.checkedint : mulu; bool overflow; const offset = mulu(index, ts.nextOf().size(e.loc), overflow); if (overflow) { error(e.loc, "array offset overflow"); return errorReturn(); } ret = new SymOffExp(e.loc, ve.var, offset); ret.type = e.type; return; } } // Convert &((a.b)[index]) to (&a.b)+index*elementsize else if (ae.e2.isIntegerExp() && ae.e1.isDotVarExp()) { sinteger_t index = ae.e2.toInteger(); DotVarExp ve = ae.e1.isDotVarExp(); if (ve.type.isTypeSArray() && ve.var.isField() && ve.e1.isPtrExp()) { TypeSArray ts = ve.type.isTypeSArray(); sinteger_t dim = ts.dim.toInteger(); if (index < 0 || index >= dim) { /* 0 for C static arrays means size is unknown, no need to check, * and address one past the end is OK, too */ if (!((dim == 0 || dim == index) && ve.var.isCsymbol())) { error(e.loc, "array index %lld is out of bounds `[0..%lld]`", index, dim); return errorReturn(); } } import core.checkedint : mulu; bool overflow; const offset = mulu(index, ts.nextOf().size(e.loc), overflow); // index*elementsize if (overflow) { error(e.loc, "array offset overflow"); return errorReturn(); } auto pe = new AddrExp(e.loc, ve); pe.type = e.type; ret = new AddExp(e.loc, pe, new IntegerExp(e.loc, offset, Type.tsize_t)); ret.type = e.type; return; } } } } void visitPtr(PtrExp e) { //printf("PtrExp::optimize(result = x%x) %s\n", result, e.toChars()); if (expOptimize(e.e1, result)) return; // Convert *&ex to ex // But only if there is no type punning involved if (auto ey = e.e1.isAddrExp()) { Expression ex = ey.e1; if (e.type.equals(ex.type)) ret = ex; else if (e.type.toBasetype().equivalent(ex.type.toBasetype())) { ret = ex.copy(); ret.type = e.type; } } if (keepLvalue) return; // Constant fold *(&structliteral + offset) if (e.e1.op == EXP.add) { Expression ex = Ptr(e.type, e.e1).copy(); if (!CTFEExp.isCantExp(ex)) { ret = ex; return; } } if (auto se = e.e1.isSymOffExp()) { VarDeclaration v = se.var.isVarDeclaration(); Expression ex = expandVar(result, v); if (ex && ex.isStructLiteralExp()) { StructLiteralExp sle = ex.isStructLiteralExp(); ex = sle.getField(e.type, cast(uint)se.offset); if (ex && !CTFEExp.isCantExp(ex)) { ret = ex; return; } } } } void visitDotVar(DotVarExp e) { //printf("DotVarExp::optimize(result = x%x) %s\n", result, e.toChars()); if (expOptimize(e.e1, result)) return; if (keepLvalue) return; Expression ex = e.e1; if (auto ve = ex.isVarExp()) { VarDeclaration v = ve.var.isVarDeclaration(); ex = expandVar(result, v); } if (ex && ex.isStructLiteralExp()) { StructLiteralExp sle = ex.isStructLiteralExp(); VarDeclaration vf = e.var.isVarDeclaration(); if (vf && !vf.overlapped) { /* https://issues.dlang.org/show_bug.cgi?id=13021 * Prevent optimization if vf has overlapped fields. */ ex = sle.getField(e.type, vf.offset); if (ex && !CTFEExp.isCantExp(ex)) { ret = ex; return; } } } } void visitNew(NewExp e) { expOptimize(e.thisexp, WANTvalue); // Optimize parameters if (e.arguments) { foreach (ref arg; (*e.arguments)[]) { expOptimize(arg, WANTvalue); } } } void visitCall(CallExp e) { //printf("CallExp::optimize(result = %d) %s\n", result, e.toChars()); // Optimize parameters with keeping lvalue-ness if (expOptimize(e.e1, result)) return; if (e.arguments) { // t1 can apparently be void for __ArrayDtor(T) calls if (auto tf = e.calledFunctionType()) { foreach (i, ref arg; (*e.arguments)[]) { Parameter p = tf.parameterList[i]; bool keep = p && p.isReference(); expOptimize(arg, WANTvalue, keep); } } } } void visitCast(CastExp e) { //printf("CastExp::optimize(result = %d) %s\n", result, e.toChars()); //printf("from %s to %s\n", e.type.toChars(), e.to.toChars()); //printf("from %s\n", e.type.toChars()); //printf("e1.type %s\n", e.e1.type.toChars()); //printf("type = %p\n", e.type); assert(e.type); const op1 = e.e1.op; Expression e1old = e.e1; if (expOptimize(e.e1, result, keepLvalue)) return; if (!keepLvalue) e.e1 = fromConstInitializer(result, e.e1); if (e.e1 == e1old && e.e1.op == EXP.arrayLiteral && e.type.toBasetype().ty == Tpointer && e.e1.type.toBasetype().ty != Tsarray) { // Casting this will result in the same expression, and // infinite loop because of Expression::implicitCastTo() return; // no change } if ((e.e1.op == EXP.string_ || e.e1.op == EXP.arrayLiteral) && (e.type.ty == Tpointer || e.type.ty == Tarray)) { const esz = e.type.nextOf().size(e.loc); const e1sz = e.e1.type.toBasetype().nextOf().size(e.e1.loc); if (esz == SIZE_INVALID || e1sz == SIZE_INVALID) return errorReturn(); if (e1sz == esz) { // https://issues.dlang.org/show_bug.cgi?id=12937 // If target type is void array, trying to paint // e.e1 with that type will cause infinite recursive optimization. if (e.type.nextOf().ty == Tvoid) return; ret = e.e1.castTo(null, e.type); //printf(" returning1 %s\n", ret.toChars()); return; } } // Returning e.e1 with changing its type void returnE_e1() { ret = (e1old == e.e1 ? e.e1.copy() : e.e1); ret.type = e.type; } if (e.e1.op == EXP.structLiteral && e.e1.type.implicitConvTo(e.type) >= MATCH.constant) { //printf(" returning2 %s\n", e.e1.toChars()); return returnE_e1(); } /* The first test here is to prevent infinite loops */ if (op1 != EXP.arrayLiteral && e.e1.op == EXP.arrayLiteral) { ret = e.e1.castTo(null, e.to); return; } if (e.e1.op == EXP.null_ && (e.type.ty == Tpointer || e.type.ty == Tclass || e.type.ty == Tarray)) { //printf(" returning3 %s\n", e.e1.toChars()); return returnE_e1(); } if (e.type.ty == Tclass && e.e1.type.ty == Tclass) { import dmd.astenums : Sizeok; // See if we can remove an unnecessary cast ClassDeclaration cdfrom = e.e1.type.isClassHandle(); ClassDeclaration cdto = e.type.isClassHandle(); if (cdfrom.errors || cdto.errors) return errorReturn(); if (cdto == ClassDeclaration.object && !cdfrom.isInterfaceDeclaration()) return returnE_e1(); // can always convert a class to Object // Need to determine correct offset before optimizing away the cast. // https://issues.dlang.org/show_bug.cgi?id=16980 if (cdfrom.size(e.loc) == SIZE_INVALID) return errorReturn(); assert(cdfrom.sizeok == Sizeok.done); assert(cdto.sizeok == Sizeok.done || !cdto.isBaseOf(cdfrom, null)); int offset; if (cdto.isBaseOf(cdfrom, &offset) && offset == 0) { //printf(" returning4 %s\n", e.e1.toChars()); return returnE_e1(); } } if (e.e1.type.mutableOf().unSharedOf().equals(e.to.mutableOf().unSharedOf())) { //printf(" returning5 %s\n", e.e1.toChars()); return returnE_e1(); } if (e.e1.isConst()) { if (e.e1.op == EXP.symbolOffset) { if (e.type.toBasetype().ty != Tsarray) { const esz = e.type.size(e.loc); const e1sz = e.e1.type.size(e.e1.loc); if (esz == SIZE_INVALID || e1sz == SIZE_INVALID) return errorReturn(); if (esz == e1sz) return returnE_e1(); } return; } if (e.to.toBasetype().ty != Tvoid) { if (e.e1.type.equals(e.type) && e.type.equals(e.to)) ret = e.e1; else ret = Cast(e.loc, e.type, e.to, e.e1).copy(); } } //printf(" returning6 %s\n", ret.toChars()); } void visitBinAssign(BinAssignExp e) { //printf("BinAssignExp::optimize(result = %d) %s\n", result, e.toChars()); if (binOptimize(e, result, /*keepLhsLvalue*/ true)) return; if (e.op == EXP.leftShiftAssign || e.op == EXP.rightShiftAssign || e.op == EXP.unsignedRightShiftAssign) { if (e.e2.isConst() == 1) { sinteger_t i2 = e.e2.toInteger(); uinteger_t sz = e.e1.type.size(e.e1.loc); assert(sz != SIZE_INVALID); sz *= 8; if (i2 < 0 || i2 >= sz) { error(e.loc, "shift assign by %lld is outside the range `0..%llu`", i2, cast(ulong)sz - 1); return errorReturn(); } } } } void visitCatAssign(CatAssignExp e) { if (auto lowering = e.lowering) optimize(lowering, result, keepLvalue); else visitBinAssign(e); } void visitBin(BinExp e) { //printf("BinExp::optimize(result = %d) %s\n", result, e.toChars()); const keepLhsLvalue = e.op == EXP.construct || e.op == EXP.blit || e.op == EXP.assign || e.op == EXP.plusPlus || e.op == EXP.minusMinus || e.op == EXP.prePlusPlus || e.op == EXP.preMinusMinus; binOptimize(e, result, keepLhsLvalue); } void visitAdd(AddExp e) { //printf("AddExp::optimize(%s)\n", e.toChars()); if (binOptimize(e, result)) return; if (e.e1.isConst() && e.e2.isConst()) { if (e.e1.op == EXP.symbolOffset && e.e2.op == EXP.symbolOffset) return; ret = Add(e.loc, e.type, e.e1, e.e2).copy(); } } void visitMin(MinExp e) { //printf("MinExp::optimize(%s)\n", e.toChars()); if (binOptimize(e, result)) return; if (e.e1.isConst() && e.e2.isConst()) { if (e.e2.op == EXP.symbolOffset) return; ret = Min(e.loc, e.type, e.e1, e.e2).copy(); } } void visitMul(MulExp e) { //printf("MulExp::optimize(result = %d) %s\n", result, e.toChars()); if (binOptimize(e, result)) return; if (e.e1.isConst() == 1 && e.e2.isConst() == 1) { ret = Mul(e.loc, e.type, e.e1, e.e2).copy(); } } void visitDiv(DivExp e) { //printf("DivExp::optimize(%s)\n", e.toChars()); if (binOptimize(e, result)) return; if (e.e1.isConst() == 1 && e.e2.isConst() == 1) { ret = Div(e.loc, e.type, e.e1, e.e2).copy(); } } void visitMod(ModExp e) { if (binOptimize(e, result)) return; if (e.e1.isConst() == 1 && e.e2.isConst() == 1) { ret = Mod(e.loc, e.type, e.e1, e.e2).copy(); } } extern (D) void shift_optimize(BinExp e, UnionExp function(const ref Loc, Type, Expression, Expression) shift) { if (binOptimize(e, result)) return; if (e.e2.isConst() == 1) { sinteger_t i2 = e.e2.toInteger(); uinteger_t sz = e.e1.type.size(e.e1.loc); assert(sz != SIZE_INVALID); sz *= 8; if (i2 < 0 || i2 >= sz) { error(e.loc, "shift by %lld is outside the range `0..%llu`", i2, cast(ulong)sz - 1); return errorReturn(); } if (e.e1.isConst() == 1) ret = (*shift)(e.loc, e.type, e.e1, e.e2).copy(); } } void visitShl(ShlExp e) { //printf("ShlExp::optimize(result = %d) %s\n", result, e.toChars()); shift_optimize(e, &Shl); } void visitShr(ShrExp e) { //printf("ShrExp::optimize(result = %d) %s\n", result, e.toChars()); shift_optimize(e, &Shr); } void visitUshr(UshrExp e) { //printf("UshrExp::optimize(result = %d) %s\n", result, toChars()); shift_optimize(e, &Ushr); } void visitAnd(AndExp e) { if (binOptimize(e, result)) return; if (e.e1.isConst() == 1 && e.e2.isConst() == 1) ret = And(e.loc, e.type, e.e1, e.e2).copy(); } void visitOr(OrExp e) { if (binOptimize(e, result)) return; if (e.e1.isConst() == 1 && e.e2.isConst() == 1) ret = Or(e.loc, e.type, e.e1, e.e2).copy(); } void visitXor(XorExp e) { if (binOptimize(e, result)) return; if (e.e1.isConst() == 1 && e.e2.isConst() == 1) ret = Xor(e.loc, e.type, e.e1, e.e2).copy(); } void visitPow(PowExp e) { if (binOptimize(e, result)) return; // All negative integral powers are illegal. if (e.e1.type.isintegral() && (e.e2.op == EXP.int64) && cast(sinteger_t)e.e2.toInteger() < 0) { error(e.loc, "cannot raise `%s` to a negative integer power. Did you mean `(cast(real)%s)^^%s` ?", e.e1.type.toBasetype().toChars(), e.e1.toChars(), e.e2.toChars()); return errorReturn(); } // If e2 *could* have been an integer, make it one. if (e.e2.op == EXP.float64 && e.e2.toReal() == real_t(cast(sinteger_t)e.e2.toReal())) { // This only applies to floating point, or positive integral powers. if (e.e1.type.isfloating() || cast(sinteger_t)e.e2.toInteger() >= 0) e.e2 = new IntegerExp(e.loc, e.e2.toInteger(), Type.tint64); } if (e.e1.isConst() == 1 && e.e2.isConst() == 1) { Expression ex = Pow(e.loc, e.type, e.e1, e.e2).copy(); if (!CTFEExp.isCantExp(ex)) { ret = ex; return; } } } void visitComma(CommaExp e) { //printf("CommaExp::optimize(result = %d) %s\n", result, e.toChars()); // Comma needs special treatment, because it may // contain compiler-generated declarations. We can interpret them, but // otherwise we must NOT attempt to constant-fold them. // In particular, if the comma returns a temporary variable, it needs // to be an lvalue (this is particularly important for struct constructors) expOptimize(e.e1, WANTvalue); expOptimize(e.e2, result, keepLvalue); if (ret.op == EXP.error) return; if (!e.e1 || e.e1.op == EXP.int64 || e.e1.op == EXP.float64 || !hasSideEffect(e.e1)) { ret = e.e2; if (ret) ret.type = e.type; } //printf("-CommaExp::optimize(result = %d) %s\n", result, e.e.toChars()); } void visitArrayLength(ArrayLengthExp e) { //printf("ArrayLengthExp::optimize(result = %d) %s\n", result, e.toChars()); if (unaOptimize(e, WANTexpand)) return; // CTFE interpret static immutable arrays (to get better diagnostics) if (auto ve = e.e1.isVarExp()) { VarDeclaration v = ve.var.isVarDeclaration(); if (v && (v.storage_class & STC.static_) && (v.storage_class & STC.immutable_) && v._init) { if (Expression ci = v.getConstInitializer()) e.e1 = ci; } } if (e.e1.op == EXP.string_ || e.e1.op == EXP.arrayLiteral || e.e1.op == EXP.assocArrayLiteral || e.e1.type.toBasetype().ty == Tsarray || e.e1.op == EXP.null_) { ret = ArrayLength(e.type, e.e1).copy(); } } void visitEqual(EqualExp e) { //printf("EqualExp::optimize(result = %x) %s\n", result, e.toChars()); if (binOptimize(e, WANTvalue)) return; Expression e1 = fromConstInitializer(result, e.e1); Expression e2 = fromConstInitializer(result, e.e2); if (e1.op == EXP.error) { ret = e1; return; } if (e2.op == EXP.error) { ret = e2; return; } ret = Equal(e.op, e.loc, e.type, e1, e2).copy(); if (CTFEExp.isCantExp(ret)) ret = e; } void visitIdentity(IdentityExp e) { //printf("IdentityExp::optimize(result = %d) %s\n", result, e.toChars()); if (binOptimize(e, WANTvalue)) return; if ((e.e1.isConst() && e.e2.isConst()) || (e.e1.op == EXP.null_ && e.e2.op == EXP.null_)) { ret = Identity(e.op, e.loc, e.type, e.e1, e.e2).copy(); if (CTFEExp.isCantExp(ret)) ret = e; } } void visitIndex(IndexExp e) { //printf("IndexExp::optimize(result = %d) %s\n", result, e.toChars()); if (expOptimize(e.e1, result & WANTexpand)) return; Expression ex = fromConstInitializer(result, e.e1); // We might know $ now setLengthVarIfKnown(e.lengthVar, ex); if (expOptimize(e.e2, WANTvalue)) return; // Don't optimize to an array literal element directly in case an lvalue is requested if (keepLvalue && ex.op == EXP.arrayLiteral) return; ret = Index(e.type, ex, e.e2, e.indexIsInBounds).copy(); if (CTFEExp.isCantExp(ret) || (!ret.isErrorExp() && keepLvalue && !ret.isLvalue())) ret = e; } void visitSlice(SliceExp e) { //printf("SliceExp::optimize(result = %d) %s\n", result, e.toChars()); if (expOptimize(e.e1, result & WANTexpand)) return; if (!e.lwr) { if (e.e1.op == EXP.string_) { // Convert slice of string literal into dynamic array Type t = e.e1.type.toBasetype(); if (Type tn = t.nextOf()) ret = e.e1.castTo(null, tn.arrayOf()); } } else { e.e1 = fromConstInitializer(result, e.e1); // We might know $ now setLengthVarIfKnown(e.lengthVar, e.e1); expOptimize(e.lwr, WANTvalue); expOptimize(e.upr, WANTvalue); if (ret.op == EXP.error) return; ret = Slice(e.type, e.e1, e.lwr, e.upr).copy(); if (CTFEExp.isCantExp(ret)) ret = e; } // https://issues.dlang.org/show_bug.cgi?id=14649 // Leave the slice form so it might be // a part of array operation. // Assume that the backend codegen will handle the form `e[]` // as an equal to `e` itself. if (ret.op == EXP.string_) { e.e1 = ret; e.lwr = null; e.upr = null; ret = e; } //printf("-SliceExp::optimize() %s\n", ret.toChars()); } void visitLogical(LogicalExp e) { //printf("LogicalExp::optimize(%d) %s\n", result, e.toChars()); if (expOptimize(e.e1, WANTvalue)) return; const oror = e.op == EXP.orOr; void returnE_e1() { ret = e.e1; if (!e.e1.type.equals(e.type)) { ret = new CastExp(e.loc, ret, e.type); ret.type = e.type; ret = optimize(ret, result, false); } } if (e.e1.toBool().hasValue(oror)) { // e_true || e2 -> e_true // e_false && e2 -> e_false return returnE_e1(); } expOptimize(e.e2, WANTvalue); if (e.e1.isConst()) { const e1Opt = e.e1.toBool(); if (e.e2.isConst()) { bool n1 = e1Opt.get(); bool n2 = e.e2.toBool().get(); ret = new IntegerExp(e.loc, oror ? (n1 || n2) : (n1 && n2), e.type); } else if (e1Opt.hasValue(!oror)) { if (e.type.toBasetype().ty == Tvoid) ret = e.e2; else { ret = new CastExp(e.loc, e.e2, e.type); ret.type = e.type; } } } else if (e.e2.isConst()) { const e2Opt = e.e2.toBool(); if (e2Opt.hasValue(oror)) { // e1 || true -> (e1, true) // e1 && false -> (e1, false) ret = IntegerExp.createBool(oror); ret = Expression.combine(e.e1, ret); if (e.type.toBasetype().ty == Tvoid) { ret = new CastExp(e.loc, ret, Type.tvoid); ret.type = e.type; } ret = optimize(ret, result, false); } else if (e2Opt.hasValue(!oror)) { // e1 || false -> e1 // e1 && true -> e1 return returnE_e1(); } } } void visitCmp(CmpExp e) { //printf("CmpExp::optimize() %s\n", e.toChars()); if (binOptimize(e, WANTvalue)) return; Expression e1 = fromConstInitializer(result, e.e1); Expression e2 = fromConstInitializer(result, e.e2); ret = Cmp(e.op, e.loc, e.type, e1, e2).copy(); if (CTFEExp.isCantExp(ret)) ret = e; } void visitCat(CatExp e) { //printf("CatExp::optimize(%d) %s\n", result, e.toChars()); if (binOptimize(e, result)) return; if (e.type == Type.tstring) if (auto ce1 = e.e1.isCatExp()) { // https://issues.dlang.org/show_bug.cgi?id=12798 // optimize ((expr ~ str1) ~ str2) // https://issues.dlang.org/show_bug.cgi?id=24078 // This optimization is only valid if `expr` is a string. // Otherwise it leads to: // `["c"] ~ "a" ~ "b"` becoming `["c"] ~ "ab"` scope CatExp cex = new CatExp(e.loc, ce1.e2, e.e2); cex.type = e.type; Expression ex = optimize(cex, result, false); if (ex != cex) { e.e1 = ce1.e1; e.e2 = ex; } } // optimize "str"[] -> "str" if (auto se1 = e.e1.isSliceExp()) { if (se1.e1.op == EXP.string_ && !se1.lwr) e.e1 = se1.e1; } if (auto se2 = e.e2.isSliceExp()) { if (se2.e1.op == EXP.string_ && !se2.lwr) e.e2 = se2.e1; } ret = Cat(e.loc, e.type, e.e1, e.e2).copy(); if (CTFEExp.isCantExp(ret)) ret = e; } void visitCond(CondExp e) { if (expOptimize(e.econd, WANTvalue)) return; const opt = e.econd.toBool(); if (opt.hasValue(true)) ret = optimize(e.e1, result, keepLvalue); else if (opt.hasValue(false)) ret = optimize(e.e2, result, keepLvalue); else { expOptimize(e.e1, result, keepLvalue); expOptimize(e.e2, result, keepLvalue); } } // Optimize the expression until it can no longer be simplified. size_t b; while (1) { if (b++ == global.recursionLimit) { error(e.loc, "infinite loop while optimizing expression"); return ErrorExp.get(); } auto ex = ret; switch (ex.op) { case EXP.variable: visitVar(ex.isVarExp()); break; case EXP.tuple: visitTuple(ex.isTupleExp()); break; case EXP.arrayLiteral: visitArrayLiteral(ex.isArrayLiteralExp()); break; case EXP.assocArrayLiteral: visitAssocArrayLiteral(ex.isAssocArrayLiteralExp()); break; case EXP.structLiteral: visitStructLiteral(ex.isStructLiteralExp()); break; case EXP.import_: case EXP.assert_: case EXP.dotIdentifier: case EXP.dotTemplateDeclaration: case EXP.dotTemplateInstance: case EXP.delegate_: case EXP.dotType: case EXP.uadd: case EXP.delete_: case EXP.vector: case EXP.vectorArray: case EXP.array: case EXP.delegatePointer: case EXP.delegateFunctionPointer: case EXP.preMinusMinus: case EXP.prePlusPlus: visitUna(cast(UnaExp)ex); break; case EXP.negate: visitNeg(ex.isNegExp()); break; case EXP.tilde: visitCom(ex.isComExp()); break; case EXP.not: visitNop(ex.isNotExp()); break; case EXP.symbolOffset: visitSymOff(ex.isSymOffExp()); break; case EXP.address: visitAddr(ex.isAddrExp()); break; case EXP.star: visitPtr(ex.isPtrExp()); break; case EXP.dotVariable: visitDotVar(ex.isDotVarExp()); break; case EXP.new_: visitNew(ex.isNewExp()); break; case EXP.call: visitCall(ex.isCallExp()); break; case EXP.cast_: visitCast(ex.isCastExp()); break; case EXP.addAssign: case EXP.minAssign: case EXP.mulAssign: case EXP.divAssign: case EXP.modAssign: case EXP.andAssign: case EXP.orAssign: case EXP.xorAssign: case EXP.powAssign: case EXP.leftShiftAssign: case EXP.rightShiftAssign: case EXP.unsignedRightShiftAssign: case EXP.concatenateDcharAssign: visitBinAssign(ex.isBinAssignExp()); break; case EXP.concatenateElemAssign: case EXP.concatenateAssign: visitCatAssign(cast(CatAssignExp) ex); break; case EXP.minusMinus: case EXP.plusPlus: case EXP.assign: case EXP.construct: case EXP.blit: case EXP.in_: case EXP.remove: case EXP.dot: visitBin(cast(BinExp)ex); break; case EXP.add: visitAdd(ex.isAddExp()); break; case EXP.min: visitMin(ex.isMinExp()); break; case EXP.mul: visitMul(ex.isMulExp()); break; case EXP.div: visitDiv(ex.isDivExp()); break; case EXP.mod: visitMod(ex.isModExp()); break; case EXP.leftShift: visitShl(ex.isShlExp()); break; case EXP.rightShift: visitShr(ex.isShrExp()); break; case EXP.unsignedRightShift: visitUshr(ex.isUshrExp()); break; case EXP.and: visitAnd(ex.isAndExp()); break; case EXP.or: visitOr(ex.isOrExp()); break; case EXP.xor: visitXor(ex.isXorExp()); break; case EXP.pow: visitPow(ex.isPowExp()); break; case EXP.comma: visitComma(ex.isCommaExp()); break; case EXP.arrayLength: visitArrayLength(ex.isArrayLengthExp()); break; case EXP.notEqual: case EXP.equal: visitEqual(ex.isEqualExp()); break; case EXP.notIdentity: case EXP.identity: visitIdentity(ex.isIdentityExp()); break; case EXP.index: visitIndex(ex.isIndexExp()); break; case EXP.slice: visitSlice(ex.isSliceExp()); break; case EXP.andAnd: case EXP.orOr: visitLogical(ex.isLogicalExp()); break; case EXP.lessThan: case EXP.lessOrEqual: case EXP.greaterThan: case EXP.greaterOrEqual: visitCmp(cast(CmpExp)ex); break; case EXP.concatenate: visitCat(ex.isCatExp()); break; case EXP.question: visitCond(ex.isCondExp()); break; default: visitExp(ex); break; } if (ex == ret) break; } return ret; } ldc-1.40.0-src/dmd/arraytypes.h0000644000000000000000000000355314727557031014762 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 2006-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/arraytypes.h */ #pragma once #include "root/array.h" #include "root/bitarray.h" typedef Array TemplateParameters; typedef Array Expressions; typedef Array Statements; typedef Array BaseClasses; typedef Array ClassDeclarations; typedef Array Dsymbols; typedef Array Objects; typedef Array DtorDeclarations; typedef Array FuncDeclarations; typedef Array Parameters; typedef Array Identifiers; typedef Array Initializers; typedef Array VarDeclarations; typedef Array Types; typedef Array Catches; typedef Array StaticDtorDeclarations; typedef Array SharedStaticDtorDeclarations; typedef Array AliasDeclarations; typedef Array Modules; typedef Array CaseStatements; typedef Array ScopeStatements; typedef Array GotoCaseStatements; typedef Array ReturnStatements; typedef Array GotoStatements; typedef Array TemplateInstances; typedef Array Ensures; typedef Array Designators; typedef Array DesigInits; ldc-1.40.0-src/dmd/ctfeexpr.d0000644000000000000000000020352314727557031014372 0ustar rootroot/** * CTFE for expressions involving pointers, slices, array concatenation etc. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/ctfeexpr.d, _ctfeexpr.d) * Documentation: https://dlang.org/phobos/dmd_ctfeexpr.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/ctfeexpr.d */ module dmd.ctfeexpr; import core.stdc.stdio; import core.stdc.string; import dmd.arraytypes; import dmd.astenums; import dmd.constfold; import dmd.compiler; import dmd.dcast : implicitConvTo; import dmd.dclass; import dmd.declaration; import dmd.dinterpret; import dmd.dstruct; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.func; import dmd.globals; import dmd.location; import dmd.mtype; import dmd.root.bitarray; import dmd.root.complex; import dmd.root.ctfloat; import dmd.root.port; import dmd.root.rmem; import dmd.tokens; import dmd.typesem; import dmd.visitor; /****************************************************************/ /* A type meant as a union of all the Expression types, * to serve essentially as a Variant that will sit on the stack * during CTFE to reduce memory consumption. */ extern (D) struct UnionExp { // yes, default constructor does nothing extern (D) this(Expression e) nothrow { memcpy(&this, cast(void*)e, e.size); } /* Extract pointer to Expression */ extern (D) Expression exp() return nothrow { return cast(Expression)&u; } /* Convert to an allocated Expression */ extern (D) Expression copy() { Expression e = exp(); //if (e.size > sizeof(u)) printf("%s\n", EXPtoString(e.op).ptr); assert(e.size <= u.sizeof); switch (e.op) { case EXP.cantExpression: return CTFEExp.cantexp; case EXP.voidExpression: return CTFEExp.voidexp; case EXP.break_: return CTFEExp.breakexp; case EXP.continue_: return CTFEExp.continueexp; case EXP.goto_: return CTFEExp.gotoexp; default: return e.copy(); } } private: // Ensure that the union is suitably aligned. align(8) union _AnonStruct_u { char[__traits(classInstanceSize, Expression)] exp; char[__traits(classInstanceSize, IntegerExp)] integerexp; char[__traits(classInstanceSize, ErrorExp)] errorexp; char[__traits(classInstanceSize, RealExp)] realexp; char[__traits(classInstanceSize, ComplexExp)] complexexp; char[__traits(classInstanceSize, SymOffExp)] symoffexp; char[__traits(classInstanceSize, StringExp)] stringexp; char[__traits(classInstanceSize, ArrayLiteralExp)] arrayliteralexp; char[__traits(classInstanceSize, AssocArrayLiteralExp)] assocarrayliteralexp; char[__traits(classInstanceSize, StructLiteralExp)] structliteralexp; char[__traits(classInstanceSize, CompoundLiteralExp)] compoundliteralexp; char[__traits(classInstanceSize, NullExp)] nullexp; char[__traits(classInstanceSize, DotVarExp)] dotvarexp; char[__traits(classInstanceSize, AddrExp)] addrexp; char[__traits(classInstanceSize, IndexExp)] indexexp; char[__traits(classInstanceSize, SliceExp)] sliceexp; char[__traits(classInstanceSize, VectorExp)] vectorexp; } _AnonStruct_u u; } void emplaceExp(T : Expression, Args...)(void* p, Args args) { static if (__VERSION__ < 2099) const init = typeid(T).initializer; else const init = __traits(initSymbol, T); p[0 .. __traits(classInstanceSize, T)] = init[]; (cast(T)p).__ctor(args); } void emplaceExp(T : UnionExp)(T* p, Expression e) nothrow { memcpy(p, cast(void*)e, e.size); } // Generate an error message when this exception is not caught void generateUncaughtError(ThrownExceptionExp tee) { UnionExp ue = void; Expression e = resolveSlice((*tee.thrown.value.elements)[0], &ue); StringExp se = e.toStringExp(); error(tee.thrown.loc, "uncaught CTFE exception `%s(%s)`", tee.thrown.type.toChars(), se ? se.toChars() : e.toChars()); /* Also give the line where the throw statement was. We won't have it * in the case where the ThrowStatement is generated internally * (eg, in ScopeStatement) */ if (tee.loc.isValid() && !tee.loc.equals(tee.thrown.loc)) .errorSupplemental(tee.loc, "thrown from here"); } /************************* * Same as getFieldIndex, but checks for a direct match with the VarDeclaration * Returns: * index of the field, or -1 if not found */ int findFieldIndexByName(const StructDeclaration sd, const VarDeclaration v) pure @safe nothrow { foreach (i, field; sd.fields) { if (field == v) return cast(int)i; } return -1; } // True if 'e' is CTFEExp::cantexp, or an exception bool exceptionOrCantInterpret(const Expression e) @safe nothrow { return e && (e.op == EXP.cantExpression || e.op == EXP.thrownException || e.op == EXP.showCtfeContext); } /************** Aggregate literals (AA/string/array/struct) ******************/ // Given expr, which evaluates to an array/AA/string literal, // return true if it needs to be copied bool needToCopyLiteral(const Expression expr) nothrow { Expression e = cast()expr; for (;;) { switch (e.op) { case EXP.arrayLiteral: return e.isArrayLiteralExp().ownedByCtfe == OwnedBy.code; case EXP.assocArrayLiteral: return e.isAssocArrayLiteralExp().ownedByCtfe == OwnedBy.code; case EXP.structLiteral: return e.isStructLiteralExp().ownedByCtfe == OwnedBy.code; case EXP.string_: case EXP.this_: case EXP.variable: return false; case EXP.assign: return false; case EXP.index: case EXP.dotVariable: case EXP.slice: case EXP.cast_: e = e.isUnaExp().e1; continue; case EXP.concatenate: return needToCopyLiteral(e.isBinExp().e1) || needToCopyLiteral(e.isBinExp().e2); case EXP.concatenateAssign: case EXP.concatenateElemAssign: case EXP.concatenateDcharAssign: e = e.isBinExp().e2; continue; default: return false; } } } private Expressions* copyLiteralArray(Expressions* oldelems, Expression basis = null) { if (!oldelems) return oldelems; incArrayAllocs(); auto newelems = new Expressions(oldelems.length); foreach (i, el; *oldelems) { (*newelems)[i] = copyLiteral(el ? el : basis).copy(); } return newelems; } // Make a copy of the ArrayLiteral, AALiteral, String, or StructLiteral. // This value will be used for in-place modification. UnionExp copyLiteral(Expression e) { UnionExp ue = void; if (auto se = e.isStringExp()) // syntaxCopy doesn't make a copy for StringExp! { char* s = cast(char*)mem.xcalloc(se.len + 1, se.sz); const slice = se.peekData(); memcpy(s, slice.ptr, slice.length); emplaceExp!(StringExp)(&ue, se.loc, s[0 .. se.len * se.sz], se.len, se.sz); StringExp se2 = ue.exp().isStringExp(); se2.committed = se.committed; se2.postfix = se.postfix; se2.type = se.type; se2.ownedByCtfe = OwnedBy.ctfe; return ue; } if (auto ale = e.isArrayLiteralExp()) { auto elements = copyLiteralArray(ale.elements, ale.basis); emplaceExp!(ArrayLiteralExp)(&ue, e.loc, e.type, elements); ArrayLiteralExp r = ue.exp().isArrayLiteralExp(); r.ownedByCtfe = OwnedBy.ctfe; return ue; } if (auto aae = e.isAssocArrayLiteralExp()) { emplaceExp!(AssocArrayLiteralExp)(&ue, aae.loc, copyLiteralArray(aae.keys), copyLiteralArray(aae.values)); AssocArrayLiteralExp r = ue.exp().isAssocArrayLiteralExp(); r.type = aae.type; r.lowering = aae.lowering; r.ownedByCtfe = OwnedBy.ctfe; return ue; } if (auto sle = e.isStructLiteralExp()) { /* syntaxCopy doesn't work for struct literals, because of a nasty special * case: block assignment is permitted inside struct literals, eg, * an int[4] array can be initialized with a single int. */ auto oldelems = sle.elements; auto newelems = new Expressions(oldelems.length); foreach (i, ref el; *newelems) { // We need the struct definition to detect block assignment auto v = sle.sd.fields[i]; auto m = (*oldelems)[i]; // If it is a void assignment, use the default initializer if (!m) m = voidInitLiteral(v.type, v).copy(); if (v.type.ty == Tarray || v.type.ty == Taarray) { // Don't have to copy array references } else { // Buzilla 15681: Copy the source element always. m = copyLiteral(m).copy(); // Block assignment from inside struct literals if (v.type.ty != m.type.ty && v.type.ty == Tsarray) { auto tsa = v.type.isTypeSArray(); auto len = cast(size_t)tsa.dim.toInteger(); m = createBlockDuplicatedArrayLiteral(&ue, e.loc, v.type, m, len); if (m == ue.exp()) m = ue.copy(); } } el = m; } emplaceExp!(StructLiteralExp)(&ue, e.loc, sle.sd, newelems, sle.stype); auto r = ue.exp().isStructLiteralExp(); r.type = e.type; r.ownedByCtfe = OwnedBy.ctfe; r.origin = sle.origin; return ue; } switch(e.op) { case EXP.function_: case EXP.delegate_: case EXP.symbolOffset: case EXP.null_: case EXP.variable: case EXP.dotVariable: case EXP.int64: case EXP.float64: case EXP.complex80: case EXP.void_: case EXP.vector: case EXP.typeid_: // Simple value types // Keep e1 for DelegateExp and DotVarExp emplaceExp!(UnionExp)(&ue, e); Expression r = ue.exp(); r.type = e.type; return ue; default: break; } if (auto se = e.isSliceExp()) { if (se.type.toBasetype().ty == Tsarray) { // same with resolveSlice() if (se.e1.op == EXP.null_) { emplaceExp!(NullExp)(&ue, se.loc, se.type); return ue; } ue = Slice(se.type, se.e1, se.lwr, se.upr); auto r = ue.exp().isArrayLiteralExp(); r.elements = copyLiteralArray(r.elements); r.ownedByCtfe = OwnedBy.ctfe; return ue; } else { // Array slices only do a shallow copy emplaceExp!(SliceExp)(&ue, e.loc, se.e1, se.lwr, se.upr); Expression r = ue.exp(); r.type = e.type; return ue; } } if (isPointer(e.type)) { // For pointers, we only do a shallow copy. if (auto ae = e.isAddrExp()) emplaceExp!(AddrExp)(&ue, e.loc, ae.e1); else if (auto ie = e.isIndexExp()) emplaceExp!(IndexExp)(&ue, e.loc, ie.e1, ie.e2); else if (auto dve = e.isDotVarExp()) { emplaceExp!(DotVarExp)(&ue, e.loc, dve.e1, dve.var, dve.hasOverloads); } else assert(0); Expression r = ue.exp(); r.type = e.type; return ue; } if (auto cre = e.isClassReferenceExp()) { emplaceExp!(ClassReferenceExp)(&ue, e.loc, cre.value, e.type); return ue; } if (e.op == EXP.error) { emplaceExp!(UnionExp)(&ue, e); return ue; } error(e.loc, "CTFE internal error: literal `%s`", e.toChars()); assert(0); } /* Deal with type painting. * Type painting is a major nuisance: we can't just set * e.type = type, because that would change the original literal. * But, we can't simply copy the literal either, because that would change * the values of any pointers. */ Expression paintTypeOntoLiteral(Type type, Expression lit) { if (lit.type.equals(type)) return lit; return paintTypeOntoLiteralCopy(type, lit).copy(); } Expression paintTypeOntoLiteral(UnionExp* pue, Type type, Expression lit) { if (lit.type.equals(type)) return lit; *pue = paintTypeOntoLiteralCopy(type, lit); return pue.exp(); } private UnionExp paintTypeOntoLiteralCopy(Type type, Expression lit) { UnionExp ue; if (lit.type.equals(type)) { emplaceExp!(UnionExp)(&ue, lit); return ue; } // If it is a cast to inout, retain the original type of the referenced part. if (type.hasWild()) { emplaceExp!(UnionExp)(&ue, lit); ue.exp().type = type; return ue; } if (auto se = lit.isSliceExp()) { emplaceExp!(SliceExp)(&ue, lit.loc, se.e1, se.lwr, se.upr); } else if (auto ie = lit.isIndexExp()) { emplaceExp!(IndexExp)(&ue, lit.loc, ie.e1, ie.e2); } else if (lit.op == EXP.arrayLiteral) { emplaceExp!(SliceExp)(&ue, lit.loc, lit, ctfeEmplaceExp!IntegerExp(Loc.initial, 0, Type.tsize_t), ArrayLength(Type.tsize_t, lit).copy()); } else if (lit.op == EXP.string_) { // For strings, we need to introduce another level of indirection emplaceExp!(SliceExp)(&ue, lit.loc, lit, ctfeEmplaceExp!IntegerExp(Loc.initial, 0, Type.tsize_t), ArrayLength(Type.tsize_t, lit).copy()); } else if (auto aae = lit.isAssocArrayLiteralExp()) { // TODO: we should be creating a reference to this AAExp, not // just a ref to the keys and values. OwnedBy wasOwned = aae.ownedByCtfe; emplaceExp!(AssocArrayLiteralExp)(&ue, lit.loc, aae.keys, aae.values); aae = ue.exp().isAssocArrayLiteralExp(); aae.ownedByCtfe = wasOwned; } else { // Can't type paint from struct to struct*; this needs another // level of indirection if (lit.op == EXP.structLiteral && isPointer(type)) error(lit.loc, "CTFE internal error: painting `%s`", type.toChars()); ue = copyLiteral(lit); } ue.exp().type = type; return ue; } /************************************* * If e is a SliceExp, constant fold it. * Params: * e = expression to resolve * pue = if not null, store resulting expression here * Returns: * resulting expression */ Expression resolveSlice(Expression e, UnionExp* pue = null) { SliceExp se = e.isSliceExp(); if (!se) return e; if (se.e1.op == EXP.null_) return se.e1; if (pue) { *pue = Slice(e.type, se.e1, se.lwr, se.upr); return pue.exp(); } else return Slice(e.type, se.e1, se.lwr, se.upr).copy(); } /* Determine the array length, without interpreting it. * e must be an array literal, or a slice * It's very wasteful to resolve the slice when we only * need the length. */ uinteger_t resolveArrayLength(Expression e) { switch (e.op) { case EXP.vector: return e.isVectorExp().dim; case EXP.null_: return 0; case EXP.slice: { auto se = e.isSliceExp(); const ilo = se.lwr.toInteger(); const iup = se.upr.toInteger(); return iup - ilo; } case EXP.string_: return e.isStringExp().len; case EXP.arrayLiteral: { const ale = e.isArrayLiteralExp(); return ale.elements ? ale.elements.length : 0; } case EXP.assocArrayLiteral: { return e.isAssocArrayLiteralExp().keys.length; } default: assert(0); } } /****************************** * Helper for NewExp * Create an array literal consisting of 'elem' duplicated 'dim' times. * Params: * pue = where to store result * loc = source location where the interpretation occurs * type = target type of the result * elem = the source of array element, it will be owned by the result * dim = element number of the result * Returns: * Constructed ArrayLiteralExp */ ArrayLiteralExp createBlockDuplicatedArrayLiteral(UnionExp* pue, const ref Loc loc, Type type, Expression elem, size_t dim) { if (type.ty == Tsarray && type.nextOf().ty == Tsarray && elem.type.ty != Tsarray) { // If it is a multidimensional array literal, do it recursively auto tsa = type.nextOf().isTypeSArray(); const len = cast(size_t)tsa.dim.toInteger(); elem = createBlockDuplicatedArrayLiteral(pue, loc, type.nextOf(), elem, len); if (elem == pue.exp()) elem = pue.copy(); } // Buzilla 15681 const tb = elem.type.toBasetype(); const mustCopy = tb.ty == Tstruct || tb.ty == Tsarray; auto elements = new Expressions(dim); foreach (i, ref el; *elements) { el = mustCopy && i ? copyLiteral(elem).copy() : elem; } emplaceExp!(ArrayLiteralExp)(pue, loc, type, elements); auto ale = pue.exp().isArrayLiteralExp(); ale.ownedByCtfe = OwnedBy.ctfe; return ale; } /****************************** * Helper for NewExp * Create a string literal consisting of 'value' duplicated 'dim' times. */ StringExp createBlockDuplicatedStringLiteral(UnionExp* pue, const ref Loc loc, Type type, dchar value, size_t dim, ubyte sz) { auto s = cast(char*)mem.xcalloc(dim, sz); foreach (elemi; 0 .. dim) { switch (sz) { case 1: s[elemi] = cast(char)value; break; case 2: (cast(wchar*)s)[elemi] = cast(wchar)value; break; case 4: (cast(dchar*)s)[elemi] = value; break; case 8: (cast(ulong*)s)[elemi] = value; break; default: assert(0); } } emplaceExp!(StringExp)(pue, loc, s[0 .. dim * sz], dim, sz); auto se = pue.exp().isStringExp(); se.type = type; se.committed = true; se.ownedByCtfe = OwnedBy.ctfe; return se; } // Return true if t is an AA bool isAssocArray(Type t) { return t.toBasetype().isTypeAArray() !is null; } // Given a template AA type, extract the corresponding built-in AA type TypeAArray toBuiltinAAType(Type t) { return t.toBasetype().isTypeAArray(); } /************** TypeInfo operations ************************************/ // Return true if type is TypeInfo_Class bool isTypeInfo_Class(const Type type) nothrow { auto tc = cast()type.isTypeClass(); return tc && (Type.dtypeinfo == tc.sym || Type.dtypeinfo.isBaseOf(tc.sym, null)); } /************** Pointer operations ************************************/ // Return true if t is a pointer (not a function pointer) bool isPointer(Type t) { Type tb = t.toBasetype(); return tb.ty == Tpointer && tb.nextOf().ty != Tfunction; } // For CTFE only. Returns true if 'e' is true or a non-null pointer. bool isTrueBool(Expression e) { return e.toBool().hasValue(true) || ((e.type.ty == Tpointer || e.type.ty == Tclass) && e.op != EXP.null_); } /* Is it safe to convert from srcPointee* to destPointee* ? * srcPointee is the genuine type (never void). * destPointee may be void. */ bool isSafePointerCast(Type srcPointee, Type destPointee) { // It's safe to cast S** to D** if it's OK to cast S* to D* while (srcPointee.ty == Tpointer && destPointee.ty == Tpointer) { srcPointee = srcPointee.nextOf(); destPointee = destPointee.nextOf(); } // It's OK if both are the same (modulo const) if (srcPointee.constConv(destPointee)) return true; // It's ok to cast from/to shared because CTFE is single threaded anyways if (srcPointee.unSharedOf() == destPointee.unSharedOf()) return true; // It's OK if function pointers differ only in safe/pure/nothrow if (srcPointee.ty == Tfunction && destPointee.ty == Tfunction) { return srcPointee.covariant(destPointee) == Covariant.yes || destPointee.covariant(srcPointee) == Covariant.yes; } // it's OK to cast to void* if (destPointee.ty == Tvoid) return true; // It's OK to cast from V[K] to void* if (srcPointee.ty == Taarray && destPointee == Type.tvoidptr) return true; // It's OK if they are the same size (static array of) integers, eg: // int* --> uint* // int[5][] --> uint[5][] if (srcPointee.ty == Tsarray && destPointee.ty == Tsarray) { if (srcPointee.size() != destPointee.size()) return false; srcPointee = srcPointee.baseElemOf(); destPointee = destPointee.baseElemOf(); } return srcPointee.isintegral() && destPointee.isintegral() && srcPointee.size() == destPointee.size(); } Expression getAggregateFromPointer(Expression e, dinteger_t* ofs) { *ofs = 0; if (auto ae = e.isAddrExp()) e = ae.e1; if (auto soe = e.isSymOffExp()) *ofs = soe.offset; if (auto dve = e.isDotVarExp()) { auto ex = dve.e1; const v = dve.var.isVarDeclaration(); assert(v); StructLiteralExp se = (ex.op == EXP.classReference) ? ex.isClassReferenceExp().value : ex.isStructLiteralExp(); // We can't use getField, because it makes a copy const i = (ex.op == EXP.classReference) ? ex.isClassReferenceExp().getFieldIndex(e.type, v.offset) : se.getFieldIndex(e.type, v.offset); e = (*se.elements)[i]; } if (auto ie = e.isIndexExp()) { // Note that each AA element is part of its own memory block if ((ie.e1.type.ty == Tarray || ie.e1.type.ty == Tsarray || ie.e1.op == EXP.string_ || ie.e1.op == EXP.arrayLiteral) && ie.e2.op == EXP.int64) { *ofs = ie.e2.toInteger(); return ie.e1; } } if (auto se = e.isSliceExp()) { if (se && e.type.toBasetype().ty == Tsarray && (se.e1.type.ty == Tarray || se.e1.type.ty == Tsarray || se.e1.op == EXP.string_ || se.e1.op == EXP.arrayLiteral) && se.lwr.op == EXP.int64) { *ofs = se.lwr.toInteger(); return se.e1; } } // It can be a `null` disguised as a cast, e.g. `cast(void*)0`. if (auto ie = e.isIntegerExp()) if (ie.type.ty == Tpointer && ie.getInteger() == 0) return new NullExp(ie.loc, e.type.nextOf()); // Those casts are invalid, but let the rest of the code handle it, // as it could be something like `x !is null`, which doesn't need // to dereference the pointer, even if the pointer is `cast(void*)420`. return e; } /** Return true if agg1 and agg2 are pointers to the same memory block */ bool pointToSameMemoryBlock(Expression agg1, Expression agg2) { if (agg1 == agg2) return true; // For integers cast to pointers, we regard them as non-comparable // unless they are identical. (This may be overly strict). if (agg1.op == EXP.int64 && agg2.op == EXP.int64 && agg1.toInteger() == agg2.toInteger()) { return true; } // Note that type painting can occur with VarExp, so we // must compare the variables being pointed to. if (agg1.op == EXP.variable && agg2.op == EXP.variable && agg1.isVarExp().var == agg2.isVarExp().var) { return true; } if (agg1.op == EXP.symbolOffset && agg2.op == EXP.symbolOffset && agg1.isSymOffExp().var == agg2.isSymOffExp().var) { return true; } return false; } // return e1 - e2 as an integer, or error if not possible Expression pointerDifference(UnionExp* pue, const ref Loc loc, Type type, Expression e1, Expression e2) { dinteger_t ofs1, ofs2; Expression agg1 = getAggregateFromPointer(e1, &ofs1); Expression agg2 = getAggregateFromPointer(e2, &ofs2); if (agg1 == agg2) { Type pointee = agg1.type.nextOf(); const sz = pointee.size(); emplaceExp!(IntegerExp)(pue, loc, (ofs1 - ofs2) * sz, type); } else if (agg1.op == EXP.string_ && agg2.op == EXP.string_ && agg1.isStringExp().peekString().ptr == agg2.isStringExp().peekString().ptr) { Type pointee = agg1.type.nextOf(); const sz = pointee.size(); emplaceExp!(IntegerExp)(pue, loc, (ofs1 - ofs2) * sz, type); } else if (agg1.op == EXP.symbolOffset && agg2.op == EXP.symbolOffset && agg1.isSymOffExp().var == agg2.isSymOffExp().var) { emplaceExp!(IntegerExp)(pue, loc, ofs1 - ofs2, type); } else { error(loc, "`%s - %s` cannot be interpreted at compile time: cannot subtract pointers to two different memory blocks", e1.toChars(), e2.toChars()); emplaceExp!(CTFEExp)(pue, EXP.cantExpression); } return pue.exp(); } // Return eptr op e2, where eptr is a pointer, e2 is an integer, // and op is EXP.add or EXP.min Expression pointerArithmetic(UnionExp* pue, const ref Loc loc, EXP op, Type type, Expression eptr, Expression e2) { if (eptr.type.nextOf().ty == Tvoid) { error(loc, "cannot perform arithmetic on `void*` pointers at compile time"); Lcant: emplaceExp!(CTFEExp)(pue, EXP.cantExpression); return pue.exp(); } if (eptr.op == EXP.address) eptr = eptr.isAddrExp().e1; dinteger_t ofs1; Expression agg1 = getAggregateFromPointer(eptr, &ofs1); if (agg1.op == EXP.symbolOffset) { if (agg1.isSymOffExp().var.type.ty != Tsarray) { error(loc, "cannot perform pointer arithmetic on arrays of unknown length at compile time"); goto Lcant; } } else if (agg1.op != EXP.string_ && agg1.op != EXP.arrayLiteral) { error(loc, "cannot perform pointer arithmetic on non-arrays at compile time"); goto Lcant; } dinteger_t ofs2 = e2.toInteger(); Type pointee = agg1.type.toBasetype().nextOf(); dinteger_t sz = pointee.size(); sinteger_t indx; dinteger_t len; if (auto soe = agg1.isSymOffExp()) { indx = ofs1 / sz; len = soe.var.type.isTypeSArray().dim.toInteger(); } else { Expression dollar = ArrayLength(Type.tsize_t, agg1).copy(); assert(!CTFEExp.isCantExp(dollar)); indx = ofs1; len = dollar.toInteger(); } if (op == EXP.add || op == EXP.addAssign || op == EXP.plusPlus) indx += ofs2 / sz; else if (op == EXP.min || op == EXP.minAssign || op == EXP.minusMinus) indx -= ofs2 / sz; else { error(loc, "CTFE internal error: bad pointer operation"); goto Lcant; } if (indx < 0 || len < indx) { error(loc, "cannot assign pointer to index %lld inside memory block `[0..%lld]`", indx, len); goto Lcant; } if (agg1.op == EXP.symbolOffset) { emplaceExp!(SymOffExp)(pue, loc, agg1.isSymOffExp().var, indx * sz); SymOffExp se = pue.exp().isSymOffExp(); se.type = type; return pue.exp(); } if (agg1.op != EXP.arrayLiteral && agg1.op != EXP.string_) { error(loc, "CTFE internal error: pointer arithmetic `%s`", agg1.toChars()); goto Lcant; } if (auto tsa = eptr.type.toBasetype().isTypeSArray()) { dinteger_t dim = tsa.dim.toInteger(); // Create a CTFE pointer &agg1[indx .. indx+dim] auto se = ctfeEmplaceExp!SliceExp(loc, agg1, ctfeEmplaceExp!IntegerExp(loc, indx, Type.tsize_t), ctfeEmplaceExp!IntegerExp(loc, indx + dim, Type.tsize_t)); se.type = type.toBasetype().nextOf(); emplaceExp!(AddrExp)(pue, loc, se); pue.exp().type = type; return pue.exp(); } // Create a CTFE pointer &agg1[indx] auto ofs = ctfeEmplaceExp!IntegerExp(loc, indx, Type.tsize_t); Expression ie = ctfeEmplaceExp!IndexExp(loc, agg1, ofs); ie.type = type.toBasetype().nextOf(); // https://issues.dlang.org/show_bug.cgi?id=13992 emplaceExp!(AddrExp)(pue, loc, ie); pue.exp().type = type; return pue.exp(); } // Return 1 if true, 0 if false // -1 if comparison is illegal because they point to non-comparable memory blocks int comparePointers(EXP op, Expression agg1, dinteger_t ofs1, Expression agg2, dinteger_t ofs2) { if (pointToSameMemoryBlock(agg1, agg2)) { int n; switch (op) { case EXP.lessThan: n = (ofs1 < ofs2); break; case EXP.lessOrEqual: n = (ofs1 <= ofs2); break; case EXP.greaterThan: n = (ofs1 > ofs2); break; case EXP.greaterOrEqual: n = (ofs1 >= ofs2); break; case EXP.identity: case EXP.equal: n = (ofs1 == ofs2); break; case EXP.notIdentity: case EXP.notEqual: n = (ofs1 != ofs2); break; default: assert(0); } return n; } const null1 = (agg1.op == EXP.null_); const null2 = (agg2.op == EXP.null_); int cmp; if (null1 || null2) { switch (op) { case EXP.lessThan: cmp = null1 && !null2; break; case EXP.greaterThan: cmp = !null1 && null2; break; case EXP.lessOrEqual: cmp = null1; break; case EXP.greaterOrEqual: cmp = null2; break; case EXP.identity: case EXP.equal: case EXP.notIdentity: // 'cmp' gets inverted below case EXP.notEqual: cmp = (null1 == null2); break; default: assert(0); } } else { switch (op) { case EXP.identity: case EXP.equal: case EXP.notIdentity: // 'cmp' gets inverted below case EXP.notEqual: cmp = 0; break; default: return -1; // memory blocks are different } } if (op == EXP.notIdentity || op == EXP.notEqual) cmp ^= 1; return cmp; } // True if conversion from type 'from' to 'to' involves a reinterpret_cast // floating point -> integer or integer -> floating point bool isFloatIntPaint(Type to, Type from) { return from.size() == to.size() && (from.isintegral() && to.isfloating() || from.isfloating() && to.isintegral()); } // Reinterpret float/int value 'fromVal' as a float/integer of type 'to'. Expression paintFloatInt(UnionExp* pue, Expression fromVal, Type to) { if (exceptionOrCantInterpret(fromVal)) return fromVal; assert(to.size() == 4 || to.size() == 8); return Compiler.paintAsType(pue, fromVal, to); } /******** Constant folding, with support for CTFE ***************************/ /// Return true if non-pointer expression e can be compared /// with >,is, ==, etc, using ctfeCmp, ctfeEqual, ctfeIdentity bool isCtfeComparable(Expression e) { if (e.op == EXP.slice) e = e.isSliceExp().e1; if (e.isConst() != 1) { if (e.op == EXP.null_ || e.op == EXP.string_ || e.op == EXP.function_ || e.op == EXP.delegate_ || e.op == EXP.arrayLiteral || e.op == EXP.structLiteral || e.op == EXP.assocArrayLiteral || e.op == EXP.classReference) { return true; } // https://issues.dlang.org/show_bug.cgi?id=14123 // TypeInfo object is comparable in CTFE if (e.op == EXP.typeid_) return true; return false; } return true; } /// Map EXP comparison ops private bool numCmp(N)(EXP op, N n1, N n2) nothrow { switch (op) { case EXP.lessThan: return n1 < n2; case EXP.lessOrEqual: return n1 <= n2; case EXP.greaterThan: return n1 > n2; case EXP.greaterOrEqual: return n1 >= n2; default: assert(0); } } /// Returns cmp OP 0; where OP is ==, !=, <, >=, etc. Result is 0 or 1 bool specificCmp(EXP op, int rawCmp) @safe nothrow { return numCmp!int(op, rawCmp, 0); } /// Returns e1 OP e2; where OP is ==, !=, <, >=, etc. Result is 0 or 1 bool intUnsignedCmp(EXP op, dinteger_t n1, dinteger_t n2) @safe nothrow { return numCmp!dinteger_t(op, n1, n2); } /// Returns e1 OP e2; where OP is ==, !=, <, >=, etc. Result is 0 or 1 bool intSignedCmp(EXP op, sinteger_t n1, sinteger_t n2) @safe nothrow { return numCmp!sinteger_t(op, n1, n2); } /// Returns e1 OP e2; where OP is ==, !=, <, >=, etc. Result is 0 or 1 bool realCmp(EXP op, real_t r1, real_t r2) @safe nothrow { // Don't rely on compiler, handle NAN arguments separately if (CTFloat.isNaN(r1) || CTFloat.isNaN(r2)) // if unordered { switch (op) { case EXP.lessThan: case EXP.lessOrEqual: case EXP.greaterThan: case EXP.greaterOrEqual: return false; default: assert(0); } } else { return numCmp!real_t(op, r1, r2); } } /* Conceptually the same as memcmp(e1, e2). * e1 and e2 may be strings, arrayliterals, or slices. * For string types, return <0 if e1 < e2, 0 if e1==e2, >0 if e1 > e2. * For all other types, return 0 if e1 == e2, !=0 if e1 != e2. * Returns: * -1,0,1 */ private int ctfeCmpArrays(const ref Loc loc, Expression e1, Expression e2, uinteger_t len) { // Resolve slices, if necessary uinteger_t lo1 = 0; uinteger_t lo2 = 0; Expression x1 = e1; if (auto sle1 = x1.isSliceExp()) { lo1 = sle1.lwr.toInteger(); x1 = sle1.e1; } auto se1 = x1.isStringExp(); auto ae1 = x1.isArrayLiteralExp(); Expression x2 = e2; if (auto sle2 = x2.isSliceExp()) { lo2 = sle2.lwr.toInteger(); x2 = sle2.e1; } auto se2 = x2.isStringExp(); auto ae2 = x2.isArrayLiteralExp(); // Now both must be either EXP.arrayLiteral or EXP.string_ if (se1 && se2) return sliceCmpStringWithString(se1, se2, cast(size_t)lo1, cast(size_t)lo2, cast(size_t)len); if (se1 && ae2) return sliceCmpStringWithArray(se1, ae2, cast(size_t)lo1, cast(size_t)lo2, cast(size_t)len); if (se2 && ae1) return -sliceCmpStringWithArray(se2, ae1, cast(size_t)lo2, cast(size_t)lo1, cast(size_t)len); assert(ae1 && ae2); // Comparing two array literals. This case is potentially recursive. // If they aren't strings, we just need an equality check rather than // a full cmp. const bool needCmp = ae1.type.nextOf().isintegral(); foreach (size_t i; 0 .. cast(size_t)len) { Expression ee1 = (*ae1.elements)[cast(size_t)(lo1 + i)]; Expression ee2 = (*ae2.elements)[cast(size_t)(lo2 + i)]; if (needCmp) { const sinteger_t c = ee1.toInteger() - ee2.toInteger(); if (c > 0) return 1; if (c < 0) return -1; } else { if (ctfeRawCmp(loc, ee1, ee2)) return 1; } } return 0; } /* Given a delegate expression e, return .funcptr. * If e is NullExp, return NULL. */ private FuncDeclaration funcptrOf(Expression e) @safe nothrow { assert(e.type.ty == Tdelegate); if (auto de = e.isDelegateExp()) return de.func; if (auto fe = e.isFuncExp()) return fe.fd; assert(e.op == EXP.null_); return null; } private bool isArray(const Expression e) @safe nothrow { return e.op == EXP.arrayLiteral || e.op == EXP.string_ || e.op == EXP.slice || e.op == EXP.null_; } /***** * Params: * loc = source file location * e1 = left operand * e2 = right operand * identity = true for `is` identity comparisons * Returns: * For strings, return <0 if e1 < e2, 0 if e1==e2, >0 if e1 > e2. * For all other types, return 0 if e1 == e2, !=0 if e1 != e2. */ private int ctfeRawCmp(const ref Loc loc, Expression e1, Expression e2, bool identity = false) { if (e1.op == EXP.classReference || e2.op == EXP.classReference) { if (e1.op == EXP.classReference && e2.op == EXP.classReference && e1.isClassReferenceExp().value == e2.isClassReferenceExp().value) return 0; return 1; } if (e1.op == EXP.typeid_ && e2.op == EXP.typeid_) { // printf("e1: %s\n", e1.toChars()); // printf("e2: %s\n", e2.toChars()); Type t1 = isType(e1.isTypeidExp().obj); Type t2 = isType(e2.isTypeidExp().obj); assert(t1); assert(t2); return t1 != t2; } // null == null, regardless of type if (e1.op == EXP.null_ && e2.op == EXP.null_) return 0; if (e1.type.ty == Tpointer && e2.type.ty == Tpointer) { // Can only be an equality test. dinteger_t ofs1, ofs2; Expression agg1 = getAggregateFromPointer(e1, &ofs1); Expression agg2 = getAggregateFromPointer(e2, &ofs2); if ((agg1 == agg2) || (agg1.op == EXP.variable && agg2.op == EXP.variable && agg1.isVarExp().var == agg2.isVarExp().var)) { if (ofs1 == ofs2) return 0; } return 1; } if (e1.type.ty == Tdelegate && e2.type.ty == Tdelegate) { // If .funcptr isn't the same, they are not equal if (funcptrOf(e1) != funcptrOf(e2)) return 1; // If both are delegate literals, assume they have the // same closure pointer. TODO: We don't support closures yet! if (e1.op == EXP.function_ && e2.op == EXP.function_) return 0; assert(e1.op == EXP.delegate_ && e2.op == EXP.delegate_); // Same .funcptr. Do they have the same .ptr? Expression ptr1 = e1.isDelegateExp().e1; Expression ptr2 = e2.isDelegateExp().e1; dinteger_t ofs1, ofs2; Expression agg1 = getAggregateFromPointer(ptr1, &ofs1); Expression agg2 = getAggregateFromPointer(ptr2, &ofs2); // If they are EXP.variable, it means they are FuncDeclarations if ((agg1 == agg2 && ofs1 == ofs2) || (agg1.op == EXP.variable && agg2.op == EXP.variable && agg1.isVarExp().var == agg2.isVarExp().var)) { return 0; } return 1; } if (isArray(e1) && isArray(e2)) { const uinteger_t len1 = resolveArrayLength(e1); const uinteger_t len2 = resolveArrayLength(e2); // workaround for dmc optimizer bug calculating wrong len for // uinteger_t len = (len1 < len2 ? len1 : len2); // if (len == 0) ... if (len1 > 0 && len2 > 0) { const uinteger_t len = (len1 < len2 ? len1 : len2); const int res = ctfeCmpArrays(loc, e1, e2, len); if (res != 0) return res; } return cast(int)(len1 - len2); } if (e1.type.isintegral()) { return e1.toInteger() != e2.toInteger(); } if (identity && e1.type.isfloating()) return !e1.isIdentical(e2); if (e1.type.isreal() || e1.type.isimaginary()) { real_t r1 = e1.type.isreal() ? e1.toReal() : e1.toImaginary(); real_t r2 = e1.type.isreal() ? e2.toReal() : e2.toImaginary(); if (CTFloat.isNaN(r1) || CTFloat.isNaN(r2)) // if unordered { return 1; // they are not equal } else { return (r1 != r2); } } else if (e1.type.iscomplex()) { return e1.toComplex() != e2.toComplex(); } if (e1.op == EXP.structLiteral && e2.op == EXP.structLiteral) { StructLiteralExp es1 = e1.isStructLiteralExp(); StructLiteralExp es2 = e2.isStructLiteralExp(); // For structs, we only need to return 0 or 1 (< and > aren't legal). if (es1.sd != es2.sd) return 1; else if ((!es1.elements || !es1.elements.length) && (!es2.elements || !es2.elements.length)) return 0; // both arrays are empty else if (!es1.elements || !es2.elements) return 1; else if (es1.elements.length != es2.elements.length) return 1; else { foreach (size_t i; 0 .. es1.elements.length) { Expression ee1 = (*es1.elements)[i]; Expression ee2 = (*es2.elements)[i]; // https://issues.dlang.org/show_bug.cgi?id=16284 if (ee1.op == EXP.void_ && ee2.op == EXP.void_) // if both are VoidInitExp continue; if (ee1 == ee2) continue; if (!ee1 || !ee2) return 1; const int cmp = ctfeRawCmp(loc, ee1, ee2, identity); if (cmp) return 1; } return 0; // All elements are equal } } if (e1.op == EXP.assocArrayLiteral && e2.op == EXP.assocArrayLiteral) { AssocArrayLiteralExp es1 = e1.isAssocArrayLiteralExp(); AssocArrayLiteralExp es2 = e2.isAssocArrayLiteralExp(); size_t dim = es1.keys.length; if (es2.keys.length != dim) return 1; BitArray used; used.length = dim; foreach (size_t i; 0 .. dim) { Expression k1 = (*es1.keys)[i]; Expression v1 = (*es1.values)[i]; Expression v2 = null; foreach (size_t j; 0 .. dim) { if (used[j]) continue; Expression k2 = (*es2.keys)[j]; if (ctfeRawCmp(loc, k1, k2, identity)) continue; used[j] = true; v2 = (*es2.values)[j]; break; } if (!v2 || ctfeRawCmp(loc, v1, v2, identity)) { return 1; } } return 0; } else if (e1.op == EXP.assocArrayLiteral && e2.op == EXP.null_) { return e1.isAssocArrayLiteralExp.keys.length != 0; } else if (e1.op == EXP.null_ && e2.op == EXP.assocArrayLiteral) { return e2.isAssocArrayLiteralExp.keys.length != 0; } error(loc, "CTFE internal error: bad compare of `%s` and `%s`", e1.toChars(), e2.toChars()); assert(0); } /// Evaluate ==, !=. Resolves slices before comparing. Returns 0 or 1 bool ctfeEqual(const ref Loc loc, EXP op, Expression e1, Expression e2) { return !ctfeRawCmp(loc, e1, e2) ^ (op == EXP.notEqual); } /// Evaluate is, !is. Resolves slices before comparing. Returns 0 or 1 bool ctfeIdentity(const ref Loc loc, EXP op, Expression e1, Expression e2) { //printf("ctfeIdentity %s %s\n", e1.toChars(), e2.toChars()); //printf("ctfeIdentity op = '%s', e1 = %s %s, e2 = %s %s\n", EXPtoString(op).ptr, // EXPtoString(e1.op).ptr, e1.toChars(), EXPtoString(e2.op).ptr, e1.toChars()); bool cmp; if (e1.op == EXP.null_) { cmp = (e2.op == EXP.null_); } else if (e2.op == EXP.null_) { cmp = false; } else if (e1.op == EXP.symbolOffset && e2.op == EXP.symbolOffset) { SymOffExp es1 = e1.isSymOffExp(); SymOffExp es2 = e2.isSymOffExp(); cmp = (es1.var == es2.var && es1.offset == es2.offset); } else if (e1.type.isfloating()) cmp = e1.isIdentical(e2); else { cmp = !ctfeRawCmp(loc, e1, e2, true); } if (op == EXP.notIdentity || op == EXP.notEqual) cmp ^= true; return cmp; } /// Evaluate >,<=, etc. Resolves slices before comparing. Returns 0 or 1 bool ctfeCmp(const ref Loc loc, EXP op, Expression e1, Expression e2) { Type t1 = e1.type.toBasetype(); Type t2 = e2.type.toBasetype(); if (t1.isString() && t2.isString()) return specificCmp(op, ctfeRawCmp(loc, e1, e2)); else if (t1.isreal()) return realCmp(op, e1.toReal(), e2.toReal()); else if (t1.isimaginary()) return realCmp(op, e1.toImaginary(), e2.toImaginary()); else if (t1.isunsigned() || t2.isunsigned()) return intUnsignedCmp(op, e1.toInteger(), e2.toInteger()); else return intSignedCmp(op, e1.toInteger(), e2.toInteger()); } UnionExp ctfeCat(const ref Loc loc, Type type, Expression e1, Expression e2) { Type t1 = e1.type.toBasetype(); Type t2 = e2.type.toBasetype(); UnionExp ue; if (e2.op == EXP.string_ && e1.op == EXP.arrayLiteral && t1.nextOf().isintegral()) { // [chars] ~ string => string (only valid for CTFE) StringExp es1 = e2.isStringExp(); ArrayLiteralExp es2 = e1.isArrayLiteralExp(); const len = es1.len + es2.elements.length; const sz = es1.sz; void* s = mem.xmalloc((len + 1) * sz); const data1 = es1.peekData(); memcpy(cast(char*)s + sz * es2.elements.length, data1.ptr, data1.length); foreach (size_t i; 0 .. es2.elements.length) { Expression es2e = (*es2.elements)[i]; if (es2e.op != EXP.int64) { emplaceExp!(CTFEExp)(&ue, EXP.cantExpression); return ue; } dinteger_t v = es2e.toInteger(); Port.valcpy(cast(char*)s + i * sz, v, sz); } // Add terminating 0 memset(cast(char*)s + len * sz, 0, sz); emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz); StringExp es = ue.exp().isStringExp(); es.committed = false; es.type = type; return ue; } if (e1.op == EXP.string_ && e2.op == EXP.arrayLiteral && t2.nextOf().isintegral()) { // string ~ [chars] => string (only valid for CTFE) // Concatenate the strings StringExp es1 = e1.isStringExp(); ArrayLiteralExp es2 = e2.isArrayLiteralExp(); const len = es1.len + es2.elements.length; const sz = es1.sz; void* s = mem.xmalloc((len + 1) * sz); auto slice = es1.peekData(); memcpy(s, slice.ptr, slice.length); foreach (size_t i; 0 .. es2.elements.length) { Expression es2e = (*es2.elements)[i]; if (es2e.op != EXP.int64) { emplaceExp!(CTFEExp)(&ue, EXP.cantExpression); return ue; } const v = es2e.toInteger(); Port.valcpy(cast(char*)s + (es1.len + i) * sz, v, sz); } // Add terminating 0 memset(cast(char*)s + len * sz, 0, sz); emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz); StringExp es = ue.exp().isStringExp(); es.sz = sz; es.committed = false; //es1.committed; es.type = type; return ue; } if (e1.op == EXP.arrayLiteral && e2.op == EXP.arrayLiteral && t1.nextOf().equals(t2.nextOf())) { // [ e1 ] ~ [ e2 ] ---> [ e1, e2 ] ArrayLiteralExp es1 = e1.isArrayLiteralExp(); ArrayLiteralExp es2 = e2.isArrayLiteralExp(); emplaceExp!(ArrayLiteralExp)(&ue, es1.loc, type, copyLiteralArray(es1.elements)); es1 = ue.exp().isArrayLiteralExp(); es1.elements.insert(es1.elements.length, copyLiteralArray(es2.elements)); return ue; } if (e1.op == EXP.arrayLiteral && e2.op == EXP.null_ && t1.nextOf().equals(t2.nextOf())) { // [ e1 ] ~ null ----> [ e1 ].dup ue = paintTypeOntoLiteralCopy(type, copyLiteral(e1).copy()); return ue; } if (e1.op == EXP.null_ && e2.op == EXP.arrayLiteral && t1.nextOf().equals(t2.nextOf())) { // null ~ [ e2 ] ----> [ e2 ].dup ue = paintTypeOntoLiteralCopy(type, copyLiteral(e2).copy()); return ue; } ue = Cat(loc, type, e1, e2); return ue; } /* Given an AA literal 'ae', and a key 'e2': * Return ae[e2] if present, or NULL if not found. */ Expression findKeyInAA(const ref Loc loc, AssocArrayLiteralExp ae, Expression e2) { /* Search the keys backwards, in case there are duplicate keys */ for (size_t i = ae.keys.length; i;) { --i; Expression ekey = (*ae.keys)[i]; const int eq = ctfeEqual(loc, EXP.equal, ekey, e2); if (eq) { return (*ae.values)[i]; } } return null; } /* Same as for constfold.Index, except that it only works for static arrays, * dynamic arrays, and strings. We know that e1 is an * interpreted CTFE expression, so it cannot have side-effects. */ Expression ctfeIndex(UnionExp* pue, const ref Loc loc, Type type, Expression e1, uinteger_t indx) { //printf("ctfeIndex(e1 = %s)\n", e1.toChars()); assert(e1.type); if (auto es1 = e1.isStringExp()) { if (indx >= es1.len) { error(loc, "string index %llu is out of bounds `[0 .. %llu]`", indx, cast(ulong)es1.len); return CTFEExp.cantexp; } emplaceExp!IntegerExp(pue, loc, es1.getIndex(cast(size_t) indx), type); return pue.exp(); } if (auto ale = e1.isArrayLiteralExp()) { if (indx >= ale.elements.length) { error(loc, "array index %llu is out of bounds `%s[0 .. %llu]`", indx, e1.toChars(), cast(ulong)ale.elements.length); return CTFEExp.cantexp; } Expression e = (*ale.elements)[cast(size_t)indx]; return paintTypeOntoLiteral(pue, type, e); } assert(0); } Expression ctfeCast(UnionExp* pue, const ref Loc loc, Type type, Type to, Expression e, bool explicitCast = false) { Expression paint() { return paintTypeOntoLiteral(pue, to, e); } if (e.op == EXP.null_) return paint(); if (e.op == EXP.classReference) { // Disallow reinterpreting class casts. Do this by ensuring that // the original class can implicitly convert to the target class. // Also do not check 'alias this' for explicit cast expressions. auto tclass = e.isClassReferenceExp().originalClass().type.isTypeClass(); auto match = explicitCast ? tclass.implicitConvToWithoutAliasThis(to.mutableOf()) : tclass.implicitConvTo(to.mutableOf()); if (match) return paint(); else { emplaceExp!(NullExp)(pue, loc, to); return pue.exp(); } } // Allow TypeInfo type painting if (isTypeInfo_Class(e.type) && e.type.implicitConvTo(to)) return paint(); // Allow casting away const for struct literals if (e.op == EXP.structLiteral && e.type.toBasetype().castMod(0) == to.toBasetype().castMod(0)) return paint(); Expression r; if (e.type.equals(type) && type.equals(to)) { // necessary not to change e's address for pointer comparisons r = e; } else if (to.toBasetype().ty == Tarray && type.toBasetype().ty == Tarray && to.toBasetype().nextOf().size() == type.toBasetype().nextOf().size()) { // https://issues.dlang.org/show_bug.cgi?id=12495 // Array reinterpret casts: eg. string to immutable(ubyte)[] return paint(); } else { *pue = Cast(loc, type, to, e); r = pue.exp(); } if (CTFEExp.isCantExp(r)) error(loc, "cannot cast `%s` to `%s` at compile time", e.toChars(), to.toChars()); if (auto ae = e.isArrayLiteralExp()) ae.ownedByCtfe = OwnedBy.ctfe; if (auto se = e.isStringExp()) se.ownedByCtfe = OwnedBy.ctfe; return r; } /******** Assignment helper functions ***************************/ /* Set dest = src, where both dest and src are container value literals * (ie, struct literals, or static arrays (can be an array literal or a string)) * Assignment is recursively in-place. * Purpose: any reference to a member of 'dest' will remain valid after the * assignment. */ void assignInPlace(Expression dest, Expression src) { if (!(dest.op == EXP.structLiteral || dest.op == EXP.arrayLiteral || dest.op == EXP.string_)) { printf("invalid op %d %d\n", src.op, dest.op); assert(0); } Expressions* oldelems; Expressions* newelems; if (dest.op == EXP.structLiteral) { assert(dest.op == src.op); oldelems = dest.isStructLiteralExp().elements; newelems = src.isStructLiteralExp().elements; auto sd = dest.isStructLiteralExp().sd; const nfields = sd.nonHiddenFields(); const nvthis = sd.fields.length - nfields; if (nvthis && oldelems.length >= nfields && oldelems.length < newelems.length) foreach (_; 0 .. newelems.length - oldelems.length) oldelems.push(null); } else if (dest.op == EXP.arrayLiteral && src.op == EXP.arrayLiteral) { oldelems = dest.isArrayLiteralExp().elements; newelems = src.isArrayLiteralExp().elements; } else if (dest.op == EXP.string_ && src.op == EXP.string_) { sliceAssignStringFromString(dest.isStringExp(), src.isStringExp(), 0); return; } else if (dest.op == EXP.arrayLiteral && src.op == EXP.string_) { sliceAssignArrayLiteralFromString(dest.isArrayLiteralExp(), src.isStringExp(), 0); return; } else if (src.op == EXP.arrayLiteral && dest.op == EXP.string_) { sliceAssignStringFromArrayLiteral(dest.isStringExp(), src.isArrayLiteralExp(), 0); return; } else { printf("invalid op %d %d\n", src.op, dest.op); assert(0); } assert(oldelems.length == newelems.length); foreach (size_t i; 0 .. oldelems.length) { Expression e = (*newelems)[i]; Expression o = (*oldelems)[i]; if (e.op == EXP.structLiteral) { assert(o.op == e.op); assignInPlace(o, e); } else if (e.type.ty == Tsarray && e.op != EXP.void_ && o.type.ty == Tsarray) { assignInPlace(o, e); } else { (*oldelems)[i] = (*newelems)[i]; } } } // Given an AA literal aae, set aae[index] = newval and return newval. Expression assignAssocArrayElement(const ref Loc loc, AssocArrayLiteralExp aae, Expression index, Expression newval) { /* Create new associative array literal reflecting updated key/value */ Expressions* keysx = aae.keys; Expressions* valuesx = aae.values; int updated = 0; for (size_t j = valuesx.length; j;) { j--; Expression ekey = (*aae.keys)[j]; int eq = ctfeEqual(loc, EXP.equal, ekey, index); if (eq) { (*valuesx)[j] = newval; updated = 1; } } if (!updated) { // Append index/newval to keysx[]/valuesx[] valuesx.push(newval); keysx.push(index); } return newval; } /// Given array literal oldval of type ArrayLiteralExp or StringExp, of length /// oldlen, change its length to newlen. If the newlen is longer than oldlen, /// all new elements will be set to the default initializer for the element type. Expression changeArrayLiteralLength(UnionExp* pue, const ref Loc loc, TypeArray arrayType, Expression oldval, size_t oldlen, size_t newlen) { Type elemType = arrayType.next; assert(elemType); Expression defaultElem = elemType.defaultInitLiteral(loc); auto elements = new Expressions(newlen); // Resolve slices size_t indxlo = 0; if (oldval.op == EXP.slice) { indxlo = cast(size_t)oldval.isSliceExp().lwr.toInteger(); oldval = oldval.isSliceExp().e1; } size_t copylen = oldlen < newlen ? oldlen : newlen; if (oldval.op == EXP.string_) { StringExp oldse = oldval.isStringExp(); void* s = mem.xcalloc(newlen + 1, oldse.sz); const data = oldse.peekData(); memcpy(s, data.ptr, copylen * oldse.sz); const defaultValue = cast(ulong)defaultElem.toInteger(); foreach (size_t elemi; copylen .. newlen) { switch (oldse.sz) { case 1: (cast(char*)s)[cast(size_t)(indxlo + elemi)] = cast(char)defaultValue; break; case 2: (cast(wchar*)s)[cast(size_t)(indxlo + elemi)] = cast(wchar)defaultValue; break; case 4: (cast(dchar*)s)[cast(size_t)(indxlo + elemi)] = cast(dchar)defaultValue; break; case 8: (cast(ulong*)s)[cast(size_t)(indxlo + elemi)] = cast(ulong)defaultValue; break; default: assert(0); } } emplaceExp!(StringExp)(pue, loc, s[0 .. newlen * oldse.sz], newlen, oldse.sz); StringExp se = pue.exp().isStringExp(); se.type = arrayType; se.sz = oldse.sz; se.committed = oldse.committed; se.ownedByCtfe = OwnedBy.ctfe; } else { if (oldlen != 0) { assert(oldval.op == EXP.arrayLiteral); ArrayLiteralExp ae = oldval.isArrayLiteralExp(); foreach (size_t i; 0 .. copylen) (*elements)[i] = (*ae.elements)[indxlo + i]; } if (elemType.ty == Tstruct || elemType.ty == Tsarray) { /* If it is an aggregate literal representing a value type, * we need to create a unique copy for each element */ foreach (size_t i; copylen .. newlen) (*elements)[i] = copyLiteral(defaultElem).copy(); } else { foreach (size_t i; copylen .. newlen) (*elements)[i] = defaultElem; } emplaceExp!(ArrayLiteralExp)(pue, loc, arrayType, elements); ArrayLiteralExp aae = pue.exp().isArrayLiteralExp(); aae.ownedByCtfe = OwnedBy.ctfe; } return pue.exp(); } /*************************** CTFE Sanity Checks ***************************/ bool isCtfeValueValid(Expression newval) { Type tb = newval.type.toBasetype(); switch (newval.op) { case EXP.int64: case EXP.float64: case EXP.complex80: return tb.isscalar(); case EXP.null_: return tb.ty == Tnull || tb.ty == Tpointer || tb.ty == Tarray || tb.ty == Taarray || tb.ty == Tclass || tb.ty == Tdelegate; case EXP.string_: return true; // CTFE would directly use the StringExp in AST. case EXP.arrayLiteral: return true; //((ArrayLiteralExp *)newval)->ownedByCtfe; case EXP.assocArrayLiteral: return true; //((AssocArrayLiteralExp *)newval)->ownedByCtfe; case EXP.structLiteral: return true; //((StructLiteralExp *)newval)->ownedByCtfe; case EXP.classReference: return true; case EXP.type: return true; case EXP.vector: return true; // vector literal case EXP.function_: return true; // function literal or delegate literal case EXP.delegate_: { // &struct.func or &clasinst.func // &nestedfunc Expression ethis = newval.isDelegateExp().e1; return (ethis.op == EXP.structLiteral || ethis.op == EXP.classReference || ethis.op == EXP.variable && ethis.isVarExp().var == newval.isDelegateExp().func); } case EXP.symbolOffset: { // function pointer, or pointer to static variable Declaration d = newval.isSymOffExp().var; return d.isFuncDeclaration() || d.isDataseg(); } case EXP.typeid_: { // always valid return true; } case EXP.address: { // e1 should be a CTFE reference Expression e1 = newval.isAddrExp().e1; return tb.ty == Tpointer && ( (e1.op == EXP.structLiteral || e1.op == EXP.arrayLiteral) && isCtfeValueValid(e1) || e1.op == EXP.variable || e1.op == EXP.dotVariable && isCtfeReferenceValid(e1) || e1.op == EXP.index && isCtfeReferenceValid(e1) || e1.op == EXP.slice && e1.type.toBasetype().ty == Tsarray ); } case EXP.slice: { // e1 should be an array aggregate const SliceExp se = newval.isSliceExp(); assert(se.lwr && se.lwr.op == EXP.int64); assert(se.upr && se.upr.op == EXP.int64); return (tb.ty == Tarray || tb.ty == Tsarray) && (se.e1.op == EXP.string_ || se.e1.op == EXP.arrayLiteral); } case EXP.void_: return true; // uninitialized value default: error(newval.loc, "CTFE internal error: illegal CTFE value `%s`", newval.toChars()); return false; } } bool isCtfeReferenceValid(Expression newval) { switch (newval.op) { case EXP.this_: return true; case EXP.variable: { const VarDeclaration v = newval.isVarExp().var.isVarDeclaration(); assert(v); // Must not be a reference to a reference return true; } case EXP.index: { const Expression eagg = newval.isIndexExp().e1; return eagg.op == EXP.string_ || eagg.op == EXP.arrayLiteral || eagg.op == EXP.assocArrayLiteral; } case EXP.dotVariable: { Expression eagg = newval.isDotVarExp().e1; return (eagg.op == EXP.structLiteral || eagg.op == EXP.classReference) && isCtfeValueValid(eagg); } default: // Internally a ref variable may directly point a stack memory. // e.g. ref int v = 1; return isCtfeValueValid(newval); } } // Used for debugging only void showCtfeExpr(Expression e, int level = 0) { for (int i = level; i > 0; --i) printf(" "); Expressions* elements = null; // We need the struct definition to detect block assignment StructDeclaration sd = null; ClassDeclaration cd = null; if (e.op == EXP.structLiteral) { elements = e.isStructLiteralExp().elements; sd = e.isStructLiteralExp().sd; printf("STRUCT type = %s %p:\n", e.type.toChars(), e); } else if (e.op == EXP.classReference) { elements = e.isClassReferenceExp().value.elements; cd = e.isClassReferenceExp().originalClass(); printf("CLASS type = %s %p:\n", e.type.toChars(), e.isClassReferenceExp().value); } else if (e.op == EXP.arrayLiteral) { elements = e.isArrayLiteralExp().elements; printf("ARRAY LITERAL type=%s %p:\n", e.type.toChars(), e); } else if (e.op == EXP.assocArrayLiteral) { printf("AA LITERAL type=%s %p:\n", e.type.toChars(), e); } else if (e.op == EXP.string_) { printf("STRING %s %p\n", e.toChars(), e.isStringExp.peekString.ptr); } else if (e.op == EXP.slice) { printf("SLICE %p: %s\n", e, e.toChars()); showCtfeExpr(e.isSliceExp().e1, level + 1); } else if (e.op == EXP.variable) { printf("VAR %p %s\n", e, e.toChars()); VarDeclaration v = e.isVarExp().var.isVarDeclaration(); if (v && getValue(v)) showCtfeExpr(getValue(v), level + 1); } else if (e.op == EXP.address) { // This is potentially recursive. We mustn't try to print the thing we're pointing to. printf("POINTER %p to %p: %s\n", e, e.isAddrExp().e1, e.toChars()); } else printf("VALUE %p: %s\n", e, e.toChars()); if (elements) { size_t fieldsSoFar = 0; for (size_t i = 0; i < elements.length; i++) { Expression z = null; VarDeclaration v = null; if (i > 15) { printf("...(total %d elements)\n", cast(int)elements.length); return; } if (sd) { v = sd.fields[i]; z = (*elements)[i]; } else if (cd) { while (i - fieldsSoFar >= cd.fields.length) { fieldsSoFar += cd.fields.length; cd = cd.baseClass; for (int j = level; j > 0; --j) printf(" "); printf(" BASE CLASS: %s\n", cd.toChars()); } v = cd.fields[i - fieldsSoFar]; assert((elements.length + i) >= (fieldsSoFar + cd.fields.length)); size_t indx = (elements.length - fieldsSoFar) - cd.fields.length + i; assert(indx < elements.length); z = (*elements)[indx]; } if (!z) { for (int j = level; j > 0; --j) printf(" "); printf(" void\n"); continue; } if (v) { // If it is a void assignment, use the default initializer if ((v.type.ty != z.type.ty) && v.type.ty == Tsarray) { for (int j = level; --j;) printf(" "); printf(" field: block initialized static array\n"); continue; } } showCtfeExpr(z, level + 1); } } } /*************************** Void initialization ***************************/ UnionExp voidInitLiteral(Type t, VarDeclaration var) { UnionExp ue; if (auto tsa = t.isTypeSArray()) { Expression elem = voidInitLiteral(tsa.next, var).copy(); // For aggregate value types (structs, static arrays) we must // create an a separate copy for each element. const mustCopy = (elem.op == EXP.arrayLiteral || elem.op == EXP.structLiteral); const d = cast(size_t)tsa.dim.toInteger(); auto elements = new Expressions(d); foreach (i; 0 .. d) { if (mustCopy && i > 0) elem = copyLiteral(elem).copy(); (*elements)[i] = elem; } emplaceExp!(ArrayLiteralExp)(&ue, var.loc, tsa, elements); ArrayLiteralExp ae = ue.exp().isArrayLiteralExp(); ae.ownedByCtfe = OwnedBy.ctfe; } else if (auto ts = t.isTypeStruct()) { auto exps = new Expressions(ts.sym.fields.length); foreach (size_t i; 0 .. ts.sym.fields.length) { (*exps)[i] = voidInitLiteral(ts.sym.fields[i].type, ts.sym.fields[i]).copy(); } emplaceExp!(StructLiteralExp)(&ue, var.loc, ts.sym, exps); StructLiteralExp se = ue.exp().isStructLiteralExp(); se.type = ts; se.ownedByCtfe = OwnedBy.ctfe; } else emplaceExp!(VoidInitExp)(&ue, var); return ue; } ldc-1.40.0-src/dmd/dstruct.d0000644000000000000000000004705714727557031014252 0ustar rootroot/** * Struct and union declarations. * * Specification: $(LINK2 https://dlang.org/spec/struct.html, Structs, Unions) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dstruct.d, _dstruct.d) * Documentation: https://dlang.org/phobos/dmd_dstruct.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dstruct.d */ module dmd.dstruct; import core.stdc.stdio; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.declaration; import dmd.dmodule; import dmd.dscope; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.opover; import dmd.target; import dmd.tokens; import dmd.typesem; import dmd.typinf; import dmd.visitor; /*************************************** * Search sd for a member function of the form: * `extern (D) string toString();` * Params: * sd = struct declaration to search * Returns: * FuncDeclaration of `toString()` if found, `null` if not */ FuncDeclaration search_toString(StructDeclaration sd) { Dsymbol s = search_function(sd, Id.tostring); FuncDeclaration fd = s ? s.isFuncDeclaration() : null; if (fd) { __gshared TypeFunction tftostring; if (!tftostring) { tftostring = new TypeFunction(ParameterList(), Type.tstring, LINK.d); tftostring = tftostring.merge().toTypeFunction(); } fd = fd.overloadExactMatch(tftostring); } return fd; } /*************************************** * Request additional semantic analysis for TypeInfo generation. * Params: * sc = context * t = type that TypeInfo is being generated for */ extern (D) void semanticTypeInfo(Scope* sc, Type t) { if (sc) { if (sc.intypeof) return; if (!sc.needsCodegen()) return; } if (!t) return; void visitVector(TypeVector t) { semanticTypeInfo(sc, t.basetype); } void visitAArray(TypeAArray t) { semanticTypeInfo(sc, t.index); semanticTypeInfo(sc, t.next); } void visitStruct(TypeStruct t) { //printf("semanticTypeInfo.visit(TypeStruct = %s)\n", t.toChars()); StructDeclaration sd = t.sym; /* Step 1: create TypeInfoDeclaration */ if (!sc) // inline may request TypeInfo. { Scope scx; scx.eSink = global.errorSink; scx._module = sd.getModule(); getTypeInfoType(sd.loc, t, &scx); version (IN_LLVM) {} else { sd.requestTypeInfo = true; } } else if (!sc.minst) { // don't yet have to generate TypeInfo instance if // the typeid(T) expression exists in speculative scope. } else { getTypeInfoType(sd.loc, t, sc); version (IN_LLVM) {} else { sd.requestTypeInfo = true; } // https://issues.dlang.org/show_bug.cgi?id=15149 // if the typeid operand type comes from a // result of auto function, it may be yet speculative. // unSpeculative(sc, sd); } /* Step 2: If the TypeInfo generation requires sd.semantic3, run it later. * This should be done even if typeid(T) exists in speculative scope. * Because it may appear later in non-speculative scope. */ if (!sd.members) return; // opaque struct if (!sd.xeq && !sd.xcmp && !sd.postblit && !sd.tidtor && !sd.xhash && !search_toString(sd)) return; // none of TypeInfo-specific members // If the struct is in a non-root module, run semantic3 to get // correct symbols for the member function. if (sd.semanticRun >= PASS.semantic3) { // semantic3 is already done } else if (TemplateInstance ti = sd.isInstantiated()) { if (ti.minst && !ti.minst.isRoot()) Module.addDeferredSemantic3(sd); } else { if (sd.inNonRoot()) { //printf("deferred sem3 for TypeInfo - sd = %s, inNonRoot = %d\n", sd.toChars(), sd.inNonRoot()); Module.addDeferredSemantic3(sd); } } } void visitTuple(TypeTuple t) { if (t.arguments) { foreach (arg; *t.arguments) { semanticTypeInfo(sc, arg.type); } } } /* Note structural similarity of this Type walker to that in isSpeculativeType() */ Type tb = t.toBasetype(); switch (tb.ty) { case Tvector: visitVector(tb.isTypeVector()); break; case Taarray: visitAArray(tb.isTypeAArray()); break; case Tstruct: visitStruct(tb.isTypeStruct()); break; case Ttuple: visitTuple (tb.isTypeTuple()); break; case Tclass: case Tenum: break; default: semanticTypeInfo(sc, tb.nextOf()); break; } } enum StructFlags : int { none = 0x0, hasPointers = 0x1, // NB: should use noPointers as in ClassFlags } /*********************************************************** * All `struct` declarations are an instance of this. */ extern (C++) class StructDeclaration : AggregateDeclaration { FuncDeclarations postblits; // Array of postblit functions FuncDeclaration postblit; // aggregate postblit FuncDeclaration xeq; // TypeInfo_Struct.xopEquals FuncDeclaration xcmp; // TypeInfo_Struct.xopCmp FuncDeclaration xhash; // TypeInfo_Struct.xtoHash extern (C++) __gshared FuncDeclaration xerreq; // object.xopEquals extern (C++) __gshared FuncDeclaration xerrcmp; // object.xopCmp // ABI-specific type(s) if the struct can be passed in registers TypeTuple argTypes; structalign_t alignment; // alignment applied outside of the struct ThreeState ispod; // if struct is POD // `bool` fields that are compacted into bit fields in a string mixin private extern (D) static struct BitFields { bool zeroInit; // !=0 if initialize with 0 fill bool hasIdentityAssign; // true if has identity opAssign bool hasBlitAssign; // true if opAssign is a blit bool hasIdentityEquals; // true if has identity opEquals bool hasNoFields; // has no fields bool hasCopyCtor; // copy constructor bool hasPointerField; // members with indirections bool hasVoidInitPointers; // void-initialized unsafe fields bool hasUnsafeBitpatterns; // @system members, pointers, bool bool hasFieldWithInvariant; // invariants bool computedTypeProperties;// the above 3 fields are computed version (IN_LLVM) {} else { // Even if struct is defined as non-root symbol, some built-in operations // (e.g. TypeidExp, NewExp, ArrayLiteralExp, etc) request its TypeInfo. // For those, today TypeInfo_Struct is generated in COMDAT. bool requestTypeInfo; } } import dmd.common.bitfields : generateBitFields; mixin(generateBitFields!(BitFields, ushort)); extern (D) this(const ref Loc loc, Identifier id, bool inObject) { super(loc, id); zeroInit = false; // assume false until we do semantic processing ispod = ThreeState.none; // For forward references type = new TypeStruct(this); if (inObject) { if (id == Id.ModuleInfo && !Module.moduleinfo) Module.moduleinfo = this; } } static StructDeclaration create(const ref Loc loc, Identifier id, bool inObject) { return new StructDeclaration(loc, id, inObject); } override StructDeclaration syntaxCopy(Dsymbol s) { StructDeclaration sd = s ? cast(StructDeclaration)s : new StructDeclaration(loc, ident, false); ScopeDsymbol.syntaxCopy(sd); return sd; } override const(char)* kind() const { return "struct"; } override final void finalizeSize() { //printf("StructDeclaration::finalizeSize() %s, sizeok = %d\n", toChars(), sizeok); assert(sizeok != Sizeok.done); if (sizeok == Sizeok.inProcess) { return; } sizeok = Sizeok.inProcess; //printf("+StructDeclaration::finalizeSize() %s, fields.length = %d, sizeok = %d\n", toChars(), fields.length, sizeok); fields.setDim(0); // workaround // Set the offsets of the fields and determine the size of the struct FieldState fieldState; bool isunion = isUnionDeclaration() !is null; for (size_t i = 0; i < members.length; i++) { Dsymbol s = (*members)[i]; s.setFieldOffset(this, &fieldState, isunion); } if (type.ty == Terror) { errors = true; return; } if (structsize == 0) { hasNoFields = true; alignsize = 1; // A fine mess of what size a zero sized struct should be final switch (classKind) { case ClassKind.d: case ClassKind.cpp: structsize = 1; break; case ClassKind.c: case ClassKind.objc: if (target.c.bitFieldStyle == TargetC.BitFieldStyle.MS) { /* Undocumented MS behavior for: * struct S { int :0; }; */ structsize = 4; } else structsize = 0; break; } } // Round struct size up to next alignsize boundary. // This will ensure that arrays of structs will get their internals // aligned properly. if (alignment.isDefault() || alignment.isPack()) structsize = (structsize + alignsize - 1) & ~(alignsize - 1); else structsize = (structsize + alignment.get() - 1) & ~(alignment.get() - 1); sizeok = Sizeok.done; //printf("-StructDeclaration::finalizeSize() %s, fields.length = %d, structsize = %d\n", toChars(), cast(int)fields.length, cast(int)structsize); if (errors) return; // Calculate fields[i].overlapped if (checkOverlappedFields()) { errors = true; return; } // Determine if struct is all zeros or not zeroInit = true; foreach (vd; fields) { if (vd._init) { if (vd._init.isVoidInitializer()) /* Treat as 0 for the purposes of putting the initializer * in the BSS segment, or doing a mass set to 0 */ continue; // Zero size fields are zero initialized if (vd.type.size(vd.loc) == 0) continue; // Examine init to see if it is all 0s. auto exp = vd.getConstInitializer(); if (!exp || !_isZeroInit(exp)) { zeroInit = false; break; } } else if (!vd.type.isZeroInit(loc)) { zeroInit = false; break; } } argTypes = target.toArgTypes(type); } /// Compute cached type properties for `TypeStruct` extern(D) final void determineTypeProperties() { if (computedTypeProperties) return; foreach (vd; fields) { if (vd.storage_class & STC.ref_ || vd.hasPointers()) { hasPointerField = true; hasUnsafeBitpatterns = true; } if (vd._init && vd._init.isVoidInitializer() && vd.type.hasPointers()) hasVoidInitPointers = true; if (vd.storage_class & STC.system || vd.type.hasUnsafeBitpatterns()) hasUnsafeBitpatterns = true; if (!vd._init && vd.type.hasVoidInitPointers()) hasVoidInitPointers = true; if (vd.type.hasInvariant()) hasFieldWithInvariant = true; } computedTypeProperties = true; } /*************************************** * Determine if struct is POD (Plain Old Data). * * POD is defined as: * $(OL * $(LI not nested) * $(LI no postblits, destructors, or assignment operators) * $(LI no `ref` fields or fields that are themselves non-POD) * ) * The idea being these are compatible with C structs. * * Returns: * true if struct is POD */ final bool isPOD() { // If we've already determined whether this struct is POD. if (ispod != ThreeState.none) return (ispod == ThreeState.yes); ispod = ThreeState.yes; import dmd.clone; bool hasCpCtorLocal; needCopyCtor(this, hasCpCtorLocal); if (enclosing || search(this, loc, Id.postblit) || search(this, loc, Id.dtor) || hasCpCtorLocal) { ispod = ThreeState.no; return false; } // Recursively check all fields are POD. for (size_t i = 0; i < fields.length; i++) { VarDeclaration v = fields[i]; if (v.storage_class & STC.ref_) { ispod = ThreeState.no; return false; } Type tv = v.type.baseElemOf(); if (tv.ty == Tstruct) { auto ts = cast(TypeStruct)tv; StructDeclaration sd = ts.sym; if (!sd.isPOD()) { ispod = ThreeState.no; return false; } } } return (ispod == ThreeState.yes); } /*************************************** * Determine if struct has copy construction (copy constructor or postblit) * Returns: * true if struct has copy construction */ final bool hasCopyConstruction() { return postblit || hasCopyCtor; } override final inout(StructDeclaration) isStructDeclaration() inout @nogc nothrow pure @safe { return this; } override void accept(Visitor v) { v.visit(this); } final uint numArgTypes() const { return argTypes && argTypes.arguments ? cast(uint) argTypes.arguments.length : 0; } final Type argType(uint index) { return index < numArgTypes() ? (*argTypes.arguments)[index].type : null; } /*************************************** * Verifies whether the struct declaration has a * constructor that is not a copy constructor. * Optionally, it can check whether the struct * declaration has a regular constructor, that * is not disabled. * * Params: * checkDisabled = if the struct has a regular non-disabled constructor * Returns: * true, if the struct has a regular (optionally, * not disabled) constructor, false otherwise. */ final bool hasRegularCtor(bool checkDisabled = false) { if (!ctor) return false; bool result; overloadApply(ctor, (Dsymbol s) { if (auto td = s.isTemplateDeclaration()) { if (checkDisabled && td.onemember) { if (auto ctorDecl = td.onemember.isCtorDeclaration()) { if (ctorDecl.storage_class & STC.disable) return 0; } } result = true; return 1; } if (auto ctorDecl = s.isCtorDeclaration()) { if (!ctorDecl.isCpCtor && (!checkDisabled || !(ctorDecl.storage_class & STC.disable))) { result = true; return 1; } } return 0; }); return result; } } /********************************** * Determine if exp is all binary zeros. * Params: * exp = expression to check * Returns: * true if it's all binary 0 */ bool _isZeroInit(Expression exp) { switch (exp.op) { case EXP.int64: return exp.toInteger() == 0; case EXP.null_: return true; case EXP.structLiteral: { auto sle = exp.isStructLiteralExp(); if (sle.sd.isNested()) return false; const isCstruct = sle.sd.isCsymbol(); // C structs are default initialized to all zeros foreach (i; 0 .. sle.sd.fields.length) { auto field = sle.sd.fields[i]; if (field.type.size(field.loc)) { auto e = sle.elements && i < sle.elements.length ? (*sle.elements)[i] : null; if (e ? !_isZeroInit(e) : !isCstruct && !field.type.isZeroInit(field.loc)) return false; } } return true; } case EXP.arrayLiteral: { auto ale = cast(ArrayLiteralExp)exp; const dim = ale.elements ? ale.elements.length : 0; if (ale.type.toBasetype().ty == Tarray) // if initializing a dynamic array return dim == 0; foreach (i; 0 .. dim) { if (!_isZeroInit(ale[i])) return false; } /* Note that true is returned for all T[0] */ return true; } case EXP.string_: { auto se = cast(StringExp)exp; if (se.type.toBasetype().ty == Tarray) // if initializing a dynamic array return se.len == 0; foreach (i; 0 .. se.len) { if (se.getIndex(i) != 0) return false; } return true; } case EXP.vector: { auto ve = cast(VectorExp) exp; return _isZeroInit(ve.e1); } case EXP.float64: case EXP.complex80: { import dmd.root.ctfloat : CTFloat; return (exp.toReal() is CTFloat.zero) && (exp.toImaginary() is CTFloat.zero); } default: return false; } } /*********************************************************** * Unions are a variation on structs. */ extern (C++) final class UnionDeclaration : StructDeclaration { extern (D) this(const ref Loc loc, Identifier id) { super(loc, id, false); } override UnionDeclaration syntaxCopy(Dsymbol s) { assert(!s); auto ud = new UnionDeclaration(loc, ident); StructDeclaration.syntaxCopy(ud); return ud; } override const(char)* kind() const { return "union"; } override inout(UnionDeclaration) isUnionDeclaration() inout { return this; } override void accept(Visitor v) { v.visit(this); } } ldc-1.40.0-src/dmd/chkformat.d0000644000000000000000000012244014727557031014526 0ustar rootroot/** * Check the arguments to `printf` and `scanf` against the `format` string. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/chkformat.d, _chkformat.d) * Documentation: https://dlang.org/phobos/dmd_chkformat.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/chkformat.d */ module dmd.chkformat; //import core.stdc.stdio : printf, scanf; import core.stdc.ctype : isdigit; import dmd.astenums; import dmd.cond; import dmd.errorsink; import dmd.expression; import dmd.globals; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.typesem; import dmd.target; /****************************************** * Check that arguments to a printf format string are compatible * with that string. Issue errors for incompatibilities. * * Follows the C99 specification for printf. * * Takes a generous, rather than strict, view of compatiblity. * For example, an unsigned value can be formatted with a signed specifier. * * Diagnosed incompatibilities are: * * 1. incompatible sizes which will cause argument misalignment * 2. deferencing arguments that are not pointers * 3. insufficient number of arguments * 4. struct arguments * 5. array and slice arguments * 6. non-pointer arguments to `s` specifier * 7. non-standard formats * 8. undefined behavior per C99 * * Per the C Standard, extra arguments are ignored. * * No attempt is made to fix the arguments or the format string. * * Params: * loc = location for error messages * format = format string * args = arguments to match with format string * isVa_list = if a "v" function (format check only) * eSink = where the error messages go * * Returns: * `true` if errors occurred * References: * C99 7.19.6.1 * https://www.cplusplus.com/reference/cstdio/printf/ */ public bool checkPrintfFormat(ref const Loc loc, scope const char[] format, scope Expression[] args, bool isVa_list, ErrorSink eSink) { //printf("checkPrintFormat('%.*s')\n", cast(int)format.length, format.ptr); size_t n; // index in args for (size_t i = 0; i < format.length;) { if (format[i] != '%') { ++i; continue; } bool widthStar; bool precisionStar; size_t j = i; const fmt = parsePrintfFormatSpecifier(format, j, widthStar, precisionStar); const slice = format[i .. j]; i = j; if (fmt == Format.percent) continue; // "%%", no arguments if (fmt == Format.GNU_m) continue; // "%m", no arguments if (isVa_list) { // format check only if (fmt == Format.error) eSink.deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr); continue; } Expression getNextArg(ref bool skip) { if (n == args.length) { if (args.length < (n + 1)) eSink.deprecation(loc, "more format specifiers than %d arguments", cast(int)n); else skip = true; return null; } return args[n++]; } void errorMsg(const char* prefix, Expression arg, const char* texpect, Type tactual) { eSink.deprecation(arg.loc, "%sargument `%s` for format specification `\"%.*s\"` must be `%s`, not `%s`", prefix ? prefix : "", arg.toChars(), cast(int)slice.length, slice.ptr, texpect, tactual.toChars()); } if (widthStar) { bool skip; auto e = getNextArg(skip); if (skip) continue; if (!e) return true; auto t = e.type.toBasetype(); if (t.ty != Tint32 && t.ty != Tuns32) errorMsg("width ", e, "int", t); } if (precisionStar) { bool skip; auto e = getNextArg(skip); if (skip) continue; if (!e) return true; auto t = e.type.toBasetype(); if (t.ty != Tint32 && t.ty != Tuns32) errorMsg("precision ", e, "int", t); } bool skip; auto e = getNextArg(skip); if (skip) continue; if (!e) return true; auto t = e.type.toBasetype(); auto tnext = t.nextOf(); const c_longsize = target.c.longsize; const ptrsize = target.ptrsize; // Types which are promoted to int are allowed. // Spec: C99 6.5.2.2.7 final switch (fmt) { case Format.u: // unsigned int case Format.d: // int if (t.ty != Tint32 && t.ty != Tuns32) errorMsg(null, e, fmt == Format.u ? "uint" : "int", t); break; case Format.hhu: // unsigned char case Format.hhd: // signed char if (t.ty != Tint32 && t.ty != Tuns32 && t.ty != Tint8 && t.ty != Tuns8) errorMsg(null, e, fmt == Format.hhu ? "ubyte" : "byte", t); break; case Format.hu: // unsigned short int case Format.hd: // short int if (t.ty != Tint32 && t.ty != Tuns32 && t.ty != Tint16 && t.ty != Tuns16) errorMsg(null, e, fmt == Format.hu ? "ushort" : "short", t); break; case Format.lu: // unsigned long int case Format.ld: // long int if (!(t.isintegral() && t.size() == c_longsize)) { if (fmt == Format.lu) errorMsg(null, e, (c_longsize == 4 ? "uint" : "ulong"), t); else errorMsg(null, e, (c_longsize == 4 ? "int" : "long"), t); if (t.isintegral() && t.size() != c_longsize) eSink.errorSupplemental(e.loc, "C `long` is %d bytes on your system", c_longsize); } break; case Format.llu: // unsigned long long int case Format.lld: // long long int if (t.ty != Tint64 && t.ty != Tuns64) errorMsg(null, e, fmt == Format.llu ? "ulong" : "long", t); break; case Format.ju: // uintmax_t case Format.jd: // intmax_t if (t.ty != Tint64 && t.ty != Tuns64) { if (fmt == Format.ju) errorMsg(null, e, "core.stdc.stdint.uintmax_t", t); else errorMsg(null, e, "core.stdc.stdint.intmax_t", t); } break; case Format.zd: // size_t if (!(t.isintegral() && t.size() == ptrsize)) errorMsg(null, e, "size_t", t); break; case Format.td: // ptrdiff_t if (!(t.isintegral() && t.size() == ptrsize)) errorMsg(null, e, "ptrdiff_t", t); break; case Format.lg: case Format.g: // double if (t.ty != Tfloat64 && t.ty != Timaginary64) errorMsg(null, e, "double", t); break; case Format.Lg: // long double if (t.ty != Tfloat80 && t.ty != Timaginary80) errorMsg(null, e, "real", t); break; case Format.p: // pointer if (t.ty != Tpointer && t.ty != Tnull && t.ty != Tclass && t.ty != Tdelegate && t.ty != Taarray) errorMsg(null, e, "void*", t); break; case Format.n: // pointer to int if (!(t.ty == Tpointer && tnext.ty == Tint32 && tnext.isMutable())) errorMsg(null, e, "int*", t); break; case Format.ln: // pointer to long int if (!(t.ty == Tpointer && tnext.isintegral() && tnext.size() == c_longsize)) errorMsg(null, e, (c_longsize == 4 ? "int*" : "long*"), t); break; case Format.lln: // pointer to long long int if (!(t.ty == Tpointer && tnext.ty == Tint64)) errorMsg(null, e, "long*", t); break; case Format.hn: // pointer to short if (!(t.ty == Tpointer && tnext.ty == Tint16)) errorMsg(null, e, "short*", t); break; case Format.hhn: // pointer to signed char if (!(t.ty == Tpointer && tnext.ty == Tint16)) errorMsg(null, e, "byte*", t); break; case Format.jn: // pointer to intmax_t if (!(t.ty == Tpointer && tnext.ty == Tint64)) errorMsg(null, e, "core.stdc.stdint.intmax_t*", t); break; case Format.zn: // pointer to size_t if (!(t.ty == Tpointer && tnext.isintegral() && tnext.isunsigned() && tnext.size() == ptrsize)) errorMsg(null, e, "size_t*", t); break; case Format.tn: // pointer to ptrdiff_t if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == ptrsize)) errorMsg(null, e, "ptrdiff_t*", t); break; case Format.c: // char if (t.ty != Tint32 && t.ty != Tuns32) errorMsg(null, e, "char", t); break; case Format.lc: // wint_t if (t.ty != Tint32 && t.ty != Tuns32) errorMsg(null, e, "wchar_t", t); break; case Format.s: // pointer to char string if (!(t.ty == Tpointer && (tnext.ty == Tchar || tnext.ty == Tint8 || tnext.ty == Tuns8))) errorMsg(null, e, "char*", t); break; case Format.ls: // pointer to wchar_t string if (!(t.ty == Tpointer && tnext.ty.isSomeChar && tnext.size() == target.c.wchar_tsize)) errorMsg(null, e, "wchar_t*", t); break; case Format.error: eSink.deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr); break; case Format.GNU_m: case Format.POSIX_ms: case Format.POSIX_mls: case Format.percent: assert(0); } } return false; } /****************************************** * Check that arguments to a scanf format string are compatible * with that string. Issue errors for incompatibilities. * * Follows the C99 specification for scanf. * * Takes a generous, rather than strict, view of compatiblity. * For example, an unsigned value can be formatted with a signed specifier. * * Diagnosed incompatibilities are: * * 1. incompatible sizes which will cause argument misalignment * 2. deferencing arguments that are not pointers * 3. insufficient number of arguments * 4. struct arguments * 5. array and slice arguments * 6. non-standard formats * 7. undefined behavior per C99 * * Per the C Standard, extra arguments are ignored. * * No attempt is made to fix the arguments or the format string. * * Params: * loc = location for error messages * format = format string * args = arguments to match with format string * isVa_list = if a "v" function (format check only) * eSink = where the error messages go * * Returns: * `true` if errors occurred * References: * C99 7.19.6.2 * https://www.cplusplus.com/reference/cstdio/scanf/ */ public bool checkScanfFormat(ref const Loc loc, scope const char[] format, scope Expression[] args, bool isVa_list, ErrorSink eSink) { size_t n = 0; for (size_t i = 0; i < format.length;) { if (format[i] != '%') { ++i; continue; } bool asterisk; size_t j = i; const fmt = parseScanfFormatSpecifier(format, j, asterisk); const slice = format[i .. j]; i = j; if (fmt == Format.percent || asterisk) continue; // "%%", "%*": no arguments if (isVa_list) { // format check only if (fmt == Format.error) eSink.deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr); continue; } Expression getNextArg() { if (n == args.length) { if (!asterisk) eSink.deprecation(loc, "more format specifiers than %d arguments", cast(int)n); return null; } return args[n++]; } void errorMsg(const char* prefix, Expression arg, const char* texpect, Type tactual) { eSink.deprecation(arg.loc, "%sargument `%s` for format specification `\"%.*s\"` must be `%s`, not `%s`", prefix ? prefix : "", arg.toChars(), cast(int)slice.length, slice.ptr, texpect, tactual.toChars()); } auto e = getNextArg(); if (!e) return true; auto t = e.type.toBasetype(); auto tnext = t.nextOf(); const c_longsize = target.c.longsize; const ptrsize = target.ptrsize; final switch (fmt) { case Format.n: case Format.d: // pointer to int if (!(t.ty == Tpointer && tnext.ty == Tint32)) errorMsg(null, e, "int*", t); break; case Format.hhn: case Format.hhd: // pointer to signed char if (!(t.ty == Tpointer && tnext.ty == Tint16)) errorMsg(null, e, "byte*", t); break; case Format.hn: case Format.hd: // pointer to short if (!(t.ty == Tpointer && tnext.ty == Tint16)) errorMsg(null, e, "short*", t); break; case Format.ln: case Format.ld: // pointer to long int if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == c_longsize)) errorMsg(null, e, (c_longsize == 4 ? "int*" : "long*"), t); break; case Format.lln: case Format.lld: // pointer to long long int if (!(t.ty == Tpointer && tnext.ty == Tint64)) errorMsg(null, e, "long*", t); break; case Format.jn: case Format.jd: // pointer to intmax_t if (!(t.ty == Tpointer && tnext.ty == Tint64)) errorMsg(null, e, "core.stdc.stdint.intmax_t*", t); break; case Format.zn: case Format.zd: // pointer to size_t if (!(t.ty == Tpointer && tnext.isintegral() && tnext.isunsigned() && tnext.size() == ptrsize)) errorMsg(null, e, "size_t*", t); break; case Format.tn: case Format.td: // pointer to ptrdiff_t if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == ptrsize)) errorMsg(null, e, "ptrdiff_t*", t); break; case Format.u: // pointer to unsigned int if (!(t.ty == Tpointer && tnext.ty == Tuns32)) errorMsg(null, e, "uint*", t); break; case Format.hhu: // pointer to unsigned char if (!(t.ty == Tpointer && tnext.ty == Tuns8)) errorMsg(null, e, "ubyte*", t); break; case Format.hu: // pointer to unsigned short int if (!(t.ty == Tpointer && tnext.ty == Tuns16)) errorMsg(null, e, "ushort*", t); break; case Format.lu: // pointer to unsigned long int if (!(t.ty == Tpointer && tnext.isintegral() && tnext.isunsigned() && tnext.size() == c_longsize)) errorMsg(null, e, (c_longsize == 4 ? "uint*" : "ulong*"), t); break; case Format.llu: // pointer to unsigned long long int if (!(t.ty == Tpointer && tnext.ty == Tuns64)) errorMsg(null, e, "ulong*", t); break; case Format.ju: // pointer to uintmax_t if (!(t.ty == Tpointer && tnext.ty == Tuns64)) errorMsg(null, e, "core.stdc.stdint.uintmax_t*", t); break; case Format.g: // pointer to float if (!(t.ty == Tpointer && tnext.ty == Tfloat32)) errorMsg(null, e, "float*", t); break; case Format.lg: // pointer to double if (!(t.ty == Tpointer && tnext.ty == Tfloat64)) errorMsg(null, e, "double*", t); break; case Format.Lg: // pointer to long double if (!(t.ty == Tpointer && tnext.ty == Tfloat80)) errorMsg(null, e, "real*", t); break; case Format.c: case Format.s: // pointer to char string if (!(t.ty == Tpointer && (tnext.ty == Tchar || tnext.ty == Tint8 || tnext.ty == Tuns8))) errorMsg(null, e, "char*", t); break; case Format.lc: case Format.ls: // pointer to wchar_t string if (!(t.ty == Tpointer && tnext.ty.isSomeChar && tnext.size() == target.c.wchar_tsize)) errorMsg(null, e, "wchar_t*", t); break; case Format.p: // double pointer if (!(t.ty == Tpointer && tnext.ty == Tpointer)) errorMsg(null, e, "void**", t); break; case Format.POSIX_ms: // pointer to pointer to char string Type tnext2 = tnext ? tnext.nextOf() : null; if (!(t.ty == Tpointer && tnext.ty == Tpointer && (tnext2.ty == Tchar || tnext2.ty == Tint8 || tnext2.ty == Tuns8))) errorMsg(null, e, "char**", t); break; case Format.POSIX_mls: // pointer to pointer to wchar_t string Type tnext2 = tnext ? tnext.nextOf() : null; if (!(t.ty == Tpointer && tnext.ty == Tpointer && tnext2.ty.isSomeChar && tnext2.size() == target.c.wchar_tsize)) errorMsg(null, e, "wchar_t**", t); break; case Format.error: eSink.deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr); break; case Format.GNU_m: case Format.percent: assert(0); } } return false; } /*****************************************************************************************************/ private: /************************************** * Parse the *format specifier* which is of the form: * * `%[*][width][length]specifier` * * Params: * format = format string * idx = index of `%` of start of format specifier, * which gets updated to index past the end of it, * even if `Format.error` is returned * asterisk = set if there is a `*` sub-specifier * Returns: * Format */ Format parseScanfFormatSpecifier(scope const char[] format, ref size_t idx, out bool asterisk) nothrow pure @safe { auto i = idx; assert(format[i] == '%'); const length = format.length; Format error() { idx = i; return Format.error; } ++i; if (i == length) return error(); if (format[i] == '%') { idx = i + 1; return Format.percent; } // * sub-specifier if (format[i] == '*') { ++i; if (i == length) return error(); asterisk = true; } // fieldWidth while (isdigit(format[i])) { i++; if (i == length) return error(); } /* Read the specifier */ Format specifier; Modifier flags = Modifier.none; switch (format[i]) { case 'm': // https://pubs.opengroup.org/onlinepubs/9699919799/functions/scanf.html // POSIX.1-2017 C Extension (CX) flags = Modifier.m; ++i; if (i == length) return error(); if (format[i] == 'l') { ++i; if (i == length) return error(); flags = Modifier.ml; } // Check valid conversion types for %m. if (format[i] == 'c' || format[i] == 's') specifier = flags == Modifier.ml ? Format.POSIX_mls : Format.POSIX_ms; else if (format[i] == 'C' || format[i] == 'S') specifier = flags == Modifier.m ? Format.POSIX_mls : Format.error; else if (format[i] == '[') goto case '['; else specifier = Format.error; ++i; break; case 'l': // Look for wchar_t scanset %l[..] immutable j = i + 1; if (j < length && format[j] == '[') { i = j; flags = Modifier.l; goto case '['; } goto default; case '[': // Read the scanset i++; if (i == length) return error(); // If the conversion specifier begins with `[]` or `[^]`, the right // bracket character is not the terminator, but in the scanlist. if (format[i] == '^') { i++; if (i == length) return error(); } if (format[i] == ']') { i++; if (i == length) return error(); } // A scanset can be anything, so we just check that it is paired while (i < length) { if (format[i] == ']') break; ++i; } // no `]` found if (i == length) return error(); specifier = flags == Modifier.none ? Format.s : flags == Modifier.l ? Format.ls : flags == Modifier.m ? Format.POSIX_ms : flags == Modifier.ml ? Format.POSIX_mls : Format.error; ++i; break; default: char genSpec; specifier = parseGenericFormatSpecifier(format, i, genSpec); if (specifier == Format.error) return error(); break; } idx = i; return specifier; // success } /************************************** * Parse the *format specifier* which is of the form: * * `%[flags][field width][.precision][length modifier]specifier` * * Params: * format = format string * idx = index of `%` of start of format specifier, * which gets updated to index past the end of it, * even if `Format.error` is returned * widthStar = set if * for width * precisionStar = set if * for precision * useGNUExts = true if parsing GNU format extensions * Returns: * Format */ Format parsePrintfFormatSpecifier(scope const char[] format, ref size_t idx, out bool widthStar, out bool precisionStar, bool useGNUExts = findCondition(global.versionids, Identifier.idPool("CRuntime_Glibc"))) nothrow pure @safe { auto i = idx; assert(format[i] == '%'); const length = format.length; bool hash; bool zero; bool flags; bool width; bool precision; Format error() { idx = i; return Format.error; } ++i; if (i == length) return error(); if (format[i] == '%') { idx = i + 1; return Format.percent; } /* Read the `flags` */ while (1) { const c = format[i]; if (c == '-' || c == '+' || c == ' ') { flags = true; } else if (c == '#') { hash = true; } else if (c == '0') { zero = true; } else break; ++i; if (i == length) return error(); } /* Read the `field width` */ { const c = format[i]; if (c == '*') { width = true; widthStar = true; ++i; if (i == length) return error(); } else if ('1' <= c && c <= '9') { width = true; ++i; if (i == length) return error(); while ('0' <= format[i] && format[i] <= '9') { ++i; if (i == length) return error(); } } } /* Read the `precision` */ if (format[i] == '.') { precision = true; ++i; if (i == length) return error(); const c = format[i]; if (c == '*') { precisionStar = true; ++i; if (i == length) return error(); } else if ('0' <= c && c <= '9') { ++i; if (i == length) return error(); while ('0' <= format[i] && format[i] <= '9') { ++i; if (i == length) return error(); } } } /* Read the specifier */ char genSpec; Format specifier; switch (format[i]) { case 'm': // https://www.gnu.org/software/libc/manual/html_node/Other-Output-Conversions.html if (useGNUExts) { specifier = Format.GNU_m; genSpec = format[i]; ++i; break; } goto default; default: specifier = parseGenericFormatSpecifier(format, i, genSpec); if (specifier == Format.error) return error(); break; } switch (genSpec) { case 'c': case 's': case 'C': case 'S': if (hash || zero) return error(); break; case 'd': case 'i': if (hash) return error(); break; case 'm': if (hash || zero || flags) return error(); break; case 'n': if (hash || zero || precision || width || flags) return error(); break; default: break; } idx = i; return specifier; // success } /* Different kinds of conversion modifiers. */ enum Modifier { none, h, // short hh, // char j, // intmax_t l, // wint_t/wchar_t ll, // long long int L, // long double m, // char** ml, // wchar_t** t, // ptrdiff_t z // size_t } /* Different kinds of formatting specifications, variations we don't care about are merged. (Like we don't care about the difference between f, e, g, a, etc.) For `scanf`, every format is a pointer. */ enum Format { d, // int hhd, // signed char hd, // short int ld, // long int lld, // long long int jd, // intmax_t zd, // size_t td, // ptrdiff_t u, // unsigned int hhu, // unsigned char hu, // unsigned short int lu, // unsigned long int llu, // unsigned long long int ju, // uintmax_t g, // float (scanf) / double (printf) lg, // double (scanf) Lg, // long double (both) s, // char string (both) ls, // wchar_t string (both) c, // char (printf) lc, // wint_t (printf) p, // pointer n, // pointer to int hhn, // pointer to signed char hn, // pointer to short ln, // pointer to long int lln, // pointer to long long int jn, // pointer to intmax_t zn, // pointer to size_t tn, // pointer to ptrdiff_t GNU_m, // GNU ext. : string corresponding to the error code in errno (printf) POSIX_ms, // POSIX ext. : dynamically allocated char string (scanf) POSIX_mls, // POSIX ext. : dynamically allocated wchar_t string (scanf) percent, // %% (i.e. no argument) error, // invalid format specification } /************************************** * Parse the *length specifier* and the *specifier* of the following form: * `[length]specifier` * * Params: * format = format string * idx = index of of start of format specifier, * which gets updated to index past the end of it, * even if `Format.error` is returned * genSpecifier = Generic specifier. For instance, it will be set to `d` if the * format is `hdd`. * Returns: * Format */ Format parseGenericFormatSpecifier(scope const char[] format, ref size_t idx, out char genSpecifier) nothrow pure @safe { const length = format.length; /* Read the `length modifier` */ const lm = format[idx]; Modifier flags; switch (lm) { case 'j': case 'z': case 't': case 'L': flags = lm == 'j' ? Modifier.j : lm == 'z' ? Modifier.z : lm == 't' ? Modifier.t : Modifier.L; ++idx; if (idx == length) return Format.error; break; case 'h': case 'l': ++idx; if (idx == length) return Format.error; if (lm == format[idx]) { flags = lm == 'h' ? Modifier.hh : Modifier.ll; ++idx; if (idx == length) return Format.error; } else flags = lm == 'h' ? Modifier.h : Modifier.l; break; default: flags = Modifier.none; break; } /* Read the `specifier` */ Format specifier; const sc = format[idx]; genSpecifier = sc; switch (sc) { case 'd': case 'i': specifier = flags == Modifier.none ? Format.d : flags == Modifier.hh ? Format.hhd : flags == Modifier.h ? Format.hd : flags == Modifier.ll ? Format.lld : flags == Modifier.l ? Format.ld : flags == Modifier.j ? Format.jd : flags == Modifier.z ? Format.zd : flags == Modifier.t ? Format.td : Format.error; break; case 'u': case 'o': case 'x': case 'X': specifier = flags == Modifier.none ? Format.u : flags == Modifier.hh ? Format.hhu : flags == Modifier.h ? Format.hu : flags == Modifier.ll ? Format.llu : flags == Modifier.l ? Format.lu : flags == Modifier.j ? Format.ju : flags == Modifier.z ? Format.zd : flags == Modifier.t ? Format.td : Format.error; break; case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': specifier = flags == Modifier.none ? Format.g : flags == Modifier.L ? Format.Lg : flags == Modifier.l ? Format.lg : Format.error; break; case 'c': specifier = flags == Modifier.none ? Format.c : flags == Modifier.l ? Format.lc : Format.error; break; case 's': specifier = flags == Modifier.none ? Format.s : flags == Modifier.l ? Format.ls : Format.error; break; case 'p': specifier = flags == Modifier.none ? Format.p : Format.error; break; case 'n': specifier = flags == Modifier.none ? Format.n : flags == Modifier.ll ? Format.lln : flags == Modifier.l ? Format.ln : flags == Modifier.hh ? Format.hhn : flags == Modifier.h ? Format.hn : flags == Modifier.j ? Format.jn : flags == Modifier.z ? Format.zn : flags == Modifier.t ? Format.tn : Format.error; break; case 'C': // POSIX.1-2017 X/Open System Interfaces (XSI) // %C format is equivalent to %lc specifier = flags == Modifier.none ? Format.lc : Format.error; break; case 'S': // POSIX.1-2017 X/Open System Interfaces (XSI) // %S format is equivalent to %ls specifier = flags == Modifier.none ? Format.ls : Format.error; break; default: specifier = Format.error; break; } ++idx; return specifier; // success } @("parseGenericFormatSpecifier") unittest { char genSpecifier; size_t idx; void testG(string fmtStr, Format expectedFormat, char expectedGenSpecifier) { idx = 0; assert(parseGenericFormatSpecifier(fmtStr, idx, genSpecifier) == expectedFormat); assert(genSpecifier == expectedGenSpecifier); } testG("hhd", Format.hhd, 'd'); testG("hn", Format.hn, 'n'); testG("ji", Format.jd, 'i'); testG("lu", Format.lu, 'u'); idx = 0; assert(parseGenericFormatSpecifier("k", idx, genSpecifier) == Format.error); } @("parsePrintfFormatSpecifier") unittest { bool useGNUExts = false; size_t idx = 0; bool widthStar; bool precisionStar; void testP(string fmtStr, Format expectedFormat, size_t expectedIdx) { idx = 0; assert(parsePrintfFormatSpecifier(fmtStr, idx, widthStar, precisionStar, useGNUExts) == expectedFormat); assert(idx == expectedIdx); } // one for each Format testP("%d", Format.d, 2); assert(!widthStar && !precisionStar); testP("%ld", Format.ld, 3); testP("%lld", Format.lld, 4); testP("%jd", Format.jd, 3); testP("%zd", Format.zd, 3); testP("%td", Format.td, 3); testP("%g", Format.g, 2); testP("%Lg", Format.Lg, 3); testP("%p", Format.p, 2); testP("%n", Format.n, 2); testP("%ln", Format.ln, 3); testP("%lln", Format.lln, 4); testP("%hn", Format.hn, 3); testP("%hhn", Format.hhn, 4); testP("%jn", Format.jn, 3); testP("%zn", Format.zn, 3); testP("%tn", Format.tn, 3); testP("%c", Format.c, 2); testP("%lc", Format.lc, 3); testP("%s", Format.s, 2); testP("%ls", Format.ls, 3); testP("%%", Format.percent, 2); // Synonyms testP("%i", Format.d, 2); testP("%u", Format.u, 2); testP("%o", Format.u, 2); testP("%x", Format.u, 2); testP("%X", Format.u, 2); testP("%f", Format.g, 2); testP("%F", Format.g, 2); testP("%G", Format.g, 2); testP("%a", Format.g, 2); testP("%La", Format.Lg, 3); testP("%A", Format.g, 2); testP("%lg", Format.lg, 3); // width, precision testP("%*d", Format.d, 3); assert(widthStar && !precisionStar); testP("%.*d", Format.d, 4); assert(!widthStar && precisionStar); testP("%*.*d", Format.d, 5); assert(widthStar && precisionStar); // Too short formats foreach (s; ["%", "%-", "%+", "% ", "%#", "%0", "%*", "%1", "%19", "%.", "%.*", "%.1", "%.12", "%j", "%z", "%t", "%l", "%h", "%ll", "%hh"]) { testP(s, Format.error, s.length); } // Undefined format combinations foreach (s; ["%#d", "%llg", "%jg", "%zg", "%tg", "%hg", "%hhg", "%#c", "%0c", "%jc", "%zc", "%tc", "%Lc", "%hc", "%hhc", "%llc", "%#s", "%0s", "%js", "%zs", "%ts", "%Ls", "%hs", "%hhs", "%lls", "%jp", "%zp", "%tp", "%Lp", "%hp", "%lp", "%hhp", "%llp", "%-n", "%+n", "% n", "%#n", "%0n", "%*n", "%1n", "%19n", "%.n", "%.*n", "%.1n", "%.12n", "%Ln", "%K"]) { testP(s, Format.error, s.length); } testP("%C", Format.lc, 2); testP("%S", Format.ls, 2); // GNU extensions: explicitly toggle ISO/GNU flag. foreach (s; ["%jm", "%zm", "%tm", "%Lm", "%hm", "%hhm", "%lm", "%llm", "%#m", "%+m", "%-m", "% m", "%0m"]) { useGNUExts = false; testP(s, Format.error, s.length); useGNUExts = true; testP(s, Format.error, s.length); } foreach (s; ["%m", "%md", "%mz", "%mc", "%mm", "%msyz", "%ml", "%mlz", "%mlc", "%mlm"]) { // valid cases, all parsed as `%m` // GNU printf() useGNUExts = true; testP(s, Format.GNU_m, 2); // ISO printf() useGNUExts = false; testP(s, Format.error, 2); } } @("parseScanfFormatSpecifier") unittest { size_t idx; bool asterisk; void testS(string fmtStr, Format expectedFormat, size_t expectedIdx) { idx = 0; assert(parseScanfFormatSpecifier(fmtStr, idx, asterisk) == expectedFormat); assert(idx == expectedIdx); } // one for each Format testS("%d", Format.d, 2); testS("%hhd", Format.hhd, 4); testS("%hd", Format.hd, 3); testS("%ld", Format.ld, 3); testS("%lld", Format.lld, 4); testS("%jd", Format.jd, 3); testS("%zd", Format.zd, 3); testS("%td", Format.td, 3); testS("%u", Format.u, 2); testS("%hhu", Format.hhu, 4); testS("%hu", Format.hu, 3); testS("%lu", Format.lu, 3); testS("%llu", Format.llu, 4); testS("%ju", Format.ju, 3); testS("%g", Format.g, 2); testS("%lg", Format.lg, 3); testS("%Lg", Format.Lg, 3); testS("%p", Format.p, 2); testS("%s", Format.s, 2); testS("%ls", Format.ls, 3); testS("%%", Format.percent, 2); // Synonyms testS("%i", Format.d, 2); testS("%n", Format.n, 2); testS("%o", Format.u, 2); testS("%x", Format.u, 2); testS("%f", Format.g, 2); testS("%e", Format.g, 2); testS("%a", Format.g, 2); testS("%c", Format.c, 2); // asterisk testS("%*d", Format.d, 3); assert(asterisk); testS("%9ld", Format.ld, 4); assert(!asterisk); testS("%*25984hhd", Format.hhd, 10); assert(asterisk); // scansets testS("%[a-zA-Z]", Format.s, 9); assert(!asterisk); testS("%*25l[a-z]", Format.ls, 10); assert(asterisk); testS("%[]]", Format.s, 4); assert(!asterisk); testS("%[^]]", Format.s, 5); assert(!asterisk); // Too short formats foreach (s; ["%", "% ", "%#", "%0", "%*", "%1", "%19", "%j", "%z", "%t", "%l", "%h", "%ll", "%hh", "%K"]) { testS(s, Format.error, s.length); } // Undefined format combinations foreach (s; ["%Ld", "%llg", "%jg", "%zg", "%tg", "%hg", "%hhg", "%jc", "%zc", "%tc", "%Lc", "%hc", "%hhc", "%llc", "%jp", "%zp", "%tp", "%Lp", "%hp", "%lp", "%hhp", "%llp", "%-", "%+", "%#", "%0", "%.", "%Ln"]) { testS(s, Format.error, s.length); } // Invalid scansets foreach (s; ["%[]", "%[^", "%[^]", "%[s", "%[0-9lld", "%[", "%l[^]"]) { testS(s, Format.error, s.length); } // Posix extensions foreach (s; ["%jm", "%zm", "%tm", "%Lm", "%hm", "%hhm", "%lm", "%llm", "%m", "%ma", "%md", "%ml", "%mm", "%mlb", "%mlj", "%mlr", "%mlz", "%LC", "%lC", "%llC", "%jC", "%tC", "%hC", "%hhC", "%zC", "%LS", "%lS", "%llS", "%jS", "%tS", "%hS", "%hhS", "%zS"]) { testS(s, Format.error, s.length); } testS("%mc", Format.POSIX_ms, 3); testS("%ms", Format.POSIX_ms, 3); testS("%m[0-9]", Format.POSIX_ms, 7); testS("%mlc", Format.POSIX_mls, 4); testS("%mls", Format.POSIX_mls, 4); testS("%ml[^0-9]", Format.POSIX_mls, 9); testS("%mC", Format.POSIX_mls, 3); testS("%mS", Format.POSIX_mls, 3); testS("%C", Format.lc, 2); testS("%S", Format.ls, 2); } ldc-1.40.0-src/dmd/parse.d0000644000000000000000000121312014727557031013657 0ustar rootroot/** * Takes a token stream from the lexer, and parses it into an abstract syntax tree. * * Specification: $(LINK2 https://dlang.org/spec/grammar.html, D Grammar) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/parse.d, _parse.d) * Documentation: https://dlang.org/phobos/dmd_parse.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/parse.d */ module dmd.parse; import core.stdc.stdio; import core.stdc.string; import dmd.astenums; import dmd.errorsink; import dmd.id; import dmd.identifier; import dmd.lexer; import dmd.location; import dmd.root.filename; import dmd.common.outbuffer; import dmd.root.rmem; import dmd.rootobject; import dmd.root.string; import dmd.tokens; alias CompileEnv = dmd.lexer.CompileEnv; /*********************************************************** */ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { AST.ModuleDeclaration* md; protected { AST.Module mod; LINK linkage; Loc linkLoc; CPPMANGLE cppmangle; Loc endloc; // set to location of last right curly int inBrackets; // inside [] of array index or slice Loc lookingForElse; // location of lonely if looking for an else bool doUnittests; // parse unittest blocks } bool transitionIn = false; /// `-transition=in` is active, `in` parameters are listed /********************* * Use this constructor for string mixins. * Input: * loc = location in source file of mixin */ extern (D) this(const ref Loc loc, AST.Module _module, const(char)[] input, bool doDocComment, ErrorSink errorSink, const CompileEnv* compileEnv, const bool doUnittests) scope { //printf("Parser::Parser()1 %d\n", doUnittests); this(_module, input, doDocComment, errorSink, compileEnv, doUnittests); scanloc = loc; } /************************************************** * Main Parser constructor. */ extern (D) this(AST.Module _module, const(char)[] input, bool doDocComment, ErrorSink errorSink, const CompileEnv* compileEnv, const bool doUnittests) scope { super(_module ? _module.srcfile.toChars() : null, input.ptr, 0, input.length, doDocComment, false, errorSink, compileEnv); //printf("Parser::Parser()2 %d\n", doUnittests); this.mod = _module; this.linkage = LINK.d; this.doUnittests = doUnittests; } /++ + Parse a module, i.e. the optional `module x.y.z` declaration and all declarations + found in the current file. + + Returns: the list of declarations or an empty list in case of malformed declarations, + the module declaration will be stored as `this.md` if found +/ AST.Dsymbols* parseModule() { if (!parseModuleDeclaration()) return errorReturn(); return parseModuleContent(); } /++ + Parse the optional module declaration + + Returns: false if a malformed module declaration was found +/ final bool parseModuleDeclaration() { const comment = token.blockComment; bool isdeprecated = false; AST.Expression msg = null; // Parse optional module attributes parseModuleAttributes(msg, isdeprecated); // ModuleDeclaration leads off if (token.value == TOK.module_) { const loc = token.loc; nextToken(); /* parse ModuleFullyQualifiedName * https://dlang.org/spec/module.html#ModuleFullyQualifiedName */ if (token.value != TOK.identifier) { error("identifier expected following `module`"); return false; } Identifier[] a; Identifier id = token.ident; while (nextToken() == TOK.dot) { a ~= id; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `package`"); return false; } id = token.ident; } md = new AST.ModuleDeclaration(loc, a, id, msg, isdeprecated); if (token.value != TOK.semicolon) error("`;` expected following module declaration instead of `%s`", token.toChars()); nextToken(); addComment(mod, comment); } return true; } /++ + Parse the content of a module, i.e. all declarations found until the end of file. + + Returns: the list of declarations or an empty list in case of malformed declarations +/ final AST.Dsymbols* parseModuleContent() { AST.Dsymbol lastDecl = mod; AST.Dsymbols* decldefs = parseDeclDefs(0, &lastDecl); if (token.value == TOK.rightCurly) { error("unmatched closing brace"); return errorReturn(); } if (token.value != TOK.endOfFile) { error("unrecognized declaration"); return errorReturn(); } return decldefs; } /++ + Skips to the end of the current declaration - denoted by either `;` or EOF + + Returns: An empty list of Dsymbols +/ private AST.Dsymbols* errorReturn() { while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); return new AST.Dsymbols(); } /********************************** * Parse the ModuleAttributes preceding a module declaration. * ModuleDeclaration: * ModuleAttributes(opt) module ModuleFullyQualifiedName ; * https://dlang.org/spec/module.html#ModuleAttributes * Params: * msg = set to the AssignExpression from DeprecatedAttribute https://dlang.org/spec/module.html#DeprecatedAttribute * isdeprecated = set to true if a DeprecatedAttribute is seen */ private void parseModuleAttributes(out AST.Expression msg, out bool isdeprecated) { Token* tk; if (!(skipAttributes(&token, &tk) && tk.value == TOK.module_)) return; // no module attributes AST.Expressions* udas = null; while (token.value != TOK.module_) { switch (token.value) { case TOK.deprecated_: { // deprecated (...) module ... if (isdeprecated) error("there is only one deprecation attribute allowed for module declaration"); isdeprecated = true; nextToken(); if (token.value == TOK.leftParenthesis) { check(TOK.leftParenthesis); msg = parseAssignExp(); check(TOK.rightParenthesis); } break; } case TOK.at: { AST.Expressions* exps = null; const stc = parseAttribute(exps); if (stc & atAttrGroup) { error("`@%s` attribute for module declaration is not supported", token.toChars()); } else { static if (is(typeof(mod.edition))) if (exps && exps.length > 0) if (auto id = (*exps)[0].isIdentifierExp()) if (id.ident == Id.__edition_latest_do_not_use) { mod.edition = Edition.latest; continue; } udas = AST.UserAttributeDeclaration.concat(udas, exps); } if (stc) nextToken(); break; } default: { error("`module` expected instead of `%s`", token.toChars()); nextToken(); break; } } } if (udas) { auto a = new AST.Dsymbols(); auto udad = new AST.UserAttributeDeclaration(udas, a); mod.userAttribDecl = udad; } } final: /** * Parses a `deprecated` declaration * * Params: * msg = Deprecated message, if any. * Used to support overriding a deprecated storage class with * a deprecated declaration with a message, but to error * if both declaration have a message. * * Returns: * Whether the deprecated declaration has a message */ private bool parseDeprecatedAttribute(ref AST.Expression msg) { if (peekNext() != TOK.leftParenthesis) return false; nextToken(); check(TOK.leftParenthesis); AST.Expression e = parseAssignExp(); check(TOK.rightParenthesis); if (msg) { error(token.loc, "conflicting storage class `deprecated(%s)` and `deprecated(%s)`", msg.toChars(), e.toChars()); } msg = e; return true; } /************************************ * Parse declarations and definitions * Params: * once = !=0 means parse exactly one decl or def * pLastDecl = set to last decl or def parsed * pAttrs = keep track of attributes * Returns: * array of declared symbols */ AST.Dsymbols* parseDeclDefs(int once, AST.Dsymbol* pLastDecl = null, PrefixAttributes!AST* pAttrs = null) { AST.Dsymbol lastDecl = null; // used to link unittest to its previous declaration if (!pLastDecl) pLastDecl = &lastDecl; const linksave = linkage; // save global state //printf("Parser::parseDeclDefs()\n"); auto decldefs = new AST.Dsymbols(); do { // parse result AST.Dsymbol s = null; AST.Dsymbols* a = null; PrefixAttributes!AST attrs; if (!once || !pAttrs) { pAttrs = &attrs; pAttrs.comment = token.blockComment.ptr; } AST.Visibility.Kind prot; StorageClass stc; AST.Condition condition; linkage = linksave; Loc startloc; Loc scdLoc; switch (token.value) { case TOK.enum_: { /* Determine if this is a manifest constant declaration, * or a conventional enum. */ const tv = peekNext(); if (tv == TOK.leftCurly || tv == TOK.colon) s = parseEnum(); else if (tv != TOK.identifier) goto Ldeclaration; else { const nextv = peekNext2(); if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) s = parseEnum(); else goto Ldeclaration; } break; } case TOK.import_: a = parseImport(); // keep pLastDecl break; case TOK.template_: s = cast(AST.Dsymbol)parseTemplateDeclaration(); break; case TOK.mixin_: { const loc = token.loc; switch (peekNext()) { case TOK.leftParenthesis: { // MixinType if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) goto Ldeclaration; // mixin(string) nextToken(); auto exps = parseArguments(); check(TOK.semicolon); s = new AST.MixinDeclaration(loc, exps); break; } case TOK.template_: // mixin template nextToken(); s = cast(AST.Dsymbol)parseTemplateDeclaration(true); break; default: s = parseMixin(); break; } break; } case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: case TOK.alias_: case TOK.identifier: case TOK.super_: case TOK.typeof_: case TOK.dot: case TOK.vector: case TOK.struct_: case TOK.union_: case TOK.class_: case TOK.interface_: case TOK.traits: Ldeclaration: a = parseDeclarations(false, pAttrs, pAttrs.comment); if (a && a.length) *pLastDecl = (*a)[a.length - 1]; break; case TOK.this_: if (peekNext() == TOK.dot) goto Ldeclaration; s = parseCtor(pAttrs); break; case TOK.tilde: s = parseDtor(pAttrs); break; case TOK.invariant_: const tv = peekNext(); if (tv == TOK.leftParenthesis || tv == TOK.leftCurly) { // invariant { statements... } // invariant() { statements... } // invariant (expression); s = parseInvariant(pAttrs); break; } error("invariant body expected, not `%s`", token.toChars()); goto Lerror; case TOK.unittest_: /** * Ignore unittests in non-root modules. * * This mainly means that unittests *inside templates* are only * ever instantiated if the module lexically declaring the * template is one of the root modules. * * E.g., compiling some project with `-unittest` does NOT * compile and later run any unittests in instantiations of * templates declared in other libraries. * * Declaring unittests *inside* templates is considered an anti- * pattern. In almost all cases, the unittests don't depend on * the template parameters, but instantiate the template with * fixed arguments (e.g., Nullable!T unittests instantiating * Nullable!int), so compiling and running identical tests for * each template instantiation is hardly desirable. * But adding a unittest right below some function being tested * is arguably good for locality, so unittests end up inside * templates. * To make sure a template's unittests are run, it should be * instantiated in the same module, e.g., some module-level * unittest. * * Another reason for ignoring unittests in templates from non- * root modules is for template codegen culling via * TemplateInstance.needsCodegen(). If the compiler decides not * to emit some Nullable!bool because there's an existing * instantiation in some non-root module, it has no idea whether * that module was compiled with -unittest too, and so whether * Nullable!int (instantiated in some unittest inside the * Nullable template) can be culled too. By ignoring unittests * in non-root modules, the compiler won't consider any * template instantiations in these unittests as candidates for * further codegen culling. */ // The isRoot check is here because it can change after parsing begins (see dmodule.d) if (doUnittests && mod.isRoot()) { linkage = LINK.d; // unittests have D linkage s = parseUnitTest(pAttrs); if (*pLastDecl) (*pLastDecl).ddocUnittest = cast(AST.UnitTestDeclaration)s; } else { // Skip over unittest block by counting { } Loc loc = token.loc; int braces = 0; while (1) { nextToken(); switch (token.value) { case TOK.leftCurly: ++braces; continue; case TOK.rightCurly: if (--braces) continue; nextToken(); break; case TOK.endOfFile: /* { */ error(loc, "closing `}` of unittest not found before end of file"); goto Lerror; default: continue; } break; } // Workaround 14894. Add an empty unittest declaration to keep // the number of symbols in this scope independent of -unittest. s = new AST.UnitTestDeclaration(loc, token.loc, STC.undefined_, null); } break; case TOK.new_: s = parseNew(pAttrs); break; case TOK.colon: case TOK.leftCurly: error("declaration expected, not `%s`", token.toChars()); goto Lerror; case TOK.rightCurly: case TOK.endOfFile: if (once) error("declaration expected, not `%s`", token.toChars()); return decldefs; case TOK.static_: { const next = peekNext(); if (next == TOK.this_) s = parseStaticCtor(pAttrs); else if (next == TOK.tilde) s = parseStaticDtor(pAttrs); else if (next == TOK.assert_) s = parseStaticAssert(); else if (next == TOK.if_) { const Loc loc = token.loc; condition = parseStaticIfCondition(); AST.Dsymbols* athen; if (token.value == TOK.colon) athen = parseBlock(pLastDecl); else { const lookingForElseSave = lookingForElse; lookingForElse = token.loc; athen = parseBlock(pLastDecl); lookingForElse = lookingForElseSave; } AST.Dsymbols* aelse = null; if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); aelse = parseBlock(pLastDecl); checkDanglingElse(elseloc); } s = new AST.StaticIfDeclaration(loc, condition, athen, aelse); } else if (next == TOK.import_) { a = parseImport(); // keep pLastDecl } else if (next == TOK.foreach_ || next == TOK.foreach_reverse_) { s = parseForeach!(AST.StaticForeachDeclaration)(token.loc, pLastDecl); } else { stc = STC.static_; goto Lstc; } break; } case TOK.const_: if (peekNext() == TOK.leftParenthesis) goto Ldeclaration; stc = STC.const_; goto Lstc; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) goto Ldeclaration; stc = STC.immutable_; goto Lstc; case TOK.shared_: { const next = peekNext(); if (next == TOK.leftParenthesis) goto Ldeclaration; if (next == TOK.static_) { TOK next2 = peekNext2(); if (next2 == TOK.this_) { s = parseSharedStaticCtor(pAttrs); break; } if (next2 == TOK.tilde) { s = parseSharedStaticDtor(pAttrs); break; } } stc = STC.shared_; goto Lstc; } case TOK.inout_: if (peekNext() == TOK.leftParenthesis) goto Ldeclaration; stc = STC.wild; goto Lstc; case TOK.final_: stc = STC.final_; goto Lstc; case TOK.auto_: stc = STC.auto_; goto Lstc; case TOK.scope_: stc = STC.scope_; goto Lstc; case TOK.override_: stc = STC.override_; goto Lstc; case TOK.abstract_: stc = STC.abstract_; goto Lstc; case TOK.synchronized_: stc = STC.synchronized_; goto Lstc; case TOK.nothrow_: stc = STC.nothrow_; goto Lstc; case TOK.pure_: stc = STC.pure_; goto Lstc; case TOK.ref_: stc = STC.ref_; goto Lstc; case TOK.gshared: stc = STC.gshared; goto Lstc; case TOK.at: { AST.Expressions* exps = null; stc = parseAttribute(exps); if (stc) goto Lstc; // it's a predefined attribute // no redundant/conflicting check for UDAs pAttrs.udas = AST.UserAttributeDeclaration.concat(pAttrs.udas, exps); goto Lautodecl; } Lstc: pAttrs.storageClass = appendStorageClass(pAttrs.storageClass, stc); scdLoc = token.loc; nextToken(); Lautodecl: /* Look for auto initializers: * storage_class identifier = initializer; * storage_class identifier(...) = initializer; */ if (token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)) { a = parseAutoDeclarations(getStorageClass!AST(pAttrs), pAttrs.comment); if (a && a.length) *pLastDecl = (*a)[a.length - 1]; if (pAttrs.udas) { s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; } /* Look for return type inference for template functions. */ Token* tk; if (token.value == TOK.identifier && skipParens(peek(&token), &tk) && skipAttributes(tk, &tk) && (tk.value == TOK.leftParenthesis || tk.value == TOK.leftCurly || tk.value == TOK.in_ || tk.value == TOK.out_ || tk.value == TOK.do_ || tk.value == TOK.goesTo || tk.value == TOK.identifier && tk.ident == Id._body)) { if (tk.value == TOK.identifier && tk.ident == Id._body) usageOfBodyKeyword(); a = parseDeclarations(true, pAttrs, pAttrs.comment); if (a && a.length) *pLastDecl = (*a)[a.length - 1]; if (pAttrs.udas) { s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; } a = parseBlock(pLastDecl, pAttrs); auto stc2 = getStorageClass!AST(pAttrs); if (stc2 != STC.undefined_) { s = new AST.StorageClassDeclaration(scdLoc, stc2, a); } if (pAttrs.udas) { if (s) { a = new AST.Dsymbols(); a.push(s); } s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; case TOK.deprecated_: { stc |= STC.deprecated_; if (!parseDeprecatedAttribute(pAttrs.depmsg)) goto Lstc; a = parseBlock(pLastDecl, pAttrs); s = new AST.DeprecatedDeclaration(pAttrs.depmsg, a); pAttrs.depmsg = null; break; } case TOK.leftBracket: { if (peekNext() == TOK.rightBracket) error("empty attribute list is not allowed"); error("use `@(attributes)` instead of `[attributes]`"); AST.Expressions* exps = parseArguments(); // no redundant/conflicting check for UDAs pAttrs.udas = AST.UserAttributeDeclaration.concat(pAttrs.udas, exps); a = parseBlock(pLastDecl, pAttrs); if (pAttrs.udas) { s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; } case TOK.extern_: { if (peekNext() != TOK.leftParenthesis) { stc = STC.extern_; goto Lstc; } const linkLoc = token.loc; auto res = parseLinkage(); if (pAttrs.link != LINK.default_) { if (pAttrs.link != res.link) { error(token.loc, "conflicting linkage `extern (%s)` and `extern (%s)`", AST.linkageToChars(pAttrs.link), AST.linkageToChars(res.link)); } else if (res.idents || res.identExps || res.cppmangle != CPPMANGLE.def) { // Allow: // extern(C++, foo) extern(C++, bar) void foo(); // to be equivalent with: // extern(C++, foo.bar) void foo(); // Allow also: // extern(C++, "ns") extern(C++, class) struct test {} // extern(C++, class) extern(C++, "ns") struct test {} } else error("redundant linkage `extern (%s)`", AST.linkageToChars(pAttrs.link)); } pAttrs.link = res.link; this.linkage = res.link; this.linkLoc = linkLoc; a = parseBlock(pLastDecl, pAttrs); if (res.idents) { assert(res.link == LINK.cpp); assert(res.idents.length); for (size_t i = res.idents.length; i;) { Identifier id = (*res.idents)[--i]; if (s) { a = new AST.Dsymbols(); a.push(s); } s = new AST.Nspace(linkLoc, id, null, a); } pAttrs.link = LINK.default_; } else if (res.identExps) { assert(res.link == LINK.cpp); assert(res.identExps.length); for (size_t i = res.identExps.length; i;) { AST.Expression exp = (*res.identExps)[--i]; if (s) { a = new AST.Dsymbols(); a.push(s); } s = new AST.CPPNamespaceDeclaration(linkLoc, exp, a); } pAttrs.link = LINK.default_; } else if (res.cppmangle != CPPMANGLE.def) { assert(res.link == LINK.cpp); s = new AST.CPPMangleDeclaration(linkLoc, res.cppmangle, a); } else if (pAttrs.link != LINK.default_) { s = new AST.LinkDeclaration(linkLoc, pAttrs.link, a); pAttrs.link = LINK.default_; } break; } case TOK.private_: prot = AST.Visibility.Kind.private_; goto Lprot; case TOK.package_: prot = AST.Visibility.Kind.package_; goto Lprot; case TOK.protected_: prot = AST.Visibility.Kind.protected_; goto Lprot; case TOK.public_: prot = AST.Visibility.Kind.public_; goto Lprot; case TOK.export_: prot = AST.Visibility.Kind.export_; goto Lprot; Lprot: { if (pAttrs.visibility.kind != AST.Visibility.Kind.undefined) { if (pAttrs.visibility.kind != prot) error(token.loc, "conflicting visibility attribute `%s` and `%s`", AST.visibilityToChars(pAttrs.visibility.kind), AST.visibilityToChars(prot)); else error("redundant visibility attribute `%s`", AST.visibilityToChars(prot)); } pAttrs.visibility.kind = prot; const attrloc = token.loc; nextToken(); // optional qualified package identifier to bind // visibility to Identifier[] pkg_prot_idents; if (pAttrs.visibility.kind == AST.Visibility.Kind.package_ && token.value == TOK.leftParenthesis) { pkg_prot_idents = parseQualifiedIdentifier("protection package"); if (pkg_prot_idents) check(TOK.rightParenthesis); else { while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); break; } } a = parseBlock(pLastDecl, pAttrs); if (pAttrs.visibility.kind != AST.Visibility.Kind.undefined) { if (pAttrs.visibility.kind == AST.Visibility.Kind.package_ && pkg_prot_idents) s = new AST.VisibilityDeclaration(attrloc, pkg_prot_idents, a); else s = new AST.VisibilityDeclaration(attrloc, pAttrs.visibility, a); pAttrs.visibility = AST.Visibility(AST.Visibility.Kind.undefined); } break; } case TOK.align_: { const attrLoc = token.loc; nextToken(); AST.Expression e = null; // default if (token.value == TOK.leftParenthesis) { nextToken(); e = parseAssignExp(); check(TOK.rightParenthesis); } if (pAttrs.setAlignment) { if (e) error("redundant alignment attribute `align(%s)`", e.toChars()); else error("redundant alignment attribute `align`"); } pAttrs.setAlignment = true; pAttrs.ealign = e; a = parseBlock(pLastDecl, pAttrs); if (pAttrs.setAlignment) { s = new AST.AlignDeclaration(attrLoc, pAttrs.ealign, a); pAttrs.setAlignment = false; pAttrs.ealign = null; } break; } case TOK.pragma_: { AST.Expressions* args = null; const loc = token.loc; nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("`pragma(identifier)` expected"); goto Lerror; } Identifier ident = token.ident; nextToken(); if (token.value == TOK.comma && peekNext() != TOK.rightParenthesis) args = parseArguments(); // pragma(identifier, args...) else check(TOK.rightParenthesis); // pragma(identifier) AST.Dsymbols* a2 = null; if (token.value == TOK.semicolon) { /* https://issues.dlang.org/show_bug.cgi?id=2354 * Accept single semicolon as an empty * DeclarationBlock following attribute. * * Attribute DeclarationBlock * Pragma DeclDef * ; */ nextToken(); } else a2 = parseBlock(pLastDecl); s = new AST.PragmaDeclaration(loc, ident, args, a2); break; } case TOK.debug_: startloc = token.loc; nextToken(); if (token.value == TOK.assign) { s = parseDebugSpecification(); break; } condition = parseDebugCondition(); goto Lcondition; case TOK.version_: startloc = token.loc; nextToken(); if (token.value == TOK.assign) { s = parseVersionSpecification(); break; } condition = parseVersionCondition(); goto Lcondition; Lcondition: { AST.Dsymbols* athen; if (token.value == TOK.colon) athen = parseBlock(pLastDecl); else { const lookingForElseSave = lookingForElse; lookingForElse = token.loc; athen = parseBlock(pLastDecl); lookingForElse = lookingForElseSave; } AST.Dsymbols* aelse = null; if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); aelse = parseBlock(pLastDecl); checkDanglingElse(elseloc); } s = new AST.ConditionalDeclaration(startloc, condition, athen, aelse); break; } case TOK.semicolon: // empty declaration //error("empty declaration"); nextToken(); continue; default: error("declaration expected, not `%s`", token.toChars()); Lerror: while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); s = null; continue; } if (s) { if (!s.isAttribDeclaration()) *pLastDecl = s; decldefs.push(s); addComment(s, pAttrs.comment); } else if (a && a.length) { decldefs.append(a); } } while (!once); linkage = linksave; return decldefs; } /***************************************** * Parse auto declarations of the form: * storageClass ident = init, ident = init, ... ; * and return the array of them. * Starts with token on the first ident. * Ends with scanner past closing ';' */ private AST.Dsymbols* parseAutoDeclarations(StorageClass storageClass, const(char)* comment) { //printf("parseAutoDeclarations\n"); auto a = new AST.Dsymbols(); while (1) { const loc = token.loc; Identifier ident = token.ident; nextToken(); // skip over ident AST.TemplateParameters* tpl = null; if (token.value == TOK.leftParenthesis) tpl = parseTemplateParameterList(); check(TOK.assign); // skip over '=' AST.Initializer _init = parseInitializer(); auto v = new AST.VarDeclaration(loc, null, ident, _init, storageClass); AST.Dsymbol s = v; if (tpl) { auto a2 = new AST.Dsymbols(); a2.push(v); auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2, 0); s = tempdecl; } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); if (!(token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign))) { error("identifier expected following comma"); break; } addComment(s, comment); continue; default: error("semicolon expected following auto declaration, not `%s`", token.toChars()); break; } break; } return a; } /******************************************** * Parse declarations after an align, visibility, or extern decl. */ private AST.Dsymbols* parseBlock(AST.Dsymbol* pLastDecl, PrefixAttributes!AST* pAttrs = null) { AST.Dsymbols* a = null; //printf("parseBlock()\n"); switch (token.value) { case TOK.semicolon: error("declaration expected following attribute, not `;`"); nextToken(); break; case TOK.endOfFile: error("declaration expected following attribute, not end of file"); break; case TOK.leftCurly: { const lcLoc = token.loc; const lookingForElseSave = lookingForElse; lookingForElse = Loc(); nextToken(); a = parseDeclDefs(0, pLastDecl); if (token.value != TOK.rightCurly) { /* left curly brace */ error("matching `}` expected, not `%s`", token.toChars()); eSink.errorSupplemental(lcLoc, "unmatched `{`"); } else nextToken(); lookingForElse = lookingForElseSave; break; } case TOK.colon: nextToken(); a = parseDeclDefs(0, pLastDecl); // grab declarations up to closing curly bracket break; default: a = parseDeclDefs(1, pLastDecl, pAttrs); break; } return a; } /** * Provide an error message if `added` contains storage classes which are * redundant with those in `orig`; otherwise, return the combination. * * Params: * orig = The already applied storage class. * added = The new storage class to add to `orig`. * * Returns: * The combination of both storage classes (`orig | added`). */ private StorageClass appendStorageClass(StorageClass orig, StorageClass added) { void checkConflictSTCGroup(bool at = false)(StorageClass group) { if (added & group && orig & group & ((orig & group) - 1)) error( at ? "conflicting attribute `@%s`" : "conflicting attribute `%s`", token.toChars()); } if (orig & added) { OutBuffer buf; AST.stcToBuffer(buf, added); error("redundant attribute `%s`", buf.peekChars()); return orig | added; } const Redundant = (STC.const_ | STC.scope_ | STC.ref_); orig |= added; if ((orig & STC.in_) && (added & Redundant)) { if (added & STC.const_) error("attribute `const` is redundant with previously-applied `in`"); else if (compileEnv.previewIn) { error("attribute `%s` is redundant with previously-applied `in`", (orig & STC.scope_) ? "scope".ptr : "ref".ptr); } else if (added & STC.ref_) { // accept using `in ref` for legacy compatibility } else { version (IN_GCC) error("attribute `scope` cannot be applied with `in`, use `-fpreview=in` instead"); else error("attribute `scope` cannot be applied with `in`, use `-preview=in` instead"); } return orig; } if ((added & STC.in_) && (orig & Redundant)) { if (orig & STC.const_) error("attribute `in` cannot be added after `const`: remove `const`"); else if (compileEnv.previewIn) { // Windows `printf` does not support `%1$s` const(char*) stc_str = (orig & STC.scope_) ? "scope".ptr : "ref".ptr; error(token.loc, "attribute `in` cannot be added after `%s`: remove `%s`", stc_str, stc_str); } else if (orig & STC.ref_) { // accept using `in ref` for legacy compatibility } else { version (IN_GCC) error("attribute `in` cannot be added after `scope`: remove `scope` and use `-fpreview=in`"); else error("attribute `in` cannot be added after `scope`: remove `scope` and use `-preview=in`"); } return orig; } checkConflictSTCGroup(STC.const_ | STC.immutable_ | STC.manifest); checkConflictSTCGroup(STC.gshared | STC.shared_); checkConflictSTCGroup!true(STC.safeGroup); return orig; } /*********************************************** * Parse attribute(s), lexer is on '@'. * * Attributes can be builtin (e.g. `@safe`, `@nogc`, etc...), * or be user-defined (UDAs). In the former case, we return the storage * class via the return value, while in thelater case we return `0` * and set `pudas`. * * Params: * pudas = An array of UDAs to append to * * Returns: * If the attribute is builtin, the return value will be non-zero. * Otherwise, 0 is returned, and `pudas` will be appended to. */ private StorageClass parseAttribute(ref AST.Expressions* udas) { nextToken(); if (token.value == TOK.identifier) { // If we find a builtin attribute, we're done, return immediately. if (StorageClass stc = isBuiltinAtAttribute(token.ident)) return stc; // Allow identifier, template instantiation, or function call // for `@Argument` (single UDA) form. AST.Expression exp = parsePrimaryExp(); if (token.value == TOK.leftParenthesis) { const loc = token.loc; AST.Expressions* args = new AST.Expressions(); AST.Identifiers* names = new AST.Identifiers(); parseNamedArguments(args, names); exp = new AST.CallExp(loc, exp, args, names); } if (udas is null) udas = new AST.Expressions(); udas.push(exp); return 0; } AST.Expression templateArgToExp(RootObject o, const ref Loc loc) { switch (o.dyncast) { case DYNCAST.expression: return cast(AST.Expression) o; case DYNCAST.type: return new AST.TypeExp(loc, cast(AST.Type)o); default: assert(0); } } if (token.value == TOK.leftParenthesis) { // Multi-UDAs ( `@( ArgumentList )`) form, concatenate with existing if (peekNext() == TOK.rightParenthesis) error("empty attribute list is not allowed"); if (udas is null) udas = new AST.Expressions(); auto args = parseTemplateArgumentList(); foreach (arg; *args) udas.push(templateArgToExp(arg, token.loc)); return 0; } if (auto o = parseTemplateSingleArgument()) { if (udas is null) udas = new AST.Expressions(); udas.push(templateArgToExp(o, token.loc)); return 0; } if (token.isKeyword()) error("`%s` is a keyword, not an `@` attribute", token.toChars()); else error("`@identifier` or `@(ArgumentList)` expected, not `@%s`", token.toChars()); return 0; } /*********************************************** * Parse const/immutable/shared/inout/nothrow/pure postfix */ private StorageClass parsePostfix(StorageClass storageClass, AST.Expressions** pudas) { while (1) { StorageClass stc; switch (token.value) { case TOK.const_: stc = STC.const_; break; case TOK.immutable_: stc = STC.immutable_; break; case TOK.shared_: stc = STC.shared_; break; case TOK.inout_: stc = STC.wild; break; case TOK.nothrow_: stc = STC.nothrow_; break; case TOK.pure_: stc = STC.pure_; break; case TOK.return_: stc = STC.return_; if (peekNext() == TOK.scope_) stc |= STC.returnScope; // recognize `return scope` break; case TOK.scope_: stc = STC.scope_; break; case TOK.at: { AST.Expressions* udas = null; stc = parseAttribute(udas); if (udas) { if (pudas) *pudas = AST.UserAttributeDeclaration.concat(*pudas, udas); else { // Disallow: // void function() @uda fp; // () @uda { return 1; } error("user-defined attributes cannot appear as postfixes"); } continue; } break; } default: Token* tk; if (skipAttributes(&token, &tk) && tk.ptr != token.ptr || token.value == TOK.static_ || token.value == TOK.extern_) { error("`%s` token is not allowed in postfix position", Token.toChars(token.value)); nextToken(); continue; } return storageClass; } storageClass = appendStorageClass(storageClass, stc); nextToken(); } } private StorageClass parseTypeCtor() { StorageClass storageClass = STC.undefined_; while (1) { if (peekNext() == TOK.leftParenthesis) return storageClass; StorageClass stc; switch (token.value) { case TOK.const_: stc = STC.const_; break; case TOK.immutable_: stc = STC.immutable_; break; case TOK.shared_: stc = STC.shared_; break; case TOK.inout_: stc = STC.wild; break; default: return storageClass; } storageClass = appendStorageClass(storageClass, stc); nextToken(); } } /************************************** * Parse constraint. * Constraint is of the form: * if ( ConstraintExpression ) */ private AST.Expression parseConstraint() { AST.Expression e = null; if (token.value == TOK.if_) { nextToken(); // skip over 'if' check(TOK.leftParenthesis); e = parseExpression(); check(TOK.rightParenthesis); } return e; } /************************************** * Parse a TemplateDeclaration. */ private AST.TemplateDeclaration parseTemplateDeclaration(bool ismixin = false) { AST.TemplateDeclaration tempdecl; Identifier id; AST.TemplateParameters* tpl; AST.Dsymbols* decldefs; AST.Expression constraint = null; const loc = token.loc; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `template`"); goto Lerr; } id = token.ident; nextToken(); tpl = parseTemplateParameterList(); if (!tpl) goto Lerr; constraint = parseConstraint(); if (token.value != TOK.leftCurly) { error("`{` expected after template parameter list, not `%s`", token.toChars()); /* } */ goto Lerr; } decldefs = parseBlock(null); tempdecl = new AST.TemplateDeclaration(loc, id, tpl, constraint, decldefs, ismixin); return tempdecl; Lerr: return null; } /****************************************** * Parse template parameter list. * Input: * flag 0: parsing "( list )" * 1: parsing non-empty "list $(RPAREN)" */ private AST.TemplateParameters* parseTemplateParameterList(int flag = 0) { auto tpl = new AST.TemplateParameters(); if (!flag && token.value != TOK.leftParenthesis) { error("parenthesized template parameter list expected following template identifier"); goto Lerr; } nextToken(); // Get array of TemplateParameters if (flag || token.value != TOK.rightParenthesis) { while (token.value != TOK.rightParenthesis) { AST.TemplateParameter tp; Loc loc; Identifier tp_ident = null; AST.Type tp_spectype = null; AST.Type tp_valtype = null; AST.Type tp_defaulttype = null; AST.Expression tp_specvalue = null; AST.Expression tp_defaultvalue = null; // Get TemplateParameter // First, look ahead to see if it is a TypeParameter or a ValueParameter const tv = peekNext(); if (token.value == TOK.alias_) { // AliasParameter nextToken(); loc = token.loc; // todo AST.Type spectype = null; if (isDeclaration(&token, NeedDeclaratorId.must, TOK.reserved, null)) { spectype = parseType(&tp_ident); } else { if (token.value != TOK.identifier) { error("identifier expected for template `alias` parameter"); goto Lerr; } tp_ident = token.ident; nextToken(); } RootObject spec = null; if (token.value == TOK.colon) // : Type { nextToken(); if (isDeclaration(&token, NeedDeclaratorId.no, TOK.reserved, null)) spec = parseType(); else spec = parseCondExp(); } RootObject def = null; if (token.value == TOK.assign) // = Type { nextToken(); if (isDeclaration(&token, NeedDeclaratorId.no, TOK.reserved, null)) def = parseType(); else def = parseCondExp(); } tp = new AST.TemplateAliasParameter(loc, tp_ident, spectype, spec, def); } else if (tv == TOK.colon || tv == TOK.assign || tv == TOK.comma || tv == TOK.rightParenthesis) { // TypeParameter if (token.value != TOK.identifier) { error("identifier expected for template type parameter"); goto Lerr; } loc = token.loc; tp_ident = token.ident; nextToken(); if (token.value == TOK.colon) // : Type { nextToken(); tp_spectype = parseType(); } if (token.value == TOK.assign) // = Type { nextToken(); tp_defaulttype = parseType(); } tp = new AST.TemplateTypeParameter(loc, tp_ident, tp_spectype, tp_defaulttype); } else if (token.value == TOK.identifier && tv == TOK.dotDotDot) { // ident... loc = token.loc; tp_ident = token.ident; nextToken(); nextToken(); tp = new AST.TemplateTupleParameter(loc, tp_ident); } else if (token.value == TOK.this_) { // ThisParameter nextToken(); if (token.value != TOK.identifier) { error("identifier expected for template `this` parameter"); goto Lerr; } loc = token.loc; tp_ident = token.ident; nextToken(); if (token.value == TOK.colon) // : Type { nextToken(); tp_spectype = parseType(); } if (token.value == TOK.assign) // = Type { nextToken(); tp_defaulttype = parseType(); } tp = new AST.TemplateThisParameter(loc, tp_ident, tp_spectype, tp_defaulttype); } else { // ValueParameter loc = token.loc; // todo tp_valtype = parseType(&tp_ident); if (!tp_ident) { error("identifier expected for template value parameter"); tp_ident = Identifier.idPool("error"); } if (token.value == TOK.colon) // : CondExpression { nextToken(); tp_specvalue = parseCondExp(); } if (token.value == TOK.assign) // = CondExpression { nextToken(); tp_defaultvalue = parseAssignExp(); } tp = new AST.TemplateValueParameter(loc, tp_ident, tp_valtype, tp_specvalue, tp_defaultvalue); } tpl.push(tp); if (token.value != TOK.comma) break; nextToken(); } } check(TOK.rightParenthesis); Lerr: return tpl; } /****************************************** * Parse template mixin. * mixin Foo; * mixin Foo!(args); * mixin a.b.c!(args).Foo!(args); * mixin Foo!(args) identifier; * mixin typeof(expr).identifier!(args); */ private AST.Dsymbol parseMixin() { AST.TemplateMixin tm; Identifier id; AST.Objects* tiargs; //printf("parseMixin()\n"); const locMixin = token.loc; nextToken(); // skip 'mixin' auto loc = token.loc; AST.TypeQualified tqual = null; if (token.value == TOK.dot) { id = Id.empty; } else { if (token.value == TOK.typeof_) { tqual = parseTypeof(); check(TOK.dot); } if (token.value != TOK.identifier) { error("identifier expected, not `%s`", token.toChars()); id = Id.empty; } else id = token.ident; nextToken(); } while (1) { tiargs = null; if (token.value == TOK.not) { tiargs = parseTemplateArguments(); } if (tiargs && token.value == TOK.dot) { auto tempinst = new AST.TemplateInstance(loc, id, tiargs); if (!tqual) tqual = new AST.TypeInstance(loc, tempinst); else tqual.addInst(tempinst); tiargs = null; } else { if (!tqual) tqual = new AST.TypeIdentifier(loc, id); else tqual.addIdent(id); } if (token.value != TOK.dot) break; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `.` instead of `%s`", token.toChars()); break; } loc = token.loc; id = token.ident; nextToken(); } id = null; if (token.value == TOK.identifier) { id = token.ident; nextToken(); } tm = new AST.TemplateMixin(locMixin, id, tqual, tiargs); if (token.value != TOK.semicolon) error("`;` expected after `mixin`"); nextToken(); return tm; } /****************************************** * Parse template arguments. * Input: * current token is opening '!' * Output: * current token is one after closing '$(RPAREN)' */ private AST.Objects* parseTemplateArguments() { AST.Objects* tiargs; nextToken(); if (token.value == TOK.leftParenthesis) { // ident!(template_arguments) tiargs = parseTemplateArgumentList(); } else { // ident!template_argument RootObject o = parseTemplateSingleArgument(); if (!o) { error("template argument expected following `!`"); } else { tiargs = new AST.Objects(); tiargs.push(o); } } if (token.value == TOK.not) { TOK tok = peekNext(); if (tok != TOK.is_ && tok != TOK.in_) { error("multiple ! arguments are not allowed"); Lagain: nextToken(); if (token.value == TOK.leftParenthesis) parseTemplateArgumentList(); else parseTemplateSingleArgument(); if (token.value == TOK.not && (tok = peekNext()) != TOK.is_ && tok != TOK.in_) goto Lagain; } } return tiargs; } /****************************************** * Parse template argument list. * Input: * current token is opening '$(LPAREN)', * or ',' for __traits * Output: * current token is one after closing '$(RPAREN)' */ private AST.Objects* parseTemplateArgumentList() { //printf("Parser::parseTemplateArgumentList()\n"); auto tiargs = new AST.Objects(); TOK endtok = TOK.rightParenthesis; assert(token.value == TOK.leftParenthesis || token.value == TOK.comma); nextToken(); // Get TemplateArgumentList while (token.value != endtok) { tiargs.push(parseTypeOrAssignExp()); if (token.value != TOK.comma) break; nextToken(); } check(endtok, "template argument list"); return tiargs; } /*************************************** * Parse a Type or an Expression * Returns: * RootObject representing the AST */ RootObject parseTypeOrAssignExp(TOK endtoken = TOK.reserved) { return isDeclaration(&token, NeedDeclaratorId.no, endtoken, null) ? parseType() // argument is a type : parseAssignExp(); // argument is an expression } /***************************** * Parse single template argument, to support the syntax: * foo!arg * Input: * current token is the arg * Returns: An AST.Type, AST.Expression, or `null` on error */ private RootObject parseTemplateSingleArgument() { //printf("parseTemplateSingleArgument()\n"); AST.Type ta; switch (token.value) { case TOK.identifier: ta = new AST.TypeIdentifier(token.loc, token.ident); goto LabelX; case TOK.vector: ta = parseVector(); goto LabelX; case TOK.void_: ta = AST.Type.tvoid; goto LabelX; case TOK.int8: ta = AST.Type.tint8; goto LabelX; case TOK.uns8: ta = AST.Type.tuns8; goto LabelX; case TOK.int16: ta = AST.Type.tint16; goto LabelX; case TOK.uns16: ta = AST.Type.tuns16; goto LabelX; case TOK.int32: ta = AST.Type.tint32; goto LabelX; case TOK.uns32: ta = AST.Type.tuns32; goto LabelX; case TOK.int64: ta = AST.Type.tint64; goto LabelX; case TOK.uns64: ta = AST.Type.tuns64; goto LabelX; case TOK.int128: ta = AST.Type.tint128; goto LabelX; case TOK.uns128: ta = AST.Type.tuns128; goto LabelX; case TOK.float32: ta = AST.Type.tfloat32; goto LabelX; case TOK.float64: ta = AST.Type.tfloat64; goto LabelX; case TOK.float80: ta = AST.Type.tfloat80; goto LabelX; case TOK.imaginary32: ta = AST.Type.timaginary32; goto LabelX; case TOK.imaginary64: ta = AST.Type.timaginary64; goto LabelX; case TOK.imaginary80: ta = AST.Type.timaginary80; goto LabelX; case TOK.complex32: ta = AST.Type.tcomplex32; goto LabelX; case TOK.complex64: ta = AST.Type.tcomplex64; goto LabelX; case TOK.complex80: ta = AST.Type.tcomplex80; goto LabelX; case TOK.bool_: ta = AST.Type.tbool; goto LabelX; case TOK.char_: ta = AST.Type.tchar; goto LabelX; case TOK.wchar_: ta = AST.Type.twchar; goto LabelX; case TOK.dchar_: ta = AST.Type.tdchar; goto LabelX; LabelX: nextToken(); return ta; case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.string_: case TOK.interpolated: case TOK.hexadecimalString: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: case TOK.this_: { // Template argument is an expression return parsePrimaryExp(); } default: return null; } } /********************************** * Parse a static assertion. * Current token is 'static'. */ private AST.StaticAssert parseStaticAssert() { const loc = token.loc; AST.Expression exp; AST.Expressions* msg = null; //printf("parseStaticAssert()\n"); nextToken(); nextToken(); check(TOK.leftParenthesis); exp = parseAssignExp(); if (token.value == TOK.comma) { if (peekNext() == TOK.rightParenthesis) { nextToken(); // consume `,` nextToken(); // consume `)` } else msg = parseArguments(); } else check(TOK.rightParenthesis); check(TOK.semicolon, "static assert"); return new AST.StaticAssert(loc, exp, msg); } /*********************************** * Parse typeof(expression). * Current token is on the 'typeof'. */ private AST.TypeQualified parseTypeof() { AST.TypeQualified t; const loc = token.loc; nextToken(); check(TOK.leftParenthesis); if (token.value == TOK.return_) // typeof(return) { nextToken(); t = new AST.TypeReturn(loc); } else { AST.Expression exp = parseExpression(); // typeof(expression) t = new AST.TypeTypeof(loc, exp); } check(TOK.rightParenthesis); return t; } /*********************************** * Parse __vector(type). * Current token is on the '__vector'. */ private AST.Type parseVector() { nextToken(); check(TOK.leftParenthesis); AST.Type tb = parseType(); check(TOK.rightParenthesis); return new AST.TypeVector(tb); } /*********************************** * Parse: * extern (linkage) * extern (C++, namespaces) * extern (C++, "namespace", "namespaces", ...) * extern (C++, (StringExp)) * The parser is on the 'extern' token. */ private ParsedLinkage!(AST) parseLinkage() { ParsedLinkage!(AST) result; nextToken(); assert(token.value == TOK.leftParenthesis); nextToken(); ParsedLinkage!(AST) returnLinkage(LINK link) { check(TOK.rightParenthesis); result.link = link; return result; } ParsedLinkage!(AST) invalidLinkage() { error("valid linkage identifiers are `D`, `C`, `C++`, `Objective-C`, `Windows`, `System`"); return returnLinkage(LINK.d); } if (token.value != TOK.identifier) return returnLinkage(LINK.d); Identifier id = token.ident; nextToken(); if (id == Id.Windows) return returnLinkage(LINK.windows); else if (id == Id.D) return returnLinkage(LINK.d); else if (id == Id.System) return returnLinkage(LINK.system); else if (id == Id.Objective) // Looking for tokens "Objective-C" { if (token.value != TOK.min) return invalidLinkage(); nextToken(); if (token.ident != Id.C) return invalidLinkage(); nextToken(); return returnLinkage(LINK.objc); } else if (id != Id.C) return invalidLinkage(); if (token.value != TOK.plusPlus) return returnLinkage(LINK.c); nextToken(); if (token.value != TOK.comma) // , namespaces or class or struct return returnLinkage(LINK.cpp); nextToken(); if (token.value == TOK.rightParenthesis) return returnLinkage(LINK.cpp); // extern(C++,) if (token.value == TOK.class_ || token.value == TOK.struct_) { result.cppmangle = token.value == TOK.class_ ? CPPMANGLE.asClass : CPPMANGLE.asStruct; nextToken(); } else if (token.value == TOK.identifier) // named scope namespace { result.idents = new AST.Identifiers(); while (1) { Identifier idn = token.ident; result.idents.push(idn); nextToken(); if (token.value == TOK.dot) { nextToken(); if (token.value == TOK.identifier) continue; error("identifier expected for C++ namespace"); result.idents = null; // error occurred, invalidate list of elements. } break; } } else // non-scoped StringExp namespace { result.identExps = new AST.Expressions(); while (1) { result.identExps.push(parseCondExp()); if (token.value != TOK.comma) break; nextToken(); // Allow trailing commas as done for argument lists, arrays, ... if (token.value == TOK.rightParenthesis) break; } } return returnLinkage(LINK.cpp); } /*********************************** * Parse ident1.ident2.ident3 * * Params: * entity = what qualified identifier is expected to resolve into. * Used only for better error message * * Returns: * array of identifiers with actual qualified one stored last */ private Identifier[] parseQualifiedIdentifier(const(char)* entity) { Identifier[] qualified; do { nextToken(); if (token.value != TOK.identifier) { error(token.loc, "`%s` expected as dot-separated identifiers, got `%s`", entity, token.toChars()); return qualified; } Identifier id = token.ident; qualified ~= id; nextToken(); } while (token.value == TOK.dot); return qualified; } private AST.DebugSymbol parseDebugSpecification() { AST.DebugSymbol s; nextToken(); if (token.value == TOK.identifier) s = new AST.DebugSymbol(token.loc, token.ident); else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) { // @@@DEPRECATED_2.111@@@ // Deprecated in 2.101, remove in 2.111 deprecation("`debug = ` is deprecated, use debug identifiers instead"); s = new AST.DebugSymbol(token.loc, cast(uint)token.unsvalue); } else { error("identifier or integer expected, not `%s`", token.toChars()); s = null; } nextToken(); if (token.value != TOK.semicolon) error("semicolon expected"); nextToken(); return s; } /************************************** * Parse a debug conditional */ private AST.Condition parseDebugCondition() { uint level = 1; Identifier id = null; Loc loc = token.loc; if (token.value == TOK.leftParenthesis) { nextToken(); if (token.value == TOK.identifier) id = token.ident; else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) { // @@@DEPRECATED_2.111@@@ // Deprecated in 2.101, remove in 2.111 deprecation("`debug( )` is deprecated, use debug identifiers instead"); level = cast(uint)token.unsvalue; } else error("identifier or integer expected inside `debug(...)`, not `%s`", token.toChars()); loc = token.loc; nextToken(); check(TOK.rightParenthesis); } return new AST.DebugCondition(loc, mod, level, id); } /************************************** * Parse a version specification */ private AST.VersionSymbol parseVersionSpecification() { AST.VersionSymbol s; nextToken(); if (token.value == TOK.identifier) s = new AST.VersionSymbol(token.loc, token.ident); else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) { // @@@DEPRECATED_2.111@@@ // Deprecated in 2.101, remove in 2.111 deprecation("`version = ` is deprecated, use version identifiers instead"); s = new AST.VersionSymbol(token.loc, cast(uint)token.unsvalue); } else { error("identifier or integer expected, not `%s`", token.toChars()); s = null; } nextToken(); if (token.value != TOK.semicolon) error("semicolon expected"); nextToken(); return s; } /************************************** * Parse a version conditional */ private AST.Condition parseVersionCondition() { uint level = 1; Identifier id = null; Loc loc; if (token.value == TOK.leftParenthesis) { nextToken(); /* Allow: * version (unittest) * version (assert) * even though they are keywords */ loc = token.loc; if (token.value == TOK.identifier) id = token.ident; else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) { // @@@DEPRECATED_2.111@@@ // Deprecated in 2.101, remove in 2.111 deprecation("`version( )` is deprecated, use version identifiers instead"); level = cast(uint)token.unsvalue; } else if (token.value == TOK.unittest_) id = Identifier.idPool(Token.toString(TOK.unittest_)); else if (token.value == TOK.assert_) id = Identifier.idPool(Token.toString(TOK.assert_)); else error("identifier or integer expected inside `version(...)`, not `%s`", token.toChars()); nextToken(); check(TOK.rightParenthesis); } else error("(condition) expected following `version`"); return new AST.VersionCondition(loc, mod, level, id); } /*********************************************** * static if (expression) * body * else * body * Current token is 'static'. */ private AST.Condition parseStaticIfCondition() { AST.Expression exp; AST.Condition condition; const loc = token.loc; nextToken(); nextToken(); if (token.value == TOK.leftParenthesis) { nextToken(); exp = parseAssignExp(); check(TOK.rightParenthesis); } else { error("(expression) expected following `static if`"); exp = null; } condition = new AST.StaticIfCondition(loc, exp); return condition; } /***************************************** * Parse a constructor definition: * this(parameters) { body } * or postblit: * this(this) { body } * or constructor template: * this(templateparameters)(parameters) { body } * Current token is 'this'. */ private AST.Dsymbol parseCtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); if (token.value == TOK.leftParenthesis && peekNext() == TOK.this_ && peekNext2() == TOK.rightParenthesis) { // this(this) { ... } nextToken(); nextToken(); check(TOK.rightParenthesis); stc = parsePostfix(stc, &udas); if (stc & STC.immutable_) deprecation("`immutable` postblit is deprecated. Please use an unqualified postblit."); if (stc & STC.shared_) deprecation("`shared` postblit is deprecated. Please use an unqualified postblit."); if (stc & STC.const_) deprecation("`const` postblit is deprecated. Please use an unqualified postblit."); if (stc & STC.static_) error(loc, "postblit cannot be `static`"); auto f = new AST.PostBlitDeclaration(loc, Loc.initial, stc, Id.postblit); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /* Look ahead to see if: * this(...)(...) * which is a constructor template */ AST.TemplateParameters* tpl = null; if (token.value == TOK.leftParenthesis && peekPastParen(&token).value == TOK.leftParenthesis) { tpl = parseTemplateParameterList(); } /* Just a regular constructor */ auto parameterList = parseParameterList(null); stc = parsePostfix(stc, &udas); if (parameterList.varargs != VarArg.none || AST.Parameter.dim(parameterList.parameters) != 0) { if (stc & STC.static_) error(loc, "constructor cannot be static"); } else if (StorageClass ss = stc & (STC.shared_ | STC.static_)) // this() { if (ss == STC.static_) error(loc, "use `static this()` to declare a static constructor"); else if (ss == (STC.shared_ | STC.static_)) error(loc, "use `shared static this()` to declare a shared static constructor"); } AST.Expression constraint = tpl ? parseConstraint() : null; AST.Type tf = new AST.TypeFunction(parameterList, null, linkage, stc); // RetrunType -> auto tf = tf.addSTC(stc); auto f = new AST.CtorDeclaration(loc, Loc.initial, stc, tf); AST.Dsymbol s = parseContracts(f, !!tpl); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } if (tpl) { // Wrap a template around it auto decldefs = new AST.Dsymbols(); decldefs.push(s); s = new AST.TemplateDeclaration(loc, f.ident, tpl, constraint, decldefs); } return s; } /***************************************** * Parse a destructor definition: * ~this() { body } * Current token is '~'. */ private AST.Dsymbol parseDtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); check(TOK.this_); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc, &udas); if (StorageClass ss = stc & (STC.shared_ | STC.static_)) { if (ss == STC.static_) error(loc, "use `static ~this()` to declare a static destructor"); else if (ss == (STC.shared_ | STC.static_)) error(loc, "use `shared static ~this()` to declare a shared static destructor"); } auto f = new AST.DtorDeclaration(loc, Loc.initial, stc, Id.dtor); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /***************************************** * Parse a static constructor definition: * static this() { body } * Current token is 'static'. */ private AST.Dsymbol parseStaticCtor(PrefixAttributes!AST* pAttrs) { //Expressions *udas = NULL; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, null) | stc; if (stc & STC.shared_) error(loc, "use `shared static this()` to declare a shared static constructor"); else if (stc & STC.static_) appendStorageClass(stc, STC.static_); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(buf, modStc); error(loc, "static constructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.StaticCtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); return s; } /***************************************** * Parse a static destructor definition: * static ~this() { body } * Current token is 'static'. */ private AST.Dsymbol parseStaticDtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); check(TOK.this_); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, &udas) | stc; if (stc & STC.shared_) error(loc, "use `shared static ~this()` to declare a shared static destructor"); else if (stc & STC.static_) appendStorageClass(stc, STC.static_); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(buf, modStc); error(loc, "static destructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.StaticDtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /***************************************** * Parse a shared static constructor definition: * shared static this() { body } * Current token is 'shared'. */ private AST.Dsymbol parseSharedStaticCtor(PrefixAttributes!AST* pAttrs) { //Expressions *udas = NULL; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); nextToken(); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, null) | stc; if (StorageClass ss = stc & (STC.shared_ | STC.static_)) appendStorageClass(stc, ss); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(buf, modStc); error(loc, "shared static constructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.SharedStaticCtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); return s; } /***************************************** * Parse a shared static destructor definition: * shared static ~this() { body } * Current token is 'shared'. */ private AST.Dsymbol parseSharedStaticDtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); nextToken(); check(TOK.this_); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, &udas) | stc; if (StorageClass ss = stc & (STC.shared_ | STC.static_)) appendStorageClass(stc, ss); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(buf, modStc); error(loc, "shared static destructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.SharedStaticDtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /***************************************** * Parse an invariant definition: * invariant { statements... } * invariant() { statements... } * invariant (expression); * Current token is 'invariant'. */ private AST.Dsymbol parseInvariant(PrefixAttributes!AST* pAttrs) { const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); if (token.value == TOK.leftParenthesis) // optional () or invariant (expression); { nextToken(); if (token.value != TOK.rightParenthesis) // invariant (expression); { AST.Expression e = parseAssignExp(), msg = null; if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); check(TOK.semicolon, "invariant"); e = new AST.AssertExp(loc, e, msg); auto fbody = new AST.ExpStatement(loc, e); auto f = new AST.InvariantDeclaration(loc, token.loc, stc, null, fbody); return f; } nextToken(); } auto fbody = parseStatement(ParseStatementFlags.curly); auto f = new AST.InvariantDeclaration(loc, token.loc, stc, null, fbody); return f; } /***************************************** * Parse a unittest definition: * unittest { body } * Current token is 'unittest'. */ private AST.Dsymbol parseUnitTest(PrefixAttributes!AST* pAttrs) { const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); const(char)* begPtr = token.ptr + 1; // skip left curly brace const(char)* endPtr = null; AST.Statement sbody = parseStatement(ParseStatementFlags.curly, &endPtr); /** Extract unittest body as a string. Must be done eagerly since memory will be released by the lexer before doc gen. */ char* docline = null; if (compileEnv.ddocOutput && endPtr > begPtr) { /* Remove trailing whitespaces */ for (const(char)* p = endPtr - 1; begPtr <= p && (*p == ' ' || *p == '\r' || *p == '\n' || *p == '\t'); --p) { endPtr = p; } size_t len = endPtr - begPtr; if (len > 0) { docline = cast(char*)mem.xmalloc_noscan(len + 2); memcpy(docline, begPtr, len); docline[len] = '\n'; // Terminate all lines by LF docline[len + 1] = '\0'; } } auto f = new AST.UnitTestDeclaration(loc, token.loc, stc, docline); f.fbody = sbody; return f; } /***************************************** * Parse a new definition: * @disable new(); * Current token is 'new'. */ private AST.Dsymbol parseNew(PrefixAttributes!AST* pAttrs) { const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); if (!(stc & STC.disable)) { error("`new` allocator must be annotated with `@disabled`"); } nextToken(); /* @@@DEPRECATED_2.108@@@ * After deprecation period (2.108), remove all code in the version(all) block. */ version (all) { auto parameterList = parseParameterList(null); // parameterList ignored if (parameterList.parameters.length > 0 || parameterList.varargs != VarArg.none) deprecation("`new` allocator with non-empty parameter list is deprecated"); auto f = new AST.NewDeclaration(loc, stc); if (token.value != TOK.semicolon) { deprecation("`new` allocator with function definition is deprecated"); parseContracts(f); // body ignored f.fbody = null; f.fensures = null; f.frequires = null; } else nextToken(); return f; } else { check(TOK.leftParenthesis); check(TOK.rightParenthesis); check(TOK.semicolon); return new AST.NewDeclaration(loc, stc); } } /********************************************** * Parse parameter list. */ private AST.ParameterList parseParameterList(AST.TemplateParameters** tpl) { auto parameters = new AST.Parameters(); VarArg varargs = VarArg.none; StorageClass varargsStc; // Attributes allowed for ... enum VarArgsStc = STC.const_ | STC.immutable_ | STC.shared_ | STC.scope_ | STC.return_ | STC.returnScope; check(TOK.leftParenthesis); while (1) { Identifier ai = null; AST.Type at; StorageClass storageClass = 0; StorageClass stc; AST.Expression ae; AST.Expressions* udas = null; for (; 1; nextToken()) { L3: switch (token.value) { case TOK.rightParenthesis: if (storageClass != 0 || udas !is null) error("basic type expected, not `)`"); break; case TOK.dotDotDot: varargs = VarArg.variadic; varargsStc = storageClass; if (varargsStc & ~VarArgsStc) { OutBuffer buf; AST.stcToBuffer(buf, varargsStc & ~VarArgsStc); error("variadic parameter cannot have attributes `%s`", buf.peekChars()); varargsStc &= VarArgsStc; } nextToken(); break; case TOK.const_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.const_; goto L2; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.immutable_; goto L2; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.shared_; goto L2; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.wild; goto L2; case TOK.at: { AST.Expressions* exps = null; StorageClass stc2 = parseAttribute(exps); if (stc2 & atAttrGroup) { error("`@%s` attribute for function parameter is not supported", token.toChars()); } else { udas = AST.UserAttributeDeclaration.concat(udas, exps); } if (token.value == TOK.dotDotDot) error("variadic parameter cannot have user-defined attributes"); if (stc2) nextToken(); goto L3; // Don't call nextToken again. } case TOK.in_: if (transitionIn) eSink.message(scanloc, "Usage of 'in' on parameter"); stc = STC.in_; if (compileEnv.previewIn) stc |= STC.constscoperef; goto L2; case TOK.out_: stc = STC.out_; goto L2; case TOK.ref_: stc = STC.ref_; goto L2; case TOK.lazy_: stc = STC.lazy_; goto L2; case TOK.scope_: stc = STC.scope_; goto L2; case TOK.final_: stc = STC.final_; goto L2; case TOK.auto_: stc = STC.auto_; goto L2; case TOK.return_: stc = STC.return_; if (peekNext() == TOK.scope_) stc |= STC.returnScope; goto L2; L2: storageClass = appendStorageClass(storageClass, stc); continue; default: { const stcx = storageClass & (STC.in_ | STC.ref_ | STC.out_ | STC.lazy_); // if stcx is not a power of 2 if (stcx & (stcx - 1) && !(stcx == (STC.in_ | STC.ref_))) error("incompatible parameter storage classes"); //if ((storageClass & STC.scope_) && (storageClass & (STC.ref_ | STC.out_))) //error("scope cannot be ref or out"); const tv = peekNext(); Loc loc; if (tpl && token.value == TOK.identifier && (tv == TOK.comma || tv == TOK.rightParenthesis || tv == TOK.dotDotDot)) { Identifier id = Identifier.generateId("__T"); loc = token.loc; at = new AST.TypeIdentifier(loc, id); if (!*tpl) *tpl = new AST.TemplateParameters(); AST.TemplateParameter tp = new AST.TemplateTypeParameter(loc, id, null, null); (*tpl).push(tp); ai = token.ident; nextToken(); } else { at = parseType(&ai, null, &loc); } ae = null; if (token.value == TOK.assign) // = defaultArg { nextToken(); ae = parseAssignExp(); } auto param = new AST.Parameter(loc, storageClass | STC.parameter, at, ai, ae, null); if (udas) { auto a = new AST.Dsymbols(); auto udad = new AST.UserAttributeDeclaration(udas, a); param.userAttribDecl = udad; } if (token.value == TOK.at) { AST.Expressions* exps = null; StorageClass stc2 = parseAttribute(exps); if (stc2 & atAttrGroup) { error("`@%s` attribute for function parameter is not supported", token.toChars()); } else { error("user-defined attributes cannot appear as postfixes", token.toChars()); } if (stc2) nextToken(); } if (token.value == TOK.dotDotDot) { /* This is: * at ai ... */ if (storageClass & (STC.out_ | STC.ref_)) error("variadic argument cannot be `out` or `ref`"); varargs = VarArg.typesafe; parameters.push(param); nextToken(); break; } parameters.push(param); if (token.value == TOK.comma) { nextToken(); goto L1; } break; } } break; } break; L1: } check(TOK.rightParenthesis); return AST.ParameterList(parameters, varargs, varargsStc); } /************************************* */ private AST.EnumDeclaration parseEnum() { AST.EnumDeclaration e; Identifier id; AST.Type memtype; auto loc = token.loc; // printf("Parser::parseEnum()\n"); nextToken(); id = null; if (token.value == TOK.identifier) { id = token.ident; nextToken(); } memtype = null; if (token.value == TOK.colon) { nextToken(); int alt = 0; const typeLoc = token.loc; memtype = parseBasicType(); memtype = parseDeclarator(memtype, alt, null); checkCstyleTypeSyntax(typeLoc, memtype, alt, null); } e = new AST.EnumDeclaration(loc, id, memtype); // opaque type if (token.value == TOK.semicolon && id) nextToken(); else if (token.value == TOK.leftCurly) { bool isAnonymousEnum = !id; //printf("enum definition\n"); e.members = new AST.Dsymbols(); nextToken(); const(char)[] comment = token.blockComment; while (token.value != TOK.rightCurly) { /* Can take the following forms... * 1. ident * 2. ident = value * 3. type ident = value * ... prefixed by valid attributes */ loc = token.loc; AST.Type type = null; Identifier ident = null; AST.Expressions* udas; StorageClass stc; AST.Expression deprecationMessage; enum attributeErrorMessage = "`%s` is not a valid attribute for enum members"; Lattrs: while (1) { switch (token.value) { case TOK.at: if (StorageClass _stc = parseAttribute(udas)) { if (_stc == STC.disable) stc |= _stc; else { OutBuffer buf; AST.stcToBuffer(buf, _stc); error(attributeErrorMessage, buf.peekChars()); } nextToken(); } break; case TOK.deprecated_: stc |= STC.deprecated_; if (!parseDeprecatedAttribute(deprecationMessage)) { nextToken(); } break; default: break Lattrs; } } if (token.value == TOK.identifier) { const tv = peekNext(); if (tv == TOK.assign || tv == TOK.comma || tv == TOK.rightCurly) { ident = token.ident; type = null; nextToken(); } else { if (isAnonymousEnum) goto Ltype; nextToken(); error("expected `,` or `=` after identifier, not `%s`", token.toChars()); } } else { if (isAnonymousEnum) { Ltype: // Type identifier type = parseType(&ident, null); if (type == AST.Type.terror) { type = null; nextToken(); } else if (!ident) { error("no identifier for declarator `%s`", type.toChars()); type = null; } else { const tv = token.value; if (tv != TOK.assign && tv != TOK.comma && tv != TOK.rightCurly) { error("expected `,` or `=` after identifier, not `%s`", token.toChars()); nextToken(); } } } else { Token* t = &token; if (isBasicType(&t)) { error("named enum cannot declare member with type", (*t).toChars()); nextToken(); } else check(TOK.identifier); // avoid extra error messages const tv = token.value; if (tv != TOK.assign && tv != TOK.comma && tv != TOK.rightCurly && tv != TOK.endOfFile) continue; } } AST.Expression value; if (token.value == TOK.assign) { nextToken(); value = parseAssignExp(); } else { value = null; if (type && isAnonymousEnum) error("initializer required after `%s` when type is specified", ident.toChars()); } AST.DeprecatedDeclaration dd; if (deprecationMessage) { dd = new AST.DeprecatedDeclaration(deprecationMessage, null); stc |= STC.deprecated_; } auto em = new AST.EnumMember(loc, ident, value, type, stc, null, dd); e.members.push(em); if (udas) { auto uad = new AST.UserAttributeDeclaration(udas, new AST.Dsymbols()); em.userAttribDecl = uad; } if (token.value != TOK.rightCurly) { addComment(em, comment); comment = null; check(TOK.comma); } addComment(em, comment); comment = token.blockComment; if (token.value == TOK.endOfFile) { error("premature end of file"); break; } } nextToken(); } else { nextToken(); error("expected `{`, not `%s` for enum declaration", token.toChars()); } //printf("-parseEnum() %s\n", e.toChars()); return e; } /******************************** * Parse struct, union, interface, class. */ private AST.Dsymbol parseAggregate() { AST.TemplateParameters* tpl = null; AST.Expression constraint; const loc = token.loc; TOK tok = token.value; //printf("Parser::parseAggregate()\n"); nextToken(); Identifier id; if (token.value != TOK.identifier) { id = null; } else { id = token.ident; nextToken(); if (token.value == TOK.leftParenthesis) { // struct/class template declaration. tpl = parseTemplateParameterList(); constraint = parseConstraint(); } } // Collect base class(es) AST.BaseClasses* baseclasses = null; if (token.value == TOK.colon) { if (tok != TOK.interface_ && tok != TOK.class_) error("base classes are not allowed for `%s`, did you mean `;`?", Token.toChars(tok)); nextToken(); baseclasses = parseBaseClasses(); } if (token.value == TOK.if_) { if (constraint) error("template constraints appear both before and after BaseClassList, put them before"); constraint = parseConstraint(); } if (constraint) { if (!id) error("template constraints not allowed for anonymous `%s`", Token.toChars(tok)); if (!tpl) error("template constraints only allowed for templates"); } AST.Dsymbols* members = null; if (token.value == TOK.leftCurly) { //printf("aggregate definition\n"); const lookingForElseSave = lookingForElse; lookingForElse = Loc(); nextToken(); members = parseDeclDefs(0); lookingForElse = lookingForElseSave; if (token.value != TOK.rightCurly) { /* { */ error(token.loc, "`}` expected following members in `%s` declaration", Token.toChars(tok)); if (id) eSink.errorSupplemental(loc, "%s `%s` starts here", Token.toChars(tok), id.toChars()); else eSink.errorSupplemental(loc, "%s starts here", Token.toChars(tok)); } nextToken(); } else if (token.value == TOK.semicolon && id) { if (baseclasses || constraint) error("members expected"); nextToken(); } else { error(token.loc, "{ } expected following `%s` declaration", Token.toChars(tok)); } AST.AggregateDeclaration a; switch (tok) { case TOK.interface_: if (!id) error(loc, "anonymous interfaces not allowed"); a = new AST.InterfaceDeclaration(loc, id, baseclasses); a.members = members; break; case TOK.class_: if (!id) error(loc, "anonymous classes not allowed"); bool inObject = md && !md.packages && md.id == Id.object; a = new AST.ClassDeclaration(loc, id, baseclasses, members, inObject); break; case TOK.struct_: if (id) { bool inObject = md && !md.packages && md.id == Id.object; a = new AST.StructDeclaration(loc, id, inObject); a.members = members; } else { /* Anonymous structs/unions are more like attributes. */ assert(!tpl); return new AST.AnonDeclaration(loc, false, members); } break; case TOK.union_: if (id) { a = new AST.UnionDeclaration(loc, id); a.members = members; } else { /* Anonymous structs/unions are more like attributes. */ assert(!tpl); return new AST.AnonDeclaration(loc, true, members); } break; default: assert(0); } if (tpl) { // Wrap a template around the aggregate declaration auto decldefs = new AST.Dsymbols(); decldefs.push(a); auto tempdecl = new AST.TemplateDeclaration(loc, id, tpl, constraint, decldefs); return tempdecl; } return a; } /******************************************* */ private AST.BaseClasses* parseBaseClasses() { auto baseclasses = new AST.BaseClasses(); for (; 1; nextToken()) { auto b = new AST.BaseClass(parseBasicType()); baseclasses.push(b); if (token.value != TOK.comma) break; } return baseclasses; } AST.Dsymbols* parseImport() { auto decldefs = new AST.Dsymbols(); Identifier aliasid = null; int isstatic = token.value == TOK.static_; if (isstatic) nextToken(); //printf("Parser::parseImport()\n"); do { L1: nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `import`"); break; } const loc = token.loc; Identifier id = token.ident; Identifier[] a; nextToken(); if (!aliasid && token.value == TOK.assign) { aliasid = id; goto L1; } while (token.value == TOK.dot) { a ~= id; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `package`"); break; } id = token.ident; nextToken(); } auto s = new AST.Import(loc, a, id, aliasid, isstatic); decldefs.push(s); /* Look for * : alias=name, alias=name; * syntax. */ if (token.value == TOK.colon) { do { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `:`"); break; } Identifier _alias = token.ident; Identifier name; nextToken(); if (token.value == TOK.assign) { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `%s=`", _alias.toChars()); break; } name = token.ident; nextToken(); } else { name = _alias; _alias = null; } s.addAlias(name, _alias); } while (token.value == TOK.comma); break; // no comma-separated imports of this form } aliasid = null; } while (token.value == TOK.comma); if (token.value == TOK.semicolon) nextToken(); else { error("`;` expected"); nextToken(); } return decldefs; } /* Parse a type and optional identifier * Params: * pident = set to Identifier if there is one, null if not * ptpl = if !null, then set to TemplateParameterList * pdeclLoc = if !null, then set to location of the declarator */ AST.Type parseType(Identifier* pident = null, AST.TemplateParameters** ptpl = null, Loc* pdeclLoc = null) { /* Take care of the storage class prefixes that * serve as type attributes: * const type * immutable type * shared type * inout type * inout const type * shared const type * shared inout type * shared inout const type */ StorageClass stc = 0; while (1) { switch (token.value) { case TOK.const_: if (peekNext() == TOK.leftParenthesis) break; // const as type constructor stc |= STC.const_; // const as storage class nextToken(); continue; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) break; stc |= STC.immutable_; nextToken(); continue; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) break; stc |= STC.shared_; nextToken(); continue; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) break; stc |= STC.wild; nextToken(); continue; default: break; } break; } const typeLoc = token.loc; AST.Type t; t = parseBasicType(); if (pdeclLoc) *pdeclLoc = token.loc; int alt = 0; t = parseDeclarator(t, alt, pident, ptpl); checkCstyleTypeSyntax(typeLoc, t, alt, pident ? *pident : null); t = t.addSTC(stc); return t; } private AST.Type parseBasicType(bool dontLookDotIdents = false) { AST.Type t; Loc loc; Identifier id; //printf("parseBasicType()\n"); switch (token.value) { case TOK.void_: t = AST.Type.tvoid; goto LabelX; case TOK.int8: t = AST.Type.tint8; goto LabelX; case TOK.uns8: t = AST.Type.tuns8; goto LabelX; case TOK.int16: t = AST.Type.tint16; goto LabelX; case TOK.uns16: t = AST.Type.tuns16; goto LabelX; case TOK.int32: t = AST.Type.tint32; goto LabelX; case TOK.uns32: t = AST.Type.tuns32; goto LabelX; case TOK.int64: t = AST.Type.tint64; nextToken(); if (token.value == TOK.int64) // if `long long` { error("use `long` for a 64 bit integer instead of `long long`"); nextToken(); } else if (token.value == TOK.float64) // if `long double` { error("use `real` instead of `long double`"); t = AST.Type.tfloat80; nextToken(); } break; case TOK.uns64: t = AST.Type.tuns64; goto LabelX; case TOK.int128: t = AST.Type.tint128; goto LabelX; case TOK.uns128: t = AST.Type.tuns128; goto LabelX; case TOK.float32: t = AST.Type.tfloat32; goto LabelX; case TOK.float64: t = AST.Type.tfloat64; goto LabelX; case TOK.float80: t = AST.Type.tfloat80; goto LabelX; case TOK.imaginary32: t = AST.Type.timaginary32; goto LabelX; case TOK.imaginary64: t = AST.Type.timaginary64; goto LabelX; case TOK.imaginary80: t = AST.Type.timaginary80; goto LabelX; case TOK.complex32: t = AST.Type.tcomplex32; goto LabelX; case TOK.complex64: t = AST.Type.tcomplex64; goto LabelX; case TOK.complex80: t = AST.Type.tcomplex80; goto LabelX; case TOK.bool_: t = AST.Type.tbool; goto LabelX; case TOK.char_: t = AST.Type.tchar; goto LabelX; case TOK.wchar_: t = AST.Type.twchar; goto LabelX; case TOK.dchar_: t = AST.Type.tdchar; goto LabelX; LabelX: nextToken(); break; case TOK.this_: case TOK.super_: case TOK.identifier: loc = token.loc; id = token.ident; nextToken(); if (token.value == TOK.not) { // ident!(template_arguments) auto tempinst = new AST.TemplateInstance(loc, id, parseTemplateArguments()); t = parseBasicTypeStartingAt(new AST.TypeInstance(loc, tempinst), dontLookDotIdents); } else { t = parseBasicTypeStartingAt(new AST.TypeIdentifier(loc, id), dontLookDotIdents); } break; case TOK.mixin_: // https://dlang.org/spec/expression.html#mixin_types loc = token.loc; nextToken(); if (token.value != TOK.leftParenthesis) error(token.loc, "found `%s` when expecting `%s` following `mixin`", token.toChars(), Token.toChars(TOK.leftParenthesis)); auto exps = parseArguments(); t = new AST.TypeMixin(loc, exps); break; case TOK.dot: // Leading . as in .foo t = parseBasicTypeStartingAt(new AST.TypeIdentifier(token.loc, Id.empty), dontLookDotIdents); break; case TOK.typeof_: // typeof(expression) t = parseBasicTypeStartingAt(parseTypeof(), dontLookDotIdents); break; case TOK.vector: t = parseVector(); break; case TOK.traits: if (AST.TraitsExp te = cast(AST.TraitsExp) parsePrimaryExp()) if (te.ident) { t = new AST.TypeTraits(token.loc, te); break; } t = new AST.TypeError; break; case TOK.const_: // const(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.const_); check(TOK.rightParenthesis); break; case TOK.immutable_: // immutable(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.immutable_); check(TOK.rightParenthesis); break; case TOK.shared_: // shared(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.shared_); check(TOK.rightParenthesis); break; case TOK.inout_: // wild(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.wild); check(TOK.rightParenthesis); break; default: error("basic type expected, not `%s`", token.toChars()); if (token.value == TOK.else_) eSink.errorSupplemental(token.loc, "There's no `static else`, use `else` instead."); t = AST.Type.terror; break; } return t; } private AST.Type parseBasicTypeStartingAt(AST.TypeQualified tid, bool dontLookDotIdents) { AST.Type maybeArray = null; // See https://issues.dlang.org/show_bug.cgi?id=1215 // A basic type can look like MyType (typical case), but also: // MyType.T -> A type // MyType[expr] -> Either a static array of MyType or a type (iif MyType is a Ttuple) // MyType[expr].T -> A type. // MyType[expr].T[expr] -> Either a static array of MyType[expr].T or a type // (iif MyType[expr].T is a Ttuple) while (1) { switch (token.value) { case TOK.dot: { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `.` instead of `%s`", token.toChars()); break; } if (maybeArray) { // This is actually a TypeTuple index, not an {a/s}array. // We need to have a while loop to unwind all index taking: // T[e1][e2].U -> T, addIndex(e1), addIndex(e2) AST.Objects dimStack; AST.Type t = maybeArray; while (true) { if (t.ty == Tsarray) { // The index expression is an Expression. AST.TypeSArray a = cast(AST.TypeSArray)t; dimStack.push(a.dim.syntaxCopy()); t = a.next.syntaxCopy(); } else if (t.ty == Taarray) { // The index expression is a Type. It will be interpreted as an expression at semantic time. AST.TypeAArray a = cast(AST.TypeAArray)t; dimStack.push(a.index.syntaxCopy()); t = a.next.syntaxCopy(); } else { break; } } assert(dimStack.length > 0); // We're good. Replay indices in the reverse order. tid = cast(AST.TypeQualified)t; while (dimStack.length) { tid.addIndex(dimStack.pop()); } maybeArray = null; } const loc = token.loc; Identifier id = token.ident; nextToken(); if (token.value == TOK.not) { auto tempinst = new AST.TemplateInstance(loc, id, parseTemplateArguments()); tid.addInst(tempinst); } else tid.addIdent(id); continue; } case TOK.leftBracket: { if (dontLookDotIdents) // workaround for https://issues.dlang.org/show_bug.cgi?id=14911 goto Lend; nextToken(); AST.Type t = maybeArray ? maybeArray : cast(AST.Type)tid; if (token.value == TOK.rightBracket) { // It's a dynamic array, and we're done: // T[].U does not make sense. t = new AST.TypeDArray(t); nextToken(); return t; } else if (isDeclaration(&token, NeedDeclaratorId.no, TOK.rightBracket, null)) { // This can be one of two things: // 1 - an associative array declaration, T[type] // 2 - an associative array declaration, T[expr] // These can only be disambiguated later. AST.Type index = parseType(); // [ type ] maybeArray = new AST.TypeAArray(t, index); check(TOK.rightBracket); } else { // This can be one of three things: // 1 - an static array declaration, T[expr] // 2 - a slice, T[expr .. expr] // 3 - a template parameter pack index expression, T[expr].U // 1 and 3 can only be disambiguated later. //printf("it's type[expression]\n"); inBrackets++; AST.Expression e = parseAssignExp(); // [ expression ] if (token.value == TOK.slice) { // It's a slice, and we're done. nextToken(); AST.Expression e2 = parseAssignExp(); // [ exp .. exp ] t = new AST.TypeSlice(t, e, e2); inBrackets--; check(TOK.rightBracket); return t; } else { maybeArray = new AST.TypeSArray(t, e); inBrackets--; check(TOK.rightBracket); continue; } } break; } default: goto Lend; } } Lend: return maybeArray ? maybeArray : cast(AST.Type)tid; } /****************************************** * Parse suffixes to type t. * * * [] * [AssignExpression] * [AssignExpression .. AssignExpression] * [Type] * delegate Parameters MemberFunctionAttributes(opt) * function Parameters FunctionAttributes(opt) * Params: * t = the already parsed type * Returns: * t with the suffixes added * See_Also: * https://dlang.org/spec/declaration.html#TypeSuffixes */ private AST.Type parseTypeSuffixes(AST.Type t) { //printf("parseTypeSuffixes()\n"); while (1) { switch (token.value) { case TOK.mul: t = new AST.TypePointer(t); nextToken(); continue; case TOK.leftBracket: // Handle []. Make sure things like // int[3][1] a; // is (array[1] of array[3] of int) nextToken(); if (token.value == TOK.rightBracket) { t = new AST.TypeDArray(t); // [] nextToken(); } else if (isDeclaration(&token, NeedDeclaratorId.no, TOK.rightBracket, null)) { // It's an associative array declaration //printf("it's an associative array\n"); AST.Type index = parseType(); // [ type ] t = new AST.TypeAArray(t, index); check(TOK.rightBracket); } else { //printf("it's type[expression]\n"); inBrackets++; AST.Expression e = parseAssignExp(); // [ expression ] if (!e) { inBrackets--; check(TOK.rightBracket); continue; } if (token.value == TOK.slice) { nextToken(); AST.Expression e2 = parseAssignExp(); // [ exp .. exp ] t = new AST.TypeSlice(t, e, e2); } else { t = new AST.TypeSArray(t, e); } inBrackets--; check(TOK.rightBracket); } continue; case TOK.delegate_: case TOK.function_: { // Handle delegate declaration: // t delegate(parameter list) nothrow pure // t function(parameter list) nothrow pure const save = token.value; nextToken(); auto parameterList = parseParameterList(null); StorageClass stc = parsePostfix(STC.undefined_, null); auto tf = new AST.TypeFunction(parameterList, t, linkage, stc); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild | STC.return_)) { if (save == TOK.function_) error("`const`/`immutable`/`shared`/`inout`/`return` attributes are only valid for non-static member functions"); else tf = cast(AST.TypeFunction)tf.addSTC(stc); } t = save == TOK.delegate_ ? new AST.TypeDelegate(tf) : new AST.TypePointer(tf); // pointer to function continue; } default: return t; } assert(0); } assert(0); } /********************** * Parse Declarator * Params: * t = base type to start with * palt = OR in 1 for C-style function pointer declaration syntax, * 2 for C-style array declaration syntax, otherwise don't modify * pident = set to Identifier if there is one, null if not * tpl = if !null, then set to TemplateParameterList * storageClass = any storage classes seen so far * pdisable = set to true if @disable seen * pudas = any user defined attributes seen so far. Merged with any more found * Returns: * type declared * Reference: https://dlang.org/spec/declaration.html#Declarator */ private AST.Type parseDeclarator(AST.Type t, ref int palt, Identifier* pident, AST.TemplateParameters** tpl = null, StorageClass storageClass = 0, bool* pdisable = null, AST.Expressions** pudas = null) { //printf("parseDeclarator(tpl = %p)\n", tpl); t = parseTypeSuffixes(t); AST.Type ts; switch (token.value) { case TOK.identifier: if (pident) *pident = token.ident; else error("unexpected identifier `%s` in declarator", token.ident.toChars()); ts = t; nextToken(); break; case TOK.leftParenthesis: { // like: T (*fp)(); // like: T ((*fp))(); if (peekNext() == TOK.mul || peekNext() == TOK.leftParenthesis) { /* Parse things with parentheses around the identifier, like: * int (*ident[3])[] * although the D style would be: * int[]*[3] ident */ palt |= 1; nextToken(); ts = parseDeclarator(t, palt, pident); check(TOK.rightParenthesis); break; } ts = t; Token* peekt = &token; /* Completely disallow C-style things like: * T (a); * Improve error messages for the common bug of a missing return type * by looking to see if (a) looks like a parameter list. */ if (isParameters(&peekt)) { error("function declaration without return type. (Note that constructors are always named `this`)"); } else error("unexpected `(` in declarator"); break; } default: ts = t; break; } // parse DeclaratorSuffixes while (1) { switch (token.value) { static if (CARRAYDECL) { /* Support C style array syntax: * int ident[] * as opposed to D-style: * int[] ident */ case TOK.leftBracket: { // This is the old C-style post [] syntax. AST.TypeNext ta; nextToken(); if (token.value == TOK.rightBracket) { // It's a dynamic array ta = new AST.TypeDArray(t); // [] nextToken(); palt |= 2; } else if (isDeclaration(&token, NeedDeclaratorId.no, TOK.rightBracket, null)) { // It's an associative array //printf("it's an associative array\n"); AST.Type index = parseType(); // [ type ] check(TOK.rightBracket); ta = new AST.TypeAArray(t, index); palt |= 2; } else { //printf("It's a static array\n"); AST.Expression e = parseAssignExp(); // [ expression ] ta = new AST.TypeSArray(t, e); check(TOK.rightBracket); palt |= 2; } /* Insert ta into * ts -> ... -> t * so that * ts -> ... -> ta -> t */ AST.Type* pt; for (pt = &ts; *pt != t; pt = &(cast(AST.TypeNext)*pt).next) { } *pt = ta; continue; } } case TOK.leftParenthesis: { if (tpl) { Token* tk = peekPastParen(&token); if (tk.value == TOK.leftParenthesis) { /* Look ahead to see if this is (...)(...), * i.e. a function template declaration */ //printf("function template declaration\n"); // Gather template parameter list *tpl = parseTemplateParameterList(); } else if (tk.value == TOK.assign) { /* or (...) =, * i.e. a variable template declaration */ //printf("variable template declaration\n"); *tpl = parseTemplateParameterList(); break; } } auto parameterList = parseParameterList(null); /* Parse const/immutable/shared/inout/nothrow/pure/return postfix */ // merge prefix storage classes StorageClass stc = parsePostfix(storageClass, pudas); AST.Type tf = new AST.TypeFunction(parameterList, t, linkage, stc); tf = tf.addSTC(stc); if (pdisable) *pdisable = stc & STC.disable ? true : false; /* Insert tf into * ts -> ... -> t * so that * ts -> ... -> tf -> t */ AST.Type* pt; for (pt = &ts; *pt != t; pt = &(cast(AST.TypeNext)*pt).next) { } *pt = tf; break; } default: break; } break; } return ts; } private void parseStorageClasses(ref StorageClass storage_class, ref LINK link, ref bool setAlignment, ref AST.Expression ealign, ref AST.Expressions* udas, out Loc linkloc) { StorageClass stc; bool sawLinkage = false; // seen a linkage declaration linkloc = Loc.initial; while (1) { switch (token.value) { case TOK.const_: if (peekNext() == TOK.leftParenthesis) break; // const as type constructor stc = STC.const_; // const as storage class goto L1; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) break; stc = STC.immutable_; goto L1; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) break; stc = STC.shared_; goto L1; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) break; stc = STC.wild; goto L1; case TOK.static_: stc = STC.static_; goto L1; case TOK.final_: stc = STC.final_; goto L1; case TOK.auto_: stc = STC.auto_; goto L1; case TOK.scope_: stc = STC.scope_; goto L1; case TOK.override_: stc = STC.override_; goto L1; case TOK.abstract_: stc = STC.abstract_; goto L1; case TOK.synchronized_: stc = STC.synchronized_; goto L1; case TOK.deprecated_: stc = STC.deprecated_; goto L1; case TOK.nothrow_: stc = STC.nothrow_; goto L1; case TOK.pure_: stc = STC.pure_; goto L1; case TOK.ref_: stc = STC.ref_; goto L1; case TOK.gshared: stc = STC.gshared; goto L1; case TOK.enum_: { const tv = peekNext(); if (tv == TOK.leftCurly || tv == TOK.colon) break; if (tv == TOK.identifier) { const nextv = peekNext2(); if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) break; } stc = STC.manifest; goto L1; } case TOK.at: { stc = parseAttribute(udas); if (stc) goto L1; continue; } L1: storage_class = appendStorageClass(storage_class, stc); nextToken(); continue; case TOK.extern_: { if (peekNext() != TOK.leftParenthesis) { stc = STC.extern_; goto L1; } if (sawLinkage) error("redundant linkage declaration"); sawLinkage = true; linkloc = token.loc; auto res = parseLinkage(); link = res.link; if (res.idents || res.identExps) { error("C++ name spaces not allowed here"); } if (res.cppmangle != CPPMANGLE.def) { error("C++ mangle declaration not allowed here"); } continue; } case TOK.align_: { nextToken(); setAlignment = true; if (token.value == TOK.leftParenthesis) { nextToken(); ealign = parseExpression(); check(TOK.rightParenthesis); } continue; } default: break; } break; } } /********************************** * Parse Declarations. * These can be: * 1. declarations at global/class level * 2. declarations at statement level * Returns: * array of Declarations. */ private AST.Dsymbols* parseDeclarations(bool autodecl, PrefixAttributes!AST* pAttrs, const(char)* comment) { StorageClass storage_class = STC.undefined_; LINK link = linkage; Loc linkloc = this.linkLoc; bool setAlignment = false; AST.Expression ealign; AST.Expressions* udas = null; //printf("parseDeclarations() %s\n", token.toChars()); if (!comment) comment = token.blockComment.ptr; /* Look for AliasReassignment */ if (token.value == TOK.identifier && peekNext() == TOK.assign) return parseAliasReassignment(comment); /* Declarations that start with `alias` */ bool isAliasDeclaration = false; auto aliasLoc = token.loc; if (token.value == TOK.alias_) { if (auto a = parseAliasDeclarations(comment)) return a; /* Handle these later: * alias StorageClasses type ident; */ isAliasDeclaration = true; } AST.Type ts; if (!autodecl) { parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); if (token.value == TOK.enum_) { AST.Dsymbol d = parseEnum(); auto a = new AST.Dsymbols(); a.push(d); if (udas) { d = new AST.UserAttributeDeclaration(udas, a); a = new AST.Dsymbols(); a.push(d); } addComment(d, comment); return a; } if (token.value == TOK.struct_ || token.value == TOK.union_ || token.value == TOK.class_ || token.value == TOK.interface_) { AST.Dsymbol s = parseAggregate(); auto a = new AST.Dsymbols(); a.push(s); if (storage_class) { s = new AST.StorageClassDeclaration(storage_class, a); a = new AST.Dsymbols(); a.push(s); } if (setAlignment) { s = new AST.AlignDeclaration(s.loc, ealign, a); a = new AST.Dsymbols(); a.push(s); } if (link != linkage) { s = new AST.LinkDeclaration(linkloc, link, a); a = new AST.Dsymbols(); a.push(s); } if (udas) { s = new AST.UserAttributeDeclaration(udas, a); a = new AST.Dsymbols(); a.push(s); } addComment(s, comment); return a; } /* Look for auto initializers: * storage_class identifier = initializer; * storage_class identifier(...) = initializer; */ if ((storage_class || udas) && token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)) { AST.Dsymbols* a = parseAutoDeclarations(storage_class, comment); if (udas) { AST.Dsymbol s = new AST.UserAttributeDeclaration(udas, a); a = new AST.Dsymbols(); a.push(s); } return a; } /* Look for return type inference for template functions. */ { Token* tk; if ((storage_class || udas) && token.value == TOK.identifier && skipParens(peek(&token), &tk) && skipAttributes(tk, &tk) && (tk.value == TOK.leftParenthesis || tk.value == TOK.leftCurly || tk.value == TOK.in_ || tk.value == TOK.out_ || tk.value == TOK.goesTo || tk.value == TOK.do_ || tk.value == TOK.identifier && tk.ident == Id._body)) { if (tk.value == TOK.identifier && tk.ident == Id._body) usageOfBodyKeyword(); ts = null; } else { ts = parseBasicType(); ts = parseTypeSuffixes(ts); } } } if (pAttrs) { storage_class |= pAttrs.storageClass; //pAttrs.storageClass = STC.undefined_; } AST.Type tfirst = null; auto a = new AST.Dsymbols(); while (1) { AST.TemplateParameters* tpl = null; bool disable; int alt = 0; const loc = token.loc; Identifier ident; auto t = parseDeclarator(ts, alt, &ident, &tpl, storage_class, &disable, &udas); assert(t); if (!tfirst) tfirst = t; else if (t != tfirst) error(token.loc, "multiple declarations must have the same type, not `%s` and `%s`", tfirst.toChars(), t.toChars()); if (token.value == TOK.colon && !ident && t.ty != Tfunction) { // Unnamed bit field ident = Identifier.generateAnonymousId("BitField"); } bool isThis = (t.ty == Tident && (cast(AST.TypeIdentifier)t).ident == Id.This && token.value == TOK.assign); if (ident) checkCstyleTypeSyntax(loc, t, alt, ident); else if (!isThis && (t != AST.Type.terror)) noIdentifierForDeclarator(t); if (isAliasDeclaration) { AST.Declaration v; AST.Initializer _init = null; /* Aliases can no longer have multiple declarators, storage classes, * linkages, or auto declarations. * These never made any sense, anyway. * The code below needs to be fixed to reject them. * The grammar has already been fixed to preclude them. */ if (udas) error("user-defined attributes not allowed for `alias` declarations"); if (token.value == TOK.assign) { nextToken(); _init = parseInitializer(); } if (_init) { error("alias cannot have initializer"); } v = new AST.AliasDeclaration(aliasLoc, ident, t); v.storage_class = storage_class; if (pAttrs) { /* AliasDeclaration distinguish @safe, @system, @trusted attributes * on prefix and postfix. * @safe alias void function() FP1; * alias @safe void function() FP2; // FP2 is not @safe * alias void function() @safe FP3; */ pAttrs.storageClass &= STC.safeGroup; } AST.Dsymbol s = v; if (link != linkage) { auto ax = new AST.Dsymbols(); ax.push(v); s = new AST.LinkDeclaration(linkloc, link, ax); } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); addComment(s, comment); continue; default: error("semicolon expected to close `alias` declaration, not `%s`", token.toChars()); break; } } else if (t.ty == Tfunction) { /* @@@DEPRECATED_2.115@@@ * change to error, deprecated in 2.105.1 */ if (storage_class & STC.manifest) deprecation("function cannot have enum storage class"); AST.Expression constraint = null; //printf("%s funcdecl t = %s, storage_class = x%lx\n", loc.toChars(), t.toChars(), storage_class); auto f = new AST.FuncDeclaration(loc, Loc.initial, ident, storage_class | (disable ? STC.disable : 0), t); if (pAttrs) pAttrs.storageClass = STC.undefined_; if (tpl) constraint = parseConstraint(); AST.Dsymbol s = parseContracts(f, !!tpl); auto tplIdent = s.ident; if (link != linkage) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.LinkDeclaration(linkloc, link, ax); } if (udas) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.UserAttributeDeclaration(udas, ax); } /* A template parameter list means it's a function template */ if (tpl) { // @@@DEPRECATED_2.114@@@ // Both deprecated in 2.104, change to error if (storage_class & STC.override_) deprecation(loc, "a function template is not virtual so cannot be marked `override`"); else if (storage_class & STC.abstract_) deprecation(loc, "a function template is not virtual so cannot be marked `abstract`"); // Wrap a template around the function declaration auto decldefs = new AST.Dsymbols(); decldefs.push(s); auto tempdecl = new AST.TemplateDeclaration(loc, tplIdent, tpl, constraint, decldefs); s = tempdecl; StorageClass stc2 = STC.undefined_; if (storage_class & STC.static_) { assert(f.storage_class & STC.static_); f.storage_class &= ~STC.static_; stc2 |= STC.static_; } if (storage_class & STC.deprecated_) { assert(f.storage_class & STC.deprecated_); f.storage_class &= ~STC.deprecated_; stc2 |= STC.deprecated_; } if (stc2 != STC.undefined_) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.StorageClassDeclaration(stc2, ax); } } a.push(s); addComment(s, comment); } else if (ident) { AST.Expression width; if (token.value == TOK.colon) { nextToken(); width = parseCondExp(); } AST.Initializer _init = null; if (token.value == TOK.assign) { nextToken(); _init = parseInitializer(); } AST.Dsymbol s; if (width) { if (_init) error("initializer not allowed for bit-field declaration"); if (storage_class) error("storage class not allowed for bit-field declaration"); s = new AST.BitFieldDeclaration(width.loc, t, ident, width); } else { auto v = new AST.VarDeclaration(loc, t, ident, _init); v.storage_class = storage_class; if (pAttrs) pAttrs.storageClass = STC.undefined_; s = v; } if (tpl && _init) { auto a2 = new AST.Dsymbols(); a2.push(s); auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2, 0); s = tempdecl; } if (setAlignment) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.AlignDeclaration(s.loc, ealign, ax); } if (link != linkage) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.LinkDeclaration(linkloc, link, ax); } if (udas) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.UserAttributeDeclaration(udas, ax); } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); addComment(s, comment); continue; default: if (loc.linnum != token.loc.linnum) { error(token.loc, "semicolon needed to end declaration of `%s`, instead of `%s`", s.toChars(), token.toChars()); eSink.errorSupplemental(loc, "`%s` declared here", s.toChars()); } else { error(token.loc, "semicolon needed to end declaration of `%s` instead of `%s`", s.toChars(), token.toChars()); } break; } } break; } return a; } /// Report an error that a declaration of type `t` is missing an identifier /// The parser is expected to sit on the next token after the type. private void noIdentifierForDeclarator(AST.Type t) { error("no identifier for declarator `%s`", t.toChars()); // A common mistake is to use a reserved keyword as an identifier, e.g. `in` or `out` if (token.isKeyword) { eSink.errorSupplemental(token.loc, "`%s` is a keyword, perhaps append `_` to make it an identifier", token.toChars()); nextToken(); } } /******************************** * Parse AliasReassignment: * identifier = type; * Parser is sitting on the identifier. * https://dlang.org/spec/declaration.html#alias-reassignment * Params: * comment = if not null, comment to attach to symbol * Returns: * array of symbols */ private AST.Dsymbols* parseAliasReassignment(const(char)* comment) { const loc = token.loc; auto ident = token.ident; nextToken(); nextToken(); // advance past = auto t = parseType(); AST.Dsymbol s = new AST.AliasAssign(loc, ident, t, null); check(TOK.semicolon, "alias reassignment"); addComment(s, comment); auto a = new AST.Dsymbols(); a.push(s); return a; } /******************************** * Parse declarations that start with `alias` * Parser is sitting on the `alias`. * https://dlang.org/spec/declaration.html#alias * Params: * comment = if not null, comment to attach to symbol * Returns: * array of symbols */ private AST.Dsymbols* parseAliasDeclarations(const(char)* comment) { const loc = token.loc; nextToken(); Loc linkloc = this.linkLoc; AST.Expressions* udas; LINK link = linkage; StorageClass storage_class = STC.undefined_; AST.Expression ealign; bool setAlignment = false; /* Look for: * alias Identifier this; * https://dlang.org/spec/class.html#alias-this */ if (token.value == TOK.identifier && peekNext() == TOK.this_) { auto s = new AST.AliasThis(loc, token.ident); nextToken(); check(TOK.this_); check(TOK.semicolon, "`alias Identifier this`"); auto a = new AST.Dsymbols(); a.push(s); addComment(s, comment); return a; } /* Look for: * alias this = identifier; */ if (token.value == TOK.this_ && peekNext() == TOK.assign && peekNext2() == TOK.identifier) { check(TOK.this_); check(TOK.assign); auto s = new AST.AliasThis(loc, token.ident); nextToken(); check(TOK.semicolon, "`alias this = Identifier`"); auto a = new AST.Dsymbols(); a.push(s); addComment(s, comment); return a; } /* Look for: * alias identifier = type; * alias identifier(...) = type; * https://dlang.org/spec/declaration.html#alias */ if (token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)) { auto a = new AST.Dsymbols(); while (1) { auto ident = token.ident; nextToken(); AST.TemplateParameters* tpl = null; if (token.value == TOK.leftParenthesis) tpl = parseTemplateParameterList(); check(TOK.assign); bool hasParsedAttributes; void parseAttributes() { if (hasParsedAttributes) // only parse once return; hasParsedAttributes = true; udas = null; storage_class = STC.undefined_; link = linkage; linkloc = this.linkLoc; setAlignment = false; ealign = null; parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); } if (token.value == TOK.at) parseAttributes; AST.Declaration v; AST.Dsymbol s; bool attributesAppended; const StorageClass funcStc = parseTypeCtor(); Token* tk; // function literal? if (token.value == TOK.function_ || token.value == TOK.delegate_ || token.value == TOK.leftParenthesis && skipAttributes(peekPastParen(&token), &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly) || token.value == TOK.leftCurly || token.value == TOK.identifier && peekNext() == TOK.goesTo || token.value == TOK.ref_ && peekNext() == TOK.leftParenthesis && skipAttributes(peekPastParen(peek(&token)), &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly) || token.value == TOK.auto_ && (peekNext() == TOK.leftParenthesis || // for better error peekNext() == TOK.ref_ && peekNext2() == TOK.leftParenthesis) ) { // function (parameters) { statements... } // delegate (parameters) { statements... } // (parameters) { statements... } // (parameters) => expression // { statements... } // identifier => expression // ref (parameters) { statements... } // ref (parameters) => expression // auto ref (parameters) { statements... } // auto ref (parameters) => expression s = parseFunctionLiteral(); if (udas !is null) { if (storage_class != 0) error("cannot put a storage-class in an `alias` declaration."); // parseAttributes shouldn't have set these variables assert(link == linkage && !setAlignment && ealign is null); auto tpl_ = cast(AST.TemplateDeclaration) s; if (tpl_ is null || tpl_.members.length != 1) { error("user-defined attributes are not allowed on `alias` declarations"); } else { auto fd = cast(AST.FuncLiteralDeclaration) (*tpl_.members)[0]; auto tf = cast(AST.TypeFunction) fd.type; assert(tf.parameterList.parameters.length > 0); auto as = new AST.Dsymbols(); (*tf.parameterList.parameters)[0].userAttribDecl = new AST.UserAttributeDeclaration(udas, as); } } v = new AST.AliasDeclaration(loc, ident, s); } else { // type parseAttributes(); if (udas) error("user-defined attributes not allowed for `alias` declarations"); auto t = parseBasicType(); t = parseTypeSuffixes(t); if (token.value == TOK.identifier) { error("unexpected identifier `%s` after `%s`", token.ident.toChars(), t.toChars()); nextToken(); } else if (token.value == TOK.leftParenthesis) { // function type: // StorageClasses Type ( Parameters ) MemberFunctionAttributes auto parameterList = parseParameterList(null); udas = null; parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); if (udas) error("user-defined attributes not allowed for `alias` declarations"); attributesAppended = true; // Note: method types can have a TypeCtor attribute storage_class = appendStorageClass(storage_class, funcStc); t = new AST.TypeFunction(parameterList, t, link, storage_class); } // Disallow meaningless storage classes on type aliases if (storage_class) { // Don't raise errors for STC that are part of a function/delegate type, e.g. // `alias F = ref pure nothrow @nogc @safe int function();` const remStc = t.isTypeFunction ? storage_class & ~(STC.FUNCATTR | STC.TYPECTOR) : { auto tp = t.isTypePointer; const isFuncType = (tp && tp.next.isTypeFunction) || t.isTypeDelegate; return isFuncType ? (storage_class & ~STC.FUNCATTR) : storage_class; }(); if (remStc) { OutBuffer buf; AST.stcToBuffer(buf, remStc); // @@@DEPRECATED_2.103@@@ // Deprecated in 2020-07, can be made an error in 2.103 eSink.deprecation(token.loc, "storage class `%s` has no effect in type aliases", buf.peekChars()); } } v = new AST.AliasDeclaration(loc, ident, t); } if (!attributesAppended) storage_class = appendStorageClass(storage_class, funcStc); v.storage_class = storage_class; s = v; if (tpl) { auto a2 = new AST.Dsymbols(); a2.push(s); auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2); s = tempdecl; } if (link != linkage) { auto a2 = new AST.Dsymbols(); a2.push(s); s = new AST.LinkDeclaration(linkloc, link, a2); } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); addComment(s, comment); if (token.value != TOK.identifier) { error("identifier expected following comma, not `%s`", token.toChars()); break; } if (peekNext() != TOK.assign && peekNext() != TOK.leftParenthesis) { error("`=` expected following identifier"); nextToken(); break; } continue; default: error("semicolon expected to close `alias` declaration, not `%s`", token.toChars()); break; } break; } return a; } // alias StorageClasses type ident; return null; } private AST.Dsymbol parseFunctionLiteral() { const loc = token.loc; AST.TemplateParameters* tpl = null; AST.ParameterList parameterList; AST.Type tret = null; StorageClass stc = 0; TOK save = TOK.reserved; switch (token.value) { case TOK.function_: case TOK.delegate_: save = token.value; nextToken(); if (token.value == TOK.auto_) { nextToken(); if (token.value == TOK.ref_) { // function auto ref (parameters) { statements... } // delegate auto ref (parameters) { statements... } stc = STC.auto_ | STC.ref_; nextToken(); } else error("`auto` can only be used as part of `auto ref` for function literal return values"); } else if (token.value == TOK.ref_) { // function ref (parameters) { statements... } // delegate ref (parameters) { statements... } stc = STC.ref_; nextToken(); } if (token.value != TOK.leftParenthesis && token.value != TOK.leftCurly && token.value != TOK.goesTo) { // function type (parameters) { statements... } // delegate type (parameters) { statements... } tret = parseBasicType(); tret = parseTypeSuffixes(tret); // function return type } if (token.value == TOK.leftParenthesis) { // function (parameters) { statements... } // delegate (parameters) { statements... } } else { // function { statements... } // delegate { statements... } break; } goto case TOK.leftParenthesis; case TOK.auto_: { nextToken(); if (token.value == TOK.ref_) { // auto ref (parameters) => expression // auto ref (parameters) { statements... } stc = STC.auto_ | STC.ref_; nextToken(); } else error("`auto` can only be used as part of `auto ref` for function literal return values"); goto case TOK.leftParenthesis; } case TOK.ref_: { // ref (parameters) => expression // ref (parameters) { statements... } stc = STC.ref_; nextToken(); goto case TOK.leftParenthesis; } case TOK.leftParenthesis: { // (parameters) => expression // (parameters) { statements... } parameterList = parseParameterList(&tpl); stc = parsePostfix(stc, null); if (StorageClass modStc = stc & STC.TYPECTOR) { if (save == TOK.function_) { OutBuffer buf; AST.stcToBuffer(buf, modStc); error("function literal cannot be `%s`", buf.peekChars()); } else save = TOK.delegate_; } break; } case TOK.leftCurly: // { statements... } break; case TOK.identifier: { // identifier => expression parameterList.parameters = new AST.Parameters(); Identifier id = Identifier.generateId("__T"); AST.Type t = new AST.TypeIdentifier(loc, id); parameterList.parameters.push(new AST.Parameter(loc, STC.parameter, t, token.ident, null, null)); tpl = new AST.TemplateParameters(); AST.TemplateParameter tp = new AST.TemplateTypeParameter(loc, id, null, null); tpl.push(tp); nextToken(); break; } default: assert(0); } auto tf = new AST.TypeFunction(parameterList, tret, linkage, stc); tf = cast(AST.TypeFunction)tf.addSTC(stc); auto fd = new AST.FuncLiteralDeclaration(loc, Loc.initial, tf, save, null, null, stc & STC.auto_); if (token.value == TOK.goesTo) { check(TOK.goesTo); if (token.value == TOK.leftCurly) { deprecation("using `(args) => { ... }` to create a delegate that returns a delegate is error-prone."); deprecationSupplemental("Use `(args) { ... }` for a multi-statement function literal or use `(args) => () { }` if you intended for the lambda to return a delegate."); } const returnloc = token.loc; AST.Expression ae = parseAssignExp(); fd.fbody = new AST.ReturnStatement(returnloc, ae); fd.endloc = token.loc; } else { parseContracts(fd); } if (tpl) { // Wrap a template around function fd auto decldefs = new AST.Dsymbols(); decldefs.push(fd); return new AST.TemplateDeclaration(fd.loc, fd.ident, tpl, null, decldefs, false, true); } return fd; } /***************************************** * Parse contracts following function declaration. */ private AST.FuncDeclaration parseContracts(AST.FuncDeclaration f, bool isTemplateFunction = false) { LINK linksave = linkage; bool literal = f.isFuncLiteralDeclaration() !is null; // The following is irrelevant, as it is overridden by sc.linkage in // TypeFunction::semantic linkage = LINK.d; // nested functions have D linkage bool requireDo = false; L1: switch (token.value) { case TOK.goesTo: if (requireDo) error("missing `do { ... }` after `in` or `out`"); const returnloc = token.loc; nextToken(); f.fbody = new AST.ReturnStatement(returnloc, parseExpression()); f.endloc = token.loc; check(TOK.semicolon); break; case TOK.leftCurly: if (requireDo) error("missing `do { ... }` after `in` or `out`"); f.fbody = parseStatement(0); f.endloc = endloc; break; case TOK.identifier: if (token.ident == Id._body) { usageOfBodyKeyword(); goto case TOK.do_; } goto default; case TOK.do_: nextToken(); f.fbody = parseStatement(ParseStatementFlags.curly); f.endloc = endloc; break; version (none) { // Do we want this for function declarations, so we can do: // int x, y, foo(), z; case TOK.comma: nextToken(); continue; } case TOK.in_: // in { statements... } // in (expression) auto loc = token.loc; nextToken(); if (!f.frequires) { f.frequires = new AST.Statements; } if (token.value == TOK.leftParenthesis) { nextToken(); AST.Expression e = parseAssignExp(), msg = null; if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); e = new AST.AssertExp(loc, e, msg); f.frequires.push(new AST.ExpStatement(loc, e)); requireDo = false; } else { auto ret = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); assert(ret); f.frequires.push(ret); requireDo = true; } goto L1; case TOK.out_: // out { statements... } // out (; expression) // out (identifier) { statements... } // out (identifier; expression) auto loc = token.loc; nextToken(); if (!f.fensures) { f.fensures = new AST.Ensures; } Identifier id = null; if (token.value != TOK.leftCurly) { check(TOK.leftParenthesis); if (token.value != TOK.identifier && token.value != TOK.semicolon) error("`(identifier) { ... }` or `(identifier; expression)` following `out` expected, not `%s`", token.toChars()); if (token.value != TOK.semicolon) { id = token.ident; nextToken(); } if (token.value == TOK.semicolon) { nextToken(); AST.Expression e = parseAssignExp(), msg = null; if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); e = new AST.AssertExp(loc, e, msg); f.fensures.push(AST.Ensure(id, new AST.ExpStatement(loc, e))); requireDo = false; goto L1; } check(TOK.rightParenthesis); } f.fensures.push(AST.Ensure(id, parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_))); requireDo = true; goto L1; case TOK.semicolon: if (!literal) { // https://issues.dlang.org/show_bug.cgi?id=15799 // Semicolon becomes a part of function declaration // only when 'do' is not required if (!requireDo) nextToken(); break; } goto default; default: if (literal) { const(char)* sbody = requireDo ? "do " : ""; error("missing `%s{ ... }` for function literal", sbody); } else if (!requireDo) // allow contracts even with no body { TOK t = token.value; if (t == TOK.const_ || t == TOK.immutable_ || t == TOK.inout_ || t == TOK.return_ || t == TOK.shared_ || t == TOK.nothrow_ || t == TOK.pure_) error("'%s' cannot be placed after a template constraint", token.toChars); else if (t == TOK.at) error("attributes cannot be placed after a template constraint"); else if (t == TOK.if_) { if (isTemplateFunction) error("template constraint must follow parameter lists and attributes"); else error("cannot use function constraints for non-template functions. Use `static if` instead"); } else error("semicolon expected following function declaration, not `%s`", token.toChars()); } break; } if (literal && !f.fbody) { // Set empty function body for error recovery f.fbody = new AST.CompoundStatement(Loc.initial, cast(AST.Statement)null); } linkage = linksave; return f; } /***************************************** */ private void checkDanglingElse(Loc elseloc) { if (token.value != TOK.else_ && token.value != TOK.catch_ && token.value != TOK.finally_ && lookingForElse.linnum != 0) { eSink.warning(elseloc, "else is dangling, add { } after condition at %s", lookingForElse.toChars()); } } /* ************************* * Issue errors if C-style syntax * Params: * alt = !=0 for C-style syntax */ private void checkCstyleTypeSyntax(Loc loc, AST.Type t, int alt, Identifier ident) { if (!alt) return; const(char)* sp = !ident ? "" : " "; const(char)* s = !ident ? "" : ident.toChars(); error(loc, "instead of C-style syntax, use D-style `%s%s%s`", t.toChars(), sp, s); } /***************************** * Ad-hoc error message for missing or extra parens that close a condition. * Params: * start = "if", "while", etc. Must be 0 terminated. * param = if the condition is a declaration, this will be non-null * condition = if param is null, then this is the conditional Expression. If condition is null, * then an error in the condition was already reported. */ private void closeCondition(string start, AST.Parameter param, AST.Expression condition) { string format; if (token.value != TOK.rightParenthesis && condition) { format = "missing closing `)` after `%s (%s`"; } else check(TOK.rightParenthesis); if (token.value == TOK.rightParenthesis) { if (condition) // if not an error in condition format = "extra `)` after `%s (%s)`"; nextToken(); } if (format) error(token.loc, format.ptr, start.ptr, param ? "declaration".ptr : condition.toChars()); } /***************************************** * Parses `foreach` statements, `static foreach` statements and * `static foreach` declarations. * Params: * Foreach = one of Statement, StaticForeachStatement, StaticForeachDeclaration * loc = location of foreach * pLastDecl = non-null for StaticForeachDeclaration * Returns: * the Foreach generated */ private Foreach parseForeach(alias Foreach)(Loc loc, AST.Dsymbol* pLastDecl) { static if (is(Foreach == AST.StaticForeachStatement) || is(Foreach == AST.StaticForeachDeclaration)) { nextToken(); } TOK op = token.value; nextToken(); check(TOK.leftParenthesis); auto parameters = new AST.Parameters(); Identifier lastai; while (1) { Identifier ai = null; AST.Type at; Loc aloc; StorageClass storageClass = 0; StorageClass stc = 0; Lagain: if (stc) { storageClass = appendStorageClass(storageClass, stc); nextToken(); } switch (token.value) { case TOK.ref_: stc = STC.ref_; goto Lagain; case TOK.scope_: stc = STC.scope_; goto Lagain; case TOK.out_: error("cannot declare `out` loop variable, use `ref` instead"); stc = STC.out_; goto Lagain; case TOK.auto_: error("cannot declare `auto` loop variable, omit `auto` to still get type inference"); stc = STC.auto_; goto Lagain; case TOK.enum_: stc = STC.manifest; goto Lagain; case TOK.alias_: storageClass = appendStorageClass(storageClass, STC.alias_); nextToken(); break; case TOK.const_: if (peekNext() != TOK.leftParenthesis) { stc = STC.const_; goto Lagain; } break; case TOK.immutable_: if (peekNext() != TOK.leftParenthesis) { stc = STC.immutable_; goto Lagain; } break; case TOK.shared_: if (peekNext() != TOK.leftParenthesis) { stc = STC.shared_; goto Lagain; } break; case TOK.inout_: if (peekNext() != TOK.leftParenthesis) { stc = STC.wild; goto Lagain; } break; default: break; } if (token.value == TOK.identifier) { const tv = peekNext(); if (tv == TOK.comma || tv == TOK.semicolon || tv == TOK.rightParenthesis) { lastai = token.ident; ai = token.ident; at = null; // infer argument type aloc = token.loc; nextToken(); goto Larg; } } at = parseType(&ai); if (!ai) noIdentifierForDeclarator(at); Larg: auto p = new AST.Parameter(aloc, storageClass, at, ai, null, null); parameters.push(p); if (token.value == TOK.comma) { nextToken(); continue; } break; } if (token.value != TOK.semicolon) { error("missing `; expression` before `)` of `foreach`"); nextToken(); if (lastai && parameters.length >= 2) { eSink.errorSupplemental(loc, "perhaps the `;` goes before `%s`", lastai.toChars()); } return null; } nextToken(); AST.Expression aggr = parseExpression(); if (token.value == TOK.slice && parameters.length == 1) { AST.Parameter p = (*parameters)[0]; nextToken(); AST.Expression upr = parseExpression(); check(TOK.rightParenthesis); Loc endloc; static if (is(Foreach == AST.Statement) || is(Foreach == AST.StaticForeachStatement)) { AST.Statement _body = parseStatement(0, null, &endloc); } else { AST.Statement _body = null; } auto rangefe = new AST.ForeachRangeStatement(loc, op, p, aggr, upr, _body, endloc); static if (is(Foreach == AST.Statement)) { return rangefe; } else static if(is(Foreach == AST.StaticForeachDeclaration)) { return new AST.StaticForeachDeclaration(new AST.StaticForeach(loc, null, rangefe), parseBlock(pLastDecl)); } else static if (is(Foreach == AST.StaticForeachStatement)) { return new AST.StaticForeachStatement(loc, new AST.StaticForeach(loc, null, rangefe)); } } else { check(TOK.rightParenthesis); Loc endloc; static if (is(Foreach == AST.Statement) || is(Foreach == AST.StaticForeachStatement)) { AST.Statement _body = parseStatement(0, null, &endloc); } else { AST.Statement _body = null; } auto aggrfe = new AST.ForeachStatement(loc, op, parameters, aggr, _body, endloc); static if (is(Foreach == AST.Statement)) { return aggrfe; } else static if(is(Foreach == AST.StaticForeachDeclaration)) { return new AST.StaticForeachDeclaration(new AST.StaticForeach(loc, aggrfe, null), parseBlock(pLastDecl)); } else static if (is(Foreach == AST.StaticForeachStatement)) { return new AST.StaticForeachStatement(loc, new AST.StaticForeach(loc, aggrfe, null)); } } } /*** * Parse an assignment condition for if or while statements. * * Returns: * The variable that is declared inside the condition */ AST.Parameter parseAssignCondition() { AST.Parameter param = null; StorageClass storageClass = 0; StorageClass stc = 0; Lwhile: while (1) { switch (token.value) { // parse ref for better error case TOK.ref_: stc = STC.ref_; break; case TOK.scope_: stc = STC.scope_; break; case TOK.auto_: stc = STC.auto_; break; case TOK.const_: if (peekNext() != TOK.leftParenthesis) { stc = STC.const_; break; } goto default; case TOK.immutable_: if (peekNext() != TOK.leftParenthesis) { stc = STC.immutable_; break; } goto default; case TOK.shared_: if (peekNext() != TOK.leftParenthesis) { stc = STC.shared_; break; } goto default; case TOK.inout_: if (peekNext() != TOK.leftParenthesis) { stc = STC.wild; break; } goto default; default: break Lwhile; } storageClass = appendStorageClass(storageClass, stc); nextToken(); } auto n = peek(&token); if (storageClass != 0 && token.value == TOK.identifier && n.value == TOK.assign) { Identifier ai = token.ident; AST.Type at = null; // infer parameter type const aloc = token.loc; nextToken(); check(TOK.assign); param = new AST.Parameter(aloc, storageClass, at, ai, null, null); } else if (isDeclaration(&token, NeedDeclaratorId.must, TOK.assign, null)) { Identifier ai; const aloc = token.loc; AST.Type at = parseType(&ai); check(TOK.assign); param = new AST.Parameter(aloc, storageClass, at, ai, null, null); } else if (storageClass != 0) error("found `%s` while expecting `=` or identifier", n.toChars()); return param; } /***************************************** * Input: * flags PSxxxx * Output: * pEndloc if { ... statements ... }, store location of closing brace, otherwise loc of last token of statement */ AST.Statement parseStatement(int flags, const(char)** endPtr = null, Loc* pEndloc = null) { AST.Statement s; AST.Condition cond; AST.Statement ifbody; AST.Statement elsebody; bool isfinal; const loc = token.loc; //printf("parseStatement()\n"); if (flags & ParseStatementFlags.curly && token.value != TOK.leftCurly) error("statement expected to be `{ }`, not `%s`", token.toChars()); switch (token.value) { case TOK.identifier: { /* A leading identifier can be a declaration, label, or expression. * The easiest case to check first is label: */ if (peekNext() == TOK.colonColon) { // skip ident:: nextToken(); nextToken(); error("use `.` for member lookup, not `::`"); break; } if (peekNext() == TOK.colon) { // It's a label Identifier ident = token.ident; nextToken(); nextToken(); if (token.value == TOK.rightCurly) s = null; else if (token.value == TOK.leftCurly) s = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); else if (flags & ParseStatementFlags.curlyScope) s = parseStatement(ParseStatementFlags.semiOk | ParseStatementFlags.curlyScope); else s = parseStatement(ParseStatementFlags.semiOk); s = new AST.LabelStatement(loc, ident, s); break; } goto case TOK.dot; } case TOK.dot: case TOK.typeof_: case TOK.vector: case TOK.traits: /* https://issues.dlang.org/show_bug.cgi?id=15163 * If tokens can be handled as * old C-style declaration or D expression, prefer the latter. */ if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) goto Ldeclaration; goto Lexp; case TOK.assert_: case TOK.this_: case TOK.super_: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.string_: case TOK.interpolated: case TOK.hexadecimalString: case TOK.leftParenthesis: case TOK.cast_: case TOK.mul: case TOK.min: case TOK.add: case TOK.tilde: case TOK.not: case TOK.plusPlus: case TOK.minusMinus: case TOK.new_: case TOK.delete_: case TOK.delegate_: case TOK.function_: case TOK.typeid_: case TOK.is_: case TOK.leftBracket: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: Lexp: { AST.Expression exp = parseExpression(); /* https://issues.dlang.org/show_bug.cgi?id=15103 * Improve declaration / initialization syntax error message * Error: found 'foo' when expecting ';' following expression * becomes Error: found `(` when expecting `;` or `=`, did you mean `Foo foo = 42`? */ if (token.value == TOK.identifier && exp.op == EXP.identifier) { error(token.loc, "found `%s` when expecting `;` or `=`, did you mean `%s %s = %s`?", peek(&token).toChars(), exp.toChars(), token.toChars(), peek(peek(&token)).toChars()); nextToken(); } else { /* * https://issues.dlang.org/show_bug.cgi?id=22529 * Avoid empty declaration error in case of missing semicolon * followed by another token and another semicolon. E.g.: * * foo() * return; * * When the missing `;` error is emitted, token is sitting on return. * If we simply use `check` to emit the error, the token is advanced * to `;` and the empty statement error would follow. To avoid that, * we check if the next token is a semicolon and simply output the error, * otherwise we fall back on the old path (advancing the token). */ if (token.value != TOK.semicolon && peek(&token).value == TOK.semicolon) error("found `%s` when expecting `;` following expression", token.toChars()); else { if (token.value != TOK.semicolon) { error("found `%s` when expecting `;` following expression", token.toChars()); eSink.errorSupplemental(exp.loc, "expression: `%s`", exp.toChars()); } nextToken(); } } s = new AST.ExpStatement(loc, exp); break; } case TOK.static_: { // Look ahead to see if it's static assert() or static if() const tv = peekNext(); if (tv == TOK.assert_) { s = new AST.StaticAssertStatement(parseStaticAssert()); break; } if (tv == TOK.if_) { cond = parseStaticIfCondition(); goto Lcondition; } if (tv == TOK.foreach_ || tv == TOK.foreach_reverse_) { s = parseForeach!(AST.StaticForeachStatement)(loc, null); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } if (tv == TOK.import_) { AST.Dsymbols* imports = parseImport(); s = new AST.ImportStatement(loc, imports); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } goto Ldeclaration; } case TOK.final_: if (peekNext() == TOK.switch_) { nextToken(); isfinal = true; goto Lswitch; } goto Ldeclaration; case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: // bug 7773: int.max is always a part of expression if (peekNext() == TOK.dot) goto Lexp; if (peekNext() == TOK.leftParenthesis) goto Lexp; goto case; // FunctionLiteral `auto ref (` case TOK.auto_: if (peekNext() == TOK.ref_ && peekNext2() == TOK.leftParenthesis) goto Lexp; goto Ldeclaration; case TOK.ref_: if (peekNext() == TOK.leftParenthesis) goto Lexp; goto Ldeclaration; case TOK.alias_: case TOK.const_: case TOK.abstract_: case TOK.extern_: case TOK.align_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: case TOK.deprecated_: case TOK.nothrow_: case TOK.pure_: case TOK.gshared: case TOK.at: case TOK.struct_: case TOK.union_: case TOK.class_: case TOK.interface_: Ldeclaration: { AST.Dsymbols* a = parseDeclarations(false, null, null); if (a.length > 1) { auto as = new AST.Statements(); as.reserve(a.length); foreach (i; 0 .. a.length) { AST.Dsymbol d = (*a)[i]; s = new AST.ExpStatement(loc, d); as.push(s); } s = new AST.CompoundDeclarationStatement(loc, as); } else if (a.length == 1) { AST.Dsymbol d = (*a)[0]; s = new AST.ExpStatement(loc, d); } else s = new AST.ExpStatement(loc, cast(AST.Expression)null); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } case TOK.enum_: { /* Determine if this is a manifest constant declaration, * or a conventional enum. */ AST.Dsymbol d; const tv = peekNext(); if (tv == TOK.leftCurly || tv == TOK.colon) d = parseEnum(); else if (tv != TOK.identifier) goto Ldeclaration; else { const nextv = peekNext2(); if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) d = parseEnum(); else goto Ldeclaration; } s = new AST.ExpStatement(loc, d); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } case TOK.mixin_: { if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) goto Ldeclaration; const tv = peekNext(); if (tv == TOK.leftParenthesis) { // mixin(string) AST.Expression e = parseAssignExp(); check(TOK.semicolon, "mixin"); if (e.op == EXP.mixin_) { AST.MixinExp cpe = cast(AST.MixinExp)e; s = new AST.MixinStatement(loc, cpe.exps); } else { s = new AST.ExpStatement(loc, e); } break; } else if (tv == TOK.template_) { // mixin template nextToken(); AST.Dsymbol d = parseTemplateDeclaration(true); s = new AST.ExpStatement(loc, d); break; } AST.Dsymbol d = parseMixin(); s = new AST.ExpStatement(loc, d); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } case TOK.leftCurly: { const lcLoc = token.loc; const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; nextToken(); //if (token.value == TOK.semicolon) // error("use `{ }` for an empty statement, not `;`"); auto statements = new AST.Statements(); while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) { statements.push(parseStatement(ParseStatementFlags.curlyScope | ParseStatementFlags.semiOk)); } if (endPtr) *endPtr = token.ptr; endloc = token.loc; if (pEndloc) { *pEndloc = token.loc; pEndloc = null; // don't set it again } s = new AST.CompoundStatement(loc, statements); if (flags & (ParseStatementFlags.scope_ | ParseStatementFlags.curlyScope)) s = new AST.ScopeStatement(loc, s, token.loc); if (token.value != TOK.rightCurly) { error(token.loc, "matching `}` expected following compound statement, not `%s`", token.toChars()); eSink.errorSupplemental(lcLoc, "unmatched `{`"); } else nextToken(); lookingForElse = lookingForElseSave; break; } case TOK.while_: { nextToken(); check(TOK.leftParenthesis); auto param = parseAssignCondition(); auto condition = parseExpression(); closeCondition("while", param, condition); Loc endloc; AST.Statement _body = parseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.WhileStatement(loc, condition, _body, endloc, param); break; } case TOK.semicolon: if (!(flags & ParseStatementFlags.semiOk)) { error("use `{ }` for an empty statement, not `;`"); } nextToken(); s = new AST.ExpStatement(loc, cast(AST.Expression)null); break; case TOK.do_: { AST.Statement _body; nextToken(); const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; _body = parseStatement(ParseStatementFlags.scope_); lookingForElse = lookingForElseSave; check(TOK.while_); check(TOK.leftParenthesis); auto condition = parseExpression(); closeCondition("do .. while", null, condition); if (token.value == TOK.semicolon) nextToken(); else error("terminating `;` required after do-while statement"); s = new AST.DoStatement(loc, _body, condition, token.loc); break; } case TOK.for_: { AST.Statement _init; AST.Expression condition; AST.Expression increment; nextToken(); check(TOK.leftParenthesis); if (token.value == TOK.semicolon) { _init = null; nextToken(); } else { const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; _init = parseStatement(0); lookingForElse = lookingForElseSave; } if (token.value == TOK.semicolon) { condition = null; nextToken(); } else { condition = parseExpression(); check(TOK.semicolon, "`for` condition"); } if (token.value == TOK.rightParenthesis) { increment = null; nextToken(); } else { increment = parseExpression(); check(TOK.rightParenthesis); } Loc endloc; AST.Statement _body = parseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.ForStatement(loc, _init, condition, increment, _body, endloc); break; } case TOK.foreach_: case TOK.foreach_reverse_: { s = parseForeach!(AST.Statement)(loc, null); break; } case TOK.if_: { nextToken(); check(TOK.leftParenthesis); auto param = parseAssignCondition(); auto condition = parseExpression(); closeCondition("if", param, condition); { const lookingForElseSave = lookingForElse; lookingForElse = loc; ifbody = parseStatement(ParseStatementFlags.scope_); lookingForElse = lookingForElseSave; } if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); elsebody = parseStatement(ParseStatementFlags.scope_); checkDanglingElse(elseloc); } else elsebody = null; if (condition && ifbody) s = new AST.IfStatement(loc, param, condition, ifbody, elsebody, token.loc); else s = null; // don't propagate parsing errors break; } case TOK.else_: error("found `else` without a corresponding `if`, `version` or `debug` statement"); goto Lerror; case TOK.scope_: if (peekNext() != TOK.leftParenthesis) goto Ldeclaration; // scope used as storage class nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("scope identifier expected"); goto Lerror; } else { TOK t = TOK.onScopeExit; Identifier id = token.ident; if (id == Id.exit) t = TOK.onScopeExit; else if (id == Id.failure) t = TOK.onScopeFailure; else if (id == Id.success) t = TOK.onScopeSuccess; else error("valid scope identifiers are `exit`, `failure`, or `success`, not `%s`", id.toChars()); nextToken(); check(TOK.rightParenthesis); AST.Statement st = parseStatement(ParseStatementFlags.scope_); s = new AST.ScopeGuardStatement(loc, t, st); break; } case TOK.debug_: nextToken(); if (token.value == TOK.assign) { if (auto ds = parseDebugSpecification()) { if (ds.ident) eSink.error(ds.loc, "%s `%s` declaration must be at module level", ds.kind, ds.toPrettyChars); else eSink.error(ds.loc, "%s `%s` level declaration must be at module level", ds.kind, ds.toPrettyChars); } break; } cond = parseDebugCondition(); goto Lcondition; case TOK.version_: nextToken(); if (token.value == TOK.assign) { if (auto vs = parseVersionSpecification()) { if (vs.ident) eSink.error(vs.loc, "%s `%s` declaration must be at module level", vs.kind, vs.toPrettyChars); else eSink.error(vs.loc, "%s `%s` level declaration must be at module level", vs.kind, vs.toPrettyChars); } break; } cond = parseVersionCondition(); goto Lcondition; Lcondition: { const lookingForElseSave = lookingForElse; lookingForElse = loc; ifbody = parseStatement(0); lookingForElse = lookingForElseSave; } elsebody = null; if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); elsebody = parseStatement(0); checkDanglingElse(elseloc); } s = new AST.ConditionalStatement(loc, cond, ifbody, elsebody); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; case TOK.pragma_: { Identifier ident; AST.Expressions* args = null; AST.Statement _body; nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("`pragma(identifier)` expected"); goto Lerror; } ident = token.ident; nextToken(); if (token.value == TOK.comma && peekNext() != TOK.rightParenthesis) args = parseArguments(); // pragma(identifier, args...); else check(TOK.rightParenthesis); // pragma(identifier); if (token.value == TOK.semicolon) { nextToken(); _body = null; } else _body = parseStatement(0); s = new AST.PragmaStatement(loc, ident, args, _body); break; } case TOK.switch_: isfinal = false; goto Lswitch; Lswitch: { nextToken(); check(TOK.leftParenthesis); auto param = parseAssignCondition(); AST.Expression condition = parseExpression(); closeCondition("switch", null, condition); AST.Statement _body = parseStatement(ParseStatementFlags.scope_); s = new AST.SwitchStatement(loc, param, condition, _body, isfinal, token.loc); break; } case TOK.case_: { AST.Expression exp; AST.Expressions cases; // array of Expression's AST.Expression last = null; nextToken(); do { exp = parseAssignExp(); cases.push(exp); if (token.value != TOK.comma) break; nextToken(); //comma } while (token.value != TOK.colon && token.value != TOK.endOfFile); check(TOK.colon); /* case exp: .. case last: */ if (token.value == TOK.slice) { if (cases.length > 1) error("only one `case` allowed for start of case range"); nextToken(); check(TOK.case_); last = parseAssignExp(); check(TOK.colon); } if (flags & ParseStatementFlags.curlyScope) { auto statements = new AST.Statements(); while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly) { auto cur = parseStatement(ParseStatementFlags.curlyScope); statements.push(cur); // https://issues.dlang.org/show_bug.cgi?id=21739 // Stop at the last break s.t. the following non-case statements are // not merged into the current case. This can happen for // case 1: ... break; // debug { case 2: ... } if (cur && cur.isBreakStatement()) break; } s = new AST.CompoundStatement(loc, statements); } else { s = parseStatement(0); } s = new AST.ScopeStatement(loc, s, token.loc); if (last) { s = new AST.CaseRangeStatement(loc, exp, last, s); } else { // Keep cases in order by building the case statements backwards for (size_t i = cases.length; i; i--) { exp = cases[i - 1]; s = new AST.CaseStatement(loc, exp, s); } } break; } case TOK.default_: { nextToken(); check(TOK.colon); if (flags & ParseStatementFlags.curlyScope) { auto statements = new AST.Statements(); while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly) { statements.push(parseStatement(ParseStatementFlags.curlyScope)); } s = new AST.CompoundStatement(loc, statements); } else s = parseStatement(0); s = new AST.ScopeStatement(loc, s, token.loc); s = new AST.DefaultStatement(loc, s); break; } case TOK.return_: { AST.Expression exp; nextToken(); exp = token.value == TOK.semicolon ? null : parseExpression(); check(TOK.semicolon, "`return` statement"); s = new AST.ReturnStatement(loc, exp); break; } case TOK.break_: { Identifier ident; nextToken(); ident = null; if (token.value == TOK.identifier) { ident = token.ident; nextToken(); } check(TOK.semicolon, "`break` statement"); s = new AST.BreakStatement(loc, ident); break; } case TOK.continue_: { Identifier ident; nextToken(); ident = null; if (token.value == TOK.identifier) { ident = token.ident; nextToken(); } check(TOK.semicolon, "`continue` statement"); s = new AST.ContinueStatement(loc, ident); break; } case TOK.goto_: { Identifier ident; nextToken(); if (token.value == TOK.default_) { nextToken(); s = new AST.GotoDefaultStatement(loc); } else if (token.value == TOK.case_) { AST.Expression exp = null; nextToken(); if (token.value != TOK.semicolon) exp = parseExpression(); s = new AST.GotoCaseStatement(loc, exp); } else { if (token.value != TOK.identifier) { error("identifier expected following `goto`"); ident = null; } else { ident = token.ident; nextToken(); } s = new AST.GotoStatement(loc, ident); } check(TOK.semicolon, "`goto` statement"); break; } case TOK.synchronized_: { AST.Expression exp; AST.Statement _body; Token* t = peek(&token); if (skipAttributes(t, &t) && t.value == TOK.class_) goto Ldeclaration; nextToken(); if (token.value == TOK.leftParenthesis) { nextToken(); exp = parseExpression(); closeCondition("synchronized", null, exp); } else exp = null; _body = parseStatement(ParseStatementFlags.scope_); s = new AST.SynchronizedStatement(loc, exp, _body); break; } case TOK.with_: { AST.Expression exp; AST.Statement _body; Loc endloc = loc; nextToken(); check(TOK.leftParenthesis); exp = parseExpression(); closeCondition("with", null, exp); _body = parseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.WithStatement(loc, exp, _body, endloc); break; } case TOK.try_: { AST.Statement _body; AST.Catches* catches = null; AST.Statement finalbody = null; nextToken(); const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; _body = parseStatement(ParseStatementFlags.scope_); lookingForElse = lookingForElseSave; while (token.value == TOK.catch_) { AST.Statement handler; AST.Catch c; AST.Type t; Identifier id; const catchloc = token.loc; nextToken(); if (token.value != TOK.leftParenthesis) { deprecation("`catch` statement without an exception specification is deprecated"); deprecationSupplemental("use `catch(Throwable)` for old behavior"); t = null; id = null; } else { check(TOK.leftParenthesis); id = null; t = parseType(&id); check(TOK.rightParenthesis); } handler = parseStatement(0); c = new AST.Catch(catchloc, t, id, handler); if (!catches) catches = new AST.Catches(); catches.push(c); } if (token.value == TOK.finally_) { nextToken(); finalbody = parseStatement(ParseStatementFlags.scope_); } s = _body; if (!catches && !finalbody) error("`catch` or `finally` expected following `try`"); else { if (catches) s = new AST.TryCatchStatement(loc, _body, catches); if (finalbody) s = new AST.TryFinallyStatement(loc, s, finalbody); } break; } case TOK.throw_: { AST.Expression exp; nextToken(); exp = parseExpression(); check(TOK.semicolon, "`throw` statement"); s = new AST.ThrowStatement(loc, exp); break; } case TOK.asm_: s = parseAsm(false); break; case TOK.import_: { /* https://issues.dlang.org/show_bug.cgi?id=16088 * * At this point it can either be an * https://dlang.org/spec/grammar.html#ImportExpression * or an * https://dlang.org/spec/grammar.html#ImportDeclaration. * See if the next token after `import` is a `(`; if so, * then it is an import expression. */ if (peekNext() == TOK.leftParenthesis) { AST.Expression e = parseExpression(); check(TOK.semicolon, "`import` Expression"); s = new AST.ExpStatement(loc, e); } else { AST.Dsymbols* imports = parseImport(); s = new AST.ImportStatement(loc, imports); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); } break; } case TOK.template_: { AST.Dsymbol d = parseTemplateDeclaration(); s = new AST.ExpStatement(loc, d); break; } default: error("found `%s` instead of statement", token.toChars()); goto Lerror; Lerror: while (token.value != TOK.rightCurly && token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); if (token.value == TOK.semicolon) nextToken(); s = new AST.ErrorStatement; break; } if (pEndloc) *pEndloc = prevloc; return s; } private AST.ExpInitializer parseExpInitializer(Loc loc) { auto ae = parseAssignExp(); return new AST.ExpInitializer(loc, ae); } private AST.Initializer parseStructInitializer(Loc loc) { /* Scan ahead to discern between a struct initializer and * parameterless function literal. * * We'll scan the topmost curly bracket level for statement-related * tokens, thereby ruling out a struct initializer. (A struct * initializer which itself contains function literals may have * statements at nested curly bracket levels.) * * It's important that this function literal check not be * pendantic, otherwise a function having the slightest syntax * error would emit confusing errors when we proceed to parse it * as a struct initializer. * * The following two ambiguous cases will be treated as a struct * initializer (best we can do without type info): * {} * {{statements...}} - i.e. it could be struct initializer * with one function literal, or function literal having an * extra level of curly brackets * If a function literal is intended in these cases (unlikely), * source can use a more explicit function literal syntax * (e.g. prefix with "()" for empty parameter list). */ int braces = 1; int parens = 0; for (auto t = peek(&token); 1; t = peek(t)) { switch (t.value) { case TOK.leftParenthesis: parens++; continue; case TOK.rightParenthesis: parens--; continue; // https://issues.dlang.org/show_bug.cgi?id=21163 // lambda params can have the `scope` storage class, e.g // `S s = { (scope Type Id){} }` case TOK.scope_: if (!parens) goto case; continue; /* Look for a semicolon or keyword of statements which don't * require a semicolon (typically containing BlockStatement). * Tokens like "else", "catch", etc. are omitted where the * leading token of the statement is sufficient. */ case TOK.asm_: case TOK.class_: case TOK.debug_: case TOK.enum_: case TOK.if_: case TOK.interface_: case TOK.pragma_: case TOK.semicolon: case TOK.struct_: case TOK.switch_: case TOK.synchronized_: case TOK.try_: case TOK.union_: case TOK.version_: case TOK.while_: case TOK.with_: if (braces == 1) return parseExpInitializer(loc); continue; case TOK.leftCurly: braces++; continue; case TOK.rightCurly: if (--braces == 0) break; continue; case TOK.endOfFile: break; default: continue; } break; } auto _is = new AST.StructInitializer(loc); bool commaExpected = false; nextToken(); while (1) { switch (token.value) { case TOK.identifier: { if (commaExpected) error("comma expected separating field initializers"); const t = peek(&token); Identifier id; if (t.value == TOK.colon) { id = token.ident; nextToken(); nextToken(); // skip over ':' } auto value = parseInitializer(); _is.addInit(id, value); commaExpected = true; continue; } case TOK.comma: if (!commaExpected) error("expression expected, not `,`"); nextToken(); commaExpected = false; continue; case TOK.rightCurly: // allow trailing comma's nextToken(); break; case TOK.endOfFile: error("found end of file instead of initializer"); break; default: if (commaExpected) error("comma expected separating field initializers"); auto value = parseInitializer(); _is.addInit(null, value); commaExpected = true; continue; } break; } return _is; } /***************************************** * Parse initializer for variable declaration. */ private AST.Initializer parseInitializer() { const loc = token.loc; switch (token.value) { case TOK.leftCurly: return parseStructInitializer(loc); case TOK.leftBracket: /* Scan ahead to see if it is an array initializer or * an expression. * If it ends with a ';' ',' or ']', it is an array initializer. */ int brackets = 1; for (auto t = peek(&token); 1; t = peek(t)) { switch (t.value) { case TOK.leftBracket: brackets++; continue; case TOK.rightBracket: if (--brackets == 0) { t = peek(t); if (t.value != TOK.semicolon && t.value != TOK.comma && t.value != TOK.rightBracket && t.value != TOK.rightCurly) return parseExpInitializer(loc); break; } continue; case TOK.endOfFile: break; default: continue; } break; } auto ia = new AST.ArrayInitializer(loc); bool commaExpected = false; nextToken(); while (1) { switch (token.value) { default: if (commaExpected) { error("comma expected separating array initializers, not `%s`", token.toChars()); nextToken(); break; } auto e = parseAssignExp(); if (!e) break; AST.Initializer value; if (token.value == TOK.colon) { nextToken(); value = parseInitializer(); } else { value = new AST.ExpInitializer(e.loc, e); e = null; } ia.addInit(e, value); commaExpected = true; continue; case TOK.leftCurly: case TOK.leftBracket: if (commaExpected) error("comma expected separating array initializers, not `%s`", token.toChars()); auto value = parseInitializer(); AST.Expression e; if (token.value == TOK.colon) { nextToken(); if (auto ei = value.isExpInitializer()) { e = ei.exp; value = parseInitializer(); } else error("initializer expression expected following colon, not `%s`", token.toChars()); } ia.addInit(e, value); commaExpected = true; continue; case TOK.comma: if (!commaExpected) error("expression expected, not `,`"); nextToken(); commaExpected = false; continue; case TOK.rightBracket: // allow trailing comma's nextToken(); break; case TOK.endOfFile: error("found `%s` instead of array initializer", token.toChars()); break; } break; } return ia; case TOK.void_: const tv = peekNext(); if (tv == TOK.semicolon || tv == TOK.comma) { nextToken(); return new AST.VoidInitializer(loc); } return parseExpInitializer(loc); default: return parseExpInitializer(loc); } } /******************** * Parse inline assembler block. * Enters with token on the `asm`. * https://dlang.org/spec/iasm.html * * AsmStatement: * asm FunctionAttributes(opt) { AsmInstructionListopt } * AsmInstructionList: * AsmInstruction ; * AsmInstruction ; AsmInstruction * * Params: * endOfLine = true if EOL means end of asm statement * Returns: * inline assembler block as a Statement */ AST.Statement parseAsm(bool endOfLine) { // Parse the asm block into a sequence of AsmStatements, // each AsmStatement is one instruction. // Separate out labels. // Defer parsing of AsmStatements until semantic processing. const loc = token.loc; Loc labelloc; nextToken(); StorageClass stc = parsePostfix(STC.undefined_, null); // optional FunctionAttributes if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild)) error("`const`/`immutable`/`shared`/`inout` attributes are not allowed on `asm` blocks"); check(TOK.leftCurly); Token* toklist = null; Token** ptoklist = &toklist; Identifier label = null; auto statements = new AST.Statements(); size_t nestlevel = 0; while (1) { if (endOfLine) nextDefineLine(); switch (token.value) { case TOK.identifier: if (!toklist) { // Look ahead to see if it is a label if (peekNext() == TOK.colon) { // It's a label label = token.ident; labelloc = token.loc; nextToken(); nextToken(); continue; } } goto default; case TOK.leftCurly: ++nestlevel; goto default; case TOK.rightCurly: if (nestlevel > 0) { --nestlevel; goto default; } if (toklist || label) { error("`asm` statements must end in `;`"); } break; case TOK.endOfLine: nextDefineLine(); goto case; case TOK.semicolon: if (nestlevel != 0) error("mismatched number of curly brackets"); if (toklist || label) { // Create AsmStatement from list of tokens we've saved AST.AsmStatement as = new AST.AsmStatement(token.loc, toklist); as.caseSensitive = !endOfLine; AST.Statement s = as; toklist = null; ptoklist = &toklist; if (label) { s = new AST.LabelStatement(labelloc, label, s); label = null; } statements.push(s); } nextToken(); continue; case TOK.endOfFile: /* { */ error("matching `}` expected, not end of file"); break; case TOK.colonColon: // treat as two separate : tokens for iasmgcc *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; *ptoklist = null; nextToken(); continue; default: *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); ptoklist = &(*ptoklist).next; *ptoklist = null; nextToken(); continue; } break; } nextToken(); if (token.value == TOK.endOfLine) nextToken(); auto s = new AST.CompoundAsmStatement(loc, statements, stc); return s; } /********************************** * Issue error if the current token is not `value`, * advance to next token. * Params: * loc = location for error message * value = token value to compare with */ void check(Loc loc, TOK value) { if (token.value != value) error(loc, "found `%s` when expecting `%s`", token.toChars(), Token.toChars(value)); nextToken(); } /********************************** * Issue error if the current token is not `value`, * advance to next token. * Params: * value = token value to compare with */ void check(TOK value) { check(token.loc, value); } /********************************** * Issue error if the current token is not `value`, * advance to next token. * Params: * value = token value to compare with * string = for error message */ void check(TOK value, const(char)* string) { if (token.value != value) error(token.loc, "found `%s` when expecting `%s` following %s", token.toChars(), Token.toChars(value), string); nextToken(); } private void checkParens(TOK value, AST.Expression e) { if (precedence[e.op] == PREC.rel && !e.parens) error(e.loc, "`%s` must be surrounded by parentheses when next to operator `%s`", e.toChars(), Token.toChars(value)); } /// enum NeedDeclaratorId { no, // Declarator part must have no identifier opt, // Declarator part identifier is optional must, // Declarator part must have identifier mustIfDstyle, // Declarator part must have identifier, but don't recognize old C-style syntax } /************************************ * Determine if the scanner is sitting on the start of a declaration. * Params: * t = current token of the scanner * needId = flag with additional requirements for a declaration * endtok = ending token * pt = will be set ending token (if not null) * Output: * true if the token `t` is a declaration, false otherwise */ private bool isDeclaration(Token* t, NeedDeclaratorId needId, TOK endtok, Token** pt) { //printf("isDeclaration(needId = %d)\n", needId); int haveId = 0; int haveTpl = 0; while (1) { if ((t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) && peek(t).value != TOK.leftParenthesis) { /* const type * immutable type * shared type * wild type */ t = peek(t); continue; } break; } if (!isBasicType(&t)) { goto Lisnot; } if (!isDeclarator(&t, &haveId, &haveTpl, endtok, needId != NeedDeclaratorId.mustIfDstyle)) goto Lisnot; // needed for `__traits(compiles, arr[0] = 0)` if (!haveId && t.value == TOK.assign) goto Lisnot; if ((needId == NeedDeclaratorId.no && !haveId) || (needId == NeedDeclaratorId.opt) || (needId == NeedDeclaratorId.must && haveId) || (needId == NeedDeclaratorId.mustIfDstyle && haveId)) { if (pt) *pt = t; goto Lis; } goto Lisnot; Lis: //printf("\tis declaration, t = %s\n", t.toChars()); return true; Lisnot: //printf("\tis not declaration\n"); return false; } // pt = test token. If found, pt is set to the token after BasicType private bool isBasicType(Token** pt) { // This code parallels parseBasicType() Token* t = *pt; switch (t.value) { case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: t = peek(t); break; case TOK.identifier: L5: t = peek(t); if (t.value == TOK.not) { goto L4; } goto L3; while (1) { L2: t = peek(t); L3: if (t.value == TOK.dot) { Ldot: t = peek(t); if (t.value != TOK.identifier) goto Lfalse; t = peek(t); if (t.value != TOK.not) goto L3; L4: /* Seen a ! * Look for: * !( args ), !identifier, etc. */ t = peek(t); switch (t.value) { case TOK.identifier: goto L5; case TOK.leftParenthesis: if (!skipParens(t, &t)) goto Lfalse; goto L3; case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.string_: case TOK.interpolated: case TOK.hexadecimalString: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: goto L2; default: goto Lfalse; } } break; } break; case TOK.dot: goto Ldot; case TOK.typeof_: case TOK.vector: case TOK.mixin_: /* typeof(exp).identifier... */ t = peek(t); if (!skipParens(t, &t)) goto Lfalse; goto L3; case TOK.traits: // __traits(getMember t = peek(t); if (t.value != TOK.leftParenthesis) goto Lfalse; auto lp = t; t = peek(t); if (t.value != TOK.identifier || t.ident != Id.getMember) goto Lfalse; if (!skipParens(lp, &lp)) goto Lfalse; // we are in a lookup for decl VS statement // so we expect a declarator following __trait if it's a type. // other usages wont be ambiguous (alias, template instance, type qual, etc.) if (lp.value != TOK.identifier) goto Lfalse; break; case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: // const(type) or immutable(type) or shared(type) or wild(type) t = peek(t); if (t.value != TOK.leftParenthesis) goto Lfalse; t = peek(t); if (!isDeclaration(t, NeedDeclaratorId.no, TOK.rightParenthesis, &t)) { goto Lfalse; } t = peek(t); break; default: goto Lfalse; } *pt = t; //printf("is\n"); return true; Lfalse: //printf("is not\n"); return false; } private bool isDeclarator(Token** pt, int* haveId, int* haveTpl, TOK endtok, bool allowAltSyntax = true) { // This code parallels parseDeclarator() Token* t = *pt; bool parens; //printf("Parser::isDeclarator() %s\n", t.toChars()); if (t.value == TOK.assign) return false; while (1) { parens = false; switch (t.value) { case TOK.mul: //case TOK.and: t = peek(t); continue; case TOK.leftBracket: t = peek(t); if (t.value == TOK.rightBracket) { t = peek(t); } else if (isDeclaration(t, NeedDeclaratorId.no, TOK.rightBracket, &t)) { // It's an associative array declaration t = peek(t); // ...[type].ident if (t.value == TOK.dot && peek(t).value == TOK.identifier) { t = peek(t); t = peek(t); } } else { // [ expression ] // [ expression .. expression ] if (!isExpression(&t)) return false; if (t.value == TOK.slice) { t = peek(t); if (!isExpression(&t)) return false; if (t.value != TOK.rightBracket) return false; t = peek(t); } else { if (t.value != TOK.rightBracket) return false; t = peek(t); // ...[index].ident if (t.value == TOK.dot && peek(t).value == TOK.identifier) { t = peek(t); t = peek(t); } } } continue; case TOK.identifier: if (*haveId) return false; *haveId = true; t = peek(t); break; case TOK.leftParenthesis: if (!allowAltSyntax) return false; // Do not recognize C-style declarations. t = peek(t); if (t.value == TOK.rightParenthesis) return false; // () is not a declarator /* Regard ( identifier ) as not a declarator * BUG: what about ( *identifier ) in * f(*p)(x); * where f is a class instance with overloaded () ? * Should we just disallow C-style function pointer declarations? */ if (t.value == TOK.identifier) { Token* t2 = peek(t); if (t2.value == TOK.rightParenthesis) return false; } if (!isDeclarator(&t, haveId, null, TOK.rightParenthesis)) return false; t = peek(t); parens = true; break; case TOK.delegate_: case TOK.function_: t = peek(t); if (!isParameters(&t)) return false; skipAttributes(t, &t); continue; default: break; } break; } while (1) { switch (t.value) { static if (CARRAYDECL) { case TOK.leftBracket: parens = false; t = peek(t); if (t.value == TOK.rightBracket) { t = peek(t); } else if (isDeclaration(t, NeedDeclaratorId.no, TOK.rightBracket, &t)) { // It's an associative array declaration t = peek(t); } else { // [ expression ] if (!isExpression(&t)) return false; if (t.value != TOK.rightBracket) return false; t = peek(t); } continue; } case TOK.leftParenthesis: parens = false; if (Token* tk = peekPastParen(t)) { if (tk.value == TOK.leftParenthesis) { if (!haveTpl) return false; *haveTpl = 1; t = tk; } else if (tk.value == TOK.assign) { if (!haveTpl) return false; *haveTpl = 1; *pt = tk; return true; } } if (!isParameters(&t)) return false; while (1) { switch (t.value) { case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: case TOK.pure_: case TOK.nothrow_: case TOK.return_: case TOK.scope_: t = peek(t); continue; case TOK.at: t = peek(t); // skip '@' t = peek(t); // skip identifier continue; default: break; } break; } continue; // Valid tokens that follow the start of a declaration case TOK.rightParenthesis: case TOK.rightBracket: case TOK.assign: case TOK.comma: case TOK.dotDotDot: case TOK.semicolon: case TOK.leftCurly: case TOK.in_: case TOK.out_: case TOK.do_: // The !parens is to disallow unnecessary parentheses if (!parens && (endtok == TOK.reserved || endtok == t.value)) { *pt = t; return true; } return false; // To recognize the shortened function declaration syntax case TOK.goesTo: /* 1. https://issues.dlang.org/show_bug.cgi?id=24088 2. We need to make sure the would-be declarator has an identifier otherwise function literals are handled incorrectly. Some special treatment is required here, it turns out that a lot of code in the compiler relies on this mess (in the parser), i.e. having isDeclarator be more precise the parsing of other things go kaboom, so we do it in a separate case. */ if (*haveId) goto case TOK.do_; goto default; case TOK.identifier: if (t.ident == Id._body) { usageOfBodyKeyword(); goto case TOK.do_; } goto default; case TOK.if_: return haveTpl ? true : false; // Used for mixin type parsing case TOK.endOfFile: if (endtok == TOK.endOfFile) goto case TOK.do_; return false; default: return false; } } assert(0); } private bool isParameters(Token** pt) { // This code parallels parseParameterList() Token* t = *pt; //printf("isParameters()\n"); if (t.value != TOK.leftParenthesis) return false; t = peek(t); for (; 1; t = peek(t)) { L1: switch (t.value) { case TOK.rightParenthesis: break; case TOK.at: Token* pastAttr; if (skipAttributes(t, &pastAttr)) { t = pastAttr; goto default; } break; case TOK.dotDotDot: t = peek(t); break; case TOK.in_: case TOK.out_: case TOK.ref_: case TOK.lazy_: case TOK.scope_: case TOK.final_: case TOK.auto_: case TOK.return_: continue; case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: t = peek(t); if (t.value == TOK.leftParenthesis) { t = peek(t); if (!isDeclaration(t, NeedDeclaratorId.no, TOK.rightParenthesis, &t)) return false; t = peek(t); // skip past closing ')' goto L2; } goto L1; default: { if (!isBasicType(&t)) return false; L2: int tmp = false; if (t.value != TOK.dotDotDot && !isDeclarator(&t, &tmp, null, TOK.reserved)) return false; if (t.value == TOK.assign) { t = peek(t); if (!isExpression(&t)) return false; } if (t.value == TOK.dotDotDot) { t = peek(t); break; } } if (t.value == TOK.comma) { continue; } break; } break; } if (t.value != TOK.rightParenthesis) return false; t = peek(t); *pt = t; return true; } private bool isExpression(Token** pt) { // This is supposed to determine if something is an expression. // What it actually does is scan until a closing right bracket // is found. Token* t = *pt; int brnest = 0; int panest = 0; int curlynest = 0; for (;; t = peek(t)) { switch (t.value) { case TOK.leftBracket: brnest++; continue; case TOK.rightBracket: if (--brnest >= 0) continue; break; case TOK.leftParenthesis: panest++; continue; case TOK.comma: if (brnest || panest) continue; break; case TOK.rightParenthesis: if (--panest >= 0) continue; break; case TOK.leftCurly: curlynest++; continue; case TOK.rightCurly: if (--curlynest >= 0) continue; return false; case TOK.slice: if (brnest) continue; break; case TOK.semicolon: if (curlynest) continue; return false; case TOK.endOfFile: return false; default: continue; } break; } *pt = t; return true; } /******************************************* * Skip parentheses. * Params: * t = on opening $(LPAREN) * pt = *pt is set to token past '$(RPAREN)' on true * Returns: * true successful * false some parsing error */ bool skipParens(Token* t, Token** pt) { if (t.value != TOK.leftParenthesis) return false; int parens = 0; while (1) { switch (t.value) { case TOK.leftParenthesis: parens++; break; case TOK.rightParenthesis: parens--; if (parens < 0) goto Lfalse; if (parens == 0) goto Ldone; break; case TOK.endOfFile: goto Lfalse; default: break; } t = peek(t); } Ldone: if (pt) *pt = peek(t); // skip found rparen return true; Lfalse: return false; } private bool skipParensIf(Token* t, Token** pt) { if (t.value != TOK.leftParenthesis) { if (pt) *pt = t; return true; } return skipParens(t, pt); } //returns true if the next value (after optional matching parens) is expected private bool hasOptionalParensThen(Token* t, TOK expected) { Token* tk; if (!skipParensIf(t, &tk)) return false; return tk.value == expected; } /******************************************* * Skip attributes. * Input: * t is on a candidate attribute * Output: * *pt is set to first non-attribute token on success * Returns: * true successful * false some parsing error */ private bool skipAttributes(Token* t, Token** pt) { while (1) { switch (t.value) { case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: case TOK.final_: case TOK.auto_: case TOK.scope_: case TOK.override_: case TOK.abstract_: case TOK.synchronized_: break; case TOK.deprecated_: if (peek(t).value == TOK.leftParenthesis) { t = peek(t); if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis continue; } break; case TOK.nothrow_: case TOK.pure_: case TOK.ref_: case TOK.gshared: case TOK.return_: break; case TOK.at: t = peek(t); if (t.value == TOK.identifier) { /* @identifier * @identifier!arg * @identifier!(arglist) * any of the above followed by (arglist) * @predefined_attribute */ if (isBuiltinAtAttribute(t.ident)) break; t = peek(t); if (t.value == TOK.not) { t = peek(t); if (t.value == TOK.leftParenthesis) { // @identifier!(arglist) if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis } else { // @identifier!arg // Do low rent skipTemplateArgument if (t.value == TOK.vector) { // identifier!__vector(type) t = peek(t); if (!skipParens(t, &t)) goto Lerror; } else t = peek(t); } } if (t.value == TOK.leftParenthesis) { if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis continue; } continue; } if (t.value == TOK.leftParenthesis) { // @( ArgumentList ) if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis continue; } goto Lerror; default: goto Ldone; } t = peek(t); } Ldone: if (pt) *pt = t; return true; Lerror: return false; } AST.Expression parseExpression() { auto loc = token.loc; //printf("Parser::parseExpression() loc = %d\n", loc.linnum); auto e = parseAssignExp(); while (token.value == TOK.comma) { nextToken(); auto e2 = parseAssignExp(); e = new AST.CommaExp(loc, e, e2, false); loc = token.loc; } return e; } /********************************* Expression Parser ***************************/ AST.Expression parsePrimaryExp() { AST.Expression e; AST.Type t; Identifier id; const loc = token.loc; //printf("parsePrimaryExp(): loc = %d\n", loc.linnum); switch (token.value) { case TOK.identifier: { if (peekNext() == TOK.arrow) { // skip `identifier ->` nextToken(); nextToken(); error("use `.` for member lookup, not `->`"); goto Lerr; } if (peekNext() == TOK.goesTo) goto case_delegate; id = token.ident; nextToken(); TOK save; if (token.value == TOK.not && (save = peekNext()) != TOK.is_ && save != TOK.in_) { // identifier!(template-argument-list) auto tempinst = new AST.TemplateInstance(loc, id, parseTemplateArguments()); e = new AST.ScopeExp(loc, tempinst); } else e = new AST.IdentifierExp(loc, id); break; } case TOK.dollar: if (!inBrackets) error("`$` is valid only inside [] of index or slice"); e = new AST.DollarExp(loc); nextToken(); break; case TOK.dot: // Signal global scope '.' operator with "" identifier e = new AST.IdentifierExp(loc, Id.empty); break; case TOK.this_: e = new AST.ThisExp(loc); nextToken(); break; case TOK.super_: e = new AST.SuperExp(loc); nextToken(); break; case TOK.int32Literal: e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint32); nextToken(); break; case TOK.uns32Literal: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns32); nextToken(); break; case TOK.int64Literal: e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint64); nextToken(); break; case TOK.uns64Literal: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns64); nextToken(); break; case TOK.float32Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat32); nextToken(); break; case TOK.float64Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat64); nextToken(); break; case TOK.float80Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat80); nextToken(); break; case TOK.imaginary32Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary32); nextToken(); break; case TOK.imaginary64Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary64); nextToken(); break; case TOK.imaginary80Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary80); nextToken(); break; case TOK.null_: e = new AST.NullExp(loc); nextToken(); break; case TOK.file: e = new AST.FileInitExp(loc, EXP.file); nextToken(); break; case TOK.fileFullPath: e = new AST.FileInitExp(loc, EXP.fileFullPath); nextToken(); break; case TOK.line: e = new AST.LineInitExp(loc); nextToken(); break; case TOK.moduleString: e = new AST.ModuleInitExp(loc); nextToken(); break; case TOK.functionString: e = new AST.FuncInitExp(loc); nextToken(); break; case TOK.prettyFunction: e = new AST.PrettyFuncInitExp(loc); nextToken(); break; case TOK.true_: e = new AST.IntegerExp(loc, 1, AST.Type.tbool); nextToken(); break; case TOK.false_: e = new AST.IntegerExp(loc, 0, AST.Type.tbool); nextToken(); break; case TOK.charLiteral: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tchar); nextToken(); break; case TOK.wcharLiteral: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.twchar); nextToken(); break; case TOK.dcharLiteral: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tdchar); nextToken(); break; case TOK.interpolated: e = new AST.InterpExp(loc, token.interpolatedSet, token.postfix); nextToken(); break; case TOK.string_: case TOK.hexadecimalString: const bool hexString = token.value == TOK.hexadecimalString; { // cat adjacent strings auto s = token.ustring; auto len = token.len; auto postfix = token.postfix; while (1) { const prev = token; nextToken(); if (token.value == TOK.string_ || token.value == TOK.hexadecimalString) { if (token.postfix) { if (token.postfix != postfix) error(token.loc, "mismatched string literal postfixes `'%c'` and `'%c'`", postfix, token.postfix); postfix = token.postfix; } error("implicit string concatenation is error-prone and disallowed in D"); eSink.errorSupplemental(token.loc, "Use the explicit syntax instead " ~ "(concatenating literals is `@nogc`): %s ~ %s", prev.toChars(), token.toChars()); const len1 = len; const len2 = token.len; len = len1 + len2; auto s2 = cast(char*)mem.xmalloc_noscan(len * char.sizeof); memcpy(s2, s, len1 * char.sizeof); memcpy(s2 + len1, token.ustring, len2 * char.sizeof); s = s2; } else break; } e = new AST.StringExp(loc, s[0 .. len], len, 1, postfix); e.isStringExp().hexString = hexString; break; } case TOK.void_: t = AST.Type.tvoid; goto LabelX; case TOK.int8: t = AST.Type.tint8; goto LabelX; case TOK.uns8: t = AST.Type.tuns8; goto LabelX; case TOK.int16: t = AST.Type.tint16; goto LabelX; case TOK.uns16: t = AST.Type.tuns16; goto LabelX; case TOK.int32: t = AST.Type.tint32; goto LabelX; case TOK.uns32: t = AST.Type.tuns32; goto LabelX; case TOK.int64: t = AST.Type.tint64; goto LabelX; case TOK.uns64: t = AST.Type.tuns64; goto LabelX; case TOK.int128: t = AST.Type.tint128; goto LabelX; case TOK.uns128: t = AST.Type.tuns128; goto LabelX; case TOK.float32: t = AST.Type.tfloat32; goto LabelX; case TOK.float64: t = AST.Type.tfloat64; goto LabelX; case TOK.float80: t = AST.Type.tfloat80; goto LabelX; case TOK.imaginary32: t = AST.Type.timaginary32; goto LabelX; case TOK.imaginary64: t = AST.Type.timaginary64; goto LabelX; case TOK.imaginary80: t = AST.Type.timaginary80; goto LabelX; case TOK.complex32: t = AST.Type.tcomplex32; goto LabelX; case TOK.complex64: t = AST.Type.tcomplex64; goto LabelX; case TOK.complex80: t = AST.Type.tcomplex80; goto LabelX; case TOK.bool_: t = AST.Type.tbool; goto LabelX; case TOK.char_: t = AST.Type.tchar; goto LabelX; case TOK.wchar_: t = AST.Type.twchar; goto LabelX; case TOK.dchar_: t = AST.Type.tdchar; goto LabelX; LabelX: const next = peekNext(); if (next != TOK.leftParenthesis && next != TOK.dot) { // defer error for better diagnostics e = new AST.TypeExp(loc, parseType); break; } nextToken(); if (token.value == TOK.leftParenthesis) { e = new AST.TypeExp(loc, t); e = new AST.CallExp(loc, e, parseArguments()); break; } check(TOK.dot); if (token.value != TOK.identifier) { error(token.loc, "found `%s` when expecting identifier following `%s`.", token.toChars(), t.toChars()); goto Lerr; } e = new AST.DotIdExp(loc, new AST.TypeExp(loc, t), token.ident); nextToken(); break; case TOK.typeof_: { t = parseTypeof(); e = new AST.TypeExp(loc, t); break; } case TOK.vector: { t = parseVector(); e = new AST.TypeExp(loc, t); break; } case TOK.typeid_: { nextToken(); check(TOK.leftParenthesis, "`typeid`"); RootObject o = parseTypeOrAssignExp(); check(TOK.rightParenthesis); e = new AST.TypeidExp(loc, o); break; } case TOK.traits: { /* __traits(identifier, args...) */ Identifier ident; AST.Objects* args = null; nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("`__traits(identifier, args...)` expected"); goto Lerr; } ident = token.ident; nextToken(); if (token.value == TOK.comma) args = parseTemplateArgumentList(); // __traits(identifier, args...) else check(TOK.rightParenthesis); // __traits(identifier) e = new AST.TraitsExp(loc, ident, args); break; } case TOK.is_: { AST.Type targ; Identifier ident = null; AST.Type tspec = null; TOK tok = TOK.reserved; TOK tok2 = TOK.reserved; AST.TemplateParameters* tpl = null; nextToken(); if (token.value != TOK.leftParenthesis) { error("expected `(` following `is`, not `%s`", token.toChars()); goto Lerr; } else { nextToken(); if (token.value == TOK.identifier && peekNext() == TOK.leftParenthesis) { error(loc, "unexpected `(` after `%s`, inside `is` expression. Try enclosing the contents of `is` with a `typeof` expression", token.toChars()); nextToken(); Token* tempTok = peekPastParen(&token); memcpy(&token, tempTok, Token.sizeof); goto Lerr; } targ = parseType(&ident); if (token.value == TOK.colon || token.value == TOK.equal) { tok = token.value; nextToken(); if (tok == TOK.equal && (token.value == TOK.struct_ || token.value == TOK.union_ || token.value == TOK.class_ || token.value == TOK.super_ || token.value == TOK.enum_ || token.value == TOK.interface_ || token.value == TOK.package_ || token.value == TOK.module_ || token.value == TOK.argumentTypes || token.value == TOK.parameters || token.value == TOK.const_ && peekNext() == TOK.rightParenthesis || token.value == TOK.immutable_ && peekNext() == TOK.rightParenthesis || token.value == TOK.shared_ && peekNext() == TOK.rightParenthesis || token.value == TOK.inout_ && peekNext() == TOK.rightParenthesis || token.value == TOK.function_ || token.value == TOK.delegate_ || token.value == TOK.return_ || (token.value == TOK.vector && peekNext() == TOK.rightParenthesis))) { tok2 = token.value; nextToken(); } else { tspec = parseType(); } } if (tspec) { if (token.value == TOK.comma) tpl = parseTemplateParameterList(1); else { tpl = new AST.TemplateParameters(); check(TOK.rightParenthesis); } } else check(TOK.rightParenthesis); } e = new AST.IsExp(loc, targ, ident, tok, tspec, tok2, tpl); break; } case TOK.assert_: { // https://dlang.org/spec/expression.html#assert_expressions AST.Expression msg = null; nextToken(); check(TOK.leftParenthesis, "`assert`"); e = parseAssignExp(); if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); e = new AST.AssertExp(loc, e, msg); break; } case TOK.mixin_: { // https://dlang.org/spec/expression.html#mixin_expressions nextToken(); if (token.value != TOK.leftParenthesis) error(token.loc, "found `%s` when expecting `%s` following `mixin`", token.toChars(), Token.toChars(TOK.leftParenthesis)); auto exps = parseArguments(); e = new AST.MixinExp(loc, exps); break; } case TOK.import_: { nextToken(); check(TOK.leftParenthesis, "`import`"); e = parseAssignExp(); check(TOK.rightParenthesis); e = new AST.ImportExp(loc, e); break; } case TOK.new_: e = parseNewExp(null); break; case TOK.auto_: { if (peekNext() == TOK.ref_ && peekNext2() == TOK.leftParenthesis) { Token* tk = peekPastParen(peek(peek(&token))); if (skipAttributes(tk, &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly)) { // auto ref (arguments) => expression // auto ref (arguments) { statements... } goto case_delegate; } } nextToken(); error("found `%s` when expecting `ref` and function literal following `auto`", token.toChars()); goto Lerr; } case TOK.ref_: { if (peekNext() == TOK.leftParenthesis) { Token* tk = peekPastParen(peek(&token)); if (skipAttributes(tk, &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly)) { // ref (arguments) => expression // ref (arguments) { statements... } goto case_delegate; } } nextToken(); error("found `%s` when expecting function literal following `ref`", token.toChars()); goto Lerr; } case TOK.leftParenthesis: { Token* tk = peekPastParen(&token); if (skipAttributes(tk, &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly)) { // (arguments) => expression // (arguments) { statements... } goto case_delegate; } // ( expression ) nextToken(); e = parseExpression(); e.parens = true; check(loc, TOK.rightParenthesis); break; } case TOK.leftBracket: { /* Parse array literals and associative array literals: * [ value, value, value ... ] * [ key:value, key:value, key:value ... ] */ auto values = new AST.Expressions(); AST.Expressions* keys = null; nextToken(); while (token.value != TOK.rightBracket && token.value != TOK.endOfFile) { e = parseAssignExp(); if (token.value == TOK.colon && (keys || values.length == 0)) { nextToken(); if (!keys) keys = new AST.Expressions(); keys.push(e); e = parseAssignExp(); } else if (keys) { error("`key:value` expected for associative array literal"); keys = null; } values.push(e); if (token.value == TOK.rightBracket) break; check(TOK.comma); } check(loc, TOK.rightBracket); if (keys) e = new AST.AssocArrayLiteralExp(loc, keys, values); else e = new AST.ArrayLiteralExp(loc, null, values); break; } case TOK.leftCurly: case TOK.function_: case TOK.delegate_: case_delegate: { AST.Dsymbol s = parseFunctionLiteral(); e = new AST.FuncExp(loc, s); break; } default: error("expression expected, not `%s`", token.toChars()); Lerr: // Anything for e, as long as it's not NULL e = new AST.IntegerExp(loc, 0, AST.Type.tint32); nextToken(); break; } return e; } private AST.Expression parseUnaryExp() { AST.Expression e; const loc = token.loc; switch (token.value) { case TOK.and: nextToken(); e = parseUnaryExp(); e = new AST.AddrExp(loc, e); break; case TOK.plusPlus: nextToken(); e = parseUnaryExp(); //e = new AddAssignExp(loc, e, new IntegerExp(loc, 1, Type::tint32)); e = new AST.PreExp(EXP.prePlusPlus, loc, e); break; case TOK.minusMinus: nextToken(); e = parseUnaryExp(); //e = new MinAssignExp(loc, e, new IntegerExp(loc, 1, Type::tint32)); e = new AST.PreExp(EXP.preMinusMinus, loc, e); break; case TOK.mul: nextToken(); e = parseUnaryExp(); e = new AST.PtrExp(loc, e); break; case TOK.min: nextToken(); e = parseUnaryExp(); e = new AST.NegExp(loc, e); break; case TOK.add: nextToken(); e = parseUnaryExp(); e = new AST.UAddExp(loc, e); break; case TOK.not: nextToken(); e = parseUnaryExp(); e = new AST.NotExp(loc, e); break; case TOK.tilde: nextToken(); e = parseUnaryExp(); e = new AST.ComExp(loc, e); break; case TOK.delete_: // @@@DEPRECATED_2.109@@@ // Use of `delete` keyword has been an error since 2.099. // Remove from the parser after 2.109. nextToken(); e = parseUnaryExp(); e = new AST.DeleteExp(loc, e, false); break; case TOK.cast_: // cast(type) expression { nextToken(); check(TOK.leftParenthesis); /* Look for cast(), cast(const), cast(immutable), * cast(shared), cast(shared const), cast(wild), cast(shared wild) */ ubyte m = 0; while (1) { switch (token.value) { case TOK.const_: if (peekNext() == TOK.leftParenthesis) break; // const as type constructor m |= MODFlags.const_; // const as storage class nextToken(); continue; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) break; m |= MODFlags.immutable_; nextToken(); continue; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) break; m |= MODFlags.shared_; nextToken(); continue; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) break; m |= MODFlags.wild; nextToken(); continue; default: break; } break; } if (token.value == TOK.rightParenthesis) { nextToken(); e = parseUnaryExp(); e = new AST.CastExp(loc, e, m); } else { AST.Type t = parseType(); // cast( type ) t = t.addSTC(AST.ModToStc(m)); // cast( const type ) check(TOK.rightParenthesis); e = parseUnaryExp(); e = new AST.CastExp(loc, e, t); } break; } case TOK.inout_: case TOK.shared_: case TOK.const_: case TOK.immutable_: // immutable(type)(arguments) / immutable(type).init { StorageClass stc = parseTypeCtor(); AST.Type t = parseBasicType(); t = t.addSTC(stc); if (stc == 0 && token.value == TOK.dot) { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `%s.`, not `%s`", t.toChars(), token.toChars()); return AST.ErrorExp.get(); } e = new AST.DotIdExp(loc, new AST.TypeExp(loc, t), token.ident); nextToken(); e = parsePostExp(e); } else { e = new AST.TypeExp(loc, t); if (token.value != TOK.leftParenthesis) { error("`(arguments)` expected following `%s`, not `%s`", t.toChars(), token.toChars()); return e; } e = new AST.CallExp(loc, e, parseArguments()); } break; } case TOK.leftParenthesis: { auto tk = peek(&token); static if (CCASTSYNTAX) { // If cast if (isDeclaration(tk, NeedDeclaratorId.no, TOK.rightParenthesis, &tk)) { tk = peek(tk); // skip over right parenthesis switch (tk.value) { case TOK.not: tk = peek(tk); if (tk.value == TOK.is_ || tk.value == TOK.in_) // !is or !in break; goto case; case TOK.dot: case TOK.plusPlus: case TOK.minusMinus: case TOK.delete_: case TOK.new_: case TOK.leftParenthesis: case TOK.identifier: case TOK.this_: case TOK.super_: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.string_: case TOK.interpolated: case TOK.function_: case TOK.delegate_: case TOK.typeof_: case TOK.traits: case TOK.vector: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: { // (type) una_exp nextToken(); // Note: `t` may be an expression that looks like a type auto t = parseType(); check(TOK.rightParenthesis); // if .identifier // or .identifier!( ... ) if (token.value == TOK.dot) { if (peekNext() != TOK.identifier && peekNext() != TOK.new_) { error("identifier or new keyword expected following `(...)`."); nextToken(); return AST.ErrorExp.get(); } e = new AST.TypeExp(loc, t); e.parens = true; e = parsePostExp(e); } else if (token.value == TOK.leftParenthesis || token.value == TOK.plusPlus || token.value == TOK.minusMinus) { // (type)(expr) // (callable)(args) // (expr)++ auto te = new AST.TypeExp(loc, t); te.parens = true; e = parsePostExp(te); } else { e = parseUnaryExp(); e = new AST.CastExp(loc, e, t); error(loc, "C style cast illegal, use `%s`", e.toChars()); } return e; } default: break; } } } e = parsePrimaryExp(); e = parsePostExp(e); break; } case TOK.throw_: { nextToken(); // Deviation from the DIP: // Parse AssignExpression instead of Expression to avoid conflicts for comma // separated lists, e.g. function arguments AST.Expression exp = parseAssignExp(); e = new AST.ThrowExp(loc, exp); break; } default: e = parsePrimaryExp(); e = parsePostExp(e); break; } assert(e); // ^^ is right associative and has higher precedence than the unary operators while (token.value == TOK.pow) { nextToken(); AST.Expression e2 = parseUnaryExp(); e = new AST.PowExp(loc, e, e2); } return e; } private AST.Expression parsePostExp(AST.Expression e) { while (1) { const loc = token.loc; switch (token.value) { case TOK.dot: nextToken(); if (token.value == TOK.identifier) { Identifier id = token.ident; nextToken(); if (token.value == TOK.not && peekNext() != TOK.is_ && peekNext() != TOK.in_) { AST.Objects* tiargs = parseTemplateArguments(); e = new AST.DotTemplateInstanceExp(loc, e, id, tiargs); } else e = new AST.DotIdExp(loc, e, id); continue; } if (token.value == TOK.new_) { e = parseNewExp(e); continue; } error("identifier or `new` expected following `.`, not `%s`", token.toChars()); break; case TOK.plusPlus: e = new AST.PostExp(EXP.plusPlus, loc, e); break; case TOK.minusMinus: e = new AST.PostExp(EXP.minusMinus, loc, e); break; case TOK.leftParenthesis: AST.Expressions* args = new AST.Expressions(); AST.Identifiers* names = new AST.Identifiers(); parseNamedArguments(args, names); e = new AST.CallExp(loc, e, args, names); continue; case TOK.leftBracket: { // array dereferences: // array[index] // array[] // array[lwr .. upr] AST.Expression index; AST.Expression upr; auto arguments = new AST.Expressions(); inBrackets++; nextToken(); while (token.value != TOK.rightBracket && token.value != TOK.endOfFile) { index = parseAssignExp(); if (token.value == TOK.slice) { // array[..., lwr..upr, ...] nextToken(); upr = parseAssignExp(); arguments.push(new AST.IntervalExp(loc, index, upr)); } else arguments.push(index); if (token.value == TOK.rightBracket) break; check(TOK.comma); } check(TOK.rightBracket); inBrackets--; e = new AST.ArrayExp(loc, e, arguments); continue; } default: return e; } nextToken(); } } private AST.Expression parseMulExp() { const loc = token.loc; auto e = parseUnaryExp(); while (1) { switch (token.value) { case TOK.mul: nextToken(); auto e2 = parseUnaryExp(); e = new AST.MulExp(loc, e, e2); continue; case TOK.div: nextToken(); auto e2 = parseUnaryExp(); e = new AST.DivExp(loc, e, e2); continue; case TOK.mod: nextToken(); auto e2 = parseUnaryExp(); e = new AST.ModExp(loc, e, e2); continue; default: break; } break; } return e; } private AST.Expression parseAddExp() { const loc = token.loc; auto e = parseMulExp(); while (1) { switch (token.value) { case TOK.add: nextToken(); auto e2 = parseMulExp(); e = new AST.AddExp(loc, e, e2); continue; case TOK.min: nextToken(); auto e2 = parseMulExp(); e = new AST.MinExp(loc, e, e2); continue; case TOK.tilde: nextToken(); auto e2 = parseMulExp(); e = new AST.CatExp(loc, e, e2); continue; default: break; } break; } return e; } private AST.Expression parseShiftExp() { const loc = token.loc; auto e = parseAddExp(); while (1) { switch (token.value) { case TOK.leftShift: nextToken(); auto e2 = parseAddExp(); e = new AST.ShlExp(loc, e, e2); continue; case TOK.rightShift: nextToken(); auto e2 = parseAddExp(); e = new AST.ShrExp(loc, e, e2); continue; case TOK.unsignedRightShift: nextToken(); auto e2 = parseAddExp(); e = new AST.UshrExp(loc, e, e2); continue; default: break; } break; } return e; } private AST.Expression parseCmpExp() { const loc = token.loc; auto e = parseShiftExp(); EXP op = EXP.reserved; switch (token.value) { case TOK.equal: op = EXP.equal; goto Lequal; case TOK.notEqual: op = EXP.notEqual; goto Lequal; Lequal: nextToken(); auto e2 = parseShiftExp(); e = new AST.EqualExp(op, loc, e, e2); break; case TOK.not: { // Attempt to identify '!is' const tv = peekNext(); if (tv == TOK.in_) { nextToken(); nextToken(); auto e2 = parseShiftExp(); e = new AST.InExp(loc, e, e2); e = new AST.NotExp(loc, e); break; } if (tv != TOK.is_) break; nextToken(); op = EXP.notIdentity; goto Lidentity; } case TOK.is_: op = EXP.identity; goto Lidentity; Lidentity: nextToken(); auto e2 = parseShiftExp(); e = new AST.IdentityExp(op, loc, e, e2); break; case TOK.lessThan: op = EXP.lessThan; goto Lcmp; case TOK.lessOrEqual: op = EXP.lessOrEqual; goto Lcmp; case TOK.greaterThan: op = EXP.greaterThan; goto Lcmp; case TOK.greaterOrEqual: op = EXP.greaterOrEqual; goto Lcmp; Lcmp: nextToken(); auto e2 = parseShiftExp(); e = new AST.CmpExp(op, loc, e, e2); break; case TOK.in_: nextToken(); auto e2 = parseShiftExp(); e = new AST.InExp(loc, e, e2); break; default: break; } return e; } private AST.Expression parseAndExp() { Loc loc = token.loc; auto e = parseCmpExp(); while (token.value == TOK.and) { checkParens(TOK.and, e); nextToken(); auto e2 = parseCmpExp(); checkParens(TOK.and, e2); e = new AST.AndExp(loc, e, e2); loc = token.loc; } return e; } private AST.Expression parseXorExp() { const loc = token.loc; auto e = parseAndExp(); while (token.value == TOK.xor) { checkParens(TOK.xor, e); nextToken(); auto e2 = parseAndExp(); checkParens(TOK.xor, e2); e = new AST.XorExp(loc, e, e2); } return e; } private AST.Expression parseOrExp() { const loc = token.loc; auto e = parseXorExp(); while (token.value == TOK.or) { checkParens(TOK.or, e); nextToken(); auto e2 = parseXorExp(); checkParens(TOK.or, e2); e = new AST.OrExp(loc, e, e2); } return e; } private AST.Expression parseAndAndExp() { const loc = token.loc; auto e = parseOrExp(); while (token.value == TOK.andAnd) { nextToken(); auto e2 = parseOrExp(); e = new AST.LogicalExp(loc, EXP.andAnd, e, e2); } return e; } private AST.Expression parseOrOrExp() { const loc = token.loc; auto e = parseAndAndExp(); while (token.value == TOK.orOr) { nextToken(); auto e2 = parseAndAndExp(); e = new AST.LogicalExp(loc, EXP.orOr, e, e2); } return e; } private AST.Expression parseCondExp() { const loc = token.loc; auto e = parseOrOrExp(); if (token.value == TOK.question) { nextToken(); auto e1 = parseExpression(); check(TOK.colon); auto e2 = parseCondExp(); e = new AST.CondExp(loc, e, e1, e2); } return e; } AST.Expression parseAssignExp() { AST.Expression e; e = parseCondExp(); if (e is null) return e; // require parens for e.g. `t ? a = 1 : b = 2` void checkRequiredParens() { if (e.op == EXP.question && !e.parens) eSink.error(e.loc, "`%s` must be surrounded by parentheses when next to operator `%s`", e.toChars(), Token.toChars(token.value)); } const loc = token.loc; switch (token.value) { case TOK.assign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.AssignExp(loc, e, e2); break; case TOK.addAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.AddAssignExp(loc, e, e2); break; case TOK.minAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.MinAssignExp(loc, e, e2); break; case TOK.mulAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.MulAssignExp(loc, e, e2); break; case TOK.divAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.DivAssignExp(loc, e, e2); break; case TOK.modAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.ModAssignExp(loc, e, e2); break; case TOK.powAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.PowAssignExp(loc, e, e2); break; case TOK.andAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.AndAssignExp(loc, e, e2); break; case TOK.orAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.OrAssignExp(loc, e, e2); break; case TOK.xorAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.XorAssignExp(loc, e, e2); break; case TOK.leftShiftAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.ShlAssignExp(loc, e, e2); break; case TOK.rightShiftAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.ShrAssignExp(loc, e, e2); break; case TOK.unsignedRightShiftAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.UshrAssignExp(loc, e, e2); break; case TOK.concatenateAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.CatAssignExp(loc, e, e2); break; default: break; } return e; } /************************* * Collect argument list. * Assume current token is ',', '$(LPAREN)' or '['. */ private AST.Expressions* parseArguments() { // function call AST.Expressions* arguments = new AST.Expressions(); parseNamedArguments(arguments, null); return arguments; } /************************* * Collect argument list. * Assume current token is ',', '$(LPAREN)' or '['. */ private void parseNamedArguments(AST.Expressions* arguments, AST.Identifiers* names) { assert(arguments); const endtok = token.value == TOK.leftBracket ? TOK.rightBracket : TOK.rightParenthesis; nextToken(); while (token.value != endtok && token.value != TOK.endOfFile) { if (peekNext() == TOK.colon) { // Named argument `name: exp` auto loc = token.loc; auto ident = token.ident; check(TOK.identifier); check(TOK.colon); if (names) names.push(ident); else error(loc, "named arguments not allowed here"); } else { if (names) names.push(null); } auto arg = parseAssignExp(); arguments.push(arg); if (token.value != TOK.comma) break; nextToken(); //comma } check(endtok); } /******************************************* */ private AST.Expression parseNewExp(AST.Expression thisexp) { const loc = token.loc; nextToken(); AST.Expressions* arguments = null; AST.Identifiers* names = null; // An anonymous nested class starts with "class" if (token.value == TOK.class_) { nextToken(); if (token.value == TOK.leftParenthesis) { arguments = new AST.Expressions(); names = new AST.Identifiers(); parseNamedArguments(arguments, names); } AST.BaseClasses* baseclasses = null; if (token.value != TOK.leftCurly) baseclasses = parseBaseClasses(); Identifier id = null; AST.Dsymbols* members = null; if (token.value != TOK.leftCurly) { error("`{ members }` expected for anonymous class"); } else { nextToken(); members = parseDeclDefs(0); if (token.value != TOK.rightCurly) error("class member expected"); nextToken(); } auto cd = new AST.ClassDeclaration(loc, id, baseclasses, members, false); auto e = new AST.NewAnonClassExp(loc, thisexp, cd, arguments); return e; } const stc = parseTypeCtor(); auto t = parseBasicType(true); t = parseTypeSuffixes(t); t = t.addSTC(stc); if (t.ty == Taarray) { AST.TypeAArray taa = cast(AST.TypeAArray)t; AST.Type index = taa.index; // `new Type[expr]` is a static array auto edim = AST.typeToExpression(index); if (edim) t = new AST.TypeSArray(taa.next, edim); } else if (token.value == TOK.leftParenthesis && t.ty != Tsarray) { arguments = new AST.Expressions(); names = new AST.Identifiers(); parseNamedArguments(arguments, names); } auto e = new AST.NewExp(loc, thisexp, t, arguments, names); return e; } /********************************************** */ private void addComment(AST.Dsymbol s, const(char)* blockComment) { if (s !is null) this.addComment(s, blockComment.toDString()); } private void addComment(AST.Dsymbol s, const(char)[] blockComment) { if (s !is null) { s.addComment(combineComments(blockComment, token.lineComment, true)); token.lineComment = null; } } /********************************************** * Recognize builtin @ attributes * Params: * ident = identifier * Returns: * storage class for attribute, 0 if not */ static StorageClass isBuiltinAtAttribute(Identifier ident) { return (ident == Id.property) ? STC.property : (ident == Id.nogc) ? STC.nogc : (ident == Id.safe) ? STC.safe : (ident == Id.trusted) ? STC.trusted : (ident == Id.system) ? STC.system : (ident == Id.live) ? STC.live : (ident == Id.future) ? STC.future : (ident == Id.disable) ? STC.disable : 0; } enum StorageClass atAttrGroup = STC.property | STC.nogc | STC.safe | STC.trusted | STC.system | STC.live | /*STC.future |*/ // probably should be included STC.disable; void usageOfBodyKeyword() { if (mod.edition >= Edition.v2024) { eSink.error(token.loc, "usage of identifer `body` as a keyword is obsolete. Use `do` instead."); } } } enum PREC : int { zero, expr, assign, cond, oror, andand, or, xor, and, equal, rel, shift, add, mul, pow, unary, primary, } /********************************** * Set operator precedence for each operator. * * Used by hdrgen */ immutable PREC[EXP.max + 1] precedence = [ EXP.type : PREC.expr, EXP.error : PREC.expr, EXP.objcClassReference : PREC.expr, // Objective-C class reference, same as EXP.type EXP.mixin_ : PREC.primary, EXP.import_ : PREC.primary, EXP.dotVariable : PREC.primary, EXP.scope_ : PREC.primary, EXP.identifier : PREC.primary, EXP.this_ : PREC.primary, EXP.super_ : PREC.primary, EXP.int64 : PREC.primary, EXP.float64 : PREC.primary, EXP.complex80 : PREC.primary, EXP.null_ : PREC.primary, EXP.string_ : PREC.primary, EXP.arrayLiteral : PREC.primary, EXP.assocArrayLiteral : PREC.primary, EXP.classReference : PREC.primary, EXP.file : PREC.primary, EXP.fileFullPath : PREC.primary, EXP.line : PREC.primary, EXP.moduleString : PREC.primary, EXP.functionString : PREC.primary, EXP.prettyFunction : PREC.primary, EXP.typeid_ : PREC.primary, EXP.is_ : PREC.primary, EXP.assert_ : PREC.primary, EXP.halt : PREC.primary, EXP.template_ : PREC.primary, EXP.dSymbol : PREC.primary, EXP.function_ : PREC.primary, EXP.variable : PREC.primary, EXP.symbolOffset : PREC.primary, EXP.structLiteral : PREC.primary, EXP.compoundLiteral : PREC.primary, EXP.arrayLength : PREC.primary, EXP.delegatePointer : PREC.primary, EXP.delegateFunctionPointer : PREC.primary, EXP.remove : PREC.primary, EXP.tuple : PREC.primary, EXP.traits : PREC.primary, EXP.overloadSet : PREC.primary, EXP.void_ : PREC.primary, EXP.vectorArray : PREC.primary, EXP._Generic : PREC.primary, // post EXP.dotTemplateInstance : PREC.primary, EXP.dotIdentifier : PREC.primary, EXP.dotTemplateDeclaration : PREC.primary, EXP.dot : PREC.primary, EXP.dotType : PREC.primary, EXP.plusPlus : PREC.primary, EXP.minusMinus : PREC.primary, EXP.prePlusPlus : PREC.primary, EXP.preMinusMinus : PREC.primary, EXP.call : PREC.primary, EXP.slice : PREC.primary, EXP.array : PREC.primary, EXP.index : PREC.primary, EXP.delegate_ : PREC.unary, EXP.address : PREC.unary, EXP.star : PREC.unary, EXP.negate : PREC.unary, EXP.uadd : PREC.unary, EXP.not : PREC.unary, EXP.tilde : PREC.unary, EXP.delete_ : PREC.unary, EXP.new_ : PREC.unary, EXP.newAnonymousClass : PREC.unary, EXP.cast_ : PREC.unary, EXP.throw_ : PREC.unary, EXP.vector : PREC.unary, EXP.pow : PREC.pow, EXP.mul : PREC.mul, EXP.div : PREC.mul, EXP.mod : PREC.mul, EXP.add : PREC.add, EXP.min : PREC.add, EXP.concatenate : PREC.add, EXP.leftShift : PREC.shift, EXP.rightShift : PREC.shift, EXP.unsignedRightShift : PREC.shift, EXP.lessThan : PREC.rel, EXP.lessOrEqual : PREC.rel, EXP.greaterThan : PREC.rel, EXP.greaterOrEqual : PREC.rel, EXP.in_ : PREC.rel, /* Note that we changed precedence, so that < and != have the same * precedence. This change is in the parser, too. */ EXP.equal : PREC.rel, EXP.notEqual : PREC.rel, EXP.identity : PREC.rel, EXP.notIdentity : PREC.rel, EXP.and : PREC.and, EXP.xor : PREC.xor, EXP.or : PREC.or, EXP.andAnd : PREC.andand, EXP.orOr : PREC.oror, EXP.question : PREC.cond, EXP.assign : PREC.assign, EXP.construct : PREC.assign, EXP.blit : PREC.assign, EXP.loweredAssignExp : PREC.assign, EXP.addAssign : PREC.assign, EXP.minAssign : PREC.assign, EXP.concatenateAssign : PREC.assign, EXP.concatenateElemAssign : PREC.assign, EXP.concatenateDcharAssign : PREC.assign, EXP.mulAssign : PREC.assign, EXP.divAssign : PREC.assign, EXP.modAssign : PREC.assign, EXP.powAssign : PREC.assign, EXP.leftShiftAssign : PREC.assign, EXP.rightShiftAssign : PREC.assign, EXP.unsignedRightShiftAssign : PREC.assign, EXP.andAssign : PREC.assign, EXP.orAssign : PREC.assign, EXP.xorAssign : PREC.assign, EXP.comma : PREC.expr, EXP.declaration : PREC.expr, EXP.interval : PREC.assign, ]; enum ParseStatementFlags : int { scope_ = 2, // start a new scope curly = 4, // { } statement is required curlyScope = 8, // { } starts a new scope semiOk = 0x10, // empty ';' are really ok } struct PrefixAttributes(AST) { StorageClass storageClass; AST.Expression depmsg; LINK link; AST.Visibility visibility; bool setAlignment; AST.Expression ealign; AST.Expressions* udas; const(char)* comment; } /// The result of the `ParseLinkage` function struct ParsedLinkage(AST) { /// What linkage was specified LINK link; /// If `extern(C++, class|struct)`, contains the `class|struct` CPPMANGLE cppmangle; /// If `extern(C++, some.identifier)`, will be the identifiers AST.Identifiers* idents; /// If `extern(C++, (some_tuple_expression)|"string"), will be the expressions AST.Expressions* identExps; } /*********************************** Private *************************************/ /*********************** * How multiple declarations are parsed. * If 1, treat as C. * If 0, treat: * int *p, i; * as: * int* p; * int* i; */ private enum CDECLSYNTAX = 0; /***** * Support C cast syntax: * (type)(expression) */ private enum CCASTSYNTAX = 1; /***** * Support postfix C array declarations, such as * int a[3][4]; */ private enum CARRAYDECL = 1; /***************************** * Destructively extract storage class from pAttrs. */ private StorageClass getStorageClass(AST)(PrefixAttributes!(AST)* pAttrs) { StorageClass stc = STC.undefined_; if (pAttrs) { stc = pAttrs.storageClass; pAttrs.storageClass = STC.undefined_; } return stc; } ldc-1.40.0-src/dmd/printast.d0000644000000000000000000001501414727557031014412 0ustar rootroot/** * Provides an AST printer for debugging. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/printast.d, _printast.d) * Documentation: https://dlang.org/phobos/dmd_printast.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/printast.d */ module dmd.printast; import core.stdc.stdio; import dmd.expression; import dmd.ctfeexpr; import dmd.tokens; import dmd.visitor; import dmd.hdrgen; /******************** * Print AST data structure in a nice format. * Params: * e = expression AST to print * indent = indentation level */ void printAST(Expression e, int indent = 0) { scope PrintASTVisitor pav = new PrintASTVisitor(indent); e.accept(pav); } private: extern (C++) final class PrintASTVisitor : Visitor { alias visit = Visitor.visit; int indent; extern (D) this(int indent) scope @safe { this.indent = indent; } override void visit(Expression e) { printIndent(indent); auto s = EXPtoString(e.op); printf("%.*s %s\n", cast(int)s.length, s.ptr, e.type ? e.type.toChars() : ""); } override void visit(IntegerExp e) { printIndent(indent); printf("Integer %lld %s\n", e.toInteger(), e.type ? e.type.toChars() : ""); } override void visit(RealExp e) { printIndent(indent); import dmd.hdrgen : floatToBuffer; import dmd.common.outbuffer : OutBuffer; OutBuffer buf; floatToBuffer(e.type, e.value, buf, false); printf("Real %s %s\n", buf.peekChars(), e.type ? e.type.toChars() : ""); } override void visit(StructLiteralExp e) { printIndent(indent); auto s = EXPtoString(e.op); printf("%.*s %s, %s\n", cast(int)s.length, s.ptr, e.type ? e.type.toChars() : "", e.toChars()); } override void visit(SymbolExp e) { printIndent(indent); printf("Symbol %s\n", e.type ? e.type.toChars() : ""); printIndent(indent + 2); printf(".var: %s\n", e.var ? e.var.toChars() : ""); } override void visit(SymOffExp e) { printIndent(indent); printf("SymOff %s\n", e.type ? e.type.toChars() : ""); printIndent(indent + 2); printf(".var: %s\n", e.var ? e.var.toChars() : ""); printIndent(indent + 2); printf(".offset: %llx\n", e.offset); } override void visit(VarExp e) { printIndent(indent); printf("Var %s\n", e.type ? e.type.toChars() : ""); printIndent(indent + 2); printf(".var: %s\n", e.var ? e.var.toChars() : ""); } override void visit(DsymbolExp e) { visit(cast(Expression)e); printIndent(indent + 2); printf(".s: %s\n", e.s ? e.s.toChars() : ""); } override void visit(DotIdExp e) { printIndent(indent); printf("DotId %s\n", e.type ? e.type.toChars() : ""); printIndent(indent + 2); printf(".ident: %s\n", e.ident.toChars()); printAST(e.e1, indent + 2); } override void visit(UnaExp e) { visit(cast(Expression)e); printAST(e.e1, indent + 2); } override void visit(CastExp e) { printIndent(indent); auto s = EXPtoString(e.op); printf("%.*s %s\n", cast(int)s.length, s.ptr, e.type ? e.type.toChars() : ""); printIndent(indent + 2); printf(".to: %s\n", e.to.toChars()); printAST(e.e1, indent + 2); } override void visit(VectorExp e) { printIndent(indent); printf("Vector %s\n", e.type ? e.type.toChars() : ""); printIndent(indent + 2); printf(".to: %s\n", e.to.toChars()); printAST(e.e1, indent + 2); } override void visit(VectorArrayExp e) { printIndent(indent); printf("VectorArray %s\n", e.type ? e.type.toChars() : ""); printAST(e.e1, indent + 2); } override void visit(DotVarExp e) { printIndent(indent); printf("DotVar %s\n", e.type ? e.type.toChars() : ""); printIndent(indent + 2); printf(".var: %s\n", e.var.toChars()); printAST(e.e1, indent + 2); } override void visit(BinExp e) { visit(cast(Expression)e); printAST(e.e1, indent + 2); printAST(e.e2, indent + 2); } override void visit(AssignExp e) { printIndent(indent); printf("Assign %s\n", e.type ? e.type.toChars() : ""); printAST(e.e1, indent + 2); printAST(e.e2, indent + 2); } override void visit(ConstructExp e) { printIndent(indent); printf("Construct %s\n", e.type ? e.type.toChars() : ""); printAST(e.e1, indent + 2); printAST(e.e2, indent + 2); } override void visit(BlitExp e) { printIndent(indent); printf("Blit %s\n", e.type ? e.type.toChars() : ""); printAST(e.e1, indent + 2); printAST(e.e2, indent + 2); } override void visit(IndexExp e) { printIndent(indent); printf("Index %s\n", e.type ? e.type.toChars() : ""); printAST(e.e1, indent + 2); printAST(e.e2, indent + 2); } override void visit(DelegateExp e) { visit(cast(Expression)e); printIndent(indent + 2); printf(".func: %s\n", e.func ? e.func.toChars() : ""); } override void visit(CompoundLiteralExp e) { visit(cast(Expression)e); printIndent(indent + 2); printf(".init: %s\n", e.initializer ? e.initializer.toChars() : ""); } override void visit(ClassReferenceExp e) { visit(cast(Expression)e); printIndent(indent + 2); printf(".value: %s\n", e.value ? e.value.toChars() : ""); printAST(e.value, indent + 2); } override void visit(ArrayLiteralExp e) { visit(cast(Expression)e); printIndent(indent + 2); printf(".basis : %s\n", e.basis ? e.basis.toChars() : ""); if (e.elements) { printIndent(indent + 2); printf("["); foreach (i, element; (*e.elements)[]) { if (i) printf(", "); printf("%s", element.toChars()); } printf("]\n"); } } static void printIndent(int indent) { foreach (i; 0 .. indent) putc(' ', stdout); } } ldc-1.40.0-src/dmd/gluelayer.d0000644000000000000000000000342414727557031014541 0ustar rootroot/** * Declarations for back-end functions that the front-end invokes. * * This 'glues' either the DMC or GCC back-end to the front-end. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/gluelayer.d, _gluelayer.d) * Documentation: https://dlang.org/phobos/dmd_gluelayer.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/gluelayer.d */ module dmd.gluelayer; import dmd.dmodule; import dmd.dscope; import dmd.dsymbol; import dmd.mtype; import dmd.statement; import dmd.root.file; version (IN_LLVM) { extern (C++) { struct AsmCode; // < 2.072: no `extern (C++, class)` support /*extern (C++, class)*/ struct IrType; Statement asmSemantic(AsmStatement s, Scope* sc); } alias code = AsmCode; alias type = IrType; } else version (NoBackend) { struct Symbol; struct code; struct block; struct BlockState; struct elem; struct TYPE; alias type = TYPE; extern(C++) abstract class ObjcGlue { static void initialize() {} } } else version (IN_GCC) { extern (C++) union tree_node; alias Symbol = tree_node; alias code = tree_node; alias type = tree_node; // stubs extern(C++) abstract class ObjcGlue { static void initialize() {} } } else { public import dmd.backend.cc : block, BlockState, Symbol; public import dmd.backend.type : type; public import dmd.backend.el : elem; public import dmd.backend.x86.code_x86 : code; public import dmd.objc_glue : ObjcGlue; } ldc-1.40.0-src/dmd/cxxfrontend.d0000644000000000000000000003706414727557031015121 0ustar rootroot/** * Contains C++ interfaces for interacting with DMD as a library. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/cxxfrontend.d, _cxxfrontend.d) * Documentation: https://dlang.org/phobos/dmd_cxxfrontend.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/cxxfrontend.d */ module dmd.cxxfrontend; import dmd.aggregate : AggregateDeclaration; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.common.outbuffer : OutBuffer; import dmd.denum : EnumDeclaration; import dmd.dmodule /*: Module*/; import dmd.dscope : Scope; import dmd.dstruct /*: StructDeclaration*/; import dmd.dsymbol : Dsymbol, ScopeDsymbol, CAsmDeclaration, SearchOpt, SearchOptFlags; import dmd.dtemplate /*: TemplateInstance, TemplateParameter, Tuple*/; import dmd.errorsink : ErrorSink; import dmd.expression /*: Expression*/; import dmd.func : FuncDeclaration; import dmd.globals; import dmd.identifier : Identifier; import dmd.init : Initializer, NeedInterpret; import dmd.location : Loc; import dmd.mtype /*: Covariant, Type, Parameter, ParameterList*/; import dmd.rootobject : RootObject; import dmd.statement : Statement, AsmStatement, GccAsmStatement; // NB: At some point in the future, we can switch to shortened function syntax. extern (C++, "dmd"): /*********************************************************** * atrtibsem.d */ Expressions* getAttributes(UserAttributeDeclaration a) { import dmd.attribsem; return dmd.attribsem.getAttributes(a); } /*********************************************************** * cppmangle.d */ const(char)* toCppMangleItanium(Dsymbol s) { import dmd.cppmangle; return dmd.cppmangle.toCppMangleItanium(s); } const(char)* cppTypeInfoMangleItanium(Dsymbol s) { import dmd.cppmangle; return dmd.cppmangle.cppTypeInfoMangleItanium(s); } const(char)* cppThunkMangleItanium(FuncDeclaration fd, int offset) { import dmd.cppmangle; return dmd.cppmangle.cppThunkMangleItanium(fd, offset); } /*********************************************************** * dinterpret.d */ Expression ctfeInterpret(Expression e) { import dmd.dinterpret; return dmd.dinterpret.ctfeInterpret(e); } /*********************************************************** * dmangle.d */ const(char)* mangleExact(FuncDeclaration fd) { import dmd.dmangle; return dmd.dmangle.mangleExact(fd); } void mangleToBuffer(Type t, ref OutBuffer buf) { import dmd.dmangle; return dmd.dmangle.mangleToBuffer(t, buf); } void mangleToBuffer(Expression e, ref OutBuffer buf) { import dmd.dmangle; return dmd.dmangle.mangleToBuffer(e, buf); } void mangleToBuffer(Dsymbol s, ref OutBuffer buf) { import dmd.dmangle; return dmd.dmangle.mangleToBuffer(s, buf); } void mangleToBuffer(TemplateInstance ti, ref OutBuffer buf) { import dmd.dmangle; return dmd.dmangle.mangleToBuffer(ti, buf); } /*********************************************************** * dmodule.d */ void getLocalClasses(Module mod, ref ClassDeclarations aclasses) { return dmd.dmodule.getLocalClasses(mod, aclasses); } FuncDeclaration findGetMembers(ScopeDsymbol dsym) { return dmd.dmodule.findGetMembers(dsym); } /*********************************************************** * doc.d */ void gendocfile(Module m, const char* ddoctext_ptr, size_t ddoctext_length, const char* datetime, ErrorSink eSink, ref OutBuffer outbuf) { import dmd.doc; return dmd.doc.gendocfile(m, ddoctext_ptr, ddoctext_length, datetime, eSink, outbuf); } /*********************************************************** * dstruct.d */ FuncDeclaration search_toString(StructDeclaration sd) { return dmd.dstruct.search_toString(sd); } /*********************************************************** * dsymbolsem.d */ void dsymbolSemantic(Dsymbol dsym, Scope* sc) { import dmd.dsymbolsem; return dmd.dsymbolsem.dsymbolSemantic(dsym, sc); } void addMember(Dsymbol dsym, Scope* sc, ScopeDsymbol sds) { import dmd.dsymbolsem; return dmd.dsymbolsem.addMember(dsym, sc, sds); } Dsymbol search(Dsymbol d, const ref Loc loc, Identifier ident, SearchOptFlags flags = SearchOpt.all) { import dmd.dsymbolsem; return dmd.dsymbolsem.search(d, loc, ident, flags); } void setScope(Dsymbol d, Scope* sc) { import dmd.dsymbolsem; return dmd.dsymbolsem.setScope(d, sc); } void importAll(Dsymbol d, Scope* sc) { import dmd.dsymbolsem; return dmd.dsymbolsem.importAll(d, sc); } /*********************************************************** * dtemplate.d */ inout(Expression) isExpression(inout RootObject o) { return dmd.dtemplate.isExpression(o); } inout(Dsymbol) isDsymbol(inout RootObject o) { return dmd.dtemplate.isDsymbol(o); } inout(Type) isType(inout RootObject o) { return dmd.dtemplate.isType(o); } inout(Tuple) isTuple(inout RootObject o) { return dmd.dtemplate.isTuple(o); } inout(Parameter) isParameter(inout RootObject o) { return dmd.dtemplate.isParameter(o); } inout(TemplateParameter) isTemplateParameter(inout RootObject o) { return dmd.dtemplate.isTemplateParameter(o); } bool isError(const RootObject o) { return dmd.dtemplate.isError(o); } void printTemplateStats(bool listInstances, ErrorSink eSink) { return dmd.dtemplate.printTemplateStats(listInstances, eSink); } /*********************************************************** * dtoh.d */ void genCppHdrFiles(ref Modules ms) { import dmd.dtoh; return dmd.dtoh.genCppHdrFiles(ms); } /*********************************************************** * enumsem.d */ Expression getDefaultValue(EnumDeclaration ed, const ref Loc loc) { import dmd.enumsem; return dmd.enumsem.getDefaultValue(ed, loc); } /*********************************************************** * expression.d */ void expandTuples(Expressions* exps, Identifiers* names = null) { return dmd.expression.expandTuples(exps, names); } /*********************************************************** * expressionsem.d */ Expression expressionSemantic(Expression e, Scope* sc) { import dmd.expressionsem; return dmd.expressionsem.expressionSemantic(e, sc); } /*********************************************************** * funcsem.d */ bool functionSemantic(FuncDeclaration fd) { import dmd.funcsem; return dmd.funcsem.functionSemantic(fd); } bool functionSemantic3(FuncDeclaration fd) { import dmd.funcsem; return dmd.funcsem.functionSemantic3(fd); } MATCH leastAsSpecialized(FuncDeclaration fd, FuncDeclaration g, Identifiers* names) { import dmd.funcsem; return dmd.funcsem.leastAsSpecialized(fd, g, names); } /*********************************************************** * hdrgen.d */ void genhdrfile(Module m, bool doFuncBodies, ref OutBuffer buf) { import dmd.hdrgen; return dmd.hdrgen.genhdrfile(m, doFuncBodies, buf); } const(char)* toChars(const Statement s) { import dmd.hdrgen; return dmd.hdrgen.toChars(s); } const(char)* toChars(const Expression e) { import dmd.hdrgen; return dmd.hdrgen.toChars(e); } const(char)* toChars(const Initializer i) { import dmd.hdrgen; return dmd.hdrgen.toChars(i); } const(char)* toChars(const Type t) { import dmd.hdrgen; return dmd.hdrgen.toChars(t); } void moduleToBuffer(ref OutBuffer buf, bool vcg_ast, Module m) { import dmd.hdrgen; return dmd.hdrgen.moduleToBuffer(buf, vcg_ast, m); } const(char)* parametersTypeToChars(ParameterList pl) { import dmd.hdrgen; return dmd.hdrgen.parametersTypeToChars(pl); } version (IN_LLVM) {} else { /*********************************************************** * iasm.d */ Statement asmSemantic(AsmStatement s, Scope *sc) { import dmd.iasm; return dmd.iasm.asmSemantic(s, sc); } void asmSemantic(CAsmDeclaration d, Scope *sc) { import dmd.iasm; return dmd.iasm.asmSemantic(d, sc); } } // !IN_LLVM /*********************************************************** * iasmgcc.d */ Statement gccAsmSemantic(GccAsmStatement s, Scope *sc) { import dmd.iasmgcc; return dmd.iasmgcc.gccAsmSemantic(s, sc); } void gccAsmSemantic(CAsmDeclaration d, Scope *sc) { import dmd.iasmgcc; return dmd.iasmgcc.gccAsmSemantic(d, sc); } /*********************************************************** * initsem.d */ Initializer initializerSemantic(Initializer init, Scope* sc, ref Type tx, NeedInterpret needInterpret) { import dmd.initsem; return dmd.initsem.initializerSemantic(init, sc, tx, needInterpret); } Expression initializerToExpression(Initializer init, Type itype = null, const bool isCfile = false) { import dmd.initsem; return dmd.initsem.initializerToExpression(init, itype, isCfile); } /*********************************************************** * json.d */ void json_generate(ref Modules modules, ref OutBuffer buf) { import dmd.json; return dmd.json.json_generate(modules, buf); } JsonFieldFlags tryParseJsonField(const(char)* fieldName) { import dmd.json; return dmd.json.tryParseJsonField(fieldName); } /*********************************************************** * mtype.d */ AggregateDeclaration isAggregate(Type t) { return dmd.mtype.isAggregate(t); } /*********************************************************** * optimize.d */ Expression optimize(Expression e, int result, bool keepLvalue = false) { import dmd.optimize; return dmd.optimize.optimize(e, result, keepLvalue); } /*********************************************************** * semantic2.d */ void semantic2(Dsymbol dsym, Scope* sc) { import dmd.semantic2; return dmd.semantic2.semantic2(dsym, sc); } /*********************************************************** * semantic3.d */ void semantic3(Dsymbol dsym, Scope* sc) { import dmd.semantic3; return dmd.semantic3.semantic3(dsym, sc); } void semanticTypeInfoMembers(StructDeclaration sd) { import dmd.semantic3; return dmd.semantic3.semanticTypeInfoMembers(sd); } /*********************************************************** * statementsem.d */ Statement statementSemantic(Statement s, Scope* sc) { import dmd.statementsem; return dmd.statementsem.statementSemantic(s, sc); } /*********************************************************** * templateparamsem.d */ bool tpsemantic(TemplateParameter tp, Scope* sc, TemplateParameters* parameters) { import dmd.templateparamsem; return dmd.templateparamsem.tpsemantic(tp, sc, parameters); } /*********************************************************** * typesem.d */ bool hasPointers(Type t) { import dmd.typesem; return dmd.typesem.hasPointers(t); } Type typeSemantic(Type type, const ref Loc loc, Scope* sc) { import dmd.typesem; return dmd.typesem.typeSemantic(type, loc, sc); } Type trySemantic(Type type, const ref Loc loc, Scope* sc) { import dmd.typesem; return dmd.typesem.trySemantic(type, loc, sc); } Type merge(Type type) { import dmd.typesem; return dmd.typesem.merge(type); } Type merge2(Type type) { import dmd.typesem; return dmd.typesem.merge2(type); } Expression defaultInit(Type mt, const ref Loc loc, const bool isCfile = false) { import dmd.typesem; return dmd.typesem.defaultInit(mt, loc, isCfile); } Dsymbol toDsymbol(Type type, Scope* sc) { import dmd.typesem; return dmd.typesem.toDsymbol(type, sc); } Covariant covariant(Type src, Type t, StorageClass* pstc = null, bool cppCovariant = false) { import dmd.typesem; return dmd.typesem.covariant(src, t, pstc, cppCovariant); } bool isBaseOf(Type tthis, Type t, int* poffset) { import dmd.typesem; return dmd.typesem.isBaseOf(tthis, t, poffset); } bool equivalent(Type src, Type t) { import dmd.typesem; return dmd.typesem.equivalent(src, t); } Type sarrayOf(Type type, dinteger_t dim) { import dmd.typesem; return dmd.typesem.sarrayOf(type, dim); } Type arrayOf(Type type) { import dmd.typesem; return dmd.typesem.arrayOf(type); } Type constOf(Type type) { import dmd.typesem; return dmd.typesem.constOf(type); } Type immutableOf(Type type) { import dmd.typesem; return dmd.typesem.immutableOf(type); } Type mutableOf(Type type) { import dmd.typesem; return dmd.typesem.mutableOf(type); } Type sharedOf(Type type) { import dmd.typesem; return dmd.typesem.sharedOf(type); } Type sharedConstOf(Type type) { import dmd.typesem; return dmd.typesem.sharedConstOf(type); } Type unSharedOf(Type type) { import dmd.typesem; return dmd.typesem.unSharedOf(type); } Type wildOf(Type type) { import dmd.typesem; return dmd.typesem.wildOf(type); } Type wildConstOf(Type type) { import dmd.typesem; return dmd.typesem.wildConstOf(type); } Type sharedWildOf(Type type) { import dmd.typesem; return dmd.typesem.sharedWildOf(type); } Type sharedWildConstOf(Type type) { import dmd.typesem; return dmd.typesem.sharedWildConstOf(type); } Type substWildTo(Type type, uint mod) { import dmd.typesem; return dmd.typesem.substWildTo(type, mod); } Type unqualify(Type type, uint m) { import dmd.typesem; return dmd.typesem.unqualify(type, m); } Type toHeadMutable(const(Type) type) { import dmd.typesem; return dmd.typesem.toHeadMutable(type); } Type aliasthisOf(Type type) { import dmd.typesem; return dmd.typesem.aliasthisOf(type); } Type castMod(Type type, MOD mod) { import dmd.typesem; return dmd.typesem.castMod(type, mod); } Type addMod(Type type, MOD mod) { import dmd.typesem; return dmd.typesem.addMod(type, mod); } Type addStorageClass(Type type, StorageClass stc) { import dmd.typesem; return dmd.typesem.addStorageClass(type, stc); } Type pointerTo(Type type) { import dmd.typesem; return dmd.typesem.pointerTo(type); } Type referenceTo(Type type) { import dmd.typesem; return dmd.typesem.referenceTo(type); } uinteger_t size(Type type) { import dmd.typesem; return dmd.typesem.size(type); } uinteger_t size(Type type, const ref Loc loc) { import dmd.typesem; return dmd.typesem.size(type, loc); } /*********************************************************** * typinf.d */ bool genTypeInfo(Expression e, const ref Loc loc, Type torig, Scope* sc) { import dmd.typinf; return dmd.typinf.genTypeInfo(e, loc, torig, sc); } version (IN_LLVM) {} else bool isSpeculativeType(Type t) { import dmd.typinf; return dmd.typinf.isSpeculativeType(t); } bool builtinTypeInfo(Type t) { import dmd.typinf; return dmd.typinf.builtinTypeInfo(t); } version (IN_LLVM) { /*********************************************************** * argtypes_aarch64.d */ TypeTuple toArgTypes_aarch64(Type t) { import dmd.argtypes_aarch64; return dmd.argtypes_aarch64.toArgTypes_aarch64(t); } bool isHFVA(Type t, int maxNumElements = 4, Type* rewriteType = null) { import dmd.argtypes_aarch64; return dmd.argtypes_aarch64.isHFVA(t, maxNumElements, rewriteType); } /*********************************************************** * argtypes_sysv_x64.d */ TypeTuple toArgTypes_sysv_x64(Type t) { import dmd.argtypes_sysv_x64; return dmd.argtypes_sysv_x64.toArgTypes_sysv_x64(t); } /*********************************************************** * argtypes_x86.d */ TypeTuple toArgTypes_x86(Type t) { import dmd.argtypes_x86; return dmd.argtypes_x86.toArgTypes_x86(t); } } ldc-1.40.0-src/dmd/enum.h0000644000000000000000000000457314727557031013526 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/enum.h */ #pragma once #include "dsymbol.h" #include "declaration.h" class Identifier; class Type; class Expression; namespace dmd { // in enumsem.d Expression *getDefaultValue(EnumDeclaration *ed, const Loc &loc); } class EnumDeclaration final : public ScopeDsymbol { public: /* The separate, and distinct, cases are: * 1. enum { ... } * 2. enum : memtype { ... } * 3. enum id { ... } * 4. enum id : memtype { ... } * 5. enum id : memtype; * 6. enum id; */ Type *type; // the TypeEnum Type *memtype; // type of the members Visibility visibility; Expression *maxval; Expression *minval; Expression *defaultval; // default initializer private: uint8_t bitFields; public: bool isdeprecated() const; bool isdeprecated(bool v); bool added() const; bool added(bool v); bool inuse() const; bool inuse(bool v); EnumDeclaration *syntaxCopy(Dsymbol *s) override; bool oneMember(Dsymbol *&ps, Identifier *ident) override; Type *getType() override; const char *kind() const override; bool isDeprecated() const override; // is Dsymbol deprecated? Visibility visible() override; bool isSpecial() const; EnumDeclaration *isEnumDeclaration() override { return this; } #if !IN_LLVM Symbol *sinit; #endif void accept(Visitor *v) override { v->visit(this); } }; class EnumMember final : public VarDeclaration { public: /* Can take the following forms: * 1. id * 2. id = value * 3. type id = value */ Expression *&value(); // A cast() is injected to 'value' after semantic(), // but 'origValue' will preserve the original value, // or previous value + 1 if none was specified. Expression *origValue; Type *origType; EnumDeclaration *ed; EnumMember *syntaxCopy(Dsymbol *s) override; const char *kind() const override; EnumMember *isEnumMember() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; ldc-1.40.0-src/dmd/statement.d0000644000000000000000000016301614727557031014560 0ustar rootroot/** * Defines AST nodes for statements. * * Specification: $(LINK2 https://dlang.org/spec/statement.html, Statements) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/statement.d, _statement.d) * Documentation: https://dlang.org/phobos/dmd_statement.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/statement.d */ module dmd.statement; import core.stdc.stdarg; import core.stdc.stdio; import dmd.arraytypes; import dmd.astenums; import dmd.ast_node; import dmd.errors; import dmd.cond; import dmd.declaration; import dmd.dsymbol; import dmd.expression; import dmd.func; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.rootobject; import dmd.sapply; import dmd.staticassert; import dmd.tokens; import dmd.visitor; /*********************************************************** * Specification: https://dlang.org/spec/statement.html */ extern (C++) abstract class Statement : ASTNode { const Loc loc; const STMT stmt; override final DYNCAST dyncast() const { return DYNCAST.statement; } final extern (D) this(const ref Loc loc, STMT stmt) @safe { this.loc = loc; this.stmt = stmt; // If this is an in{} contract scope statement (skip for determining // inlineStatus of a function body for header content) } Statement syntaxCopy() { assert(0); } /************************************* * Do syntax copy of an array of Statement's. */ static Statements* arraySyntaxCopy(Statements* a) { Statements* b = null; if (a) { b = a.copy(); foreach (i, s; *a) { (*b)[i] = s ? s.syntaxCopy() : null; } } return b; } Statement getRelatedLabeled() { return this; } /**************************** * Determine if an enclosed `break` would apply to this * statement, such as if it is a loop or switch statement. * Returns: * `true` if it does */ bool hasBreak() const pure nothrow { //printf("Statement::hasBreak()\n"); return false; } /**************************** * Determine if an enclosed `continue` would apply to this * statement, such as if it is a loop statement. * Returns: * `true` if it does */ bool hasContinue() const pure nothrow { return false; } /********************************** * Returns: * `true` if statement uses exception handling */ extern (D) final bool usesEH() { extern (C++) final class UsesEH : StoppableVisitor { alias visit = typeof(super).visit; public: override void visit(Statement s) { } override void visit(TryCatchStatement s) { stop = true; } override void visit(TryFinallyStatement s) { stop = true; } override void visit(ScopeGuardStatement s) { stop = true; } override void visit(SynchronizedStatement s) { stop = true; } } scope UsesEH ueh = new UsesEH(); return walkPostorder(this, ueh); } /********************************** * Returns: * `true` if statement 'comes from' somewhere else, like a goto */ extern (D) final bool comeFrom() { extern (C++) final class ComeFrom : StoppableVisitor { alias visit = typeof(super).visit; public: override void visit(Statement s) { } override void visit(CaseStatement s) { stop = true; } override void visit(DefaultStatement s) { stop = true; } override void visit(LabelStatement s) { stop = true; } override void visit(AsmStatement s) { stop = true; } } scope ComeFrom cf = new ComeFrom(); return walkPostorder(this, cf); } /********************************** * Returns: * `true` if statement has executable code. */ extern (D) final bool hasCode() { extern (C++) final class HasCode : StoppableVisitor { alias visit = typeof(super).visit; public: override void visit(Statement s) { stop = true; } override void visit(ExpStatement s) { if (s.exp !is null) { stop = s.exp.hasCode(); } } override void visit(CompoundStatement s) { } override void visit(ScopeStatement s) { } override void visit(ImportStatement s) { } override void visit(CaseStatement s) { } override void visit(DefaultStatement s) { } override void visit(LabelStatement s) { } } scope HasCode hc = new HasCode(); return walkPostorder(this, hc); } /******************************* * Find last statement in a sequence of statements. * Returns: * the last statement, or `null` if there isn't one */ inout(Statement) last() inout nothrow pure { return this; } /************************** * Support Visitor Pattern * Params: * v = visitor */ override void accept(Visitor v) { v.visit(this); } /************************************ * Does this statement end with a return statement? * * I.e. is it a single return statement or some compound statement * that unconditionally hits a return statement. * Returns: * return statement it ends with, otherwise null */ pure nothrow @nogc inout(ReturnStatement) endsWithReturnStatement() inout { return null; } version (IN_LLVM) { pure nothrow @nogc inout(CompoundAsmStatement) endsWithAsm() inout { return null; } } final pure inout nothrow @nogc @trusted { /******************** * A cheaper method of doing downcasting of Statements. * Returns: * the downcast statement if it can be downcasted, otherwise `null` */ inout(ErrorStatement) isErrorStatement() { return stmt == STMT.Error ? cast(typeof(return))this : null; } inout(PeelStatement) isPeelStatement() { return stmt == STMT.Peel ? cast(typeof(return))this : null; } inout(ScopeStatement) isScopeStatement() { return stmt == STMT.Scope ? cast(typeof(return))this : null; } inout(ExpStatement) isExpStatement() { return stmt == STMT.Exp ? cast(typeof(return))this : null; } inout(CompoundStatement) isCompoundStatement() { return stmt == STMT.Compound ? cast(typeof(return))this : null; } inout(ReturnStatement) isReturnStatement() { return stmt == STMT.Return ? cast(typeof(return))this : null; } inout(IfStatement) isIfStatement() { return stmt == STMT.If ? cast(typeof(return))this : null; } inout(ConditionalStatement) isConditionalStatement() { return stmt == STMT.Conditional ? cast(typeof(return))this : null; } inout(StaticForeachStatement) isStaticForeachStatement() { return stmt == STMT.StaticForeach ? cast(typeof(return))this : null; } inout(CaseStatement) isCaseStatement() { return stmt == STMT.Case ? cast(typeof(return))this : null; } inout(DefaultStatement) isDefaultStatement() { return stmt == STMT.Default ? cast(typeof(return))this : null; } inout(LabelStatement) isLabelStatement() { return stmt == STMT.Label ? cast(typeof(return))this : null; } inout(GotoStatement) isGotoStatement() { return stmt == STMT.Goto ? cast(typeof(return))this : null; } inout(GotoDefaultStatement) isGotoDefaultStatement() { return stmt == STMT.GotoDefault ? cast(typeof(return))this : null; } inout(GotoCaseStatement) isGotoCaseStatement() { return stmt == STMT.GotoCase ? cast(typeof(return))this : null; } inout(BreakStatement) isBreakStatement() { return stmt == STMT.Break ? cast(typeof(return))this : null; } inout(DtorExpStatement) isDtorExpStatement() { return stmt == STMT.DtorExp ? cast(typeof(return))this : null; } inout(MixinStatement) isMixinStatement() { return stmt == STMT.Mixin ? cast(typeof(return))this : null; } inout(ForwardingStatement) isForwardingStatement() { return stmt == STMT.Forwarding ? cast(typeof(return))this : null; } inout(DoStatement) isDoStatement() { return stmt == STMT.Do ? cast(typeof(return))this : null; } inout(WhileStatement) isWhileStatement() { return stmt == STMT.While ? cast(typeof(return))this : null; } inout(ForStatement) isForStatement() { return stmt == STMT.For ? cast(typeof(return))this : null; } inout(ForeachStatement) isForeachStatement() { return stmt == STMT.Foreach ? cast(typeof(return))this : null; } inout(SwitchStatement) isSwitchStatement() { return stmt == STMT.Switch ? cast(typeof(return))this : null; } inout(ContinueStatement) isContinueStatement() { return stmt == STMT.Continue ? cast(typeof(return))this : null; } inout(WithStatement) isWithStatement() { return stmt == STMT.With ? cast(typeof(return))this : null; } inout(TryCatchStatement) isTryCatchStatement() { return stmt == STMT.TryCatch ? cast(typeof(return))this : null; } inout(ThrowStatement) isThrowStatement() { return stmt == STMT.Throw ? cast(typeof(return))this : null; } inout(DebugStatement) isDebugStatement() { return stmt == STMT.Debug ? cast(typeof(return))this : null; } inout(TryFinallyStatement) isTryFinallyStatement() { return stmt == STMT.TryFinally ? cast(typeof(return))this : null; } inout(ScopeGuardStatement) isScopeGuardStatement() { return stmt == STMT.ScopeGuard ? cast(typeof(return))this : null; } inout(SwitchErrorStatement) isSwitchErrorStatement() { return stmt == STMT.SwitchError ? cast(typeof(return))this : null; } inout(UnrolledLoopStatement) isUnrolledLoopStatement() { return stmt == STMT.UnrolledLoop ? cast(typeof(return))this : null; } inout(ForeachRangeStatement) isForeachRangeStatement() { return stmt == STMT.ForeachRange ? cast(typeof(return))this : null; } inout(CompoundDeclarationStatement) isCompoundDeclarationStatement() { return stmt == STMT.CompoundDeclaration ? cast(typeof(return))this : null; } inout(CompoundAsmStatement) isCompoundAsmStatement() { return stmt == STMT.CompoundAsm ? cast(typeof(return))this : null; } inout(PragmaStatement) isPragmaStatement() { return stmt == STMT.Pragma ? cast(typeof(return))this : null; } inout(StaticAssertStatement) isStaticAssertStatement() { return stmt == STMT.StaticAssert ? cast(typeof(return))this : null; } inout(CaseRangeStatement) isCaseRangeStatement() { return stmt == STMT.CaseRange ? cast(typeof(return))this : null; } inout(SynchronizedStatement) isSynchronizedStatement() { return stmt == STMT.Synchronized ? cast(typeof(return))this : null; } inout(AsmStatement) isAsmStatement() { return stmt == STMT.Asm ? cast(typeof(return))this : null; } inout(InlineAsmStatement) isInlineAsmStatement() { return stmt == STMT.InlineAsm ? cast(typeof(return))this : null; } inout(GccAsmStatement) isGccAsmStatement() { return stmt == STMT.GccAsm ? cast(typeof(return))this : null; } inout(ImportStatement) isImportStatement() { return stmt == STMT.Import ? cast(typeof(return))this : null; } } } /*********************************************************** * Any Statement that fails semantic() or has a component that is an ErrorExp or * a TypeError should return an ErrorStatement from semantic(). */ extern (C++) final class ErrorStatement : Statement { extern (D) this() { super(Loc.initial, STMT.Error); import dmd.globals; assert(global.gaggedErrors || global.errors); } override ErrorStatement syntaxCopy() { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class PeelStatement : Statement { Statement s; extern (D) this(Statement s) @safe { super(s.loc, STMT.Peel); this.s = s; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#ExpressionStatement */ extern (C++) class ExpStatement : Statement { Expression exp; final extern (D) this(const ref Loc loc, Expression exp) @safe { super(loc, STMT.Exp); this.exp = exp; } final extern (D) this(const ref Loc loc, Expression exp, STMT stmt) @safe { super(loc, stmt); this.exp = exp; } final extern (D) this(const ref Loc loc, Dsymbol declaration) @safe { super(loc, STMT.Exp); this.exp = new DeclarationExp(loc, declaration); } static ExpStatement create(const ref Loc loc, Expression exp) @safe { return new ExpStatement(loc, exp); } override ExpStatement syntaxCopy() { return new ExpStatement(loc, exp ? exp.syntaxCopy() : null); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class DtorExpStatement : ExpStatement { // Wraps an expression that is the destruction of 'var' VarDeclaration var; extern (D) this(const ref Loc loc, Expression exp, VarDeclaration var) @safe { super(loc, exp, STMT.DtorExp); this.var = var; } override DtorExpStatement syntaxCopy() { return new DtorExpStatement(loc, exp ? exp.syntaxCopy() : null, var); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#mixin-statement */ // Note: was called CompileStatement extern (C++) final class MixinStatement : Statement { Expressions* exps; extern (D) this(const ref Loc loc, Expression exp) { Expressions* exps = new Expressions(); exps.push(exp); this(loc, exps); } extern (D) this(const ref Loc loc, Expressions* exps) @safe { super(loc, STMT.Mixin); this.exps = exps; } override MixinStatement syntaxCopy() { return new MixinStatement(loc, Expression.arraySyntaxCopy(exps)); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) class CompoundStatement : Statement { Statements* statements; /** * Construct a `CompoundStatement` using an already existing * array of `Statement`s * * Params: * loc = Instantiation information * statements = An array of `Statement`s, that will referenced by this class */ final extern (D) this(const ref Loc loc, Statements* statements) @safe { super(loc, STMT.Compound); this.statements = statements; } final extern (D) this(const ref Loc loc, Statements* statements, STMT stmt) @safe { super(loc, stmt); this.statements = statements; } /** * Construct a `CompoundStatement` from an array of `Statement`s * * Params: * loc = Instantiation information * sts = A variadic array of `Statement`s, that will copied in this class * The entries themselves will not be copied. */ final extern (D) this(const ref Loc loc, Statement[] sts...) { super(loc, STMT.Compound); statements = new Statements(); statements.reserve(sts.length); foreach (s; sts) statements.push(s); } static CompoundStatement create(const ref Loc loc, Statement s1, Statement s2) { return new CompoundStatement(loc, s1, s2); } override CompoundStatement syntaxCopy() { return new CompoundStatement(loc, Statement.arraySyntaxCopy(statements)); } override final inout(ReturnStatement) endsWithReturnStatement() inout nothrow pure { foreach (s; *statements) { if (s) { if (inout rs = s.endsWithReturnStatement()) return rs; } } return null; } override final inout(Statement) last() inout nothrow pure { Statement s = null; for (size_t i = statements.length; i; --i) { s = cast(Statement)(*statements)[i - 1]; if (s) { s = cast(Statement)s.last(); if (s) break; } } return cast(inout)s; } override void accept(Visitor v) { v.visit(this); } version (IN_LLVM) { override inout(CompoundAsmStatement) endsWithAsm() inout pure nothrow @nogc { // make the last inner statement decide if (statements && statements.length) { size_t last = statements.length - 1; if (auto s = (*statements)[last]) return s.endsWithAsm(); } return null; } } } /*********************************************************** */ extern (C++) final class CompoundDeclarationStatement : CompoundStatement { extern (D) this(const ref Loc loc, Statements* statements) @safe { super(loc, statements, STMT.CompoundDeclaration); } override CompoundDeclarationStatement syntaxCopy() { return new CompoundDeclarationStatement(loc, Statement.arraySyntaxCopy(statements)); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * The purpose of this is so that continue will go to the next * of the statements, and break will go to the end of the statements. */ extern (C++) final class UnrolledLoopStatement : Statement { Statements* statements; extern (D) this(const ref Loc loc, Statements* statements) @safe { super(loc, STMT.UnrolledLoop); this.statements = statements; } override UnrolledLoopStatement syntaxCopy() { return new UnrolledLoopStatement(loc, Statement.arraySyntaxCopy(statements)); } override bool hasBreak() const pure nothrow { return true; } override bool hasContinue() const pure nothrow { return true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class ScopeStatement : Statement { Statement statement; Loc endloc; // location of closing curly bracket extern (D) this(const ref Loc loc, Statement statement, Loc endloc) @safe { super(loc, STMT.Scope); this.statement = statement; this.endloc = endloc; } override ScopeStatement syntaxCopy() { return new ScopeStatement(loc, statement ? statement.syntaxCopy() : null, endloc); } override inout(ReturnStatement) endsWithReturnStatement() inout nothrow pure { if (statement) return statement.endsWithReturnStatement(); return null; } override bool hasBreak() const pure nothrow { //printf("ScopeStatement::hasBreak() %s\n", toChars()); return statement ? statement.hasBreak() : false; } override bool hasContinue() const pure nothrow { return statement ? statement.hasContinue() : false; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * Statement whose symbol table contains foreach index variables in a * local scope and forwards other members to the parent scope. This * wraps a statement. * * Also see: `dmd.attrib.ForwardingAttribDeclaration` */ extern (C++) final class ForwardingStatement : Statement { /// The symbol containing the `static foreach` variables. ForwardingScopeDsymbol sym = null; /// The wrapped statement. Statement statement; extern (D) this(const ref Loc loc, ForwardingScopeDsymbol sym, Statement statement) @safe { super(loc, STMT.Forwarding); this.sym = sym; assert(statement); this.statement = statement; } extern (D) this(const ref Loc loc, Statement statement) @safe { auto sym = new ForwardingScopeDsymbol(); sym.symtab = new DsymbolTable(); this(loc, sym, statement); } override ForwardingStatement syntaxCopy() { return new ForwardingStatement(loc, statement.syntaxCopy()); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#while-statement */ extern (C++) final class WhileStatement : Statement { Parameter param; Expression condition; Statement _body; Loc endloc; // location of closing curly bracket extern (D) this(const ref Loc loc, Expression condition, Statement _body, Loc endloc, Parameter param = null) @safe { super(loc, STMT.While); this.condition = condition; this._body = _body; this.endloc = endloc; this.param = param; } override WhileStatement syntaxCopy() { return new WhileStatement(loc, condition.syntaxCopy(), _body ? _body.syntaxCopy() : null, endloc, param ? param.syntaxCopy() : null); } override bool hasBreak() const pure nothrow { return true; } override bool hasContinue() const pure nothrow { return true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#do-statement */ extern (C++) final class DoStatement : Statement { Statement _body; Expression condition; Loc endloc; // location of ';' after while extern (D) this(const ref Loc loc, Statement _body, Expression condition, Loc endloc) @safe { super(loc, STMT.Do); this._body = _body; this.condition = condition; this.endloc = endloc; } override DoStatement syntaxCopy() { return new DoStatement(loc, _body ? _body.syntaxCopy() : null, condition.syntaxCopy(), endloc); } override bool hasBreak() const pure nothrow { return true; } override bool hasContinue() const pure nothrow { return true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#for-statement */ extern (C++) final class ForStatement : Statement { Statement _init; Expression condition; Expression increment; Statement _body; Loc endloc; // location of closing curly bracket // When wrapped in try/finally clauses, this points to the outermost one, // which may have an associated label. Internal break/continue statements // treat that label as referring to this loop. Statement relatedLabeled; extern (D) this(const ref Loc loc, Statement _init, Expression condition, Expression increment, Statement _body, Loc endloc) @safe { super(loc, STMT.For); this._init = _init; this.condition = condition; this.increment = increment; this._body = _body; this.endloc = endloc; } override ForStatement syntaxCopy() { return new ForStatement(loc, _init ? _init.syntaxCopy() : null, condition ? condition.syntaxCopy() : null, increment ? increment.syntaxCopy() : null, _body.syntaxCopy(), endloc); } override Statement getRelatedLabeled() { return relatedLabeled ? relatedLabeled : this; } override bool hasBreak() const pure nothrow { //printf("ForStatement::hasBreak()\n"); return true; } override bool hasContinue() const pure nothrow { return true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#foreach-statement */ extern (C++) final class ForeachStatement : Statement { TOK op; // TOK.foreach_ or TOK.foreach_reverse_ Parameters* parameters; // array of Parameters, one for each ForeachType Expression aggr; // ForeachAggregate Statement _body; // NoScopeNonEmptyStatement Loc endloc; // location of closing curly bracket VarDeclaration key; VarDeclaration value; FuncDeclaration func; // function we're lexically in Statements* cases; // put breaks, continues, gotos and returns here ScopeStatements* gotos; // forward referenced goto's go here extern (D) this(const ref Loc loc, TOK op, Parameters* parameters, Expression aggr, Statement _body, Loc endloc) @safe { super(loc, STMT.Foreach); this.op = op; this.parameters = parameters; this.aggr = aggr; this._body = _body; this.endloc = endloc; } override ForeachStatement syntaxCopy() { return new ForeachStatement(loc, op, Parameter.arraySyntaxCopy(parameters), aggr.syntaxCopy(), _body ? _body.syntaxCopy() : null, endloc); } override bool hasBreak() const pure nothrow { return true; } override bool hasContinue() const pure nothrow { return true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#foreach-range-statement */ extern (C++) final class ForeachRangeStatement : Statement { TOK op; // TOK.foreach_ or TOK.foreach_reverse_ Parameter prm; // loop index variable Expression lwr; Expression upr; Statement _body; Loc endloc; // location of closing curly bracket VarDeclaration key; extern (D) this(const ref Loc loc, TOK op, Parameter prm, Expression lwr, Expression upr, Statement _body, Loc endloc) @safe { super(loc, STMT.ForeachRange); this.op = op; this.prm = prm; this.lwr = lwr; this.upr = upr; this._body = _body; this.endloc = endloc; } override ForeachRangeStatement syntaxCopy() { return new ForeachRangeStatement(loc, op, prm.syntaxCopy(), lwr.syntaxCopy(), upr.syntaxCopy(), _body ? _body.syntaxCopy() : null, endloc); } override bool hasBreak() const pure nothrow { return true; } override bool hasContinue() const pure nothrow { return true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#if-statement */ extern (C++) final class IfStatement : Statement { Parameter prm; Expression condition; Statement ifbody; Statement elsebody; VarDeclaration match; // for MatchExpression results Loc endloc; // location of closing curly bracket extern (D) this(const ref Loc loc, Parameter prm, Expression condition, Statement ifbody, Statement elsebody, Loc endloc) @safe { super(loc, STMT.If); this.prm = prm; this.condition = condition; this.ifbody = ifbody; this.elsebody = elsebody; this.endloc = endloc; } override IfStatement syntaxCopy() { return new IfStatement(loc, prm ? prm.syntaxCopy() : null, condition.syntaxCopy(), ifbody ? ifbody.syntaxCopy() : null, elsebody ? elsebody.syntaxCopy() : null, endloc); } override void accept(Visitor v) { v.visit(this); } /****** * Returns: true if `if (__ctfe)` */ bool isIfCtfeBlock() { if (auto cv = condition.isVarExp()) return cv.var.ident == Id.ctfe; return false; } } /*********************************************************** * https://dlang.org/spec/version.html#ConditionalStatement */ extern (C++) final class ConditionalStatement : Statement { Condition condition; Statement ifbody; Statement elsebody; extern (D) this(const ref Loc loc, Condition condition, Statement ifbody, Statement elsebody) @safe { super(loc, STMT.Conditional); this.condition = condition; this.ifbody = ifbody; this.elsebody = elsebody; } override ConditionalStatement syntaxCopy() { return new ConditionalStatement(loc, condition.syntaxCopy(), ifbody.syntaxCopy(), elsebody ? elsebody.syntaxCopy() : null); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/version.html#StaticForeachStatement * Static foreach statements, like: * void main() * { * static foreach(i; 0 .. 10) * { * pragma(msg, i); * } * } */ extern (C++) final class StaticForeachStatement : Statement { StaticForeach sfe; extern (D) this(const ref Loc loc, StaticForeach sfe) @safe { super(loc, STMT.StaticForeach); this.sfe = sfe; } override StaticForeachStatement syntaxCopy() { return new StaticForeachStatement(loc, sfe.syntaxCopy()); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#pragma-statement */ extern (C++) final class PragmaStatement : Statement { const Identifier ident; Expressions* args; // array of Expression's Statement _body; extern (D) this(const ref Loc loc, const Identifier ident, Expressions* args, Statement _body) @safe { super(loc, STMT.Pragma); this.ident = ident; this.args = args; this._body = _body; } override PragmaStatement syntaxCopy() { return new PragmaStatement(loc, ident, Expression.arraySyntaxCopy(args), _body ? _body.syntaxCopy() : null); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/version.html#StaticAssert */ extern (C++) final class StaticAssertStatement : Statement { StaticAssert sa; extern (D) this(StaticAssert sa) @safe { super(sa.loc, STMT.StaticAssert); this.sa = sa; } override StaticAssertStatement syntaxCopy() { return new StaticAssertStatement(sa.syntaxCopy(null)); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#switch-statement */ extern (C++) final class SwitchStatement : Statement { Parameter param; Expression condition; /// switch(condition) Statement _body; /// bool isFinal; /// https://dlang.org/spec/statement.html#final-switch-statement Loc endloc; bool hasDefault; /// true if has default statement bool hasVars; /// true if has variable case values DefaultStatement sdefault; /// default: Statement tryBody; /// set to TryCatchStatement or TryFinallyStatement if in _body portion TryFinallyStatement tf; /// set if in the 'finally' block of a TryFinallyStatement GotoCaseStatements gotoCases; /// array of unresolved GotoCaseStatement's CaseStatements* cases; /// array of CaseStatement's VarDeclaration lastVar; /// last observed variable declaration in this statement version (IN_LLVM) { bool hasGotoDefault; // true iff there is a `goto default` statement for this switch } extern (D) this(const ref Loc loc, Parameter param, Expression condition, Statement _body, bool isFinal, Loc endloc) { super(loc, STMT.Switch); this.param = param; this.condition = condition; this._body = _body; this.isFinal = isFinal; this.endloc = endloc; } override SwitchStatement syntaxCopy() { return new SwitchStatement(loc, param ? param.syntaxCopy() : null, condition.syntaxCopy(), _body.syntaxCopy(), isFinal, endloc); } override bool hasBreak() const pure nothrow { return true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#CaseStatement */ extern (C++) final class CaseStatement : Statement { Expression exp; Statement statement; int index; // which case it is (since we sort this) VarDeclaration lastVar; void* extra; // for use by Statement_toIR() version (IN_LLVM) { bool gototarget; // true iff this is the target of a 'goto case' } extern (D) this(const ref Loc loc, Expression exp, Statement statement) @safe { super(loc, STMT.Case); this.exp = exp; this.statement = statement; } override CaseStatement syntaxCopy() { return new CaseStatement(loc, exp.syntaxCopy(), statement.syntaxCopy()); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#CaseRangeStatement */ extern (C++) final class CaseRangeStatement : Statement { Expression first; Expression last; Statement statement; extern (D) this(const ref Loc loc, Expression first, Expression last, Statement statement) @safe { super(loc, STMT.CaseRange); this.first = first; this.last = last; this.statement = statement; } override CaseRangeStatement syntaxCopy() { return new CaseRangeStatement(loc, first.syntaxCopy(), last.syntaxCopy(), statement.syntaxCopy()); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#DefaultStatement */ extern (C++) final class DefaultStatement : Statement { Statement statement; VarDeclaration lastVar; version (IN_LLVM) { bool gototarget; // true iff this is the target of a 'goto default' } extern (D) this(const ref Loc loc, Statement statement) @safe { super(loc, STMT.Default); this.statement = statement; } override DefaultStatement syntaxCopy() { return new DefaultStatement(loc, statement.syntaxCopy()); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#GotoStatement */ extern (C++) final class GotoDefaultStatement : Statement { SwitchStatement sw; extern (D) this(const ref Loc loc) @safe { super(loc, STMT.GotoDefault); } override GotoDefaultStatement syntaxCopy() { return new GotoDefaultStatement(loc); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#GotoStatement */ extern (C++) final class GotoCaseStatement : Statement { Expression exp; // null, or which case to goto CaseStatement cs; // case statement it resolves to version (IN_LLVM) { SwitchStatement sw; } extern (D) this(const ref Loc loc, Expression exp) @safe { super(loc, STMT.GotoCase); this.exp = exp; } override GotoCaseStatement syntaxCopy() { return new GotoCaseStatement(loc, exp ? exp.syntaxCopy() : null); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class SwitchErrorStatement : Statement { Expression exp; extern (D) this(const ref Loc loc) @safe { super(loc, STMT.SwitchError); } final extern (D) this(const ref Loc loc, Expression exp) @safe { super(loc, STMT.SwitchError); this.exp = exp; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#return-statement */ extern (C++) final class ReturnStatement : Statement { Expression exp; size_t caseDim; extern (D) this(const ref Loc loc, Expression exp) @safe { super(loc, STMT.Return); this.exp = exp; } override ReturnStatement syntaxCopy() { return new ReturnStatement(loc, exp ? exp.syntaxCopy() : null); } override inout(ReturnStatement) endsWithReturnStatement() inout nothrow pure { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#break-statement */ extern (C++) final class BreakStatement : Statement { Identifier ident; version (IN_LLVM) { // LDC: only set if ident is set: label statement to jump to LabelStatement target; } extern (D) this(const ref Loc loc, Identifier ident) @safe { super(loc, STMT.Break); this.ident = ident; } override BreakStatement syntaxCopy() { return new BreakStatement(loc, ident); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#continue-statement */ extern (C++) final class ContinueStatement : Statement { Identifier ident; version (IN_LLVM) { // LDC: only set if ident is set: label statement to jump to LabelStatement target; } extern (D) this(const ref Loc loc, Identifier ident) @safe { super(loc, STMT.Continue); this.ident = ident; } override ContinueStatement syntaxCopy() { return new ContinueStatement(loc, ident); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#SynchronizedStatement */ extern (C++) final class SynchronizedStatement : Statement { Expression exp; Statement _body; extern (D) this(const ref Loc loc, Expression exp, Statement _body) @safe { super(loc, STMT.Synchronized); this.exp = exp; this._body = _body; } override SynchronizedStatement syntaxCopy() { return new SynchronizedStatement(loc, exp ? exp.syntaxCopy() : null, _body ? _body.syntaxCopy() : null); } override bool hasBreak() const pure nothrow { return false; //true; } override bool hasContinue() const pure nothrow { return false; //true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#with-statement */ extern (C++) final class WithStatement : Statement { Expression exp; Statement _body; VarDeclaration wthis; Loc endloc; extern (D) this(const ref Loc loc, Expression exp, Statement _body, Loc endloc) @safe { super(loc, STMT.With); this.exp = exp; this._body = _body; this.endloc = endloc; } override WithStatement syntaxCopy() { return new WithStatement(loc, exp.syntaxCopy(), _body ? _body.syntaxCopy() : null, endloc); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#try-statement */ extern (C++) final class TryCatchStatement : Statement { Statement _body; Catches* catches; Statement tryBody; /// set to enclosing TryCatchStatement or TryFinallyStatement if in _body portion extern (D) this(const ref Loc loc, Statement _body, Catches* catches) @safe { super(loc, STMT.TryCatch); this._body = _body; this.catches = catches; } override TryCatchStatement syntaxCopy() { auto a = new Catches(catches.length); foreach (i, c; *catches) { (*a)[i] = c.syntaxCopy(); } return new TryCatchStatement(loc, _body.syntaxCopy(), a); } override bool hasBreak() const pure nothrow { return false; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#Catch */ extern (C++) final class Catch : RootObject { const Loc loc; Type type; Identifier ident; Statement handler; VarDeclaration var; bool errors; // set if semantic processing errors // was generated by the compiler, wasn't present in source code bool internalCatch; extern (D) this(const ref Loc loc, Type type, Identifier ident, Statement handler) @safe { //printf("Catch(%s, loc = %s)\n", id.toChars(), loc.toChars()); this.loc = loc; this.type = type; this.ident = ident; this.handler = handler; } Catch syntaxCopy() { auto c = new Catch(loc, type ? type.syntaxCopy() : getThrowable(), ident, (handler ? handler.syntaxCopy() : null)); c.internalCatch = internalCatch; return c; } } /*********************************************************** * https://dlang.org/spec/statement.html#try-statement */ extern (C++) final class TryFinallyStatement : Statement { Statement _body; Statement finalbody; Statement tryBody; /// set to enclosing TryCatchStatement or TryFinallyStatement if in _body portion bool bodyFallsThru; /// true if _body falls through to finally extern (D) this(const ref Loc loc, Statement _body, Statement finalbody) @safe { super(loc, STMT.TryFinally); this._body = _body; this.finalbody = finalbody; this.bodyFallsThru = true; // assume true until statementSemantic() } static TryFinallyStatement create(const ref Loc loc, Statement _body, Statement finalbody) @safe { return new TryFinallyStatement(loc, _body, finalbody); } override TryFinallyStatement syntaxCopy() { return new TryFinallyStatement(loc, _body.syntaxCopy(), finalbody.syntaxCopy()); } override bool hasBreak() const pure nothrow { return false; //true; } override bool hasContinue() const pure nothrow { return false; //true; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#scope-guard-statement */ extern (C++) final class ScopeGuardStatement : Statement { TOK tok; Statement statement; extern (D) this(const ref Loc loc, TOK tok, Statement statement) @safe { super(loc, STMT.ScopeGuard); this.tok = tok; this.statement = statement; } override ScopeGuardStatement syntaxCopy() { return new ScopeGuardStatement(loc, tok, statement.syntaxCopy()); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#throw-statement */ extern (C++) final class ThrowStatement : Statement { Expression exp; // was generated by the compiler, wasn't present in source code bool internalThrow; extern (D) this(const ref Loc loc, Expression exp) @safe { super(loc, STMT.Throw); this.exp = exp; } override ThrowStatement syntaxCopy() { auto s = new ThrowStatement(loc, exp.syntaxCopy()); s.internalThrow = internalThrow; return s; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class DebugStatement : Statement { Statement statement; extern (D) this(const ref Loc loc, Statement statement) @safe { super(loc, STMT.Debug); this.statement = statement; } override DebugStatement syntaxCopy() { return new DebugStatement(loc, statement ? statement.syntaxCopy() : null); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#goto-statement */ extern (C++) final class GotoStatement : Statement { Identifier ident; LabelDsymbol label; Statement tryBody; /// set to TryCatchStatement or TryFinallyStatement if in _body portion TryFinallyStatement tf; ScopeGuardStatement os; VarDeclaration lastVar; bool inCtfeBlock; /// set if goto is inside an `if (__ctfe)` block extern (D) this(const ref Loc loc, Identifier ident) @safe { super(loc, STMT.Goto); this.ident = ident; } override GotoStatement syntaxCopy() { return new GotoStatement(loc, ident); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#LabeledStatement */ extern (C++) final class LabelStatement : Statement { Identifier ident; Statement statement; Statement tryBody; /// set to TryCatchStatement or TryFinallyStatement if in _body portion TryFinallyStatement tf; ScopeGuardStatement os; VarDeclaration lastVar; Statement gotoTarget; // interpret void* extra; // used by Statement_toIR() bool breaks; // someone did a 'break ident' bool inCtfeBlock; // inside a block dominated by `if (__ctfe)` extern (D) this(const ref Loc loc, Identifier ident, Statement statement) @safe { super(loc, STMT.Label); this.ident = ident; this.statement = statement; } override LabelStatement syntaxCopy() { return new LabelStatement(loc, ident, statement ? statement.syntaxCopy() : null); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** */ extern (C++) final class LabelDsymbol : Dsymbol { LabelStatement statement; bool deleted; // set if rewritten to return in foreach delegate bool iasm; // set if used by inline assembler // set if label was defined multiple times, to avoid duplicate errors // can be removed if generic error message deduplication is implemented bool duplicated; extern (D) this(Identifier ident, const ref Loc loc = Loc.initial) @safe { super(loc, ident); } static LabelDsymbol create(Identifier ident) @safe { return new LabelDsymbol(ident); } // is this a LabelDsymbol()? override LabelDsymbol isLabel() { return this; } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/statement.html#asm */ extern (C++) class AsmStatement : Statement { Token* tokens; bool caseSensitive; // for register names extern (D) this(const ref Loc loc, Token* tokens) @safe { super(loc, STMT.Asm); this.tokens = tokens; } extern (D) this(const ref Loc loc, Token* tokens, STMT stmt) @safe { super(loc, stmt); this.tokens = tokens; } override AsmStatement syntaxCopy() { return new AsmStatement(loc, tokens); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://dlang.org/spec/iasm.html */ extern (C++) final class InlineAsmStatement : AsmStatement { void* asmcode; uint asmalign; // alignment of this statement uint regs; // mask of registers modified (must match regm_t in back end) bool refparam; // true if function parameter is referenced bool naked; // true if function is to be naked version (IN_LLVM) { // non-zero if this is a branch, contains the target label LabelDsymbol isBranchToLabel; } extern (D) this(const ref Loc loc, Token* tokens) @safe { super(loc, tokens, STMT.InlineAsm); } override InlineAsmStatement syntaxCopy() { version (IN_LLVM) { auto a_s = new InlineAsmStatement(loc, tokens); a_s.refparam = refparam; a_s.naked = naked; return a_s; } else { return new InlineAsmStatement(loc, tokens); } } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html * Assembler instructions with D expression operands. */ extern (C++) final class GccAsmStatement : AsmStatement { StorageClass stc; // attributes of the asm {} block Expression insn; // string expression that is the template for assembler code Expressions* args; // input and output operands of the statement uint outputargs; // of the operands in 'args', the number of output operands Identifiers* names; // list of symbolic names for the operands Expressions* constraints; // list of string constants specifying constraints on operands Expressions* clobbers; // list of string constants specifying clobbers and scratch registers Identifiers* labels; // list of goto labels GotoStatements* gotos; // of the goto labels, the equivalent statements they represent extern (D) this(const ref Loc loc, Token* tokens) @safe { super(loc, tokens, STMT.GccAsm); } override GccAsmStatement syntaxCopy() { return new GccAsmStatement(loc, tokens); } override void accept(Visitor v) { v.visit(this); } } /*********************************************************** * a complete asm {} block */ extern (C++) final class CompoundAsmStatement : CompoundStatement { StorageClass stc; // postfix attributes like nothrow/pure/@trusted version (IN_LLVM) { void* abiret; // llvm::Value* } extern (D) this(const ref Loc loc, Statements* statements, StorageClass stc) @safe { super(loc, statements, STMT.CompoundAsm); this.stc = stc; } override CompoundAsmStatement syntaxCopy() { return new CompoundAsmStatement(loc, Statement.arraySyntaxCopy(statements), stc); } override void accept(Visitor v) { v.visit(this); } version (IN_LLVM) { override final inout(CompoundAsmStatement) endsWithAsm() inout pure nothrow @nogc { // yes this is inline asm return this; } } } /*********************************************************** * https://dlang.org/spec/module.html#ImportDeclaration */ extern (C++) final class ImportStatement : Statement { Dsymbols* imports; // Array of Import's extern (D) this(const ref Loc loc, Dsymbols* imports) @safe { super(loc, STMT.Import); this.imports = imports; } override ImportStatement syntaxCopy() { auto m = new Dsymbols(imports.length); foreach (i, s; *imports) { (*m)[i] = s.syntaxCopy(null); } return new ImportStatement(loc, m); } override void accept(Visitor v) { v.visit(this); } } mixin template VisitStatement(Result) { Result VisitStatement(Statement s) { final switch (s.stmt) { case STMT.Error: mixin(visitStmtCase("Error")); case STMT.Scope: mixin(visitStmtCase("Scope")); case STMT.Exp: mixin(visitStmtCase("Exp")); case STMT.Compound: mixin(visitStmtCase("Compound")); case STMT.Return: mixin(visitStmtCase("Return")); case STMT.If: mixin(visitStmtCase("If")); case STMT.Conditional: mixin(visitStmtCase("Conditional")); case STMT.StaticForeach: mixin(visitStmtCase("StaticForeach")); case STMT.Case: mixin(visitStmtCase("Case")); case STMT.Default: mixin(visitStmtCase("Default")); case STMT.Label: mixin(visitStmtCase("Label")); case STMT.Goto: mixin(visitStmtCase("Goto")); case STMT.GotoDefault: mixin(visitStmtCase("GotoDefault")); case STMT.GotoCase: mixin(visitStmtCase("GotoCase")); case STMT.Break: mixin(visitStmtCase("Break")); case STMT.DtorExp: mixin(visitStmtCase("DtorExp")); case STMT.Mixin: mixin(visitStmtCase("Mixin")); case STMT.Forwarding: mixin(visitStmtCase("Forwarding")); case STMT.Do: mixin(visitStmtCase("Do")); case STMT.While: mixin(visitStmtCase("While")); case STMT.For: mixin(visitStmtCase("For")); case STMT.Foreach: mixin(visitStmtCase("Foreach")); case STMT.Switch: mixin(visitStmtCase("Switch")); case STMT.Continue: mixin(visitStmtCase("Continue")); case STMT.With: mixin(visitStmtCase("With")); case STMT.TryCatch: mixin(visitStmtCase("TryCatch")); case STMT.Throw: mixin(visitStmtCase("Throw")); case STMT.Debug: mixin(visitStmtCase("Debug")); case STMT.TryFinally: mixin(visitStmtCase("TryFinally")); case STMT.ScopeGuard: mixin(visitStmtCase("ScopeGuard")); case STMT.SwitchError: mixin(visitStmtCase("SwitchError")); case STMT.UnrolledLoop: mixin(visitStmtCase("UnrolledLoop")); case STMT.ForeachRange: mixin(visitStmtCase("ForeachRange")); case STMT.CompoundDeclaration: mixin(visitStmtCase("CompoundDeclaration")); case STMT.Peel: mixin(visitStmtCase("Peel")); case STMT.CompoundAsm: mixin(visitStmtCase("CompoundAsm")); case STMT.Pragma: mixin(visitStmtCase("Pragma")); case STMT.StaticAssert: mixin(visitStmtCase("StaticAssert")); case STMT.CaseRange: mixin(visitStmtCase("CaseRange")); case STMT.Synchronized: mixin(visitStmtCase("Synchronized")); case STMT.Asm: mixin(visitStmtCase("Asm")); case STMT.InlineAsm: mixin(visitStmtCase("InlineAsm")); case STMT.GccAsm: mixin(visitStmtCase("GccAsm")); case STMT.Import: mixin(visitStmtCase("Import")); } } } /**************************************** * CTFE-only helper function for VisitInitializer. * Params: * handler = string for the name of the visit handler * Returns: boilerplate code for a case */ pure string visitStmtCase(string handler) @safe { if (__ctfe) { return " enum isVoid = is(Result == void); auto sx = s.is"~handler~"Statement(); static if (__traits(compiles, visit"~handler~"(sx))) { static if (isVoid) { visit"~handler~"(sx); return; } else { if (Result r = visit"~handler~"(sx)) return r; return Result.init; } } else static if (__traits(compiles, visitDefaultCase(s))) { static if (isVoid) { visitDefaultCase(sx); return; } else { if (Result r = visitDefaultCase(s)) return r; return Result.init; } } else static assert(0, "~handler~"); "; } assert(0); } ldc-1.40.0-src/dmd/parsetimevisitor.d0000644000000000000000000004251114727557031016161 0ustar rootroot/** * Defines a visitor for the AST. * * Other visitors derive from this class. * * Documentation: https://dlang.org/phobos/dmd_parsetimevisitor.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/parsetimevisitor.d */ module dmd.parsetimevisitor; /** Basic and dumm visitor which implements a visit method for each AST node * implemented in AST. This visitor is the parent of strict, transitive * and permissive visitors. */ extern (C++) class ParseTimeVisitor(AST) { public: void visit(AST.Dsymbol) { assert(0); } void visit(AST.Parameter) { assert(0); } void visit(AST.Statement) { assert(0); } void visit(AST.Type) { assert(0); } void visit(AST.Expression) { assert(0); } void visit(AST.TemplateParameter) { assert(0); } void visit(AST.Condition) { assert(0); } void visit(AST.Initializer) { assert(0); } //======================================================================================= // Dsymbols void visit(AST.AliasThis s) { visit(cast(AST.Dsymbol)s); } void visit(AST.Declaration s) { visit(cast(AST.Dsymbol)s); } void visit(AST.ScopeDsymbol s) { visit(cast(AST.Dsymbol)s); } void visit(AST.Import s) { visit(cast(AST.Dsymbol)s); } void visit(AST.AttribDeclaration s) { visit(cast(AST.Dsymbol)s); } void visit(AST.StaticAssert s) { visit(cast(AST.Dsymbol)s); } void visit(AST.DebugSymbol s) { visit(cast(AST.Dsymbol)s); } void visit(AST.VersionSymbol s) { visit(cast(AST.Dsymbol)s); } void visit(AST.AliasAssign s) { visit(cast(AST.Dsymbol)s); } void visit(AST.CAsmDeclaration s) { visit(cast(AST.Dsymbol)s); } // ScopeDsymbols void visit(AST.Package s) { visit(cast(AST.ScopeDsymbol)s); } void visit(AST.EnumDeclaration s) { visit(cast(AST.ScopeDsymbol)s); } void visit(AST.AggregateDeclaration s) { visit(cast(AST.ScopeDsymbol)s); } void visit(AST.TemplateDeclaration s) { visit(cast(AST.ScopeDsymbol)s); } void visit(AST.TemplateInstance s) { visit(cast(AST.ScopeDsymbol)s); } void visit(AST.Nspace s) { visit(cast(AST.ScopeDsymbol)s); } //========================================================================================= // Declarations void visit(AST.VarDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.FuncDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.AliasDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.TupleDeclaration s) { visit(cast(AST.Declaration)s); } // FuncDeclarations void visit(AST.FuncLiteralDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.PostBlitDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.CtorDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.DtorDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.InvariantDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.UnitTestDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.NewDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.StaticCtorDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.StaticDtorDeclaration s) { visit(cast(AST.FuncDeclaration)s); } void visit(AST.SharedStaticCtorDeclaration s) { visit(cast(AST.StaticCtorDeclaration)s); } void visit(AST.SharedStaticDtorDeclaration s) { visit(cast(AST.StaticDtorDeclaration)s); } // AttribDeclarations void visit(AST.MixinDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.UserAttributeDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.LinkDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.AnonDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.AlignDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.CPPMangleDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.CPPNamespaceDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.VisibilityDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.PragmaDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.StorageClassDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.ConditionalDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.StaticForeachDeclaration s) { visit(cast(AST.AttribDeclaration)s); } //============================================================================================== // Miscellaneous void visit(AST.DeprecatedDeclaration s) { visit(cast(AST.StorageClassDeclaration)s); } void visit(AST.StaticIfDeclaration s) { visit(cast(AST.ConditionalDeclaration)s); } void visit(AST.EnumMember s) { visit(cast(AST.VarDeclaration)s); } void visit(AST.Module s) { visit(cast(AST.Package)s); } void visit(AST.StructDeclaration s) { visit(cast(AST.AggregateDeclaration)s); } void visit(AST.UnionDeclaration s) { visit(cast(AST.StructDeclaration)s); } void visit(AST.ClassDeclaration s) { visit(cast(AST.AggregateDeclaration)s); } void visit(AST.InterfaceDeclaration s) { visit(cast(AST.ClassDeclaration)s); } void visit(AST.TemplateMixin s) { visit(cast(AST.TemplateInstance)s); } void visit(AST.BitFieldDeclaration s) { visit(cast(AST.VarDeclaration)s); } //============================================================================================ // Statements void visit(AST.ImportStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ScopeStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ReturnStatement s) { visit(cast(AST.Statement)s); } void visit(AST.LabelStatement s) { visit(cast(AST.Statement)s); } void visit(AST.StaticAssertStatement s) { visit(cast(AST.Statement)s); } void visit(AST.MixinStatement s) { visit(cast(AST.Statement)s); } void visit(AST.WhileStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ForStatement s) { visit(cast(AST.Statement)s); } void visit(AST.DoStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ForeachRangeStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ForeachStatement s) { visit(cast(AST.Statement)s); } void visit(AST.IfStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ScopeGuardStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ConditionalStatement s) { visit(cast(AST.Statement)s); } void visit(AST.StaticForeachStatement s) { visit(cast(AST.Statement)s); } void visit(AST.PragmaStatement s) { visit(cast(AST.Statement)s); } void visit(AST.SwitchStatement s) { visit(cast(AST.Statement)s); } void visit(AST.CaseRangeStatement s) { visit(cast(AST.Statement)s); } void visit(AST.CaseStatement s) { visit(cast(AST.Statement)s); } void visit(AST.DefaultStatement s) { visit(cast(AST.Statement)s); } void visit(AST.BreakStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ContinueStatement s) { visit(cast(AST.Statement)s); } void visit(AST.GotoDefaultStatement s) { visit(cast(AST.Statement)s); } void visit(AST.GotoCaseStatement s) { visit(cast(AST.Statement)s); } void visit(AST.GotoStatement s) { visit(cast(AST.Statement)s); } void visit(AST.SynchronizedStatement s) { visit(cast(AST.Statement)s); } void visit(AST.WithStatement s) { visit(cast(AST.Statement)s); } void visit(AST.TryCatchStatement s) { visit(cast(AST.Statement)s); } void visit(AST.TryFinallyStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ThrowStatement s) { visit(cast(AST.Statement)s); } void visit(AST.AsmStatement s) { visit(cast(AST.Statement)s); } void visit(AST.ExpStatement s) { visit(cast(AST.Statement)s); } void visit(AST.CompoundStatement s) { visit(cast(AST.Statement)s); } // CompoundStatements void visit(AST.CompoundDeclarationStatement s) { visit(cast(AST.CompoundStatement)s); } void visit(AST.CompoundAsmStatement s) { visit(cast(AST.CompoundStatement)s); } // AsmStatements void visit(AST.InlineAsmStatement s) { visit(cast(AST.AsmStatement)s); } void visit(AST.GccAsmStatement s) { visit(cast(AST.AsmStatement)s); } //========================================================================================= // Types void visit(AST.TypeBasic t) { visit(cast(AST.Type)t); } void visit(AST.TypeError t) { visit(cast(AST.Type)t); } void visit(AST.TypeNull t) { visit(cast(AST.Type)t); } void visit(AST.TypeNoreturn t) { visit(cast(AST.Type)t); } void visit(AST.TypeVector t) { visit(cast(AST.Type)t); } void visit(AST.TypeEnum t) { visit(cast(AST.Type)t); } void visit(AST.TypeTuple t) { visit(cast(AST.Type)t); } void visit(AST.TypeClass t) { visit(cast(AST.Type)t); } void visit(AST.TypeStruct t) { visit(cast(AST.Type)t); } void visit(AST.TypeNext t) { visit(cast(AST.Type)t); } void visit(AST.TypeQualified t) { visit(cast(AST.Type)t); } void visit(AST.TypeTraits t) { visit(cast(AST.Type)t); } void visit(AST.TypeMixin t) { visit(cast(AST.Type)t); } void visit(AST.TypeTag t) { visit(cast(AST.Type)t); } // TypeNext void visit(AST.TypeReference t) { visit(cast(AST.TypeNext)t); } void visit(AST.TypeSlice t) { visit(cast(AST.TypeNext)t); } void visit(AST.TypeDelegate t) { visit(cast(AST.TypeNext)t); } void visit(AST.TypePointer t) { visit(cast(AST.TypeNext)t); } void visit(AST.TypeFunction t) { visit(cast(AST.TypeNext)t); } void visit(AST.TypeArray t) { visit(cast(AST.TypeNext)t); } // TypeArray void visit(AST.TypeDArray t) { visit(cast(AST.TypeArray)t); } void visit(AST.TypeAArray t) { visit(cast(AST.TypeArray)t); } void visit(AST.TypeSArray t) { visit(cast(AST.TypeArray)t); } // TypeQualified void visit(AST.TypeIdentifier t) { visit(cast(AST.TypeQualified)t); } void visit(AST.TypeReturn t) { visit(cast(AST.TypeQualified)t); } void visit(AST.TypeTypeof t) { visit(cast(AST.TypeQualified)t); } void visit(AST.TypeInstance t) { visit(cast(AST.TypeQualified)t); } //================================================================================= // Expressions void visit(AST.DeclarationExp e) { visit(cast(AST.Expression)e); } void visit(AST.IntegerExp e) { visit(cast(AST.Expression)e); } void visit(AST.NewAnonClassExp e) { visit(cast(AST.Expression)e); } void visit(AST.IsExp e) { visit(cast(AST.Expression)e); } void visit(AST.RealExp e) { visit(cast(AST.Expression)e); } void visit(AST.NullExp e) { visit(cast(AST.Expression)e); } void visit(AST.TypeidExp e) { visit(cast(AST.Expression)e); } void visit(AST.TraitsExp e) { visit(cast(AST.Expression)e); } void visit(AST.StringExp e) { visit(cast(AST.Expression)e); } void visit(AST.InterpExp e) { visit(cast(AST.Expression)e); } void visit(AST.NewExp e) { visit(cast(AST.Expression)e); } void visit(AST.AssocArrayLiteralExp e) { visit(cast(AST.Expression)e); } void visit(AST.ArrayLiteralExp e) { visit(cast(AST.Expression)e); } void visit(AST.MixinExp e) { visit(cast(AST.Expression)e); } void visit(AST.FuncExp e) { visit(cast(AST.Expression)e); } void visit(AST.IntervalExp e) { visit(cast(AST.Expression)e); } void visit(AST.TypeExp e) { visit(cast(AST.Expression)e); } void visit(AST.ScopeExp e) { visit(cast(AST.Expression)e); } void visit(AST.IdentifierExp e) { visit(cast(AST.Expression)e); } void visit(AST.UnaExp e) { visit(cast(AST.Expression)e); } void visit(AST.DefaultInitExp e) { visit(cast(AST.Expression)e); } void visit(AST.BinExp e) { visit(cast(AST.Expression)e); } void visit(AST.DsymbolExp e) { visit(cast(AST.Expression)e); } void visit(AST.TemplateExp e) { visit(cast(AST.Expression)e); } void visit(AST.SymbolExp e) { visit(cast(AST.Expression)e); } void visit(AST.TupleExp e) { visit(cast(AST.Expression)e); } void visit(AST.ThisExp e) { visit(cast(AST.Expression)e); } void visit(AST.GenericExp e) { visit(cast(AST.Expression)e); } // Miscellaneous void visit(AST.VarExp e) { visit(cast(AST.SymbolExp)e); } void visit(AST.DollarExp e) { visit(cast(AST.IdentifierExp)e); } void visit(AST.SuperExp e) { visit(cast(AST.ThisExp)e); } // UnaExp void visit(AST.AddrExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.PreExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.PtrExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.NegExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.UAddExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.NotExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.ComExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.DeleteExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.CastExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.CallExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.DotIdExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.AssertExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.ThrowExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.ImportExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.DotTemplateInstanceExp e) { visit(cast(AST.UnaExp)e); } void visit(AST.ArrayExp e) { visit(cast(AST.UnaExp)e); } // DefaultInitExp void visit(AST.FuncInitExp e) { visit(cast(AST.DefaultInitExp)e); } void visit(AST.PrettyFuncInitExp e) { visit(cast(AST.DefaultInitExp)e); } void visit(AST.FileInitExp e) { visit(cast(AST.DefaultInitExp)e); } void visit(AST.LineInitExp e) { visit(cast(AST.DefaultInitExp)e); } void visit(AST.ModuleInitExp e) { visit(cast(AST.DefaultInitExp)e); } // BinExp void visit(AST.CommaExp e) { visit(cast(AST.BinExp)e); } void visit(AST.PostExp e) { visit(cast(AST.BinExp)e); } void visit(AST.PowExp e) { visit(cast(AST.BinExp)e); } void visit(AST.MulExp e) { visit(cast(AST.BinExp)e); } void visit(AST.DivExp e) { visit(cast(AST.BinExp)e); } void visit(AST.ModExp e) { visit(cast(AST.BinExp)e); } void visit(AST.AddExp e) { visit(cast(AST.BinExp)e); } void visit(AST.MinExp e) { visit(cast(AST.BinExp)e); } void visit(AST.CatExp e) { visit(cast(AST.BinExp)e); } void visit(AST.ShlExp e) { visit(cast(AST.BinExp)e); } void visit(AST.ShrExp e) { visit(cast(AST.BinExp)e); } void visit(AST.UshrExp e) { visit(cast(AST.BinExp)e); } void visit(AST.EqualExp e) { visit(cast(AST.BinExp)e); } void visit(AST.InExp e) { visit(cast(AST.BinExp)e); } void visit(AST.IdentityExp e) { visit(cast(AST.BinExp)e); } void visit(AST.CmpExp e) { visit(cast(AST.BinExp)e); } void visit(AST.AndExp e) { visit(cast(AST.BinExp)e); } void visit(AST.XorExp e) { visit(cast(AST.BinExp)e); } void visit(AST.OrExp e) { visit(cast(AST.BinExp)e); } void visit(AST.LogicalExp e) { visit(cast(AST.BinExp)e); } void visit(AST.CondExp e) { visit(cast(AST.BinExp)e); } void visit(AST.AssignExp e) { visit(cast(AST.BinExp)e); } void visit(AST.BinAssignExp e) { visit(cast(AST.BinExp)e); } // BinAssignExp void visit(AST.AddAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.MinAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.MulAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.DivAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.ModAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.PowAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.AndAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.OrAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.XorAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.ShlAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.ShrAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.UshrAssignExp e) { visit(cast(AST.BinAssignExp)e); } void visit(AST.CatAssignExp e) { visit(cast(AST.BinAssignExp)e); } // CatAssignExp void visit(AST.CatElemAssignExp e) { visit(cast(AST.CatAssignExp)e); } void visit(AST.CatDcharAssignExp e) { visit(cast(AST.CatAssignExp)e); } //=============================================================================== // TemplateParameter void visit(AST.TemplateAliasParameter tp) { visit(cast(AST.TemplateParameter)tp); } void visit(AST.TemplateTypeParameter tp) { visit(cast(AST.TemplateParameter)tp); } void visit(AST.TemplateTupleParameter tp) { visit(cast(AST.TemplateParameter)tp); } void visit(AST.TemplateValueParameter tp) { visit(cast(AST.TemplateParameter)tp); } void visit(AST.TemplateThisParameter tp) { visit(cast(AST.TemplateTypeParameter)tp); } //=============================================================================== // Condition void visit(AST.StaticIfCondition c) { visit(cast(AST.Condition)c); } void visit(AST.DVCondition c) { visit(cast(AST.Condition)c); } void visit(AST.DebugCondition c) { visit(cast(AST.DVCondition)c); } void visit(AST.VersionCondition c) { visit(cast(AST.DVCondition)c); } //=============================================================================== // Initializer void visit(AST.ExpInitializer i) { visit(cast(AST.Initializer)i); } void visit(AST.StructInitializer i) { visit(cast(AST.Initializer)i); } void visit(AST.ArrayInitializer i) { visit(cast(AST.Initializer)i); } void visit(AST.VoidInitializer i) { visit(cast(AST.Initializer)i); } void visit(AST.DefaultInitializer i) { visit(cast(AST.Initializer)i); } void visit(AST.CInitializer i) { visit(cast(AST.CInitializer)i); } } ldc-1.40.0-src/dmd/blockexit.d0000644000000000000000000004036614727557031014542 0ustar rootroot/** * Find out in what ways control flow can exit a statement block. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/blockexit.d, _blockexit.d) * Documentation: https://dlang.org/phobos/dmd_blockexit.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/blockexit.d */ module dmd.blockexit; import core.stdc.stdio; import dmd.arraytypes; import dmd.astenums; import dmd.canthrow; import dmd.dclass; import dmd.declaration; import dmd.errorsink; import dmd.expression; import dmd.func; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.statement; import dmd.tokens; /** * BE stands for BlockExit. * * It indicates if a statement does transfer control to another block. * A block is a sequence of statements enclosed in { } */ enum BE : int { none = 0, fallthru = 1, throw_ = 2, return_ = 4, goto_ = 8, halt = 0x10, break_ = 0x20, continue_ = 0x40, errthrow = 0x80, any = (fallthru | throw_ | return_ | goto_ | halt), } /********************************************* * Determine mask of ways that a statement can exit. * * Only valid after semantic analysis. * Params: * s = statement to check for block exit status * func = function that statement s is in * eSink = generate an error if it throws * Returns: * BE.xxxx */ int blockExit(Statement s, FuncDeclaration func, ErrorSink eSink) { int result = BE.none; void visitDefaultCase(Statement s) { printf("Statement::blockExit(%p)\n", s); printf("%s\n", s.toChars()); assert(0); } void visitError(ErrorStatement s) { result = BE.none; } void visitExp(ExpStatement s) { result = BE.fallthru; if (s.exp) { if (s.exp.op == EXP.halt) { result = BE.halt; return; } if (AssertExp a = s.exp.isAssertExp()) { if (a.e1.toBool().hasValue(false)) // if it's an assert(0) { result = BE.halt; return; } } if (s.exp.type && s.exp.type.toBasetype().isTypeNoreturn()) result = BE.halt; result |= canThrow(s.exp, func, eSink); } } void visitDtorExp(DtorExpStatement s) { visitExp(s); } void visitMixin(MixinStatement s) { assert(global.errors); result = BE.fallthru; } void visitCompound(CompoundStatement cs) { //printf("CompoundStatement.blockExit(%p) %d result = x%X\n", cs, cs.statements.length, result); result = BE.fallthru; Statement slast = null; foreach (s; *cs.statements) { if (s) { //printf("result = x%x\n", result); //printf("s: %s\n", s.toChars()); if (result & BE.fallthru && slast) { slast = slast.last(); if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement())) { // Allow if last case/default was empty CaseStatement sc = slast.isCaseStatement(); DefaultStatement sd = slast.isDefaultStatement(); auto sl = (sc ? sc.statement : (sd ? sd.statement : null)); if (sl && (!sl.hasCode() || sl.isErrorStatement())) { } else if (func.getModule().filetype != FileType.c) { const(char)* gototype = s.isCaseStatement() ? "case" : "default"; // @@@DEPRECATED_2.110@@@ https://issues.dlang.org/show_bug.cgi?id=22999 // Deprecated in 2.100 // Make an error in 2.110 if (sl && sl.isCaseStatement()) global.errorSink.deprecation(s.loc, "switch case fallthrough - use 'goto %s;' if intended", gototype); else global.errorSink.error(s.loc, "switch case fallthrough - use 'goto %s;' if intended", gototype); } } } if ((result & BE.fallthru) || s.comeFrom()) { result &= ~BE.fallthru; result |= blockExit(s, func, eSink); } slast = s; } } } void visitUnrolledLoop(UnrolledLoopStatement uls) { result = BE.fallthru; foreach (s; *uls.statements) { if (s) { int r = blockExit(s, func, eSink); result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru); if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0) result &= ~BE.fallthru; } } } void visitScope(ScopeStatement s) { //printf("ScopeStatement::blockExit(%p)\n", s.statement); result = blockExit(s.statement, func, eSink); } void visitWhile(WhileStatement s) { assert(global.errors); result = BE.fallthru; } void visitDo(DoStatement s) { if (s._body) { result = blockExit(s._body, func, eSink); if (result == BE.break_) { result = BE.fallthru; return; } if (result & BE.continue_) result |= BE.fallthru; } else result = BE.fallthru; if (result & BE.fallthru) { result |= canThrow(s.condition, func, eSink); if (!(result & BE.break_) && s.condition.toBool().hasValue(true)) result &= ~BE.fallthru; } result &= ~(BE.break_ | BE.continue_); } void visitFor(ForStatement s) { result = BE.fallthru; if (s._init) { result = blockExit(s._init, func, eSink); if (!(result & BE.fallthru)) return; } if (s.condition) { result |= canThrow(s.condition, func, eSink); const opt = s.condition.toBool(); if (opt.hasValue(true)) result &= ~BE.fallthru; else if (opt.hasValue(false)) return; } else result &= ~BE.fallthru; // the body must do the exiting if (s._body) { int r = blockExit(s._body, func, eSink); if (r & (BE.break_ | BE.goto_)) result |= BE.fallthru; result |= r & ~(BE.fallthru | BE.break_ | BE.continue_); } if (s.increment) result |= canThrow(s.increment, func, eSink); } void visitForeach(ForeachStatement s) { result = BE.fallthru; result |= canThrow(s.aggr, func, eSink); if (s._body) result |= blockExit(s._body, func, eSink) & ~(BE.break_ | BE.continue_); } void visitForeachRange(ForeachRangeStatement s) { assert(global.errors); result = BE.fallthru; } void visitIf(IfStatement s) { //printf("IfStatement::blockExit(%p)\n", s); result = BE.none; result |= canThrow(s.condition, func, eSink); const opt = s.condition.toBool(); if (opt.hasValue(true)) { result |= blockExit(s.ifbody, func, eSink); } else if (opt.hasValue(false)) { result |= blockExit(s.elsebody, func, eSink); } else { result |= blockExit(s.ifbody, func, eSink); result |= blockExit(s.elsebody, func, eSink); } //printf("IfStatement::blockExit(%p) = x%x\n", s, result); } void visitConditional(ConditionalStatement s) { result = blockExit(s.ifbody, func, eSink); if (s.elsebody) result |= blockExit(s.elsebody, func, eSink); } void visitPragma(PragmaStatement s) { result = BE.fallthru; } void visitStaticAssert(StaticAssertStatement s) { result = BE.fallthru; } void visitSwitch(SwitchStatement s) { result = BE.none; result |= canThrow(s.condition, func, eSink); if (s._body) { result |= blockExit(s._body, func, eSink); if (result & BE.break_) { result |= BE.fallthru; result &= ~BE.break_; } } else result |= BE.fallthru; } void visitCase(CaseStatement s) { result = blockExit(s.statement, func, eSink); } void visitDefault(DefaultStatement s) { result = blockExit(s.statement, func, eSink); } void visitGotoDefault(GotoDefaultStatement s) { result = BE.goto_; } void visitGotoCase(GotoCaseStatement s) { result = BE.goto_; } void visitSwitchError(SwitchErrorStatement s) { // Switch errors are non-recoverable result = BE.halt; } void visitReturn(ReturnStatement s) { result = BE.return_; if (s.exp) result |= canThrow(s.exp, func, eSink); } void visitBreak(BreakStatement s) { //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_); result = s.ident ? BE.goto_ : BE.break_; } void visitContinue(ContinueStatement s) { result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_; } void visitSynchronized(SynchronizedStatement s) { result = blockExit(s._body, func, eSink); } void visitWith(WithStatement s) { result = BE.none; result |= canThrow(s.exp, func, eSink); result |= blockExit(s._body, func, eSink); } void visitTryCatch(TryCatchStatement s) { assert(s._body); result = blockExit(s._body, func, null); int catchresult = 0; foreach (c; *s.catches) { if (c.type == Type.terror) continue; int cresult = blockExit(c.handler, func, eSink); /* If we're catching Object, then there is no throwing */ Identifier id = c.type.toBasetype().isClassHandle().ident; if (c.internalCatch && (cresult & BE.fallthru)) { // https://issues.dlang.org/show_bug.cgi?id=11542 // leave blockExit flags of the body cresult &= ~BE.fallthru; } else if (id == Id.Object || id == Id.Throwable) { result &= ~(BE.throw_ | BE.errthrow); } else if (id == Id.Exception) { result &= ~BE.throw_; } catchresult |= cresult; } if (eSink && (result & BE.throw_)) { // now explain why this is nothrow blockExit(s._body, func, eSink); } result |= catchresult; } void visitTryFinally(TryFinallyStatement s) { result = BE.fallthru; if (s._body) result = blockExit(s._body, func, null); // check finally body as well, it may throw (bug #4082) int finalresult = BE.fallthru; if (s.finalbody) finalresult = blockExit(s.finalbody, func, null); // If either body or finalbody halts if (result == BE.halt) finalresult = BE.none; if (finalresult == BE.halt) result = BE.none; if (eSink) { // now explain why this is nothrow if (s._body && (result & BE.throw_)) blockExit(s._body, func, eSink); if (s.finalbody && (finalresult & BE.throw_)) blockExit(s.finalbody, func, eSink); } if (!(finalresult & BE.fallthru)) result &= ~BE.fallthru; result |= finalresult & ~BE.fallthru; } void visitScopeGuard(ScopeGuardStatement s) { // At this point, this statement is just an empty placeholder result = BE.fallthru; } void visitThrow(ThrowStatement s) { if (s.internalThrow) { // https://issues.dlang.org/show_bug.cgi?id=8675 // Allow throwing 'Throwable' object even if eSink. result = BE.fallthru; return; } result = checkThrow(s.loc, s.exp, func, eSink); } void visitGoto(GotoStatement s) { //printf("GotoStatement::blockExit(%p)\n", s); result = BE.goto_; } void visitLabel(LabelStatement s) { //printf("LabelStatement::blockExit(%p)\n", s); result = blockExit(s.statement, func, eSink); if (s.breaks) result |= BE.fallthru; } void visitCompoundAsm(CompoundAsmStatement s) { // Assume the worst result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt; if (!(s.stc & STC.nothrow_)) { if(func) func.setThrow(s.loc, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); if (eSink) eSink.error(s.loc, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); // TODO else result |= BE.throw_; } } void visitImport(ImportStatement s) { result = BE.fallthru; } if (!s) return BE.fallthru; mixin VisitStatement!void visit; visit.VisitStatement(s); return result; } /++ + Checks whether `throw ` throws an `Exception` or an `Error` + and raises an error if this violates `nothrow`. + + Params: + loc = location of the `throw` + exp = expression yielding the throwable + eSink = if !null then inside of a `nothrow` scope + func = function containing the `throw` + + Returns: `BE.[err]throw` depending on the type of `exp` +/ BE checkThrow(ref const Loc loc, Expression exp, FuncDeclaration func, ErrorSink eSink) { Type t = exp.type.toBasetype(); ClassDeclaration cd = t.isClassHandle(); assert(cd); if (cd.isErrorException()) { return BE.errthrow; } if (eSink) eSink.error(loc, "`%s` is thrown but not caught", exp.type.toChars()); else if (func) func.setThrow(loc, "`%s` is thrown but not caught", exp.type); return BE.throw_; } ldc-1.40.0-src/dmd/statement.h0000644000000000000000000005717014727557031014567 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/statement.h */ #pragma once #include "arraytypes.h" #include "ast_node.h" #include "dsymbol.h" #include "visitor.h" #include "tokens.h" struct Scope; class Expression; class LabelDsymbol; class Identifier; class IfStatement; class ExpStatement; class DefaultStatement; class VarDeclaration; class Condition; class ErrorStatement; class ReturnStatement; class CompoundStatement; class Parameter; class StaticAssert; class AsmStatement; class GotoStatement; class ScopeStatement; class TryCatchStatement; class TryFinallyStatement; class CaseStatement; class DefaultStatement; class LabelStatement; class StaticForeach; // Back end #if IN_LLVM namespace llvm { class Value; } using code = struct AsmCode; #else struct code; #endif /* How a statement exits; this is returned by blockExit() */ enum BE : int32_t { BEnone = 0, BEfallthru = 1, BEthrow = 2, BEreturn = 4, BEgoto = 8, BEhalt = 0x10, BEbreak = 0x20, BEcontinue = 0x40, BEerrthrow = 0x80, BEany = (BEfallthru | BEthrow | BEreturn | BEgoto | BEhalt) }; typedef unsigned char STMT; enum { STMTerror, STMTpeel, STMTexp, STMTdtorExp, STMTmixin, STMTcompound, STMTcompoundDeclaration, STMTcompoundAsm, STMTunrolledLoop, STMTscope, STMTforwarding, STMTwhile, STMTdo, STMTfor, STMTforeach, STMTforeachRange, STMTif, STMTconditional, STMTstaticForeach, STMTpragma, STMTstaticAssert, STMTswitch, STMTcase, STMTcaseRange, STMTdefault, STMTgotoDefault, STMTgotoCase, STMTswitchError, STMTreturn, STMTbreak, STMTcontinue, STMTsynchronized, STMTwith, STMTtryCatch, STMTtryFinally, STMTscopeGuard, STMTthrow, STMTdebug, STMTgoto, STMTlabel, STMTasm, STMTinlineAsm, STMTgccAsm, STMTimport }; class Statement : public ASTNode { public: Loc loc; STMT stmt; DYNCAST dyncast() const override final { return DYNCAST_STATEMENT; } virtual Statement *syntaxCopy(); virtual Statement *getRelatedLabeled() { return this; } virtual bool hasBreak() const; virtual bool hasContinue() const; bool hasCode(); virtual Statement *last(); virtual ReturnStatement *endsWithReturnStatement() { return nullptr; } #if IN_LLVM virtual CompoundAsmStatement *endsWithAsm(); #endif ErrorStatement *isErrorStatement() { return stmt == STMTerror ? (ErrorStatement*)this : nullptr; } ScopeStatement *isScopeStatement() { return stmt == STMTscope ? (ScopeStatement*)this : nullptr; } ExpStatement *isExpStatement() { return stmt == STMTexp ? (ExpStatement*)this : nullptr; } CompoundStatement *isCompoundStatement() { return stmt == STMTcompound ? (CompoundStatement*)this : nullptr; } #if IN_LLVM CompoundAsmStatement *isCompoundAsmStatement() { return stmt == STMTcompoundAsm ? (CompoundAsmStatement*)this : nullptr; } GccAsmStatement *isGccAsmStatement() { return stmt == STMTgccAsm ? (GccAsmStatement *)this : nullptr; } #endif ReturnStatement *isReturnStatement() { return stmt == STMTreturn ? (ReturnStatement*)this : nullptr; } IfStatement *isIfStatement() { return stmt == STMTif ? (IfStatement*)this : nullptr; } ConditionalStatement *isConditionalStatement() { return stmt == STMTconditional ? (ConditionalStatement*)this : nullptr; } StaticForeachStatement *isStaticForeachStatement() { return stmt == STMTstaticForeach ? (StaticForeachStatement*)this : nullptr; } CaseStatement *isCaseStatement() { return stmt == STMTcase ? (CaseStatement*)this : nullptr; } DefaultStatement *isDefaultStatement() { return stmt == STMTdefault ? (DefaultStatement*)this : nullptr; } LabelStatement *isLabelStatement() { return stmt == STMTlabel ? (LabelStatement*)this : nullptr; } GotoDefaultStatement *isGotoDefaultStatement() { return stmt == STMTgotoDefault ? (GotoDefaultStatement*)this : nullptr; } GotoCaseStatement *isGotoCaseStatement() { return stmt == STMTgotoCase ? (GotoCaseStatement*)this : nullptr; } BreakStatement *isBreakStatement() { return stmt == STMTbreak ? (BreakStatement*)this : nullptr; } DtorExpStatement *isDtorExpStatement() { return stmt == STMTdtorExp ? (DtorExpStatement*)this : nullptr; } MixinStatement *isMixinStatement() { return stmt == STMTmixin ? (MixinStatement*)this : nullptr; } ForwardingStatement *isForwardingStatement() { return stmt == STMTforwarding ? (ForwardingStatement*)this : nullptr; } DoStatement *isDoStatement() { return stmt == STMTdo ? (DoStatement*)this : nullptr; } ForStatement *isForStatement() { return stmt == STMTfor ? (ForStatement*)this : nullptr; } ForeachStatement *isForeachStatement() { return stmt == STMTforeach ? (ForeachStatement*)this : nullptr; } SwitchStatement *isSwitchStatement() { return stmt == STMTswitch ? (SwitchStatement*)this : nullptr; } ContinueStatement *isContinueStatement() { return stmt == STMTcontinue ? (ContinueStatement*)this : nullptr; } WithStatement *isWithStatement() { return stmt == STMTwith ? (WithStatement*)this : nullptr; } TryCatchStatement *isTryCatchStatement() { return stmt == STMTtryCatch ? (TryCatchStatement*)this : nullptr; } ThrowStatement *isThrowStatement() { return stmt == STMTthrow ? (ThrowStatement*)this : nullptr; } DebugStatement *isDebugStatement() { return stmt == STMTdebug ? (DebugStatement*)this : nullptr; } TryFinallyStatement *isTryFinallyStatement() { return stmt == STMTtryFinally ? (TryFinallyStatement*)this : nullptr; } ScopeGuardStatement *isScopeGuardStatement() { return stmt == STMTscopeGuard ? (ScopeGuardStatement*)this : nullptr; } SwitchErrorStatement *isSwitchErrorStatement() { return stmt == STMTswitchError ? (SwitchErrorStatement*)this : nullptr; } UnrolledLoopStatement *isUnrolledLoopStatement() { return stmt == STMTunrolledLoop ? (UnrolledLoopStatement*)this : nullptr; } ForeachRangeStatement *isForeachRangeStatement() { return stmt == STMTforeachRange ? (ForeachRangeStatement*)this : nullptr; } CompoundDeclarationStatement *isCompoundDeclarationStatement() { return stmt == STMTcompoundDeclaration ? (CompoundDeclarationStatement*)this : nullptr; } void accept(Visitor *v) override { v->visit(this); } }; /** Any Statement that fails semantic() or has a component that is an ErrorExp or * a TypeError should return an ErrorStatement from semantic(). */ class ErrorStatement final : public Statement { public: ErrorStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class PeelStatement final : public Statement { public: Statement *s; void accept(Visitor *v) override { v->visit(this); } }; class ExpStatement : public Statement { public: Expression *exp; static ExpStatement *create(const Loc &loc, Expression *exp); ExpStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class DtorExpStatement final : public ExpStatement { public: /* Wraps an expression that is the destruction of 'var' */ VarDeclaration *var; DtorExpStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class MixinStatement final : public Statement { public: Expressions *exps; MixinStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class CompoundStatement : public Statement { public: Statements *statements; static CompoundStatement *create(const Loc &loc, Statement *s1, Statement *s2); CompoundStatement *syntaxCopy() override; ReturnStatement *endsWithReturnStatement() override final; Statement *last() override final; void accept(Visitor *v) override { v->visit(this); } #if IN_LLVM CompoundAsmStatement *endsWithAsm() override; #endif }; class CompoundDeclarationStatement final : public CompoundStatement { public: CompoundDeclarationStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; /* The purpose of this is so that continue will go to the next * of the statements, and break will go to the end of the statements. */ class UnrolledLoopStatement final : public Statement { public: Statements *statements; UnrolledLoopStatement *syntaxCopy() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class ScopeStatement final : public Statement { public: Statement *statement; Loc endloc; // location of closing curly bracket ScopeStatement *syntaxCopy() override; ReturnStatement *endsWithReturnStatement() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class ForwardingStatement final : public Statement { public: ForwardingScopeDsymbol *sym; Statement *statement; ForwardingStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class WhileStatement final : public Statement { public: Parameter *param; Expression *condition; Statement *_body; Loc endloc; // location of closing curly bracket WhileStatement *syntaxCopy() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class DoStatement final : public Statement { public: Statement *_body; Expression *condition; Loc endloc; // location of ';' after while DoStatement *syntaxCopy() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class ForStatement final : public Statement { public: Statement *_init; Expression *condition; Expression *increment; Statement *_body; Loc endloc; // location of closing curly bracket // When wrapped in try/finally clauses, this points to the outermost one, // which may have an associated label. Internal break/continue statements // treat that label as referring to this loop. Statement *relatedLabeled; ForStatement *syntaxCopy() override; Statement *getRelatedLabeled() override { return relatedLabeled ? relatedLabeled : this; } bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class ForeachStatement final : public Statement { public: TOK op; // TOKforeach or TOKforeach_reverse Parameters *parameters; // array of Parameter*'s Expression *aggr; Statement *_body; Loc endloc; // location of closing curly bracket VarDeclaration *key; VarDeclaration *value; FuncDeclaration *func; // function we're lexically in Statements *cases; // put breaks, continues, gotos and returns here ScopeStatements *gotos; // forward referenced goto's go here ForeachStatement *syntaxCopy() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class ForeachRangeStatement final : public Statement { public: TOK op; // TOKforeach or TOKforeach_reverse Parameter *prm; // loop index variable Expression *lwr; Expression *upr; Statement *_body; Loc endloc; // location of closing curly bracket VarDeclaration *key; ForeachRangeStatement *syntaxCopy() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class IfStatement final : public Statement { public: Parameter *prm; Expression *condition; Statement *ifbody; Statement *elsebody; VarDeclaration *match; // for MatchExpression results Loc endloc; // location of closing curly bracket IfStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } bool isIfCtfeBlock(); }; class ConditionalStatement final : public Statement { public: Condition *condition; Statement *ifbody; Statement *elsebody; ConditionalStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class StaticForeachStatement final : public Statement { public: StaticForeach *sfe; StaticForeachStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class PragmaStatement final : public Statement { public: Identifier *ident; Expressions *args; // array of Expression's Statement *_body; PragmaStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class StaticAssertStatement final : public Statement { public: StaticAssert *sa; StaticAssertStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class SwitchStatement final : public Statement { public: Parameter *param; Expression *condition; Statement *_body; d_bool isFinal; Loc endloc; d_bool hasDefault; // true if default statement d_bool hasVars; // true if has variable case values DefaultStatement *sdefault; Statement *tryBody; // set to TryCatchStatement or TryFinallyStatement if in _body portion TryFinallyStatement *tf; GotoCaseStatements gotoCases; // array of unresolved GotoCaseStatement's CaseStatements *cases; // array of CaseStatement's VarDeclaration *lastVar; #if IN_LLVM bool hasGotoDefault; // true iff there is a `goto default` statement for this switch #endif SwitchStatement *syntaxCopy() override; bool hasBreak() const override; void accept(Visitor *v) override { v->visit(this); } }; class CaseStatement final : public Statement { public: Expression *exp; Statement *statement; int index; // which case it is (since we sort this) VarDeclaration *lastVar; void* extra; // for use by Statement_toIR() #if IN_LLVM bool gototarget; // true iff this is the target of a 'goto case' #endif CaseStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class CaseRangeStatement final : public Statement { public: Expression *first; Expression *last; Statement *statement; CaseRangeStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class DefaultStatement final : public Statement { public: Statement *statement; VarDeclaration *lastVar; #if IN_LLVM bool gototarget; // true iff this is the target of a 'goto default' #endif DefaultStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class GotoDefaultStatement final : public Statement { public: SwitchStatement *sw; GotoDefaultStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class GotoCaseStatement final : public Statement { public: Expression *exp; // NULL, or which case to goto CaseStatement *cs; // case statement it resolves to #if IN_LLVM SwitchStatement *sw; #endif GotoCaseStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class SwitchErrorStatement final : public Statement { public: Expression *exp; void accept(Visitor *v) override { v->visit(this); } }; class ReturnStatement final : public Statement { public: Expression *exp; size_t caseDim; ReturnStatement *syntaxCopy() override; ReturnStatement *endsWithReturnStatement() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; class BreakStatement final : public Statement { public: Identifier *ident; #if IN_LLVM // LDC: only set if ident is set: label statement to jump to LabelStatement *target; #endif BreakStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class ContinueStatement final : public Statement { public: Identifier *ident; #if IN_LLVM // LDC: only set if ident is set: label statement to jump to LabelStatement *target; #endif ContinueStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class SynchronizedStatement final : public Statement { public: Expression *exp; Statement *_body; SynchronizedStatement *syntaxCopy() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class WithStatement final : public Statement { public: Expression *exp; Statement *_body; VarDeclaration *wthis; Loc endloc; WithStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TryCatchStatement final : public Statement { public: Statement *_body; Catches *catches; Statement *tryBody; /// set to enclosing TryCatchStatement or TryFinallyStatement if in _body portion TryCatchStatement *syntaxCopy() override; bool hasBreak() const override; void accept(Visitor *v) override { v->visit(this); } }; class Catch final : public RootObject { public: Loc loc; Type *type; Identifier *ident; Statement *handler; VarDeclaration *var; // set if semantic processing errors d_bool errors; // was generated by the compiler, // wasn't present in source code d_bool internalCatch; Catch *syntaxCopy(); }; class TryFinallyStatement final : public Statement { public: Statement *_body; Statement *finalbody; Statement *tryBody; // set to enclosing TryCatchStatement or TryFinallyStatement if in _body portion d_bool bodyFallsThru; // true if _body falls through to finally static TryFinallyStatement *create(const Loc &loc, Statement *body, Statement *finalbody); TryFinallyStatement *syntaxCopy() override; bool hasBreak() const override; bool hasContinue() const override; void accept(Visitor *v) override { v->visit(this); } }; class ScopeGuardStatement final : public Statement { public: TOK tok; Statement *statement; ScopeGuardStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class ThrowStatement final : public Statement { public: Expression *exp; // was generated by the compiler, // wasn't present in source code d_bool internalThrow; ThrowStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class DebugStatement final : public Statement { public: Statement *statement; DebugStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class GotoStatement final : public Statement { public: Identifier *ident; LabelDsymbol *label; Statement *tryBody; /// set to enclosing TryCatchStatement or TryFinallyStatement if in _body portion TryFinallyStatement *tf; ScopeGuardStatement *os; VarDeclaration *lastVar; d_bool inCtfeBlock; GotoStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class LabelStatement final : public Statement { public: Identifier *ident; Statement *statement; Statement *tryBody; /// set to enclosing TryCatchStatement or TryFinallyStatement if in _body portion TryFinallyStatement *tf; ScopeGuardStatement *os; VarDeclaration *lastVar; Statement *gotoTarget; // interpret void* extra; // used by Statement_toIR() d_bool breaks; // someone did a 'break ident' d_bool inCtfeBlock; LabelStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class LabelDsymbol final : public Dsymbol { public: LabelStatement *statement; d_bool deleted; // set if rewritten to return in foreach delegate d_bool iasm; // set if used by inline assembler d_bool duplicated; // set if multiply defined, to avoid duplicate error messages static LabelDsymbol *create(Identifier *ident); LabelDsymbol *isLabel() override; void accept(Visitor *v) override { v->visit(this); } }; namespace dmd { // in statementsem.d Statement* statementSemantic(Statement *s, Scope *sc); // in iasm.d Statement* asmSemantic(AsmStatement *s, Scope *sc); // in iasmgcc.d Statement *gccAsmSemantic(GccAsmStatement *s, Scope *sc); } class AsmStatement : public Statement { public: Token *tokens; d_bool caseSensitive; // for register names AsmStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class InlineAsmStatement final : public AsmStatement { public: void *asmcode; unsigned asmalign; // alignment of this statement unsigned regs; // mask of registers modified (must match regm_t in back end) d_bool refparam; // true if function parameter is referenced d_bool naked; // true if function is to be naked #if IN_LLVM // non-zero if this is a branch, contains the target label LabelDsymbol *isBranchToLabel; #endif InlineAsmStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; // A GCC asm statement - assembler instructions with D expression operands class GccAsmStatement final : public AsmStatement { public: StorageClass stc; // attributes of the asm {} block Expression *insn; // string expression that is the template for assembler code Expressions *args; // input and output operands of the statement unsigned outputargs; // of the operands in 'args', the number of output operands Identifiers *names; // list of symbolic names for the operands Expressions *constraints; // list of string constants specifying constraints on operands Expressions *clobbers; // list of string constants specifying clobbers and scratch registers Identifiers *labels; // list of goto labels GotoStatements *gotos; // of the goto labels, the equivalent statements they represent GccAsmStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; // a complete asm {} block class CompoundAsmStatement final : public CompoundStatement { public: StorageClass stc; // postfix attributes like nothrow/pure/@trusted #if IN_LLVM llvm::Value *abiret; #endif CompoundAsmStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } #if IN_LLVM CompoundAsmStatement *endsWithAsm() override; #endif }; class ImportStatement final : public Statement { public: Dsymbols *imports; // Array of Import's ImportStatement *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; ldc-1.40.0-src/dmd/astenums.d0000644000000000000000000003615314727557031014414 0ustar rootroot/** * Defines enums common to dmd and dmd as parse library. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/astenums.d, _astenums.d) * Documentation: https://dlang.org/phobos/dmd_astenums.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/astenums.d */ module dmd.astenums; enum Sizeok : ubyte { none, /// size of aggregate is not yet able to compute fwd, /// size of aggregate is ready to compute inProcess, /// in the midst of computing the size done, /// size of aggregate is set correctly } /// D Language version enum Edition : ubyte { none, legacy, /// Before the introduction of editions v2024, /// Experimental first new edition latest = v2024 /// Newest edition that this compiler knows of } enum Baseok : ubyte { none, /// base classes not computed yet start, /// in process of resolving base classes done, /// all base classes are resolved semanticdone, /// all base classes semantic done } enum MODFlags : int { none = 0, // default (mutable) const_ = 1, // type is const immutable_ = 4, // type is immutable shared_ = 2, // type is shared wild = 8, // type is wild wildconst = (MODFlags.wild | MODFlags.const_), // type is wild const mutable = 0x10, // type is mutable (only used in wildcard matching) } alias MOD = ubyte; enum STC : ulong // transfer changes to declaration.h { undefined_ = 0, static_ = 1, /// `static` extern_ = 2, /// `extern` const_ = 4, /// `const` final_ = 8, /// `final` abstract_ = 0x10, /// `abstract` parameter = 0x20, /// is function parameter field = 0x40, /// is field of struct, union or class override_ = 0x80, /// `override` auto_ = 0x100, /// `auto` synchronized_ = 0x200, /// `synchronized` deprecated_ = 0x400, /// `deprecated` in_ = 0x800, /// `in` parameter out_ = 0x1000, /// `out` parameter lazy_ = 0x2000, /// `lazy` parameter foreach_ = 0x4000, /// variable for foreach loop variadic = 0x8000, /// the `variadic` parameter in: T foo(T a, U b, V variadic...) constscoperef = 0x1_0000, /// when `in` means const|scope|ref templateparameter = 0x2_0000, /// template parameter ref_ = 0x4_0000, /// `ref` scope_ = 0x8_0000, /// `scope` scopeinferred = 0x20_0000, /// `scope` has been inferred and should not be part of mangling, `scope_` must also be set return_ = 0x40_0000, /// 'return ref' or 'return scope' for function parameters returnScope = 0x80_0000, /// if `ref return scope` then resolve to `ref` and `return scope` returninferred = 0x100_0000, /// `return` has been inferred and should not be part of mangling, `return_` must also be set immutable_ = 0x200_0000, /// `immutable` // = 0x400_0000, manifest = 0x800_0000, /// manifest constant nodtor = 0x1000_0000, /// do not run destructor nothrow_ = 0x2000_0000, /// `nothrow` meaning never throws exceptions pure_ = 0x4000_0000, /// `pure` function tls = 0x8000_0000, /// thread local alias_ = 0x1_0000_0000, /// `alias` parameter shared_ = 0x2_0000_0000, /// accessible from multiple threads gshared = 0x4_0000_0000, /// accessible from multiple threads, but not typed as `shared` wild = 0x8_0000_0000, /// for wild type constructor property = 0x10_0000_0000, /// `@property` safe = 0x20_0000_0000, /// `@safe` trusted = 0x40_0000_0000, /// `@trusted` system = 0x80_0000_0000, /// `@system` ctfe = 0x100_0000_0000, /// can be used in CTFE, even if it is static disable = 0x200_0000_0000, /// for functions that are not callable result = 0x400_0000_0000, /// for result variables passed to out contracts nodefaultctor = 0x800_0000_0000, /// must be set inside constructor temp = 0x1000_0000_0000, /// temporary variable rvalue = 0x2000_0000_0000, /// force rvalue for variables nogc = 0x4000_0000_0000, /// `@nogc` autoref = 0x8000_0000_0000, /// Mark for the already deduced `auto ref` parameter inference = 0x1_0000_0000_0000, /// do attribute inference exptemp = 0x2_0000_0000_0000, /// temporary variable that has lifetime restricted to an expression future = 0x4_0000_0000_0000, /// introducing new base class function local = 0x8_0000_0000_0000, /// do not forward (see dmd.dsymbol.ForwardingScopeDsymbol). live = 0x10_0000_0000_0000, /// function `@live` attribute register = 0x20_0000_0000_0000, /// `register` storage class (ImportC) volatile_ = 0x40_0000_0000_0000, /// destined for volatile in the back end safeGroup = STC.safe | STC.trusted | STC.system, IOR = STC.constscoperef | STC.in_ | STC.ref_ | STC.out_, TYPECTOR = (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild), FUNCATTR = (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.property | STC.live | safeGroup), /* These are visible to the user, i.e. are expressed by the user */ visibleStorageClasses = (STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.const_ | STC.final_ | STC.abstract_ | STC.synchronized_ | STC.deprecated_ | STC.future | STC.override_ | STC.lazy_ | STC.alias_ | STC.out_ | STC.in_ | STC.manifest | STC.immutable_ | STC.shared_ | STC.wild | STC.nothrow_ | STC.nogc | STC.pure_ | STC.ref_ | STC.return_ | STC.tls | STC.gshared | STC.property | STC.safeGroup | STC.disable | STC.local | STC.live), /* These storage classes "flow through" to the inner scope of a Dsymbol */ flowThruAggregate = STC.safeGroup, /// for an AggregateDeclaration flowThruFunction = ~(STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.abstract_ | STC.deprecated_ | STC.override_ | STC.TYPECTOR | STC.final_ | STC.tls | STC.gshared | STC.ref_ | STC.return_ | STC.property | STC.nothrow_ | STC.pure_ | STC.safe | STC.trusted | STC.system), /// for a FuncDeclaration } alias StorageClass = ulong; /******** * Determine if it's the ambigous case of where `return` attaches to. * Params: * stc = STC flags * Returns: * true if (`ref` | `out`) and `scope` and `return` */ @safe pure @nogc nothrow bool isRefReturnScope(const ulong stc) { return (stc & (STC.scope_ | STC.return_)) == (STC.scope_ | STC.return_) && stc & (STC.ref_ | STC.out_); } /* This is different from the one in declaration.d, make that fix a separate PR */ static if (0) __gshared const(StorageClass) STCStorageClass = (STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.const_ | STC.final_ | STC.abstract_ | STC.synchronized_ | STC.deprecated_ | STC.override_ | STC.lazy_ | STC.alias_ | STC.out_ | STC.in_ | STC.manifest | STC.immutable_ | STC.shared_ | STC.wild | STC.nothrow_ | STC.nogc | STC.pure_ | STC.ref_ | STC.return_ | STC.tls | STC.gshared | STC.property | STC.live | STC.safeGroup | STC.disable); enum TY : ubyte { Tarray, // slice array, aka T[] Tsarray, // static array, aka T[dimension] Taarray, // associative array, aka T[type] Tpointer, Treference, Tfunction, Tident, Tclass, Tstruct, Tenum, Tdelegate, Tnone, Tvoid, Tint8, Tuns8, Tint16, Tuns16, Tint32, Tuns32, Tint64, Tuns64, Tfloat32, Tfloat64, Tfloat80, Timaginary32, Timaginary64, Timaginary80, Tcomplex32, Tcomplex64, Tcomplex80, Tbool, Tchar, Twchar, Tdchar, Terror, Tinstance, Ttypeof, Ttuple, Tslice, Treturn, Tnull, Tvector, Tint128, Tuns128, Ttraits, Tmixin, Tnoreturn, Ttag, } enum TMAX = TY.max + 1; alias Tarray = TY.Tarray; alias Tsarray = TY.Tsarray; alias Taarray = TY.Taarray; alias Tpointer = TY.Tpointer; alias Treference = TY.Treference; alias Tfunction = TY.Tfunction; alias Tident = TY.Tident; alias Tclass = TY.Tclass; alias Tstruct = TY.Tstruct; alias Tenum = TY.Tenum; alias Tdelegate = TY.Tdelegate; alias Tnone = TY.Tnone; alias Tvoid = TY.Tvoid; alias Tint8 = TY.Tint8; alias Tuns8 = TY.Tuns8; alias Tint16 = TY.Tint16; alias Tuns16 = TY.Tuns16; alias Tint32 = TY.Tint32; alias Tuns32 = TY.Tuns32; alias Tint64 = TY.Tint64; alias Tuns64 = TY.Tuns64; alias Tfloat32 = TY.Tfloat32; alias Tfloat64 = TY.Tfloat64; alias Tfloat80 = TY.Tfloat80; alias Timaginary32 = TY.Timaginary32; alias Timaginary64 = TY.Timaginary64; alias Timaginary80 = TY.Timaginary80; alias Tcomplex32 = TY.Tcomplex32; alias Tcomplex64 = TY.Tcomplex64; alias Tcomplex80 = TY.Tcomplex80; alias Tbool = TY.Tbool; alias Tchar = TY.Tchar; alias Twchar = TY.Twchar; alias Tdchar = TY.Tdchar; alias Terror = TY.Terror; alias Tinstance = TY.Tinstance; alias Ttypeof = TY.Ttypeof; alias Ttuple = TY.Ttuple; alias Tslice = TY.Tslice; alias Treturn = TY.Treturn; alias Tnull = TY.Tnull; alias Tvector = TY.Tvector; alias Tint128 = TY.Tint128; alias Tuns128 = TY.Tuns128; alias Ttraits = TY.Ttraits; alias Tmixin = TY.Tmixin; alias Tnoreturn = TY.Tnoreturn; alias Ttag = TY.Ttag; enum TFlags { integral = 1, floating = 2, unsigned = 4, real_ = 8, imaginary = 0x10, complex = 0x20, } enum PKG : int { unknown, /// not yet determined whether it's a package.d or not module_, /// already determined that's an actual package.d package_, /// already determined that's an actual package } enum ThreeState : ubyte { none, /// state is not yet computed no, /// state is false yes, /// state is true } enum TRUST : ubyte { default_ = 0, system = 1, // @system (same as TRUST.default) trusted = 2, // @trusted safe = 3, // @safe } enum PURE : ubyte { impure = 0, // not pure at all fwdref = 1, // it's pure, but not known which level yet weak = 2, // no mutable globals are read or written const_ = 3, // parameters are values or const = strongly pure } // Whether alias this dependency is recursive or not enum AliasThisRec : int { no = 0, // no alias this recursion yes = 1, // alias this has recursive dependency fwdref = 2, // not yet known typeMask = 3, // mask to read no/yes/fwdref tracing = 0x4, // mark in progress of implicitConvTo/deduceWild tracingDT = 0x8, // mark in progress of deduceType } /*************** * Variadic argument lists * https://dlang.org/spec/function.html#variadic */ enum VarArg : ubyte { none = 0, /// fixed number of arguments variadic = 1, /// (T t, ...) can be C-style (core.stdc.stdarg) or D-style (core.vararg) typesafe = 2, /// (T t ...) typesafe https://dlang.org/spec/function.html#typesafe_variadic_functions /// or https://dlang.org/spec/function.html#typesafe_variadic_functions KRvariadic = 3, /// K+R C style variadics (no function prototype) } /************************* * Identify Statement types with this enum rather than * virtual functions */ enum STMT : ubyte { Error, Peel, Exp, DtorExp, Mixin, Compound, CompoundDeclaration, CompoundAsm, UnrolledLoop, Scope, Forwarding, While, Do, For, Foreach, ForeachRange, If, Conditional, StaticForeach, Pragma, StaticAssert, Switch, Case, CaseRange, Default, GotoDefault, GotoCase, SwitchError, Return, Break, Continue, Synchronized, With, TryCatch, TryFinally, ScopeGuard, Throw, Debug, Goto, Label, Asm, InlineAsm, GccAsm, Import, } /********************** * Discriminant for which kind of initializer */ enum InitKind : ubyte { void_, default_, error, struct_, array, exp, C_, } /// A linkage attribute as defined by `extern(XXX)` /// /// https://dlang.org/spec/attribute.html#linkage enum LINK : ubyte { default_, d, c, cpp, windows, objc, system, } /// Whether to mangle an external aggregate as a struct or class, as set by `extern(C++, struct)` enum CPPMANGLE : ubyte { def, /// default asStruct, /// `extern(C++, struct)` asClass, /// `extern(C++, class)` } /// Function match levels /// /// https://dlang.org/spec/function.html#function-overloading enum MATCH : int { nomatch, /// no match convert, /// match with conversions constant, /// match with conversion to const exact, /// exact match } /// Inline setting as defined by `pragma(inline, XXX)` enum PINLINE : ubyte { default_, /// as specified on the command line never, /// never inline always, /// always inline } /// Source file type enum FileType : ubyte { d, /// normal D source file dhdr, /// D header file (.di) ddoc, /// Ddoc documentation file (.dd) c, /// C source file } /// In which context checks for assertions, contracts, bounds checks etc. are enabled enum CHECKENABLE : ubyte { _default, /// initial value off, /// never do checking on, /// always do checking safeonly, /// do checking only in @safe functions } /// What should happend when an assertion fails enum CHECKACTION : ubyte { D, /// call D assert on failure C, /// call C assert on failure halt, /// cause program halt on failure context, /// call D assert with the error context on failure } extern (C++) struct structalign_t { private: ushort value = 0; // unknown enum STRUCTALIGN_DEFAULT = 1234; // default = match whatever the corresponding C compiler does bool pack; // use #pragma pack semantics public: pure @safe @nogc nothrow: bool isDefault() const { return value == STRUCTALIGN_DEFAULT; } void setDefault() { value = STRUCTALIGN_DEFAULT; } bool isUnknown() const { return value == 0; } // value is not set void setUnknown() { value = 0; } void set(uint value) { this.value = cast(ushort)value; } uint get() const { return value; } bool isPack() const { return pack; } void setPack(bool pack) { this.pack = pack; } } /// Use to return D arrays from C++ functions extern (C++) struct DArray(T) { T[] data; } ldc-1.40.0-src/dmd/objc.h0000644000000000000000000000514414727557031013472 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 2015-2024 by The D Language Foundation, All Rights Reserved * written by Michel Fortin * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/objc.h */ #pragma once #include "root/dsystem.h" #include "arraytypes.h" class AggregateDeclaration; class AttribDeclaration; class ClassDeclaration; class FuncDeclaration; class Identifier; class InterfaceDeclaration; struct Scope; struct ObjcSelector { const char *stringvalue; size_t stringlen; size_t paramCount; static void _init(); static ObjcSelector *create(FuncDeclaration *fdecl); }; struct ObjcClassDeclaration { d_bool isMeta; d_bool isExtern; #if IN_LLVM d_bool isSwiftStub; #endif Identifier* identifier; ClassDeclaration* classDeclaration; ClassDeclaration* metaclass; DArray methodList; bool isRootClass() const; }; struct ObjcFuncDeclaration { ObjcSelector* selector; VarDeclaration* selectorParameter; d_bool isOptional; }; class Objc { public: static void _init(); virtual void setObjc(ClassDeclaration* cd) = 0; virtual void setObjc(InterfaceDeclaration*) = 0; virtual const char *toPrettyChars(ClassDeclaration *cd, bool qualifyTypes) const = 0; virtual void setSelector(FuncDeclaration*, Scope* sc) = 0; virtual void validateSelector(FuncDeclaration* fd) = 0; virtual void checkLinkage(FuncDeclaration* fd) = 0; virtual bool isVirtual(const FuncDeclaration*) const = 0; virtual void setAsOptional(FuncDeclaration *fd, Scope *sc) const = 0; #if IN_LLVM virtual void setAsSwiftStub(ClassDeclaration* cd, Scope *sc) const = 0; #endif virtual void validateOptional(FuncDeclaration *fd) const = 0; virtual ClassDeclaration* getParent(FuncDeclaration*, ClassDeclaration*) const = 0; virtual void addToClassMethodList(FuncDeclaration*, ClassDeclaration*) const = 0; virtual AggregateDeclaration* isThis(FuncDeclaration* fd) = 0; virtual VarDeclaration* createSelectorParameter(FuncDeclaration*, Scope*) const = 0; virtual void setMetaclass(InterfaceDeclaration* id, Scope*) const = 0; virtual void setMetaclass(ClassDeclaration* id, Scope*) const = 0; virtual ClassDeclaration* getRuntimeMetaclass(ClassDeclaration* cd) = 0; virtual void addSymbols(AttribDeclaration*, ClassDeclarations*, ClassDeclarations*) const = 0; virtual void addSymbols(ClassDeclaration*, ClassDeclarations*, ClassDeclarations*) const = 0; }; ldc-1.40.0-src/dmd/inlinecost.d0000644000000000000000000003502514727557031014721 0ustar rootroot/** * Compute the cost of inlining a function call by counting expressions. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/inlinecost.d, _inlinecost.d) * Documentation: https://dlang.org/phobos/dmd_inlinecost.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/inlinecost.d */ module dmd.inlinecost; import core.stdc.stdio; import core.stdc.string; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.dclass; import dmd.declaration; import dmd.dmodule; import dmd.dscope; import dmd.dstruct; import dmd.dsymbol; import dmd.expression; import dmd.func; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.init; import dmd.mtype; import dmd.opover; import dmd.postordervisitor; import dmd.statement; import dmd.tokens; import dmd.visitor; enum COST_MAX = 250; private enum STATEMENT_COST = 0x1000; private enum STATEMENT_COST_MAX = 250 * STATEMENT_COST; // STATEMENT_COST be power of 2 and greater than COST_MAX static assert((STATEMENT_COST & (STATEMENT_COST - 1)) == 0); static assert(STATEMENT_COST > COST_MAX); /********************************* * Determine if too expensive to inline. * Params: * cost = cost of inlining * Returns: * true if too costly */ bool tooCostly(int cost) pure nothrow @safe { return ((cost & (STATEMENT_COST - 1)) >= COST_MAX); } /********************************* * Determine cost of inlining Expression * Params: * e = Expression to determine cost of * Returns: * cost of inlining e */ int inlineCostExpression(Expression e) { scope InlineCostVisitor icv = new InlineCostVisitor(false, true, true, null); icv.expressionInlineCost(e); return icv.cost; } /********************************* * Determine cost of inlining function * Params: * fd = function to determine cost of * hasthis = if the function call has explicit 'this' expression * hdrscan = if generating a header file * Returns: * cost of inlining fd */ int inlineCostFunction(FuncDeclaration fd, bool hasthis, bool hdrscan) { scope InlineCostVisitor icv = new InlineCostVisitor(hasthis, hdrscan, false, fd); fd.fbody.accept(icv); return icv.cost; } /** * Indicates if a nested aggregate prevents or not a function to be inlined. * It's used to compute the cost but also to avoid a copy of the aggregate * while the inliner processes. * * Params: * e = the declaration expression that may represent an aggregate. * * Returns: `null` if `e` is not an aggregate or if it is an aggregate that * doesn't permit inlining, and the aggregate otherwise. */ AggregateDeclaration isInlinableNestedAggregate(DeclarationExp e) { AggregateDeclaration result; if (e.declaration.isAnonymous() && e.declaration.isAttribDeclaration) { AttribDeclaration ad = e.declaration.isAttribDeclaration; if (ad.decl.length == 1) { if ((result = (*ad.decl)[0].isAggregateDeclaration) !is null) { // classes would have to be destroyed if (auto cdecl = result.isClassDeclaration) return null; // if it's a struct: must not have dtor StructDeclaration sdecl = result.isStructDeclaration; if (sdecl && (sdecl.fieldDtor || sdecl.dtor)) return null; // the aggregate must be static UnionDeclaration udecl = result.isUnionDeclaration; if ((sdecl || udecl) && !(result.storage_class & STC.static_)) return null; return result; } } } else if ((result = e.declaration.isStructDeclaration) !is null) { return result; } else if ((result = e.declaration.isUnionDeclaration) !is null) { return result; } return null; } private: /*********************************************************** * Compute cost of inlining. * * Walk trees to determine if inlining can be done, and if so, * if it is too complex to be worth inlining or not. */ extern (C++) final class InlineCostVisitor : Visitor { alias visit = Visitor.visit; public: int nested; bool hasthis; bool hdrscan; // if inline scan for 'header' content bool allowAlloca; FuncDeclaration fd; int cost; // zero start for subsequent AST extern (D) this() scope @safe { } extern (D) this(bool hasthis, bool hdrscan, bool allowAlloca, FuncDeclaration fd) scope @safe { this.hasthis = hasthis; this.hdrscan = hdrscan; this.allowAlloca = allowAlloca; this.fd = fd; } extern (D) this(InlineCostVisitor icv) scope @safe { nested = icv.nested; hasthis = icv.hasthis; hdrscan = icv.hdrscan; allowAlloca = icv.allowAlloca; fd = icv.fd; } override void visit(Statement s) { //printf("Statement.inlineCost = %d\n", COST_MAX); //printf("%p\n", s.isScopeStatement()); //printf("%s\n", s.toChars()); cost += COST_MAX; // default is we can't inline it } override void visit(ExpStatement s) { expressionInlineCost(s.exp); } override void visit(CompoundStatement s) { scope InlineCostVisitor icv = new InlineCostVisitor(this); foreach (i; 0 .. s.statements.length) { if (Statement s2 = (*s.statements)[i]) { /* Specifically allow: * if (condition) * return exp1; * return exp2; */ IfStatement ifs; Statement s3; if ((ifs = s2.isIfStatement()) !is null && ifs.ifbody && ifs.ifbody.endsWithReturnStatement() && !ifs.elsebody && i + 1 < s.statements.length && (s3 = (*s.statements)[i + 1]) !is null && s3.endsWithReturnStatement() ) { if (ifs.prm) // if variables are declared { cost = COST_MAX; return; } expressionInlineCost(ifs.condition); ifs.ifbody.accept(this); s3.accept(this); } else s2.accept(icv); if (tooCostly(icv.cost)) break; } } cost += icv.cost; } override void visit(UnrolledLoopStatement s) { scope InlineCostVisitor icv = new InlineCostVisitor(this); foreach (s2; *s.statements) { if (s2) { s2.accept(icv); if (tooCostly(icv.cost)) break; } } cost += icv.cost; } override void visit(ScopeStatement s) { cost++; if (s.statement) s.statement.accept(this); } override void visit(IfStatement s) { /* Can't declare variables inside ?: expressions, so * we cannot inline if a variable is declared. */ if (s.prm) { cost = COST_MAX; return; } expressionInlineCost(s.condition); if (s.isIfCtfeBlock()) { cost = COST_MAX; return; } /* Specifically allow: * if (condition) * return exp1; * else * return exp2; * Otherwise, we can't handle return statements nested in if's. */ if (s.elsebody && s.ifbody && s.ifbody.endsWithReturnStatement() && s.elsebody.endsWithReturnStatement()) { s.ifbody.accept(this); s.elsebody.accept(this); //printf("cost = %d\n", cost); } else { nested += 1; if (s.ifbody) s.ifbody.accept(this); if (s.elsebody) s.elsebody.accept(this); nested -= 1; } //printf("IfStatement.inlineCost = %d\n", cost); } override void visit(ReturnStatement s) { // Can't handle return statements nested in if's if (nested) { cost = COST_MAX; } else { expressionInlineCost(s.exp); } } override void visit(ImportStatement s) { } override void visit(ForStatement s) { cost += STATEMENT_COST; if (s._init) s._init.accept(this); if (s.condition) s.condition.accept(this); if (s.increment) s.increment.accept(this); if (s._body) s._body.accept(this); //printf("ForStatement: inlineCost = %d\n", cost); } override void visit(ThrowStatement s) { cost += STATEMENT_COST; s.exp.accept(this); } /* -------------------------- */ void expressionInlineCost(Expression e) { //printf("expressionInlineCost()\n"); //e.print(); if (e) { extern (C++) final class LambdaInlineCost : StoppableVisitor { alias visit = typeof(super).visit; InlineCostVisitor icv; public: extern (D) this(InlineCostVisitor icv) @safe { this.icv = icv; } override void visit(Expression e) { e.accept(icv); stop = icv.cost >= COST_MAX; } } scope InlineCostVisitor icv = new InlineCostVisitor(this); scope LambdaInlineCost lic = new LambdaInlineCost(icv); walkPostorder(e, lic); cost += icv.cost; } } override void visit(Expression e) { cost++; } override void visit(VarExp e) { //printf("VarExp.inlineCost3() %s\n", toChars()); Type tb = e.type.toBasetype(); if (auto ts = tb.isTypeStruct()) { StructDeclaration sd = ts.sym; if (sd.isNested()) { /* An inner struct will be nested inside another function hierarchy than where * we're inlining into, so don't inline it. * At least not until we figure out how to 'move' the struct to be nested * locally. Example: * struct S(alias pred) { void unused_func(); } * void abc() { int w; S!(w) m; } * void bar() { abc(); } */ cost = COST_MAX; return; } } FuncDeclaration fd = e.var.isFuncDeclaration(); if (fd && fd.isNested()) // https://issues.dlang.org/show_bug.cgi?id=7199 for test case cost = COST_MAX; else cost++; } override void visit(ThisExp e) { //printf("ThisExp.inlineCost3() %s\n", toChars()); if (!fd) { cost = COST_MAX; return; } if (!hdrscan) { if (fd.isNested() || !hasthis) { cost = COST_MAX; return; } } cost++; } override void visit(StructLiteralExp e) { //printf("StructLiteralExp.inlineCost3() %s\n", toChars()); if (e.sd.isNested()) cost = COST_MAX; else cost++; } override void visit(NewExp e) { //printf("NewExp.inlineCost3() %s\n", e.toChars()); AggregateDeclaration ad = isAggregate(e.newtype); if (ad && ad.isNested()) cost = COST_MAX; else cost++; } override void visit(FuncExp e) { //printf("FuncExp.inlineCost3()\n"); // Right now, this makes the function be output to the .obj file twice. cost = COST_MAX; } override void visit(DelegateExp e) { //printf("DelegateExp.inlineCost3()\n"); cost = COST_MAX; } override void visit(DeclarationExp e) { //printf("DeclarationExp.inlineCost3()\n"); if (auto vd = e.declaration.isVarDeclaration()) { if (auto td = vd.toAlias().isTupleDeclaration()) { cost = COST_MAX; // finish DeclarationExp.doInlineAs return; } if (!hdrscan && vd.isDataseg()) { cost = COST_MAX; return; } if (vd.edtor) { // if destructor required // needs work to make this work cost = COST_MAX; return; } // Scan initializer (vd.init) if (vd._init) { if (auto ie = vd._init.isExpInitializer()) { expressionInlineCost(ie.exp); } } ++cost; } // aggregates are accepted under certain circumstances if (isInlinableNestedAggregate(e)) { cost++; return; } // These can contain functions, which when copied, get output twice. if (e.declaration.isStructDeclaration() || e.declaration.isClassDeclaration() || e.declaration.isFuncDeclaration() || e.declaration.isAttribDeclaration() || e.declaration.isTemplateMixin()) { cost = COST_MAX; return; } //printf("DeclarationExp.inlineCost3('%s')\n", toChars()); } override void visit(CallExp e) { //printf("CallExp.inlineCost3() %s\n", toChars()); // in LDC, we only use the inliner for default arguments static if (IN_LLVM) cost++; // https://issues.dlang.org/show_bug.cgi?id=3500 // super.func() calls must be devirtualized, and the inliner // can't handle that at present. else if (e.e1.op == EXP.dotVariable && (cast(DotVarExp)e.e1).e1.op == EXP.super_) cost = COST_MAX; else if (e.f && e.f.ident == Id.__alloca && e.f._linkage == LINK.c && !allowAlloca) cost = COST_MAX; // inlining alloca may cause stack overflows else cost++; } } ldc-1.40.0-src/dmd/safe.d0000644000000000000000000002503714727557031013472 0ustar rootroot/** * Checks whether member access or array casting is allowed in `@safe` code. * * Specification: $(LINK2 https://dlang.org/spec/function.html#function-safety, Function Safety) * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/safe.d, _safe.d) * Documentation: https://dlang.org/phobos/dmd_safe.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/safe.d */ module dmd.safe; import core.stdc.stdio; import dmd.aggregate; import dmd.astenums; import dmd.dcast : implicitConvTo; import dmd.dclass; import dmd.declaration; import dmd.dscope; import dmd.expression; import dmd.id; import dmd.identifier; import dmd.mtype; import dmd.target; import dmd.tokens; import dmd.typesem : hasPointers, arrayOf, size; import dmd.funcsem : setUnsafe, setUnsafePreview; /************************************************************* * Check for unsafe access in @safe code: * 1. read overlapped pointers * 2. write misaligned pointers * 3. write overlapped storage classes * Print error if unsafe. * Params: * sc = scope * e = expression to check * readonly = if access is read-only * printmsg = print error message if true * Returns: * true if error */ bool checkUnsafeAccess(Scope* sc, Expression e, bool readonly, bool printmsg) { //printf("checkUnsafeAccess(e: '%s', readonly: %d, printmsg: %d)\n", e.toChars(), readonly, printmsg); if (e.op != EXP.dotVariable) return false; auto dve = cast(DotVarExp)e; if (VarDeclaration v = dve.var.isVarDeclaration()) { if (!sc.func) return false; auto ad = v.isMember2(); if (!ad) return false; import dmd.globals : global; if (v.isSystem()) { if (sc.setUnsafePreview(global.params.systemVariables, !printmsg, e.loc, "cannot access `@system` field `%s.%s` in `@safe` code", ad, v)) return true; } // This branch shouldn't be here, but unfortunately calling `ad.determineSize` // breaks code with circular reference errors. Specifically, test23589.d fails if (ad.sizeok != Sizeok.done && !sc.func.isSafeBypassingInference()) return false; // needed to set v.overlapped and v.overlapUnsafe if (ad.sizeok != Sizeok.done) ad.determineSize(ad.loc); import dmd.globals : FeatureState; const hasPointers = v.type.hasPointers(); if (hasPointers) { if (v.overlapped) { if (sc.func.isSafeBypassingInference() && sc.setUnsafe(!printmsg, e.loc, "field `%s.%s` cannot access pointers in `@safe` code that overlap other fields", ad, v)) { return true; } else { // @@@DEPRECATED_2.116@@@ // https://issues.dlang.org/show_bug.cgi?id=20655 // Inferring `@system` because of union access breaks code, // so make it a deprecation safety violation as of 2.106 // To turn into an error, remove `isSafeBypassingInference` check in the // above if statement and remove the else branch sc.setUnsafePreview(FeatureState.default_, !printmsg, e.loc, "field `%s.%s` cannot access pointers in `@safe` code that overlap other fields", ad, v); } } } if (v.type.hasInvariant()) { if (v.overlapped) { if (sc.setUnsafe(!printmsg, e.loc, "field `%s.%s` cannot access structs with invariants in `@safe` code that overlap other fields", ad, v)) return true; } } // @@@DEPRECATED_2.119@@@ // https://issues.dlang.org/show_bug.cgi?id=24477 // Should probably be turned into an error in a new edition if (v.type.hasUnsafeBitpatterns() && v.overlapped && sc.setUnsafePreview( FeatureState.default_, !printmsg, e.loc, "cannot access overlapped field `%s.%s` with unsafe bit patterns in `@safe` code", ad, v) ) { return true; } if (readonly || !e.type.isMutable()) return false; if (hasPointers && v.type.toBasetype().ty != Tstruct) { if ((!ad.type.alignment.isDefault() && ad.type.alignment.get() < target.ptrsize || (v.offset & (target.ptrsize - 1)))) { if (sc.setUnsafe(!printmsg, e.loc, "field `%s.%s` cannot modify misaligned pointers in `@safe` code", ad, v)) return true; } } if (v.overlapUnsafe) { if (sc.setUnsafe(!printmsg, e.loc, "field `%s.%s` cannot modify fields in `@safe` code that overlap fields with other storage classes", ad, v)) { return true; } } } return false; } /********************************************** * Determine if it is @safe to cast e from tfrom to tto. * Params: * e = expression to be cast * tfrom = type of e * tto = type to cast e to * msg = reason why cast is unsafe or deprecated * Returns: * true if @safe or deprecated */ bool isSafeCast(Expression e, Type tfrom, Type tto, ref string msg) { // Implicit conversions are always safe if (tfrom.implicitConvTo(tto)) return true; if (!tto.hasPointers()) return true; auto tfromb = tfrom.toBasetype(); auto ttob = tto.toBasetype(); if (ttob.ty == Tclass && tfromb.ty == Tclass) { ClassDeclaration cdfrom = tfromb.isClassHandle(); ClassDeclaration cdto = ttob.isClassHandle(); int offset; if (cdfrom == cdto) goto Lsame; if (!cdfrom.isBaseOf(cdto, &offset) && !((cdfrom.isInterfaceDeclaration() || cdto.isInterfaceDeclaration()) && cdfrom.classKind == ClassKind.d && cdto.classKind == ClassKind.d)) { msg = "Source object type is incompatible with target type"; return false; } // no RTTI if (cdfrom.isCPPinterface() || cdto.isCPPinterface()) { msg = "No dynamic type information for extern(C++) classes"; return false; } if (cdfrom.classKind == ClassKind.cpp || cdto.classKind == ClassKind.cpp) msg = "No dynamic type information for extern(C++) classes"; Lsame: if (!MODimplicitConv(tfromb.mod, ttob.mod)) { msg = "Incompatible type qualifier"; return false; } return true; } if (ttob.ty == Tarray && tfromb.ty == Tsarray) // https://issues.dlang.org/show_bug.cgi?id=12502 tfromb = tfromb.nextOf().arrayOf(); if (ttob.ty == Tarray && tfromb.ty == Tarray || ttob.ty == Tpointer && tfromb.ty == Tpointer) { Type ttobn = ttob.nextOf().toBasetype(); Type tfromn = tfromb.nextOf().toBasetype(); /* From void[] to anything mutable is unsafe because: * int*[] api; * void[] av = api; * int[] ai = cast(int[]) av; * ai[0] = 7; * *api[0] crash! */ if (tfromn.ty == Tvoid && ttobn.isMutable()) { if (ttob.ty == Tarray && e.op == EXP.arrayLiteral) return true; msg = "`void` data may contain pointers and target element type is mutable"; return false; } // If the struct is opaque we don't know about the struct members then the cast becomes unsafe if (ttobn.ty == Tstruct && !(cast(TypeStruct)ttobn).sym.members) { msg = "Target element type is opaque"; return false; } if (tfromn.ty == Tstruct && !(cast(TypeStruct)tfromn).sym.members) { msg = "Source element type is opaque"; return false; } if (e.op != EXP.arrayLiteral) { // For bool, only 0 and 1 are safe values // Runtime array cast reinterprets data if (ttobn.ty == Tbool && tfromn.ty != Tbool) msg = "Source element may have bytes which are not 0 or 1"; else if (ttobn.hasUnsafeBitpatterns()) msg = "Target element type has unsafe bit patterns"; // Can't alias a bool pointer with a non-bool pointer if (ttobn.ty != Tbool && tfromn.ty == Tbool && ttobn.isMutable()) msg = "Target element could be assigned a byte which is not 0 or 1"; else if (tfromn.hasUnsafeBitpatterns() && ttobn.isMutable()) msg = "Source element type has unsafe bit patterns and target element type is mutable"; } const frompointers = tfromn.hasPointers(); const topointers = ttobn.hasPointers(); if (frompointers && !topointers && ttobn.isMutable()) { msg = "Target element type is mutable and source element type contains a pointer"; return false; } if (!frompointers && topointers) { msg = "Target element type contains a pointer"; return false; } if (!topointers && ttobn.ty != Tfunction && tfromn.ty != Tfunction && (ttob.ty == Tarray || ttobn.size() <= tfromn.size()) && MODimplicitConv(tfromn.mod, ttobn.mod)) { return true; } } msg = "Source type is incompatible with target type containing a pointer"; return false; } /************************************************* * Check for unsafe use of `.ptr` or `.funcptr` * Params: * sc = context * e = expression for error messages * id = `ptr` or `funcptr` * flag = DotExpFlag * Returns: * true if error */ bool checkUnsafeDotExp(Scope* sc, Expression e, Identifier id, int flag) { if (!(flag & DotExpFlag.noDeref)) // this use is attempting a dereference { if (id == Id.ptr) return sc.setUnsafe(false, e.loc, "`%s.ptr` cannot be used in `@safe` code, use `&%s[0]` instead", e, e); else return sc.setUnsafe(false, e.loc, "`%s.%s` cannot be used in `@safe` code", e, id); } return false; } ldc-1.40.0-src/dmd/rootobject.d0000644000000000000000000000256314727557031014725 0ustar rootroot/** * Provide the root object that AST classes in dmd inherit from. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: Walter Bright, https://www.digitalmars.com * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/rootobject.d, _rootobject.d) * Documentation: https://dlang.org/phobos/dmd_rootobject.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/rootobject.d */ module dmd.rootobject; /*********************************************************** */ enum DYNCAST : int { object, expression, dsymbol, type, identifier, tuple, parameter, statement, condition, templateparameter, initializer, } /*********************************************************** */ extern (C++) class RootObject { this() nothrow pure @nogc @safe scope { } bool equals(const RootObject o) const { return o is this; } const(char)* toChars() const { assert(0); } /// extern(D) const(char)[] toString() const { import core.stdc.string : strlen; auto p = this.toChars(); return p[0 .. strlen(p)]; } DYNCAST dyncast() const nothrow pure @nogc @safe { return DYNCAST.object; } } ldc-1.40.0-src/dmd/mtype.h0000644000000000000000000006115314727557031013715 0ustar rootroot /* Compiler implementation of the D programming language * Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * written by Walter Bright * https://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * https://www.boost.org/LICENSE_1_0.txt * https://github.com/dlang/dmd/blob/master/src/dmd/mtype.h */ #pragma once #include "root/dcompat.h" // for d_size_t #include "arraytypes.h" #include "ast_node.h" #include "expression.h" #include "globals.h" #include "visitor.h" struct Scope; class AggregateDeclaration; class Identifier; class Expression; class StructDeclaration; class ClassDeclaration; class EnumDeclaration; class TypeInfoDeclaration; class Dsymbol; class TemplateInstance; class TemplateDeclaration; class TypeBasic; class Parameter; // Back end #ifdef IN_GCC typedef union tree_node type; #elif IN_LLVM using type = class IrType; #else typedef struct TYPE type; #endif namespace dmd { Type *typeSemantic(Type *t, const Loc &loc, Scope *sc); Type *merge(Type *type); } enum class TY : uint8_t { Tarray, // slice array, aka T[] Tsarray, // static array, aka T[dimension] Taarray, // associative array, aka T[type] Tpointer, Treference, Tfunction, Tident, Tclass, Tstruct, Tenum, Tdelegate, Tnone, Tvoid, Tint8, Tuns8, Tint16, Tuns16, Tint32, Tuns32, Tint64, Tuns64, Tfloat32, Tfloat64, Tfloat80, Timaginary32, Timaginary64, Timaginary80, Tcomplex32, Tcomplex64, Tcomplex80, Tbool, Tchar, Twchar, Tdchar, Terror, Tinstance, Ttypeof, Ttuple, Tslice, Treturn, Tnull, Tvector, Tint128, Tuns128, Ttraits, Tmixin, Tnoreturn, TMAX }; #define SIZE_INVALID (~(uinteger_t)0) // error return from size() functions /** * type modifiers * pick this order of numbers so switch statements work better */ enum MODFlags { MODnone = 0, // default (mutable) MODconst = 1, // type is const MODimmutable = 4, // type is immutable MODshared = 2, // type is shared MODwild = 8, // type is wild MODwildconst = (MODwild | MODconst), // type is wild const MODmutable = 0x10 // type is mutable (only used in wildcard matching) }; typedef unsigned char MOD; enum VarArgValues { VARARGnone = 0, /// fixed number of arguments VARARGvariadic = 1, /// T t, ...) can be C-style (core.stdc.stdarg) or D-style (core.vararg) VARARGtypesafe = 2, /// T t ...) typesafe https://dlang.org/spec/function.html#typesafe_variadic_functions /// or https://dlang.org/spec/function.html#typesafe_variadic_functions VARARGKRvariadic = 3 /// K+R C style variadics (no function prototype) }; typedef unsigned char VarArg; enum class Covariant { distinct = 0, /// types are distinct yes = 1, /// types are covariant no = 2, /// arguments match as far as overloading goes, but types are not covariant fwdref = 3, /// cannot determine covariance because of forward references }; class Type : public ASTNode { public: TY ty; MOD mod; // modifiers MODxxxx char *deco; void* mcache; Type *pto; // merged pointer to this type Type *rto; // reference to this type Type *arrayof; // array of this type TypeInfoDeclaration *vtinfo; // TypeInfo object for this Type type *ctype; // for back end static Type *tvoid; static Type *tint8; static Type *tuns8; static Type *tint16; static Type *tuns16; static Type *tint32; static Type *tuns32; static Type *tint64; static Type *tuns64; static Type *tint128; static Type *tuns128; static Type *tfloat32; static Type *tfloat64; static Type *tfloat80; static Type *timaginary32; static Type *timaginary64; static Type *timaginary80; static Type *tcomplex32; static Type *tcomplex64; static Type *tcomplex80; static Type *tbool; static Type *tchar; static Type *twchar; static Type *tdchar; // Some special types static Type *tshiftcnt; static Type *tvoidptr; // void* static Type *tstring; // immutable(char)[] static Type *twstring; // immutable(wchar)[] static Type *tdstring; // immutable(dchar)[] static Type *terror; // for error recovery static Type *tnull; // for null type static Type *tnoreturn; // for bottom type typeof(*null) static Type *tsize_t; // matches size_t alias static Type *tptrdiff_t; // matches ptrdiff_t alias static Type *thash_t; // matches hash_t alias static ClassDeclaration *dtypeinfo; static ClassDeclaration *typeinfoclass; static ClassDeclaration *typeinfointerface; static ClassDeclaration *typeinfostruct; static ClassDeclaration *typeinfopointer; static ClassDeclaration *typeinfoarray; static ClassDeclaration *typeinfostaticarray; static ClassDeclaration *typeinfoassociativearray; static ClassDeclaration *typeinfovector; static ClassDeclaration *typeinfoenum; static ClassDeclaration *typeinfofunction; static ClassDeclaration *typeinfodelegate; static ClassDeclaration *typeinfotypelist; static ClassDeclaration *typeinfoconst; static ClassDeclaration *typeinfoinvariant; static ClassDeclaration *typeinfoshared; static ClassDeclaration *typeinfowild; static TemplateDeclaration *rtinfo; #if IN_LLVM static TemplateDeclaration *rtinfoImpl; #endif static Type *basic[(int)TY::TMAX]; virtual const char *kind(); Type *copy() const; virtual Type *syntaxCopy(); bool equals(const RootObject * const o) const override; // kludge for template.isType() DYNCAST dyncast() const override final { return DYNCAST_TYPE; } size_t getUniqueID() const; const char *toChars() const override; char *toPrettyChars(bool QualifyTypes = false); static void _init(); virtual unsigned alignsize(); void modToBuffer(OutBuffer& buf) const; char *modToChars() const; virtual bool isintegral(); virtual bool isfloating(); // real, imaginary, or complex virtual bool isreal(); virtual bool isimaginary(); virtual bool iscomplex(); virtual bool isscalar(); virtual bool isunsigned(); virtual bool isscope(); virtual bool isString(); virtual bool isAssignable(); virtual bool isBoolean(); bool isConst() const { return (mod & MODconst) != 0; } bool isImmutable() const { return (mod & MODimmutable) != 0; } bool isMutable() const { return (mod & (MODconst | MODimmutable | MODwild)) == 0; } bool isShared() const { return (mod & MODshared) != 0; } bool isSharedConst() const { return (mod & (MODshared | MODconst)) == (MODshared | MODconst); } bool isWild() const { return (mod & MODwild) != 0; } bool isWildConst() const { return (mod & MODwildconst) == MODwildconst; } bool isSharedWild() const { return (mod & (MODshared | MODwild)) == (MODshared | MODwild); } bool isNaked() const { return mod == 0; } Type *nullAttributes() const; bool hasDeprecatedAliasThis(); virtual Type *makeConst(); virtual Type *makeImmutable(); virtual Type *makeShared(); virtual Type *makeSharedConst(); virtual Type *makeWild(); virtual Type *makeWildConst(); virtual Type *makeSharedWild(); virtual Type *makeSharedWildConst(); virtual Type *makeMutable(); Type *toBasetype(); virtual MATCH constConv(Type *to); virtual unsigned char deduceWild(Type *t, bool isRef); virtual ClassDeclaration *isClassHandle(); virtual structalign_t alignment(); virtual Expression *defaultInitLiteral(const Loc &loc); virtual bool isZeroInit(const Loc &loc = Loc()); // if initializer is 0 virtual int hasWild() const; virtual bool hasVoidInitPointers(); virtual bool hasUnsafeBitpatterns(); virtual bool hasInvariant(); virtual Type *nextOf(); Type *baseElemOf(); virtual bool needsDestruction(); virtual bool needsCopyOrPostblit(); virtual bool needsNested(); TypeFunction *toTypeFunction(); // For eliminating dynamic_cast virtual TypeBasic *isTypeBasic(); TypeFunction *isPtrToFunction(); TypeFunction *isFunction_Delegate_PtrToFunction(); TypeError *isTypeError(); TypeVector *isTypeVector(); TypeSArray *isTypeSArray(); TypeDArray *isTypeDArray(); TypeAArray *isTypeAArray(); TypePointer *isTypePointer(); TypeReference *isTypeReference(); TypeFunction *isTypeFunction(); TypeDelegate *isTypeDelegate(); TypeIdentifier *isTypeIdentifier(); TypeInstance *isTypeInstance(); TypeTypeof *isTypeTypeof(); TypeReturn *isTypeReturn(); TypeStruct *isTypeStruct(); TypeEnum *isTypeEnum(); TypeClass *isTypeClass(); TypeTuple *isTypeTuple(); TypeSlice *isTypeSlice(); TypeNull *isTypeNull(); TypeMixin *isTypeMixin(); TypeTraits *isTypeTraits(); TypeNoreturn *isTypeNoreturn(); TypeTag *isTypeTag(); void accept(Visitor *v) override { v->visit(this); } }; class TypeError final : public Type { public: const char *kind() override; TypeError *syntaxCopy() override; Expression *defaultInitLiteral(const Loc &loc) override; void accept(Visitor *v) override { v->visit(this); } }; class TypeNext : public Type { public: Type *next; int hasWild() const override final; Type *nextOf() override final; Type *makeConst() override final; Type *makeImmutable() override final; Type *makeShared() override final; Type *makeSharedConst() override final; Type *makeWild() override final; Type *makeWildConst() override final; Type *makeSharedWild() override final; Type *makeSharedWildConst() override final; Type *makeMutable() override final; MATCH constConv(Type *to) override; unsigned char deduceWild(Type *t, bool isRef) override final; void transitive(); void accept(Visitor *v) override { v->visit(this); } }; class TypeBasic final : public Type { public: const char *dstring; unsigned flags; const char *kind() override; TypeBasic *syntaxCopy() override; unsigned alignsize() override; bool isintegral() override; bool isfloating() override; bool isreal() override; bool isimaginary() override; bool iscomplex() override; bool isscalar() override; bool isunsigned() override; bool isZeroInit(const Loc &loc) override; // For eliminating dynamic_cast TypeBasic *isTypeBasic() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeVector final : public Type { public: Type *basetype; static TypeVector *create(Type *basetype); const char *kind() override; TypeVector *syntaxCopy() override; unsigned alignsize() override; bool isintegral() override; bool isfloating() override; bool isscalar() override; bool isunsigned() override; bool isBoolean() override; Expression *defaultInitLiteral(const Loc &loc) override; TypeBasic *elementType(); bool isZeroInit(const Loc &loc) override; void accept(Visitor *v) override { v->visit(this); } }; class TypeArray : public TypeNext { public: void accept(Visitor *v) override { v->visit(this); } }; // Static array, one with a fixed dimension class TypeSArray final : public TypeArray { public: Expression *dim; const char *kind() override; TypeSArray *syntaxCopy() override; bool isIncomplete(); unsigned alignsize() override; bool isString() override; bool isZeroInit(const Loc &loc) override; structalign_t alignment() override; MATCH constConv(Type *to) override; Expression *defaultInitLiteral(const Loc &loc) override; bool hasUnsafeBitpatterns() override; bool hasVoidInitPointers() override; bool hasInvariant() override; bool needsDestruction() override; bool needsCopyOrPostblit() override; bool needsNested() override; void accept(Visitor *v) override { v->visit(this); } }; // Dynamic array, no dimension class TypeDArray final : public TypeArray { public: const char *kind() override; TypeDArray *syntaxCopy() override; unsigned alignsize() override; bool isString() override; bool isZeroInit(const Loc &loc) override; bool isBoolean() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeAArray final : public TypeArray { public: Type *index; // key type Loc loc; static TypeAArray *create(Type *t, Type *index); const char *kind() override; TypeAArray *syntaxCopy() override; bool isZeroInit(const Loc &loc) override; bool isBoolean() override; MATCH constConv(Type *to) override; void accept(Visitor *v) override { v->visit(this); } }; class TypePointer final : public TypeNext { public: static TypePointer *create(Type *t); const char *kind() override; TypePointer *syntaxCopy() override; MATCH constConv(Type *to) override; bool isscalar() override; bool isZeroInit(const Loc &loc) override; void accept(Visitor *v) override { v->visit(this); } }; class TypeReference final : public TypeNext { public: const char *kind() override; TypeReference *syntaxCopy() override; bool isZeroInit(const Loc &loc) override; void accept(Visitor *v) override { v->visit(this); } }; enum RET { RETregs = 1, // returned in registers RETstack = 2 // returned on stack }; enum class TRUST : unsigned char { default_ = 0, system = 1, // @system (same as TRUSTdefault) trusted = 2, // @trusted safe = 3 // @safe }; enum TRUSTformat { TRUSTformatDefault, // do not emit @system when trust == TRUSTdefault TRUSTformatSystem // emit @system when trust == TRUSTdefault }; enum class PURE : unsigned char { impure = 0, // not pure at all fwdref = 1, // it's pure, but not known which level yet weak = 2, // no mutable globals are read or written const_ = 3, // parameters are values or const }; class Parameter final : public ASTNode { public: Loc loc; StorageClass storageClass; Type *type; Identifier *ident; Expression *defaultArg; UserAttributeDeclaration *userAttribDecl; // user defined attributes static Parameter *create(const Loc &loc, StorageClass storageClass, Type *type, Identifier *ident, Expression *defaultArg, UserAttributeDeclaration *userAttribDecl); Parameter *syntaxCopy(); Type *isLazyArray(); bool isLazy() const; bool isReference() const; // kludge for template.isType() DYNCAST dyncast() const override { return DYNCAST_PARAMETER; } void accept(Visitor *v) override { v->visit(this); } static size_t dim(Parameters *parameters); static Parameter *getNth(Parameters *parameters, d_size_t nth); const char *toChars() const override; bool isCovariant(bool returnByRef, const Parameter *p, bool previewIn) const; }; struct ParameterList { Parameters* parameters; StorageClass stc; VarArg varargs; d_bool hasIdentifierList; // true if C identifier-list style size_t length(); Parameter *operator[](size_t i) { return Parameter::getNth(parameters, i); } }; class TypeFunction final : public TypeNext { public: // .next is the return type ParameterList parameterList; // function parameters uint16_t bitFields; LINK linkage; // calling convention TRUST trust; // level of trust PURE purity; // PURExxxx char inuse; ArgumentList inferenceArguments; // function arguments static TypeFunction *create(Parameters *parameters, Type *treturn, VarArg varargs, LINK linkage, StorageClass stc = 0); const char *kind() override; TypeFunction *syntaxCopy() override; bool hasLazyParameters(); bool isDstyleVariadic() const; MATCH constConv(Type *to) override; bool isnothrow() const; void isnothrow(bool v); bool isnogc() const; void isnogc(bool v); bool isproperty() const; void isproperty(bool v); bool isref() const; void isref(bool v); bool isreturn() const; void isreturn(bool v); bool isreturnscope() const; void isreturnscope(bool v); bool isScopeQual() const; void isScopeQual(bool v); bool isreturninferred() const; void isreturninferred(bool v); bool isscopeinferred() const; void isscopeinferred(bool v); bool islive() const; void islive(bool v); bool incomplete() const; void incomplete(bool v); bool isInOutParam() const; void isInOutParam(bool v); bool isInOutQual() const; void isInOutQual(bool v); bool iswild() const; void accept(Visitor *v) override { v->visit(this); } }; class TypeDelegate final : public TypeNext { public: // .next is a TypeFunction static TypeDelegate *create(TypeFunction *t); const char *kind() override; TypeDelegate *syntaxCopy() override; unsigned alignsize() override; bool isZeroInit(const Loc &loc) override; bool isBoolean() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeTraits final : public Type { Loc loc; /// The expression to resolve as type or symbol. TraitsExp *exp; /// Cached type/symbol after semantic analysis. RootObject *obj; const char *kind() override; TypeTraits *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeMixin final : public Type { Loc loc; Expressions *exps; RootObject *obj; const char *kind() override; TypeMixin *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeQualified : public Type { public: Loc loc; // array of Identifier and TypeInstance, // representing ident.ident!tiargs.ident. ... etc. Objects idents; void accept(Visitor *v) override { v->visit(this); } }; class TypeIdentifier final : public TypeQualified { public: Identifier *ident; Dsymbol *originalSymbol; // The symbol representing this identifier, before alias resolution static TypeIdentifier *create(const Loc &loc, Identifier *ident); const char *kind() override; TypeIdentifier *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; /* Similar to TypeIdentifier, but with a TemplateInstance as the root */ class TypeInstance final : public TypeQualified { public: TemplateInstance *tempinst; const char *kind() override; TypeInstance *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeTypeof final : public TypeQualified { public: Expression *exp; int inuse; const char *kind() override; TypeTypeof *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeReturn final : public TypeQualified { public: const char *kind() override; TypeReturn *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; // Whether alias this dependency is recursive or not. enum AliasThisRec { RECno = 0, // no alias this recursion RECyes = 1, // alias this has recursive dependency RECfwdref = 2, // not yet known RECtypeMask = 3,// mask to read no/yes/fwdref RECtracing = 0x4, // mark in progress of implicitConvTo/deduceWild RECtracingDT = 0x8 // mark in progress of deduceType }; class TypeStruct final : public Type { public: StructDeclaration *sym; AliasThisRec att; d_bool inuse; static TypeStruct *create(StructDeclaration *sym); const char *kind() override; unsigned alignsize() override; TypeStruct *syntaxCopy() override; structalign_t alignment() override; Expression *defaultInitLiteral(const Loc &loc) override; bool isZeroInit(const Loc &loc) override; bool isAssignable() override; bool isBoolean() override; bool needsDestruction() override; bool needsCopyOrPostblit() override; bool needsNested() override; bool hasVoidInitPointers() override; bool hasUnsafeBitpatterns() override; bool hasInvariant() override; MATCH constConv(Type *to) override; unsigned char deduceWild(Type *t, bool isRef) override; void accept(Visitor *v) override { v->visit(this); } }; class TypeEnum final : public Type { public: EnumDeclaration *sym; const char *kind() override; TypeEnum *syntaxCopy() override; unsigned alignsize() override; Type *memType(const Loc &loc); bool isintegral() override; bool isfloating() override; bool isreal() override; bool isimaginary() override; bool iscomplex() override; bool isscalar() override; bool isunsigned() override; bool isBoolean() override; bool isString() override; bool isAssignable() override; bool needsDestruction() override; bool needsCopyOrPostblit() override; bool needsNested() override; MATCH constConv(Type *to) override; bool isZeroInit(const Loc &loc) override; bool hasVoidInitPointers() override; bool hasUnsafeBitpatterns() override; bool hasInvariant() override; Type *nextOf() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeClass final : public Type { public: ClassDeclaration *sym; AliasThisRec att; CPPMANGLE cppmangle; const char *kind() override; TypeClass *syntaxCopy() override; ClassDeclaration *isClassHandle() override; MATCH constConv(Type *to) override; unsigned char deduceWild(Type *t, bool isRef) override; bool isZeroInit(const Loc &loc) override; bool isscope() override; bool isBoolean() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeTuple final : public Type { public: // 'logically immutable' cached global - don't modify (neither pointer nor pointee)! static TypeTuple *empty; Parameters *arguments; // types making up the tuple static TypeTuple *create(Parameters *arguments); static TypeTuple *create(); static TypeTuple *create(Type *t1); static TypeTuple *create(Type *t1, Type *t2); const char *kind() override; TypeTuple *syntaxCopy() override; bool equals(const RootObject * const o) const override; void accept(Visitor *v) override { v->visit(this); } }; class TypeSlice final : public TypeNext { public: Expression *lwr; Expression *upr; const char *kind() override; TypeSlice *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeNull final : public Type { public: const char *kind() override; TypeNull *syntaxCopy() override; bool isBoolean() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeNoreturn final : public Type { public: const char *kind() override; TypeNoreturn *syntaxCopy() override; MATCH constConv(Type* to) override; bool isBoolean() override; unsigned alignsize() override; void accept(Visitor *v) override { v->visit(this); } }; class TypeTag final : public Type { public: TypeTag *syntaxCopy() override; void accept(Visitor *v) override { v->visit(this); } }; /**************************************************************/ namespace dmd { // If the type is a class or struct, returns the symbol for it, else null. AggregateDeclaration *isAggregate(Type *t); bool hasPointers(Type *t); // return the symbol to which type t resolves Dsymbol *toDsymbol(Type *t, Scope *sc); bool equivalent(Type *src, Type *t); Covariant covariant(Type *, Type *, StorageClass * = nullptr, bool = false); bool isBaseOf(Type *tthis, Type *t, int *poffset); Type *trySemantic(Type *type, const Loc &loc, Scope *sc); Type *pointerTo(Type *type); Type *referenceTo(Type *type); Type *merge2(Type *type); Type *sarrayOf(Type *type, dinteger_t dim); Type *arrayOf(Type *type); Type *constOf(Type *type); Type *immutableOf(Type *type); Type *mutableOf(Type *type); Type *sharedOf(Type *type); Type *sharedConstOf(Type *type); Type *unSharedOf(Type *type); Type *wildOf(Type *type); Type *wildConstOf(Type *type); Type *sharedWildOf(Type *type); Type *sharedWildConstOf(Type *type); Type *unqualify(Type *type, unsigned m); Type *toHeadMutable(Type *type); Type *aliasthisOf(Type *type); Type *castMod(Type *type, MOD mod); Type *addMod(Type *type, MOD mod); Type *addStorageClass(Type *type, StorageClass stc); Type *substWildTo(Type *type, unsigned mod); uinteger_t size(Type *type); uinteger_t size(Type *type, const Loc &loc); MATCH implicitConvTo(Type* from, Type* to); } ldc-1.40.0-src/dmd/dimport.d0000644000000000000000000001253114727557031014225 0ustar rootroot/** * A `Dsymbol` representing a renamed import. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dimport.d, _dimport.d) * Documentation: https://dlang.org/phobos/dmd_dimport.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dimport.d */ module dmd.dimport; import dmd.arraytypes; import dmd.dmodule; import dmd.dsymbol; import dmd.errors; import dmd.identifier; import dmd.location; import dmd.visitor; /*********************************************************** */ extern (C++) final class Import : Dsymbol { /* static import aliasId = pkg1.pkg2.id : alias1 = name1, alias2 = name2; */ Identifier[] packages; // array of Identifier's representing packages Identifier id; // module Identifier Identifier aliasId; int isstatic; // !=0 if static import Visibility visibility; // Pairs of alias=name to bind into current namespace Identifiers names; Identifiers aliases; Module mod; Package pkg; // leftmost package/module // corresponding AliasDeclarations for alias=name pairs AliasDeclarations aliasdecls; extern (D) this(const ref Loc loc, Identifier[] packages, Identifier id, Identifier aliasId, int isstatic) { Identifier selectIdent() { // select Dsymbol identifier (bracketed) if (aliasId) { // import [aliasId] = std.stdio; return aliasId; } else if (packages.length > 0) { // import [std].stdio; return packages[0]; } else { // import [id]; return id; } } super(loc, selectIdent()); assert(id); version (none) { import core.stdc.stdio; printf("Import::Import("); foreach (id; packages) { printf("%s.", id.toChars()); } printf("%s)\n", id.toChars()); } this.packages = packages; this.id = id; this.aliasId = aliasId; this.isstatic = isstatic; this.visibility = Visibility.Kind.private_; // default to private } extern (D) void addAlias(Identifier name, Identifier _alias) { if (isstatic) .error(loc, "%s `%s` cannot have an import bind list", kind, toPrettyChars); if (!aliasId) this.ident = null; // make it an anonymous import names.push(name); aliases.push(_alias); } override const(char)* kind() const { return isstatic ? "static import" : "import"; } override Visibility visible() pure nothrow @nogc @safe { return visibility; } // copy only syntax trees override Import syntaxCopy(Dsymbol s) { assert(!s); auto si = new Import(loc, packages, id, aliasId, isstatic); si.comment = comment; for (size_t i = 0; i < names.length; i++) { si.addAlias(names[i], aliases[i]); } return si; } /******************************* * Mark the imported packages as accessible from the current * scope. This access check is necessary when using FQN b/c * we're using a single global package tree. * https://issues.dlang.org/show_bug.cgi?id=313 */ extern (D) void addPackageAccess(ScopeDsymbol scopesym) { //printf("Import::addPackageAccess('%s') %p\n", toPrettyChars(), this); if (packages.length > 0) { // import a.b.c.d; auto p = pkg; // a scopesym.addAccessiblePackage(p, visibility); foreach (id; packages[1 .. $]) // [b, c] { auto sym = p.symtab.lookup(id); // https://issues.dlang.org/show_bug.cgi?id=17991 // An import of truly empty file/package can happen // https://issues.dlang.org/show_bug.cgi?id=20151 // Package in the path conflicts with a module name if (sym is null) break; // https://issues.dlang.org/show_bug.cgi?id=23327 // Package conflicts with symbol of the same name p = sym.isPackage(); if (p is null) break; scopesym.addAccessiblePackage(p, visibility); } } scopesym.addAccessiblePackage(mod, visibility); // d } override Dsymbol toAlias() { if (aliasId) return mod; return this; } override bool overloadInsert(Dsymbol s) { /* Allow multiple imports with the same package base, but disallow * alias collisions * https://issues.dlang.org/show_bug.cgi?id=5412 */ assert(ident && ident == s.ident); if (aliasId) return false; const imp = s.isImport(); return imp && !imp.aliasId; } override inout(Import) isImport() inout { return this; } override void accept(Visitor v) { v.visit(this); } } ldc-1.40.0-src/dmd/astcodegen.d0000644000000000000000000001063214727557031014663 0ustar rootroot/** * Defines AST nodes for the code generation stage. * * Documentation: https://dlang.org/phobos/dmd_astcodegen.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/astcodegen.d */ module dmd.astcodegen; struct ASTCodegen { public import dmd.aggregate; public import dmd.aliasthis; public import dmd.arraytypes; public import dmd.attrib; public import dmd.cond; public import dmd.dclass; public import dmd.declaration; public import dmd.denum; public import dmd.dimport; public import dmd.dmodule; public import dmd.dstruct; public import dmd.dsymbol; public import dmd.dtemplate; public import dmd.dversion; public import dmd.expression; public import dmd.func; public import dmd.hdrgen; public import dmd.init; public import dmd.initsem; public import dmd.mtype; public import dmd.nspace; public import dmd.statement; public import dmd.staticassert; public import dmd.typesem; public import dmd.ctfeexpr; public import dmd.init : Designator; alias initializerToExpression = dmd.initsem.initializerToExpression; alias typeToExpression = dmd.typesem.typeToExpression; alias UserAttributeDeclaration = dmd.attrib.UserAttributeDeclaration; alias Ensure = dmd.func.Ensure; // workaround for bug in older DMD frontends alias ErrorExp = dmd.expression.ErrorExp; alias MODFlags = dmd.mtype.MODFlags; alias Type = dmd.mtype.Type; alias Parameter = dmd.mtype.Parameter; alias Tarray = dmd.mtype.Tarray; alias Taarray = dmd.mtype.Taarray; alias Tbool = dmd.mtype.Tbool; alias Tchar = dmd.mtype.Tchar; alias Tdchar = dmd.mtype.Tdchar; alias Tdelegate = dmd.mtype.Tdelegate; alias Tenum = dmd.mtype.Tenum; alias Terror = dmd.mtype.Terror; alias Tfloat32 = dmd.mtype.Tfloat32; alias Tfloat64 = dmd.mtype.Tfloat64; alias Tfloat80 = dmd.mtype.Tfloat80; alias Tfunction = dmd.mtype.Tfunction; alias Tpointer = dmd.mtype.Tpointer; alias Treference = dmd.mtype.Treference; alias Tident = dmd.mtype.Tident; alias Tint8 = dmd.mtype.Tint8; alias Tint16 = dmd.mtype.Tint16; alias Tint32 = dmd.mtype.Tint32; alias Tint64 = dmd.mtype.Tint64; alias Tsarray = dmd.mtype.Tsarray; alias Tstruct = dmd.mtype.Tstruct; alias Tuns8 = dmd.mtype.Tuns8; alias Tuns16 = dmd.mtype.Tuns16; alias Tuns32 = dmd.mtype.Tuns32; alias Tuns64 = dmd.mtype.Tuns64; alias Tvoid = dmd.mtype.Tvoid; alias Twchar = dmd.mtype.Twchar; alias Tnoreturn = dmd.mtype.Tnoreturn; alias Timaginary32 = dmd.mtype.Timaginary32; alias Timaginary64 = dmd.mtype.Timaginary64; alias Timaginary80 = dmd.mtype.Timaginary80; alias Tcomplex32 = dmd.mtype.Tcomplex32; alias Tcomplex64 = dmd.mtype.Tcomplex64; alias Tcomplex80 = dmd.mtype.Tcomplex80; alias ModToStc = dmd.mtype.ModToStc; alias ParameterList = dmd.mtype.ParameterList; alias VarArg = dmd.mtype.VarArg; alias STC = dmd.declaration.STC; alias Dsymbol = dmd.dsymbol.Dsymbol; alias Dsymbols = dmd.dsymbol.Dsymbols; alias Visibility = dmd.dsymbol.Visibility; alias stcToBuffer = dmd.hdrgen.stcToBuffer; alias linkageToChars = dmd.hdrgen.linkageToChars; alias visibilityToChars = dmd.hdrgen.visibilityToChars; alias isType = dmd.dtemplate.isType; alias isExpression = dmd.dtemplate.isExpression; alias isTuple = dmd.dtemplate.isTuple; alias SearchOpt = dmd.dsymbol.SearchOpt; alias PASS = dmd.dsymbol.PASS; } ldc-1.40.0-src/dmd/res/0000755000000000000000000000000014727557031013171 5ustar rootrootldc-1.40.0-src/dmd/res/default_ddoc_theme.ddoc0000644000000000000000000004306314727557031017631 0ustar rootrootLPAREN = ( RPAREN = ) BACKTICK = ` DOLLAR = $ COMMA = , QUOTE = " LF = $(LF) ESCAPES = //>/ /&/&/ H1 =

$0

H2 =

$0

H3 =

$0

H4 =

$0

H5 =
$0
H6 =
$0
B = $0 I = $0 EM = $0 STRONG = $0 U = $0 P =

$0

DL =
$0
DT =
$0
DD =
$0
TABLE = $0
THEAD = $0 TBODY = $0 TR = $0 TH = $0 TD = $0 TH_ALIGN = $+ TD_ALIGN = $+ OL =
    $0
OL_START =
    $2
UL =
    $0
LI =
  • $0
  • BIG = $0 SMALL = $0 BR =
    HR =
    LINK = $0 LINK2 = $+ LINK_TITLE = $3 SYMBOL_LINK = $(DDOC_PSYMBOL $+) PHOBOS_PATH = https://dlang.org/phobos/ DOC_ROOT_std = $(PHOBOS_PATH) DOC_ROOT_core = $(PHOBOS_PATH) DOC_ROOT_etc = $(PHOBOS_PATH) DOC_ROOT_object = $(PHOBOS_PATH) DOC_EXTENSION = .html IMAGE = $+ IMAGE_TITLE = $3 BLOCKQUOTE =
    $0
    DEPRECATED = $0 RED = $0 BLUE = $0 GREEN = $0 YELLOW = $0 BLACK = $0 WHITE = $0 D_CODE =
    1. $0
    OTHER_CODE =
    1. $+
    D_INLINECODE = $0 DDOC_BACKQUOTED = $(D_INLINECODE $0) D_COMMENT = $0 D_STRING = $0 D_KEYWORD = $0 D_PSYMBOL = $0 D_PARAM = $0 DDOC_BLANKLINE =

    DDOC_COMMENT = DDOC = $(TITLE)

    $(TITLE)

    $(BODY)
    $(LF) DDOC_MODULE_MEMBERS =
    $(DDOC_MEMBERS $0)
    $(LF) DDOC_CLASS_MEMBERS = $(DDOC_MEMBERS $0)$(LF) DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0)$(LF) DDOC_ENUM_MEMBERS = $(DDOC_MEMBERS $0)$(LF) DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0)$(LF) DDOC_MEMBERS =
      $0
    DDOC_MEMBER =
  • $0
  • DDOC_MEMBER_HEADER =
    $0
    DDOC_HEADER_ANCHOR =
    $2
    DDOC_DECL =

    Declaration

    $0

    DDOC_ANCHOR = DDOC_DECL_DD =
    $0
    DDOC_SECTIONS =
    $0
    $(LF) DDOC_SUMMARY =

    $0

    $(LF) DDOC_DESCRIPTION =

    Discussion

    $0

    $(LF) DDOC_EXAMPLES =

    Examples

    $0

    DDOC_RETURNS =

    Return Value

    $0

    $(LF) DDOC_PARAMS =

    Parameters

    $0
    $(LF) DDOC_PARAM_ROW = $0 $(LF) DDOC_PARAM_ID = $0 $(LF) DDOC_PARAM_DESC =

    $0

    DDOC_LICENSE =

    License

    $0

    $(LF) DDOC_AUTHORS =

    Authors

    $0

    $(LF) DDOC_BUGS =

    Bugs

    $0

    $(LF) DDOC_COPYRIGHT = $(LF) DDOC_DATE =

    Date

    $0

    $(LF) DDOC_DEPRECATED =

    Deprecated

    $0

    $(LF) DDOC_HISTORY =

    History

    $0

    $(LF) DDOC_SEE_ALSO =

    See Also

    $0

    $(LF) DDOC_STANDARDS =

    Standards

    $0

    DDOC_THROWS =

    Throws

    $0

    DDOC_VERSION =

    Version

    $0

    DDOC_SECTION =

    $0

    $(LF) DDOC_SECTION_H = $0:$(LF) DDOC_DITTO =
    $0 DDOC_PSYMBOL = $0 DDOC_ENUM_BASETYPE = $0 DDOC_PSUPER_SYMBOL = $0 DDOC_KEYWORD = $0 DDOC_PARAM = $0 DDOC_CONSTRAINT = $(DDOC_CONSTRAINT) if ($0) DDOC_OVERLOAD_SEPARATOR = $0 DDOC_TEMPLATE_PARAM_LIST = $0 DDOC_TEMPLATE_PARAM = $0 DDOC_LINK_AUTODETECT = $(LINK $0) DDOC_AUTO_PSYMBOL = $(DDOC_PSYMBOL $0) DDOC_AUTO_KEYWORD = $(DDOC_KEYWORD $0) DDOC_AUTO_PARAM = $(DDOC_PARAM $0) DDOC_AUTO_PSYMBOL_SUPPRESS = $0 ldc-1.40.0-src/dmd/cppmanglewin.d0000644000000000000000000011461714727557031015243 0ustar rootroot/** * Do mangling for C++ linkage for Digital Mars C++ and Microsoft Visual C++. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: Walter Bright, https://www.digitalmars.com * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/cppmanglewin.d, _cppmanglewin.d) * Documentation: https://dlang.org/phobos/dmd_cppmanglewin.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/cppmanglewin.d */ module dmd.cppmanglewin; import core.stdc.stdio; import dmd.arraytypes; import dmd.astenums; import dmd.cppmangle : isAggregateDtor, isCppOperator, CppOperator; import dmd.dclass; import dmd.declaration; import dmd.denum : isSpecialEnumIdent; import dmd.dstruct; import dmd.dsymbol; import dmd.dtemplate; import dmd.errors; import dmd.errorsink; import dmd.expression; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.common.outbuffer; import dmd.rootobject; import dmd.target; import dmd.tokens; import dmd.typesem; import dmd.visitor; const(char)* toCppMangleMSVC(Dsymbol s) { scope VisualCPPMangler v = new VisualCPPMangler(s.loc, global.errorSink); auto p = v.mangleOf(s); if (v.errors) fatal(); // because this error should be handled in frontend return p; } const(char)* cppTypeInfoMangleMSVC(Dsymbol s) // IN_LLVM: not @safe { //printf("cppTypeInfoMangle(%s)\n", s.toChars()); version (IN_LLVM) { // Return the mangled name of the RTTI Type Descriptor. // Reverse-engineered using a few C++ exception classes. scope VisualCPPMangler v = new VisualCPPMangler(s.loc, global.errorSink); v.buf.writestring("\1??_R0?AV"); v.mangleIdent(s); v.buf.writestring("@8"); return v.buf.extractChars(); } else { assert(0); } } private extern (C++) final class VisualCPPMangler : Visitor { alias visit = Visitor.visit; Identifier[10] saved_idents; Type[10] saved_types; Loc loc; /// location for use in error messages ErrorSink eSink; bool isNotTopType; /** When mangling one argument, we can call visit several times (for base types of arg type) * but must save only arg type: * For example: if we have an int** argument, we should save "int**" but visit will be called for "int**", "int*", "int" * This flag is set up by the visit(NextType, ) function and should be reset when the arg type output is finished. */ bool ignoreConst; /// in some cases we should ignore CV-modifiers. bool escape; /// toplevel const non-pointer types need a '$$C' escape in addition to a cv qualifier. bool mangleReturnType; /// return type shouldn't be saved and substituted in arguments bool errors; /// errors occurred OutBuffer buf; extern (D) this(VisualCPPMangler rvl) scope @safe { saved_idents[] = rvl.saved_idents[]; saved_types[] = rvl.saved_types[]; loc = rvl.loc; } public: extern (D) this(Loc loc, ErrorSink eSink) scope @safe { saved_idents[] = null; saved_types[] = null; this.loc = loc; this.eSink = eSink; } override void visit(Type type) { if (checkImmutableShared(type, loc)) return; eSink.error(loc, "internal compiler error: type `%s` cannot be mapped to C++\n", type.toChars()); errors = true; } override void visit(TypeNull type) { if (checkImmutableShared(type, loc)) return; if (checkTypeSaved(type)) return; buf.writestring("$$T"); isNotTopType = false; ignoreConst = false; } override void visit(TypeNoreturn type) { if (checkImmutableShared(type, loc)) return; if (checkTypeSaved(type)) return; buf.writeByte('X'); // yes, mangle it like `void` isNotTopType = false; ignoreConst = false; } override void visit(TypeBasic type) { //printf("visit(TypeBasic); is_not_top_type = %d\n", isNotTopType); if (checkImmutableShared(type, loc)) return; if (type.isConst() && isNotTopType) { if (checkTypeSaved(type)) return; } if ((type.ty == Tbool) && checkTypeSaved(type)) // try to replace long name with number { return; } switch (type.ty) { case Tint64: case Tuns64: case Tint128: case Tuns128: version (IN_LLVM) {} else { case Tfloat80: } case Twchar: if (checkTypeSaved(type)) return; break; default: break; } mangleModifier(type); switch (type.ty) { case Tvoid: buf.writeByte('X'); break; case Tint8: buf.writeByte('C'); break; case Tuns8: buf.writeByte('E'); break; case Tint16: buf.writeByte('F'); break; case Tuns16: buf.writeByte('G'); break; case Tint32: buf.writeByte('H'); break; case Tuns32: buf.writeByte('I'); break; case Tfloat32: buf.writeByte('M'); break; case Tint64: buf.writestring("_J"); break; case Tuns64: buf.writestring("_K"); break; case Tint128: buf.writestring("_L"); break; case Tuns128: buf.writestring("_M"); break; case Tfloat64: buf.writeByte('N'); break; case Tfloat80: version (IN_LLVM) { // unlike DMD, LDC uses 64-bit `real` for Windows/MSVC targets, // corresponding to MSVC++ long double buf.writeByte('O'); // Visual C++ long double } else { buf.writestring("_T"); // Intel long double } break; case Tbool: buf.writestring("_N"); break; case Tchar: buf.writeByte('D'); break; case Twchar: buf.writestring("_S"); // Visual C++ char16_t (since C++11) break; case Tdchar: buf.writestring("_U"); // Visual C++ char32_t (since C++11) break; default: visit(cast(Type)type); return; } isNotTopType = false; ignoreConst = false; } override void visit(TypeVector type) { //printf("visit(TypeVector); is_not_top_type = %d\n", isNotTopType); if (checkTypeSaved(type)) return; mangleModifier(type); buf.writestring("T__m128@@"); // may be better as __m128i or __m128d? isNotTopType = false; ignoreConst = false; } override void visit(TypeSArray type) { // This method can be called only for static variable type mangling. //printf("visit(TypeSArray); is_not_top_type = %d\n", isNotTopType); if (checkTypeSaved(type)) return; // first dimension always mangled as const pointer buf.writeByte('P'); isNotTopType = true; assert(type.next); if (type.next.ty == Tsarray) { mangleArray(cast(TypeSArray)type.next); } else { type.next.accept(this); } } // attention: D int[1][2]* arr mapped to C++ int arr[][2][1]; (because it's more typical situation) // There is not way to map int C++ (*arr)[2][1] to D override void visit(TypePointer type) { //printf("visit(TypePointer); is_not_top_type = %d\n", isNotTopType); if (checkImmutableShared(type, loc)) return; assert(type.next); if (type.next.ty == Tfunction) { const(char)* arg = mangleFunctionType(cast(TypeFunction)type.next); // compute args before checking to save; args should be saved before function type // If we've mangled this function early, previous call is meaningless. // However we should do it before checking to save types of function arguments before function type saving. // If this function was already mangled, types of all it arguments are save too, thus previous can't save // anything if function is saved. if (checkTypeSaved(type)) return; if (type.isConst()) buf.writeByte('Q'); // const else buf.writeByte('P'); // mutable buf.writeByte('6'); // pointer to a function buf.writestring(arg); isNotTopType = false; ignoreConst = false; return; } else if (type.next.ty == Tsarray) { if (checkTypeSaved(type)) return; mangleModifier(type); buf.writeByte('Q'); // const if (target.isLP64) buf.writeByte('E'); isNotTopType = true; mangleArray(cast(TypeSArray)type.next); return; } else { if (checkTypeSaved(type)) return; mangleModifier(type); if (type.isConst()) { buf.writeByte('Q'); // const } else { buf.writeByte('P'); // mutable } if (target.isLP64) buf.writeByte('E'); isNotTopType = true; type.next.accept(this); } } override void visit(TypeReference type) { //printf("visit(TypeReference); type = %s\n", type.toChars()); if (checkTypeSaved(type)) return; if (checkImmutableShared(type, loc)) return; buf.writeByte('A'); // mutable if (target.isLP64) buf.writeByte('E'); isNotTopType = true; assert(type.next); if (type.next.ty == Tsarray) { mangleArray(cast(TypeSArray)type.next); } else { type.next.accept(this); } } override void visit(TypeFunction type) { const(char)* arg = mangleFunctionType(type); buf.writestring("$$A6"); buf.writestring(arg); isNotTopType = false; ignoreConst = false; } override void visit(TypeStruct type) { if (checkTypeSaved(type)) return; //printf("visit(TypeStruct); is_not_top_type = %d\n", isNotTopType); mangleModifier(type); const agg = type.sym.isStructDeclaration(); if (type.sym.isUnionDeclaration()) buf.writeByte('T'); else buf.writeByte(agg.cppmangle == CPPMANGLE.asClass ? 'V' : 'U'); mangleIdent(type.sym); isNotTopType = false; ignoreConst = false; } override void visit(TypeEnum type) { //printf("visit(TypeEnum); is_not_top_type = %d\n", cast(int)(flags & isNotTopType)); const id = type.sym.ident; string c; if (id == Id.__c_long_double) c = "O"; // VC++ long double else if (id == Id.__c_long) c = "J"; // VC++ long else if (id == Id.__c_ulong) c = "K"; // VC++ unsigned long else if (id == Id.__c_longlong) c = "_J"; // VC++ long long else if (id == Id.__c_ulonglong) c = "_K"; // VC++ unsigned long long else if (id == Id.__c_char) c = "D"; // VC++ char else if (id == Id.__c_wchar_t) c = "_W"; if (c.length) { if (checkImmutableShared(type, loc)) return; if (type.isConst() && isNotTopType) { if (checkTypeSaved(type)) return; } mangleModifier(type); buf.writestring(c); } else { if (checkTypeSaved(type)) return; mangleModifier(type); buf.writestring("W4"); mangleIdent(type.sym); } isNotTopType = false; ignoreConst = false; } // D class mangled as pointer to C++ class // const(Object) mangled as Object const* const override void visit(TypeClass type) { //printf("visit(TypeClass); is_not_top_type = %d\n", isNotTopType); if (checkTypeSaved(type)) return; if (isNotTopType) mangleModifier(type); if (type.isConst()) buf.writeByte('Q'); else buf.writeByte('P'); if (target.isLP64) buf.writeByte('E'); isNotTopType = true; mangleModifier(type); const cldecl = type.sym.isClassDeclaration(); buf.writeByte(cldecl.cppmangle == CPPMANGLE.asStruct ? 'U' : 'V'); mangleIdent(type.sym); isNotTopType = false; ignoreConst = false; } const(char)* mangleOf(Dsymbol s) { VarDeclaration vd = s.isVarDeclaration(); FuncDeclaration fd = s.isFuncDeclaration(); if (vd) { mangleVariable(vd); } else if (fd) { mangleFunction(fd); } else { assert(0); } return buf.extractChars(); } private: extern(D): void mangleFunction(FuncDeclaration d) { // ? assert(d); buf.writeByte('?'); mangleIdent(d); if (d.needThis()) // ::= { // Pivate methods always non-virtual in D and it should be mangled as non-virtual in C++ //printf("%s: isVirtualMethod = %d, isVirtual = %d, vtblIndex = %d, interfaceVirtual = %p\n", //d.toChars(), d.isVirtualMethod(), d.isVirtual(), cast(int)d.vtblIndex, d.interfaceVirtual); if ((d.isVirtual() && (d.vtblIndex != -1 || d.interfaceVirtual || overrideInterface(d))) || (d.isDtorDeclaration() && d.parent.isClassDeclaration() && !d.isFinal())) { mangleVisibility(buf, d, "EMU"); } else { mangleVisibility(buf, d, "AIQ"); } if (target.isLP64) buf.writeByte('E'); if (d.type.isConst()) { buf.writeByte('B'); } else { buf.writeByte('A'); } } else if (d.isMember2()) // static function { // ::= mangleVisibility(buf, d, "CKS"); } else // top-level function { // ::= Y buf.writeByte('Y'); } const(char)* args = mangleFunctionType(cast(TypeFunction)d.type, d.needThis(), d.isCtorDeclaration() || isAggregateDtor(d)); buf.writestring(args); } void mangleVariable(VarDeclaration d) { // ::= ? assert(d); // fake mangling for fields to fix https://issues.dlang.org/show_bug.cgi?id=16525 if (!(d.storage_class & (STC.extern_ | STC.field | STC.gshared))) { eSink.error(d.loc, "%s `%s` internal compiler error: C++ static non-__gshared non-extern variables not supported", d.kind, d.toPrettyChars); errors = true; return; } buf.writeByte('?'); mangleIdent(d); assert((d.storage_class & STC.field) || !d.needThis()); Dsymbol parent = d.toParent(); while (parent && parent.isNspace()) { parent = parent.toParent(); } if (parent && parent.isModule()) // static member { buf.writeByte('3'); } else { mangleVisibility(buf, d, "012"); } Type t = d.type; if (checkImmutableShared(t, loc)) return; const cv_mod = t.isConst() ? 'B' : 'A'; if (t.ty != Tpointer) t = t.mutableOf(); t.accept(this); if ((t.ty == Tpointer || t.ty == Treference || t.ty == Tclass) && target.isLP64) { buf.writeByte('E'); } buf.writeByte(cv_mod); } /** * Mangles a template value * * Params: * o = expression that represents the value * tv = template value */ void mangleTemplateValue(RootObject o, TemplateValueParameter tv, Dsymbol sym) { if (!tv.valType.isintegral()) { eSink.error(sym.loc, "%s `%s` internal compiler error: C++ %s template value parameter is not supported", sym.kind, sym.toPrettyChars, tv.valType.toChars()); errors = true; return; } buf.writeByte('$'); buf.writeByte('0'); Expression e = isExpression(o); assert(e); if (tv.valType.isunsigned()) { mangleNumber(buf, e.toUInteger()); } else { sinteger_t val = e.toInteger(); if (val < 0) { val = -val; buf.writeByte('?'); } mangleNumber(buf, val); } } /** * Mangles a template alias parameter * * Params: * o = the alias value, a symbol or expression */ void mangleTemplateAlias(RootObject o, Dsymbol sym) { Dsymbol d = isDsymbol(o); Expression e = isExpression(o); if (d && d.isFuncDeclaration()) { buf.writeByte('$'); buf.writeByte('1'); mangleFunction(d.isFuncDeclaration()); } else if (e && e.op == EXP.variable && (cast(VarExp)e).var.isVarDeclaration()) { buf.writeByte('$'); buf.writeByte('E'); mangleVariable((cast(VarExp)e).var.isVarDeclaration()); } else if (d && d.isTemplateDeclaration() && d.isTemplateDeclaration().onemember) { Dsymbol ds = d.isTemplateDeclaration().onemember; if (ds.isUnionDeclaration()) { buf.writeByte('T'); } else if (ds.isStructDeclaration()) { buf.writeByte('U'); } else if (ds.isClassDeclaration()) { buf.writeByte('V'); } else { eSink.error(sym.loc, "%s `%s` internal compiler error: C++ templates support only integral value, type parameters, alias templates and alias function parameters", sym.kind, sym.toPrettyChars); errors = true; return; } mangleIdent(d); } else { eSink.error(sym.loc, "%s `%s` internal compiler error: `%s` is unsupported parameter for C++ template", sym.kind, sym.toPrettyChars, o.toChars()); errors = true; } } /** * Mangles a template alias parameter * * Params: * o = type */ void mangleTemplateType(RootObject o) { escape = true; Type t = isType(o); assert(t); t.accept(this); escape = false; } /** * Mangles the name of a symbol * * Params: * sym = symbol to mangle * dont_use_back_reference = dont use back referencing */ void mangleName(Dsymbol sym, bool dont_use_back_reference) { //printf("mangleName('%s')\n", sym.toChars()); if (string s = mangleSpecialName(sym)) { buf.writestring(s); return; } void writeName(Identifier name) { assert(name); if (dont_use_back_reference) saveIdent(name); else if (checkAndSaveIdent(name)) return; buf.writestring(name.toString()); buf.writeByte('@'); } auto ti = sym.isTemplateInstance(); if (!ti) { if (auto ag = sym.isAggregateDeclaration()) { if (ag.pMangleOverride) { writeName(ag.pMangleOverride.id); return; } } writeName(sym.ident); return; } auto id = ti.tempdecl.ident; auto symName = id.toString(); int firstTemplateArg = 0; // test for special symbols if (mangleOperator(buf, ti,symName,firstTemplateArg)) return; TemplateInstance actualti = ti; bool needNamespaces; if (auto ag = ti.aliasdecl ? ti.aliasdecl.isAggregateDeclaration() : null) { if (ag.pMangleOverride) { if (ag.pMangleOverride.agg) { if (auto aggti = ag.pMangleOverride.agg.isInstantiated()) actualti = aggti; else { writeName(ag.pMangleOverride.id); if (sym.parent && !sym.parent.needThis()) for (auto ns = ag.pMangleOverride.agg.toAlias().cppnamespace; ns !is null && ns.ident !is null; ns = ns.cppnamespace) writeName(ns.ident); return; } id = ag.pMangleOverride.id; symName = id.toString(); needNamespaces = true; } else { writeName(ag.pMangleOverride.id); for (auto ns = ti.toAlias().cppnamespace; ns !is null && ns.ident !is null; ns = ns.cppnamespace) writeName(ns.ident); return; } } } scope VisualCPPMangler tmp = new VisualCPPMangler(loc, eSink); tmp.buf.writeByte('?'); tmp.buf.writeByte('$'); tmp.buf.writestring(symName); tmp.saved_idents[0] = id; if (symName == id.toString()) tmp.buf.writeByte('@'); bool is_var_arg = false; for (size_t i = firstTemplateArg; i < actualti.tiargs.length; i++) { RootObject o = (*actualti.tiargs)[i]; TemplateParameter tp = null; TemplateValueParameter tv = null; TemplateTupleParameter tt = null; if (!is_var_arg) { TemplateDeclaration td = actualti.tempdecl.isTemplateDeclaration(); assert(td); tp = (*td.parameters)[i]; tv = tp.isTemplateValueParameter(); tt = tp.isTemplateTupleParameter(); } if (tt) { is_var_arg = true; tp = null; } if (tv) { tmp.mangleTemplateValue(o, tv, actualti); } else if (!tp || tp.isTemplateTypeParameter()) { Type t = isType(o); if (t is null) { eSink.error(actualti.loc, "%s `%s` internal compiler error: C++ `%s` template value parameter is not supported", actualti.kind, actualti.toPrettyChars, o.toChars()); errors = true; return; } tmp.mangleTemplateType(o); } else if (tp.isTemplateAliasParameter()) { tmp.mangleTemplateAlias(o, actualti); } else { eSink.error(sym.loc, "%s `%s` internal compiler error: C++ templates support only integral value, type parameters, alias templates and alias function parameters", sym.kind, sym.toPrettyChars); errors = true; return; } } writeName(Identifier.idPool(tmp.buf.extractSlice())); if (needNamespaces && actualti != ti) { for (auto ns = ti.toAlias().cppnamespace; ns !is null && ns.ident !is null; ns = ns.cppnamespace) writeName(ns.ident); } } // returns true if name already saved bool checkAndSaveIdent(Identifier name) @safe { foreach (i, ref id; saved_idents) { if (!id) // no saved same name { id = name; break; } if (id == name) // ok, we've found same name. use index instead of name { buf.writeByte(cast(uint)i + '0'); return true; } } return false; } /** * Issues error and returns true if `type` is shared or immutable * Params: * type = type to check * Returns: * true if type is shared or immutable * false otherwise */ bool checkImmutableShared(Type type, Loc loc) { if (type.isImmutable() || type.isShared()) { eSink.error(loc, "internal compiler error: `shared` or `immutable` types cannot be mapped to C++ (%s)", type.toChars()); errors = true; return true; } return false; } void saveIdent(Identifier name) @safe { foreach (ref id; saved_idents) { if (!id) // no saved same name { id = name; break; } if (id == name) // ok, we've found same name. use index instead of name { return; } } } void mangleIdent(Dsymbol sym, bool dont_use_back_reference = false) { // ::= @ // ::= // ::= // ::= @ // ::= ?$ @