mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-06 07:38:24 +00:00
Merge branch 'devel' into araq-generic-inst-fix
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
# see https://man.sr.ht/builds.sr.ht/compatibility.md#freebsd
|
||||
image: freebsd/latest
|
||||
packages:
|
||||
- databases/sqlite3
|
||||
- devel/boehm-gc-threaded
|
||||
- devel/pcre
|
||||
- devel/sdl20
|
||||
- devel/sfml
|
||||
- www/node
|
||||
- devel/gmake
|
||||
sources:
|
||||
- https://github.com/nim-lang/Nim
|
||||
environment:
|
||||
CC: /usr/bin/clang
|
||||
tasks:
|
||||
- setup: |
|
||||
cd Nim
|
||||
git clone --depth 1 -q https://github.com/nim-lang/csources.git
|
||||
gmake -C csources -j $(sysctl -n hw.ncpu)
|
||||
bin/nim c --skipUserCfg --skipParentCfg koch
|
||||
echo 'export PATH=$HOME/Nim/bin:$PATH' >> $HOME/.buildenv
|
||||
- test: |
|
||||
cd Nim
|
||||
if ! ./koch runCI; then
|
||||
nim c -r tools/ci_testresults.nim
|
||||
exit 1
|
||||
fi
|
||||
triggers:
|
||||
- action: email
|
||||
condition: failure
|
||||
to: Andreas Rumpf <rumpf_a@web.de>
|
||||
@@ -1,34 +0,0 @@
|
||||
## do not edit directly; auto-generated by `nim r tools/ci_generate.nim`
|
||||
|
||||
image: openbsd/latest
|
||||
packages:
|
||||
- gmake
|
||||
- sqlite3
|
||||
- node
|
||||
- boehm-gc
|
||||
- pcre
|
||||
- sfml
|
||||
- sdl2
|
||||
- libffi
|
||||
sources:
|
||||
- https://github.com/nim-lang/Nim
|
||||
environment:
|
||||
NIM_TESTAMENT_BATCH: "0_2"
|
||||
CC: /usr/bin/clang
|
||||
tasks:
|
||||
- setup: |
|
||||
cd Nim
|
||||
git clone --depth 1 -q https://github.com/nim-lang/csources.git
|
||||
gmake -C csources -j $(sysctl -n hw.ncpuonline)
|
||||
bin/nim c koch
|
||||
echo 'export PATH=$HOME/Nim/bin:$PATH' >> $HOME/.buildenv
|
||||
- test: |
|
||||
cd Nim
|
||||
if ! ./koch runCI; then
|
||||
nim c -r tools/ci_testresults.nim
|
||||
exit 1
|
||||
fi
|
||||
triggers:
|
||||
- action: email
|
||||
condition: failure
|
||||
to: Andreas Rumpf <rumpf_a@web.de>
|
||||
@@ -1,34 +0,0 @@
|
||||
## do not edit directly; auto-generated by `nim r tools/ci_generate.nim`
|
||||
|
||||
image: openbsd/latest
|
||||
packages:
|
||||
- gmake
|
||||
- sqlite3
|
||||
- node
|
||||
- boehm-gc
|
||||
- pcre
|
||||
- sfml
|
||||
- sdl2
|
||||
- libffi
|
||||
sources:
|
||||
- https://github.com/nim-lang/Nim
|
||||
environment:
|
||||
NIM_TESTAMENT_BATCH: "1_2"
|
||||
CC: /usr/bin/clang
|
||||
tasks:
|
||||
- setup: |
|
||||
cd Nim
|
||||
git clone --depth 1 -q https://github.com/nim-lang/csources.git
|
||||
gmake -C csources -j $(sysctl -n hw.ncpuonline)
|
||||
bin/nim c koch
|
||||
echo 'export PATH=$HOME/Nim/bin:$PATH' >> $HOME/.buildenv
|
||||
- test: |
|
||||
cd Nim
|
||||
if ! ./koch runCI; then
|
||||
nim c -r tools/ci_testresults.nim
|
||||
exit 1
|
||||
fi
|
||||
triggers:
|
||||
- action: email
|
||||
condition: failure
|
||||
to: Andreas Rumpf <rumpf_a@web.de>
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -4,3 +4,6 @@
|
||||
# duplicated, which is easily identifiable and fixable.
|
||||
/changelog.md merge=union
|
||||
|
||||
# bug https://github.com/dom96/choosenim/issues/256 for WSL CRLF
|
||||
*.sh text eol=lf
|
||||
/config/build_config.txt text eol=lf
|
||||
|
||||
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Have you found an unexpected behavior? Use this template.
|
||||
title: Think about the title, twice
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Function `echo` outputs the wrong string.
|
||||
|
||||
### Example
|
||||
```nim
|
||||
echo "Hello World!"
|
||||
# This code should be a minimum reproducible example:
|
||||
# try to simplify and minimize as much as possible. If it's a compiler
|
||||
# issue, try to minimize further by removing any imports if possible.
|
||||
```
|
||||
|
||||
### Current Output
|
||||
please check whether the problem still exists in git head before posting,
|
||||
see [rebuilding the compiler](https://nim-lang.github.io/Nim/intern.html#rebuilding-the-compiler).
|
||||
```
|
||||
Hola mundo!
|
||||
```
|
||||
|
||||
### Expected Output
|
||||
```
|
||||
Hello World!
|
||||
```
|
||||
|
||||
### Possible Solution
|
||||
|
||||
* In file xyz there is a call that might be the cause of it.
|
||||
|
||||
### Additional Information
|
||||
If it's a regression, you can help us by identifying which version introduced
|
||||
the bug, see [Bisecting for regressions](https://nim-lang.github.io/Nim/intern.html#bisecting-for-regressions),
|
||||
or at least try known past releases (eg `choosenim 1.2.0`).
|
||||
|
||||
If it's a pre-existing compiler bug, see [Debugging the compiler](https://nim-lang.github.io/Nim/intern.html#debugging-the-compiler)
|
||||
which should give more context on a compiler crash.
|
||||
|
||||
* Issue #abc is related, but different because of ...
|
||||
* This issue is blocking my project xyz
|
||||
|
||||
```
|
||||
$ nim -v
|
||||
Nim Compiler Version 0.1.2
|
||||
# make sure to include the git hash if not using a tagged release
|
||||
```
|
||||
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: "Bug Report"
|
||||
description: "Create a new bug report. Have you found an unexpected behavior? Use this form."
|
||||
title: "Think about the title, twice."
|
||||
labels: ["unconfirmed"]
|
||||
body:
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- **Please provide a minimal code example that reproduces the Bug!** :bug:
|
||||
Reports with a reproducible example and descriptive detailed information will likely receive fixes faster.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Use DETAILED DESCRIPTIVE information about the problem.
|
||||
Here, you go into more details about your Bug report. This section can be a few paragraphs long.
|
||||
placeholder: Bug reports with reproducible code and detailed information will be fixed faster.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: nim-version
|
||||
attributes:
|
||||
label: Nim Version
|
||||
description: Copy and paste the output of `nim -v` on the command line. For development versions, make sure to include the commit hash.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: current-logs
|
||||
attributes:
|
||||
label: Current Output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
placeholder: Bug reports with reproducible code and detailed information will be fixed faster.
|
||||
render: text
|
||||
|
||||
- type: textarea
|
||||
id: expected-logs
|
||||
attributes:
|
||||
label: Expected Output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
placeholder: Bug reports with reproducible code and detailed information will be fixed faster.
|
||||
render: text
|
||||
|
||||
- type: textarea
|
||||
id: possible-solution
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
description: Propose a possible solution.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: extra-info
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Any additional relevant information.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Thanks for your contributions!, your Bug report will receive feedback from the community soon...
|
||||
- Please check whether the problem still exists in the devel branch, see [rebuilding the compiler](https://nim-lang.github.io/Nim/intern.html#rebuilding-the-compiler).
|
||||
- Consider writing a PR targetting devel branch after filing this, see [contributing](https://nim-lang.github.io/Nim/contributing.html).
|
||||
- If it's a pre-existing compiler bug, see [Debugging the compiler](https://nim-lang.github.io/Nim/intern.html#debugging-the-compiler)
|
||||
which should give more context on a compiler crash.
|
||||
- If it's a regression, you can help us by identifying which version introduced the bug,
|
||||
see [Bisecting for regressions](https://nim-lang.github.io/Nim/intern.html#bisecting-for-regressions),
|
||||
or at least try known past releases (e.g. `choosenim 2.0.0`). The Nim repo also supports online bisecting
|
||||
via making a comment, which contains a code block starting by `!nim c`, `!nim js` etc. , see [nimrun-action](https://github.com/juancarlospaco/nimrun-action).
|
||||
- [Please, consider a Donation for the Nim project.](https://nim-lang.org/donate.html)
|
||||
35
.github/ISSUE_TEMPLATE/feature_request.md
vendored
35
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Do you want to suggest a new feature? Use this template.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
Notice there is now a separate repository for the detailed RFCs and proposals:
|
||||
https://github.com/nim-lang/RFCs
|
||||
|
||||
If you have a simple feature request, you can post it here using this template,
|
||||
but bear in mind that adding new features to the language is currently a low priority.
|
||||
-->
|
||||
|
||||
|
||||
### Summary
|
||||
<!--- Short summary of your proposed feature -->
|
||||
|
||||
|
||||
### Description
|
||||
<!--- Describe your solution, what problem does it fix? -->
|
||||
|
||||
|
||||
### Alternatives
|
||||
<!--- Are there any alternatives you've considered? -->
|
||||
|
||||
|
||||
### Additional Information
|
||||
<!--- For Example:
|
||||
* A link to a project where the issue is relevant.
|
||||
* A link to a related issue or discussion.
|
||||
-->
|
||||
72
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
72
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: "Feature request"
|
||||
description: "Create a new Feature Request. Do you want to suggest a new feature? Use this form."
|
||||
title: "Think about the title, twice."
|
||||
labels: ["unconfirmed"]
|
||||
body:
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Please provide a minimal code example that illustrates the basic idea behind your feature request.
|
||||
Reports with full repro code and descriptive detailed information will likely receive feedback faster.
|
||||
- There is a separate repository for the detailed RFCs and proposals: https://github.com/nim-lang/RFCs.
|
||||
If you have a simple feature request, you can post it here using this form,
|
||||
but bear in mind that adding new features to the language is currently a low priority.
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
description: |
|
||||
Use DETAILED DESCRIPTIVE information about the feature request.
|
||||
Here, you go into more details about your ideas. This section can be a few paragraphs long.
|
||||
placeholder: Short summary of your proposed feature.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe your solution, what problem does it fix?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: Are there any alternatives you've considered?
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: Examples
|
||||
attributes:
|
||||
label: Examples
|
||||
description: Provide examples for your feature request here.
|
||||
placeholder: Example code here.
|
||||
|
||||
- type: textarea
|
||||
id: incompatibility
|
||||
attributes:
|
||||
label: Backwards Compatibility
|
||||
description: If your Feature Request introduces backward-incompatible changes, describe them and propose how to deal with them.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: links
|
||||
attributes:
|
||||
label: Links
|
||||
description: Please copy and paste any relevant links to projects, issues, discussions, technical documentations, code samples, etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Thanks for your contributions!, your ideas will receive feedback from the community soon...
|
||||
- **Remember to :star: Star the Nim project on GitHub!.**
|
||||
- Consider writing a PR targetting devel branch after filing this, see [contributing](https://nim-lang.github.io/Nim/contributing.html).
|
||||
- [Please, consider a Donation for the Nim project.](https://nim-lang.org/donate.html)
|
||||
69
.github/stale.yml
vendored
69
.github/stale.yml
vendored
@@ -1,69 +0,0 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 365
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 30
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- ARC
|
||||
- bounty
|
||||
- Codegen
|
||||
- Crash
|
||||
- Generics
|
||||
- High Priority
|
||||
- Macros
|
||||
- Next release
|
||||
- Showstopper
|
||||
- Static[T]
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity.
|
||||
If you think it is still a valid PR, please rebase it on the latest devel;
|
||||
otherwise it will be closed. Thank you for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 20
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: pulls
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
||||
31
.github/workflows/bisects.yml
vendored
Normal file
31
.github/workflows/bisects.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# See https://github.com/juancarlospaco/nimrun-action/issues/3#issuecomment-1607344901
|
||||
name: issue comments bisects
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
jobs:
|
||||
bisects:
|
||||
if: |
|
||||
github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '!nim ') && github.event.issue.pull_request == null && github.event.comment.author_association != 'NONE'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, windows-latest, macos-latest]
|
||||
name: ${{ matrix.platform }}-bisects
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: 'devel'
|
||||
|
||||
- uses: juancarlospaco/nimrun-action@nim
|
||||
if: |
|
||||
runner.os == 'Linux' && contains(github.event.comment.body, '-d:linux' ) ||
|
||||
runner.os == 'Windows' && contains(github.event.comment.body, '-d:windows') ||
|
||||
runner.os == 'macOS' && contains(github.event.comment.body, '-d:osx' ) ||
|
||||
runner.os == 'Linux' && !contains(github.event.comment.body, '-d:linux') && !contains(github.event.comment.body, '-d:windows') && !contains(github.event.comment.body, '-d:osx')
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
122
.github/workflows/ci.yml.disabled
vendored
122
.github/workflows/ci.yml.disabled
vendored
@@ -1,122 +0,0 @@
|
||||
name: Continous Integration
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04, macos-10.15, windows-2019]
|
||||
cpu: [amd64, i386]
|
||||
cpp: ['false', 'true']
|
||||
pkg: ['false', 'true']
|
||||
exclude:
|
||||
- os: ubuntu-18.04
|
||||
cpp: 'true'
|
||||
- os: ubuntu-18.04
|
||||
pkg: 'true'
|
||||
- os: macos-10.15
|
||||
cpu: i386
|
||||
- os: macos-10.15
|
||||
pkg: 'true'
|
||||
- os: windows-2019
|
||||
cpu: i386
|
||||
- os: windows-2019
|
||||
cpp: 'true'
|
||||
name: '${{ matrix.os }} (${{ matrix.cpu }}, cpp: ${{ matrix.cpp }}, pkg: ${{ matrix.pkg }})'
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
NIM_COMPILE_TO_CPP: ${{ matrix.cpp }}
|
||||
NIM_TEST_PACKAGES: ${{ matrix.pkg }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
- name: 'Checkout csources'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nim-lang/csources
|
||||
path: csources
|
||||
|
||||
- name: 'Install node.js 8.x'
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '8.x'
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
run: |
|
||||
sudo apt-fast update -qq
|
||||
DEBIAN_FRONTEND='noninteractive' \
|
||||
sudo apt-fast install --no-install-recommends -yq \
|
||||
libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \
|
||||
valgrind libc6-dbg
|
||||
- name: 'Install dependencies (Linux i386)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
|
||||
sudo apt-fast update -qq
|
||||
DEBIAN_FRONTEND='noninteractive' \
|
||||
sudo apt-fast install --no-install-recommends --allow-downgrades -yq \
|
||||
g++-multilib gcc-multilib libcurl4-openssl-dev:i386 libgc-dev:i386 \
|
||||
libsdl1.2-dev:i386 libsfml-dev:i386 libglib2.0-dev:i386 \
|
||||
libffi-dev:i386
|
||||
|
||||
cat << EOF > bin/gcc
|
||||
#!/bin/bash
|
||||
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > bin/g++
|
||||
#!/bin/bash
|
||||
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
|
||||
chmod 755 bin/gcc
|
||||
chmod 755 bin/g++
|
||||
- name: 'Install dependencies (macOS)'
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install boehmgc make sfml
|
||||
- name: 'Install dependencies (Windows)'
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir dist
|
||||
curl -L https://nim-lang.org/download/mingw64.7z -o dist/mingw64.7z
|
||||
curl -L https://nim-lang.org/download/dlls.zip -o dist/dlls.zip
|
||||
7z x dist/mingw64.7z -odist
|
||||
7z x dist/dlls.zip -obin
|
||||
echo "${{ github.workspace }}/dist/mingw64/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Add build binaries to PATH'
|
||||
shell: bash
|
||||
run: echo "${{ github.workspace }}/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Build csources'
|
||||
shell: bash
|
||||
run: |
|
||||
ncpu=
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
|
||||
make -C csources -j $ncpu CC=gcc ucpu='${{ matrix.cpu }}'
|
||||
- name: 'Build koch'
|
||||
shell: bash
|
||||
run: nim c koch
|
||||
- name: 'Run CI'
|
||||
shell: bash
|
||||
run: ./koch runCI
|
||||
|
||||
- name: 'Show failed tests'
|
||||
if: failure()
|
||||
shell: bash
|
||||
run: nim c -r tools/ci_testresults.nim
|
||||
115
.github/workflows/ci_bench.yml
vendored
Normal file
115
.github/workflows/ci_bench.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
name: Benchmarks CI
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'devel'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
cpu: [amd64]
|
||||
name: '${{ matrix.os }}'
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60 # refs bug #18178
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js 20.x'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
run: |
|
||||
sudo apt-fast update -qq
|
||||
DEBIAN_FRONTEND='noninteractive' \
|
||||
sudo apt-fast install --no-install-recommends -yq \
|
||||
libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \
|
||||
valgrind libc6-dbg libblas-dev xorg-dev
|
||||
|
||||
- name: 'Add build binaries to PATH'
|
||||
shell: bash
|
||||
run: echo "${{ github.workspace }}/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Build csourcesAny'
|
||||
shell: bash
|
||||
run: . ci/funs.sh && nimBuildCsourcesIfNeeded CC=gcc ucpu='${{ matrix.cpu }}'
|
||||
|
||||
- name: 'Build koch'
|
||||
shell: bash
|
||||
run: nim c koch
|
||||
|
||||
- name: 'Build Nim'
|
||||
shell: bash
|
||||
run: ./koch boot -d:release -d:nimStrictMode --lib:lib
|
||||
|
||||
- name: 'Build Nimble'
|
||||
shell: bash
|
||||
run: ./koch nimble
|
||||
|
||||
- name: 'Action'
|
||||
shell: bash
|
||||
run: nim c -r -d:release ci/action.nim
|
||||
|
||||
- name: 'Checkout minimize'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'nim-lang/ci_bench'
|
||||
path: minimize
|
||||
|
||||
- name: 'Run minimize benchmarks'
|
||||
shell: bash
|
||||
run: ./minimize/minimize ci-bench
|
||||
|
||||
- name: 'Restore minimize cached database'
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: minimize.csv
|
||||
key: minimize-db-key
|
||||
|
||||
- name: 'Update minimize db'
|
||||
shell: bash
|
||||
run: ./minimize/minimize update-db
|
||||
|
||||
- name: 'Save minimize cached database'
|
||||
if: |
|
||||
github.event_name == 'push' && github.ref == 'refs/heads/devel' &&
|
||||
matrix.target == 'linux'
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: minimize.csv
|
||||
key: minimize-db-key
|
||||
|
||||
- name: 'Generate minimize report'
|
||||
shell: bash
|
||||
run: ./minimize/minimize generate-report
|
||||
|
||||
- name: 'Archive minimize report'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: minimize-report
|
||||
path: |
|
||||
minimize/minimize.html
|
||||
minimize/minimize.csv
|
||||
|
||||
# Requires additional permissions, see:
|
||||
# https://github.com/nim-lang/Nim/actions/runs/4778177321/jobs/8494423792?pr=21566
|
||||
# - name: 'Publish HTML report'
|
||||
# uses: rossjrw/pr-preview-action@v1
|
||||
# with:
|
||||
# source-dir: minimize
|
||||
# umbrella-dir: minimize
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract md summary
|
||||
run: |
|
||||
cat minimize/summary.md >> $GITHUB_STEP_SUMMARY
|
||||
115
.github/workflows/ci_docs.yml
vendored
115
.github/workflows/ci_docs.yml
vendored
@@ -2,24 +2,10 @@ name: Nim Docs CI
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'compiler/docgen.nim'
|
||||
- 'compiler/renderverbatim.nim'
|
||||
- 'config/nimdoc.cfg'
|
||||
- 'doc/**.rst'
|
||||
- 'doc/nimdoc.css'
|
||||
- 'lib/**.nim'
|
||||
- 'nimdoc/testproject/expected/testproject.html'
|
||||
- 'tools/dochack/dochack.nim'
|
||||
- 'tools/kochdocs.nim'
|
||||
- '.github/workflows/ci_docs.yml'
|
||||
- 'koch.nim'
|
||||
pull_request:
|
||||
# Run only on changes on these files.
|
||||
paths:
|
||||
- 'compiler/docgen.nim'
|
||||
- 'compiler/renderverbatim.nim'
|
||||
- 'compiler/**.nim'
|
||||
- 'config/nimdoc.cfg'
|
||||
- 'doc/**.rst'
|
||||
- 'doc/**.md'
|
||||
- 'doc/nimdoc.css'
|
||||
- 'lib/**.nim'
|
||||
- 'nimdoc/testproject/expected/testproject.html'
|
||||
@@ -28,28 +14,48 @@ on:
|
||||
- '.github/workflows/ci_docs.yml'
|
||||
- 'koch.nim'
|
||||
|
||||
pull_request:
|
||||
# Run only on changes on these files.
|
||||
paths:
|
||||
- 'compiler/**.nim'
|
||||
- 'config/nimdoc.cfg'
|
||||
- 'doc/**.rst'
|
||||
- 'doc/**.md'
|
||||
- 'doc/nimdoc.css'
|
||||
- 'lib/**.nim'
|
||||
- 'nimdoc/testproject/expected/testproject.html'
|
||||
- 'tools/dochack/dochack.nim'
|
||||
- 'tools/kochdocs.nim'
|
||||
- '.github/workflows/ci_docs.yml'
|
||||
- 'koch.nim'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: |
|
||||
!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[skip ci]')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [linux, windows, osx]
|
||||
include:
|
||||
- target: linux
|
||||
os: ubuntu-18.04
|
||||
os: ubuntu-20.04
|
||||
- target: windows
|
||||
os: windows-2019
|
||||
- target: osx
|
||||
os: macos-10.15
|
||||
os: macos-12
|
||||
|
||||
name: ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60 # refs bug #18178
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install build dependencies (macOS)'
|
||||
if: runner.os == 'macOS'
|
||||
@@ -59,64 +65,33 @@ jobs:
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir dist
|
||||
curl -L https://nim-lang.org/download/mingw64.7z -o dist/mingw64.7z
|
||||
curl -L https://nim-lang.org/download/dlls.zip -o dist/dlls.zip
|
||||
7z x dist/mingw64.7z -odist
|
||||
7z x dist/dlls.zip -obin
|
||||
set -e
|
||||
. ci/funs.sh
|
||||
nimInternalInstallDepsWindows
|
||||
echo "${{ github.workspace }}/dist/mingw64/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Add build binaries to PATH'
|
||||
shell: bash
|
||||
run: echo "${{ github.workspace }}/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Get current csources version'
|
||||
id: csources-version
|
||||
- name: 'System information'
|
||||
shell: bash
|
||||
run: |
|
||||
sha=$(git ls-remote https://github.com/nim-lang/csources master | cut -f 1)
|
||||
echo "::set-output name=sha::$sha"
|
||||
run: . ci/funs.sh && nimCiSystemInfo
|
||||
|
||||
- name: 'Get prebuilt csources from cache'
|
||||
id: csources-cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: bin
|
||||
key: '${{ matrix.os }}-${{ steps.csources-version.outputs.sha }}'
|
||||
|
||||
- name: 'Checkout csources'
|
||||
if: steps.csources-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nim-lang/csources
|
||||
path: csources
|
||||
|
||||
- name: 'Build 1-stage compiler from csources'
|
||||
- name: 'Build csourcesAny (posix)'
|
||||
# this would work on windows and other CI use this on windows,
|
||||
# but we ensure here that `ci/build_autogen.bat` keeps working on windows.
|
||||
if: runner.os != 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
ext=
|
||||
[[ '${{ runner.os }}' == 'Windows' ]] && ext=.exe
|
||||
if [[ ! -x bin/nim-csources$ext ]]; then
|
||||
ncpu=
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
run: . ci/funs.sh && nimBuildCsourcesIfNeeded CC=gcc
|
||||
# was previously using caching via `actions/cache@v1` but this wasn't
|
||||
# used in other CI pipelines and it's unclear the added complexity
|
||||
# was worth the saving; can be revisited if needed.
|
||||
|
||||
make -C csources -j $ncpu CC=gcc
|
||||
cp bin/nim{,-csources}$ext
|
||||
else
|
||||
echo 'Cache hit, using prebuilt csources'
|
||||
cp bin/nim{-csources,}$ext
|
||||
fi
|
||||
- name: 'Build csourcesAny (windows)'
|
||||
if: runner.os == 'Windows'
|
||||
shell: cmd
|
||||
run: ci/build_autogen.bat
|
||||
|
||||
- name: 'Build koch'
|
||||
shell: bash
|
||||
@@ -134,7 +109,7 @@ jobs:
|
||||
if: |
|
||||
github.event_name == 'push' && github.ref == 'refs/heads/devel' &&
|
||||
matrix.target == 'linux'
|
||||
uses: crazy-max/ghaction-github-pages@v1
|
||||
uses: crazy-max/ghaction-github-pages@v4
|
||||
with:
|
||||
build_dir: doc/html
|
||||
env:
|
||||
|
||||
74
.github/workflows/ci_packages.yml
vendored
74
.github/workflows/ci_packages.yml
vendored
@@ -1,34 +1,42 @@
|
||||
name: Packages CI
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'devel'
|
||||
- 'version-2-0'
|
||||
- 'version-1-6'
|
||||
- 'version-1-2'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: |
|
||||
!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[skip ci]')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-18.04, macos-10.15]
|
||||
os: [ubuntu-20.04, macos-12]
|
||||
cpu: [amd64]
|
||||
batch: ["0_3", "1_3", "2_3"] # list of `index_num`
|
||||
batch: ["allowed_failures", "0_3", "1_3", "2_3"] # list of `index_num`
|
||||
name: '${{ matrix.os }} (batch: ${{ matrix.batch }})'
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60 # refs bug #18178
|
||||
env:
|
||||
NIM_TEST_PACKAGES: "1"
|
||||
NIM_TESTAMENT_BATCH: ${{ matrix.batch }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
- name: 'Checkout csources'
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nim-lang/csources
|
||||
path: csources
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js 12.x'
|
||||
uses: actions/setup-node@v1
|
||||
- name: 'Install node.js 20.x'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '12.x'
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
run: |
|
||||
@@ -44,43 +52,23 @@ jobs:
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir dist
|
||||
curl -L https://nim-lang.org/download/mingw64.7z -o dist/mingw64.7z
|
||||
curl -L https://nim-lang.org/download/dlls.zip -o dist/dlls.zip
|
||||
7z x dist/mingw64.7z -odist
|
||||
7z x dist/dlls.zip -obin
|
||||
echo "${{ github.workspace }}/dist/mingw64/bin" >> "${GITHUB_PATH}"
|
||||
set -e
|
||||
. ci/funs.sh
|
||||
nimInternalInstallDepsWindows
|
||||
echo_run echo "${{ github.workspace }}/dist/mingw64/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Add build binaries to PATH'
|
||||
shell: bash
|
||||
run: echo "${{ github.workspace }}/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Build csources'
|
||||
- name: 'System information'
|
||||
shell: bash
|
||||
run: |
|
||||
ncpu=
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
run: . ci/funs.sh && nimCiSystemInfo
|
||||
|
||||
make -C csources -j $ncpu CC=gcc ucpu='${{ matrix.cpu }}'
|
||||
- name: 'Build koch'
|
||||
- name: 'Build csourcesAny'
|
||||
shell: bash
|
||||
run: nim c koch
|
||||
- name: 'Run CI'
|
||||
shell: bash
|
||||
run: ./koch runCI
|
||||
run: . ci/funs.sh && nimBuildCsourcesIfNeeded CC=gcc ucpu='${{ matrix.cpu }}'
|
||||
|
||||
- name: 'Show failed tests'
|
||||
if: failure()
|
||||
- name: 'koch, Run CI'
|
||||
shell: bash
|
||||
run: nim c -r tools/ci_testresults.nim
|
||||
run: . ci/funs.sh && nimInternalBuildKochAndRunCI
|
||||
|
||||
90
.github/workflows/ci_publish.yml
vendored
Normal file
90
.github/workflows/ci_publish.yml
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
name: Tracking orc-booting compiler memory usage
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event.pull_request.merged == true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
cpu: [amd64]
|
||||
name: '${{ matrix.os }}'
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js 20.x'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
run: |
|
||||
sudo apt-fast update -qq
|
||||
DEBIAN_FRONTEND='noninteractive' \
|
||||
sudo apt-fast install --no-install-recommends -yq \
|
||||
libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \
|
||||
valgrind libc6-dbg libblas-dev xorg-dev
|
||||
- name: 'Install dependencies (macOS)'
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install boehmgc make sfml gtk+3
|
||||
- name: 'Install dependencies (Windows)'
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
. ci/funs.sh
|
||||
nimInternalInstallDepsWindows
|
||||
echo_run echo "${{ github.workspace }}/dist/mingw64/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Add build binaries to PATH'
|
||||
shell: bash
|
||||
run: echo "${{ github.workspace }}/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'System information'
|
||||
shell: bash
|
||||
run: . ci/funs.sh && nimCiSystemInfo
|
||||
|
||||
- name: 'Build csourcesAny'
|
||||
shell: bash
|
||||
run: . ci/funs.sh && nimBuildCsourcesIfNeeded CC=gcc ucpu='${{ matrix.cpu }}'
|
||||
|
||||
- name: 'Build koch'
|
||||
shell: bash
|
||||
run: nim c koch
|
||||
|
||||
- name: 'Build Nim'
|
||||
shell: bash
|
||||
run: ./koch boot -d:release -d:nimStrictMode --lib:lib
|
||||
|
||||
- name: 'Action'
|
||||
shell: bash
|
||||
run: nim c -r -d:release ci/action.nim
|
||||
|
||||
- name: 'Comment'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
try {
|
||||
const data = fs.readFileSync('ci/nimcache/results.txt', 'utf8');
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: data
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
25
.github/workflows/stale.yml
vendored
Normal file
25
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# https://github.com/actions/stale#usage
|
||||
name: Stale pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Midnight.
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-pr-stale: 365
|
||||
days-before-pr-close: 30
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
exempt-pr-labels: "ARC,bounty,Codegen,Crash,Generics,High Priority,Macros,Next release,Showstopper,Static[T]"
|
||||
exempt-issue-labels: "Showstopper,Severe,bounty,Compiler Crash,Medium Priority"
|
||||
stale-pr-message: >
|
||||
This pull request is stale because it has been open for 1 year with no activity.
|
||||
Contribute more commits on the pull request and rebase it on the latest devel,
|
||||
or it will be closed in 30 days. Thank you for your contributions.
|
||||
close-pr-message: >
|
||||
This pull request has been marked as stale and closed due to inactivity after 395 days.
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -3,9 +3,9 @@
|
||||
!*.*
|
||||
|
||||
# Cache
|
||||
nimcache/
|
||||
rnimcache/
|
||||
dnimcache/
|
||||
nimcache*/
|
||||
rnimcache*/
|
||||
dnimcache*/
|
||||
|
||||
*.o
|
||||
!/icons/*.o
|
||||
@@ -64,7 +64,11 @@ lib/**/*.html
|
||||
testament.db
|
||||
/tests/**/*.json
|
||||
/tests/**/*.js
|
||||
|
||||
/csources
|
||||
/csources_v1
|
||||
/csources_v2
|
||||
|
||||
/dist/
|
||||
# /lib/fusion # fusion is now unbundled; `git status` should reveal if it's there so users can act on it
|
||||
|
||||
@@ -72,14 +76,18 @@ testament.db
|
||||
.*/
|
||||
~*
|
||||
|
||||
# testament cruft; TODO: generate these in a gitignore'd dir in the first place.
|
||||
# testament cruft; TODO: generate these in a gitignore'd dir (./build) in the first place.
|
||||
testresults/
|
||||
test.txt
|
||||
/test.ini
|
||||
|
||||
tweeter.db
|
||||
tweeter_test.db
|
||||
megatest.nim
|
||||
|
||||
/tests/megatest.nim
|
||||
/tests/ic/*_temp.nim
|
||||
/tests/navigator/*_temp.nim
|
||||
|
||||
|
||||
/outputExpected.txt
|
||||
/outputGotten.txt
|
||||
@@ -102,3 +110,5 @@ htmldocs
|
||||
nimdoc.out.css
|
||||
# except here:
|
||||
!/nimdoc/testproject/expected/*
|
||||
pkgs/
|
||||
/compiler/compiler/
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
image: ubuntu:16.04
|
||||
|
||||
stages:
|
||||
- pre-build
|
||||
- build
|
||||
- deploy
|
||||
- test
|
||||
|
||||
.linux_set_path: &linux_set_path_def
|
||||
before_script:
|
||||
- export PATH=$(pwd)/bin${PATH:+:$PATH}
|
||||
tags:
|
||||
- linux
|
||||
|
||||
.windows_set_path: &win_set_path_def
|
||||
before_script:
|
||||
- set PATH=%CD%\bin;%PATH%
|
||||
tags:
|
||||
- windows
|
||||
|
||||
|
||||
build-windows:
|
||||
stage: build
|
||||
script:
|
||||
- ci\build.bat
|
||||
artifacts:
|
||||
paths:
|
||||
- bin\nim.exe
|
||||
- bin\nimd.exe
|
||||
- compiler\nim.exe
|
||||
- koch.exe
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- windows
|
||||
|
||||
deploy-windows:
|
||||
stage: deploy
|
||||
script:
|
||||
- koch.exe winrelease
|
||||
artifacts:
|
||||
paths:
|
||||
- build/*.exe
|
||||
- build/*.zip
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- windows
|
||||
- fast
|
||||
|
||||
|
||||
|
||||
test-windows:
|
||||
stage: test
|
||||
<<: *win_set_path_def
|
||||
script:
|
||||
- call ci\deps.bat
|
||||
- nim c testament\tester
|
||||
- testament\tester.exe all
|
||||
tags:
|
||||
- windows
|
||||
- fast
|
||||
52
.travis.yml
52
.travis.yml
@@ -1,52 +0,0 @@
|
||||
sudo: false
|
||||
language: c
|
||||
|
||||
dist: xenial
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
env:
|
||||
- NIM_COMPILE_TO_CPP=false
|
||||
- CPU=amd64
|
||||
|
||||
addons:
|
||||
apt:
|
||||
# update the list above if more deps are introduced
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libsdl1.2-dev
|
||||
- libgc-dev
|
||||
- libsfml-dev
|
||||
- libc6-dbg
|
||||
- valgrind
|
||||
|
||||
before_script:
|
||||
- git clone --depth 1 https://github.com/nim-lang/csources.git
|
||||
- export PATH="$PWD/bin${PATH:+:$PATH}"
|
||||
- make -C csources -j 2 LD=$CC ucpu=$CPU
|
||||
|
||||
script:
|
||||
- echo "travis_fold:start:nim_c_koch"
|
||||
- nim c koch
|
||||
- echo "travis_fold:end:nim_c_koch"
|
||||
- echo "travis_fold:start:koch_boot"
|
||||
- ./koch boot
|
||||
- echo "travis_fold:end:koch_boot"
|
||||
- echo "travis_fold:start:koch_doc"
|
||||
- ./koch doc
|
||||
- echo "travis_fold:end:koch_doc"
|
||||
|
||||
before_deploy:
|
||||
# Make https://nim-lang.github.io/Nim work the same as https://nim-lang.github.io/Nim/overview.html
|
||||
- cp -f ./doc/html/overview.html ./doc/html/index.html
|
||||
|
||||
deploy: # https://nim-lang.github.io/Nim
|
||||
provider: pages
|
||||
# local-dir *has* to be a relative path from the repo root.
|
||||
local-dir: "doc/html"
|
||||
skip-cleanup: true
|
||||
github-token: "$GITHUB_OAUTH_TOKEN"
|
||||
keep-history: false
|
||||
on:
|
||||
branch: devel
|
||||
@@ -1,37 +0,0 @@
|
||||
version: '{build}'
|
||||
|
||||
environment:
|
||||
DLLS_URL: https://nim-lang.org/download/dlls.zip
|
||||
DLLS_ARCHIVE: dlls.zip
|
||||
MINGW_DIR: mingw64
|
||||
MINGW_URL: https://nim-lang.org/download/mingw64.7z
|
||||
MINGW_ARCHIVE: mingw64.7z
|
||||
|
||||
matrix:
|
||||
- NIM_TEST_PACKAGES: false
|
||||
- NIM_TEST_PACKAGES: true
|
||||
|
||||
cache:
|
||||
- '%MINGW_ARCHIVE%'
|
||||
- '%DLLS_ARCHIVE%'
|
||||
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 8 # node 8 or later is required to test js async stuff
|
||||
- IF not exist "%DLLS_ARCHIVE%" appveyor DownloadFile "%DLLS_URL%" -FileName "%DLLS_ARCHIVE%"
|
||||
- 7z x -y "%DLLS_ARCHIVE%" -o"%CD%\BIN"> nul
|
||||
- IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
|
||||
- 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\DIST"> nul
|
||||
- SET PATH=%CD%\DIST\%MINGW_DIR%\BIN;%CD%\BIN;%PATH%
|
||||
- git clone --depth 1 https://github.com/nim-lang/csources
|
||||
- cd csources
|
||||
- build64.bat
|
||||
- cd ..
|
||||
|
||||
build_script:
|
||||
- openssl version
|
||||
- openssl version -d
|
||||
- bin\nim c koch
|
||||
- koch runCI
|
||||
|
||||
deploy: off
|
||||
@@ -1,12 +1,17 @@
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
- 'devel'
|
||||
- 'version-*'
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
variables:
|
||||
- name: skipci
|
||||
value: false
|
||||
|
||||
jobs:
|
||||
- job: packages
|
||||
|
||||
@@ -15,18 +20,19 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
Linux_amd64:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
vmImage: 'ubuntu-20.04'
|
||||
CPU: amd64
|
||||
# Linux_i386:
|
||||
# # bug #17325: fails on 'ubuntu-16.04' because it now errors with:
|
||||
# # g++-multilib : Depends: gcc-multilib (>= 4:5.3.1-1ubuntu1) but it is not going to be installed
|
||||
# vmImage: 'ubuntu-18.04'
|
||||
# CPU: i386
|
||||
# regularly breaks, refs bug #17325
|
||||
# Linux_i386:
|
||||
# # on 'ubuntu-16.04' (not supported anymore anyways) it errored with:
|
||||
# # g++-multilib : Depends: gcc-multilib (>= 4:5.3.1-1ubuntu1) but it is not going to be installed
|
||||
# vmImage: 'ubuntu-18.04'
|
||||
# CPU: i386
|
||||
OSX_amd64:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-12'
|
||||
CPU: amd64
|
||||
OSX_amd64_cpp:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-12'
|
||||
CPU: amd64
|
||||
NIM_COMPILE_TO_CPP: true
|
||||
Windows_amd64_batch0_3:
|
||||
@@ -52,18 +58,24 @@ jobs:
|
||||
steps:
|
||||
- bash: git config --global core.autocrlf false
|
||||
displayName: 'Disable auto conversion to CRLF by git (Windows-only)'
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
fetchDepth: 2 # see D20210329T004830
|
||||
|
||||
- bash: git clone --depth 1 https://github.com/nim-lang/csources
|
||||
displayName: 'Checkout Nim csources'
|
||||
- bash: |
|
||||
set -e
|
||||
. ci/funs.sh
|
||||
if nimIsCiSkip; then
|
||||
echo '##vso[task.setvariable variable=skipci]true'
|
||||
fi
|
||||
displayName: 'Check whether to skip CI'
|
||||
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
displayName: 'Install node.js 12.x'
|
||||
versionSpec: '20.x'
|
||||
displayName: 'Install node.js 20.x'
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'))
|
||||
|
||||
- bash: |
|
||||
set -e
|
||||
@@ -73,7 +85,7 @@ jobs:
|
||||
echo_run sudo apt-fast install --no-install-recommends -yq \
|
||||
libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev valgrind libc6-dbg
|
||||
displayName: 'Install dependencies (amd64 Linux)'
|
||||
condition: and(eq(variables['Agent.OS'], 'Linux'), eq(variables['CPU'], 'amd64'))
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'), eq(variables['Agent.OS'], 'Linux'), eq(variables['CPU'], 'amd64'))
|
||||
|
||||
- bash: |
|
||||
set -e
|
||||
@@ -113,82 +125,44 @@ jobs:
|
||||
echo_run chmod 755 bin/g++
|
||||
|
||||
displayName: 'Install dependencies (i386 Linux)'
|
||||
condition: and(eq(variables['Agent.OS'], 'Linux'), eq(variables['CPU'], 'i386'))
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'), eq(variables['Agent.OS'], 'Linux'), eq(variables['CPU'], 'i386'))
|
||||
|
||||
- bash: brew install boehmgc make sfml
|
||||
displayName: 'Install dependencies (OSX)'
|
||||
condition: eq(variables['Agent.OS'], 'Darwin')
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'), eq(variables['Agent.OS'], 'Darwin'))
|
||||
|
||||
- bash: |
|
||||
set -e
|
||||
. ci/funs.sh
|
||||
echo_run mkdir dist
|
||||
echo_run curl -L https://nim-lang.org/download/mingw64.7z -o dist/mingw64.7z
|
||||
echo_run curl -L https://nim-lang.org/download/dlls.zip -o dist/dlls.zip
|
||||
echo_run 7z x dist/mingw64.7z -odist
|
||||
echo_run 7z x dist/dlls.zip -obin
|
||||
nimInternalInstallDepsWindows
|
||||
echo_run echo '##vso[task.prependpath]$(System.DefaultWorkingDirectory)/dist/mingw64/bin'
|
||||
|
||||
displayName: 'Install dependencies (Windows)'
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- bash: echo '##vso[task.prependpath]$(System.DefaultWorkingDirectory)/bin'
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'))
|
||||
displayName: 'Add build binaries to PATH'
|
||||
|
||||
- bash: |
|
||||
set -e
|
||||
. ci/funs.sh
|
||||
echo_run echo 'PATH:' "$PATH"
|
||||
echo_run echo '##[section]gcc version'
|
||||
echo_run gcc -v
|
||||
echo_run echo '##[section]nodejs version'
|
||||
echo_run node -v
|
||||
echo_run echo '##[section]make version'
|
||||
echo_run make -v
|
||||
- bash: . ci/funs.sh && nimCiSystemInfo
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'))
|
||||
displayName: 'System information'
|
||||
|
||||
- bash: echo '##vso[task.setvariable variable=csources_version]'"$(git -C csources rev-parse HEAD)"
|
||||
displayName: 'Get csources version'
|
||||
- bash: . ci/funs.sh && nimBuildCsourcesIfNeeded CC=gcc ucpu=$(CPU)
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'))
|
||||
displayName: 'Build csourcesAny'
|
||||
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: 'csources | "$(Agent.OS)" | $(CPU) | $(csources_version)'
|
||||
path: csources/bin
|
||||
displayName: 'Restore built csources'
|
||||
# this could be revived if performance justifies it (needs a few updates)
|
||||
# - task: Cache@2
|
||||
# inputs:
|
||||
# key: 'csourcesAny | "$(Agent.OS)" | $(CPU) | $(csources_version)'
|
||||
# path: $(nim_csources)
|
||||
# condition: and(succeeded(), eq(variables['skipci'], 'false'))
|
||||
# displayName: 'Restore built csourcesAny'
|
||||
|
||||
- bash: |
|
||||
set -e
|
||||
. ci/funs.sh
|
||||
ncpu=
|
||||
ext=
|
||||
case '$(Agent.OS)' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'Darwin')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows_NT')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
ext=.exe
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
|
||||
if [[ -x csources/bin/nim$ext ]]; then
|
||||
echo_run echo "Found cached compiler, skipping build"
|
||||
else
|
||||
echo_run make -C csources -j $ncpu CC=gcc ucpu=$(CPU) koch=no
|
||||
fi
|
||||
|
||||
echo_run cp csources/bin/nim$ext bin
|
||||
displayName: 'Build 1-stage compiler from csources'
|
||||
|
||||
- bash: nim c koch
|
||||
displayName: 'Build koch'
|
||||
|
||||
# set result to omit the "bash exited with error code '1'" message
|
||||
- bash: ./koch runCI || echo '##vso[task.complete result=Failed]'
|
||||
displayName: 'Run CI'
|
||||
- bash: . ci/funs.sh && nimInternalBuildKochAndRunCI
|
||||
# would could also show on failure: echo '##vso[task.complete result=Failed]'
|
||||
condition: and(succeeded(), eq(variables['skipci'], 'false'))
|
||||
displayName: 'koch, Run CI'
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
|
||||
@@ -7,14 +7,14 @@ which nim > /dev/null || (echo "nim not in PATH"; exit 1)
|
||||
which gdb > /dev/null || (echo "gdb not in PATH"; exit 1)
|
||||
which readlink > /dev/null || (echo "readlink not in PATH."; exit 1)
|
||||
|
||||
if [[ "$(uname -s)" == "Darwin" || "(uname -s)" == *"BSD" ]]; then
|
||||
if [[ $(uname -s) == Darwin || $(uname -s) == *BSD ]]; then
|
||||
NIM_SYSROOT=$(dirname $(dirname $(readlink -f $(which nim))))
|
||||
else
|
||||
NIM_SYSROOT=$(dirname $(dirname $(readlink -e $(which nim))))
|
||||
fi
|
||||
|
||||
# Find out where the pretty printer Python module is
|
||||
GDB_PYTHON_MODULE_PATH="$NIM_SYSROOT/tools/nim-gdb.py"
|
||||
GDB_PYTHON_MODULE_PATH="$NIM_SYSROOT/tools/debug/nim-gdb.py"
|
||||
|
||||
# Run GDB with the additional arguments that load the pretty printers
|
||||
# Set the environment variable `NIM_GDB` to overwrite the call to a
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
nim-gdb
|
||||
@@ -3,7 +3,7 @@ for %%i in (nim.exe) do (set NIM_BIN=%%~dp$PATH:i)
|
||||
|
||||
for %%i in ("%NIM_BIN%\..\") do (set NIM_ROOT=%%~fi)
|
||||
|
||||
set @GDB_PYTHON_MODULE_PATH=%NIM_ROOT%\tools\nim-gdb.py
|
||||
set @GDB_PYTHON_MODULE_PATH=%NIM_ROOT%\tools\debug\nim-gdb.py
|
||||
set @NIM_GDB=gdb.exe
|
||||
|
||||
@echo source %@GDB_PYTHON_MODULE_PATH%> wingdbcommand.txt
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
@echo off
|
||||
rem build development version of the compiler; can be rerun safely
|
||||
if not exist csources (
|
||||
git clone --depth 1 https://github.com/nim-lang/csources.git
|
||||
rem DO NO EDIT DIRECTLY! auto-generated by `nim r tools/ci_generate.nim`
|
||||
rem Build development version of the compiler; can be rerun safely
|
||||
rem bare bones version of ci/funs.sh adapted for windows.
|
||||
|
||||
rem Read in some common shared variables (shared with other tools),
|
||||
rem see https://stackoverflow.com/questions/3068929/how-to-read-file-contents-into-a-variable-in-a-batch-file
|
||||
for /f "delims== tokens=1,2" %%G in (config/build_config.txt) do set %%G=%%H
|
||||
SET nim_csources=bin\nim_csources_%nim_csourcesHash%.exe
|
||||
echo "building from csources: %nim_csources%"
|
||||
|
||||
if not exist %nim_csourcesDir% (
|
||||
git clone -q --depth 1 -b %nim_csourcesBranch% %nim_csourcesUrl% %nim_csourcesDir%
|
||||
)
|
||||
if not exist bin\nim.exe (
|
||||
cd csources
|
||||
if PROCESSOR_ARCHITECTURE == AMD64 (
|
||||
|
||||
if not exist %nim_csources% (
|
||||
cd %nim_csourcesDir%
|
||||
git checkout %nim_csourcesHash%
|
||||
echo "%PROCESSOR_ARCHITECTURE%"
|
||||
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
|
||||
SET ARCH=64
|
||||
)
|
||||
CALL build.bat
|
||||
cd ..
|
||||
copy /y bin\nim.exe %nim_csources%
|
||||
)
|
||||
bin\nim.exe c --skipUserCfg --skipParentCfg koch
|
||||
koch.exe boot -d:release --skipUserCfg --skipParentCfg
|
||||
koch.exe tools --skipUserCfg --skipParentCfg
|
||||
|
||||
bin\nim.exe c --noNimblePath --skipUserCfg --skipParentCfg --hints:off koch
|
||||
koch boot -d:release --skipUserCfg --skipParentCfg --hints:off
|
||||
koch tools --skipUserCfg --skipParentCfg --hints:off
|
||||
|
||||
53
build_all.sh
53
build_all.sh
@@ -1,52 +1,17 @@
|
||||
#! /bin/sh
|
||||
#!/bin/sh
|
||||
# DO NO EDIT DIRECTLY! auto-generated by `nim r tools/ci_generate.nim`
|
||||
|
||||
# build development version of the compiler; can be rerun safely.
|
||||
# arguments can be passed, e.g. `--os freebsd`
|
||||
# arguments can be passed, e.g.:
|
||||
# CC=gcc ucpu=amd64 uos=darwin
|
||||
|
||||
set -u # error on undefined variables
|
||||
set -e # exit on first error
|
||||
|
||||
echo_run(){
|
||||
echo "$*"
|
||||
"$@"
|
||||
}
|
||||
. ci/funs.sh
|
||||
nimBuildCsourcesIfNeeded "$@"
|
||||
|
||||
[ -d csources ] || echo_run git clone -q --depth 1 https://github.com/nim-lang/csources.git
|
||||
|
||||
nim_csources=bin/nim_csources
|
||||
|
||||
build_nim_csources_via_script(){
|
||||
echo_run cd csources
|
||||
echo_run sh build.sh "$@"
|
||||
}
|
||||
|
||||
build_nim_csources(){
|
||||
# avoid changing dir in case of failure
|
||||
(
|
||||
if [ $# -ne 0 ]; then
|
||||
# some args were passed (e.g.: `--cpu i386`), need to call build.sh
|
||||
build_nim_csources_via_script "$@"
|
||||
else
|
||||
# no args, use multiple Make jobs (5X faster on 16 cores: 10s instead of 50s)
|
||||
makeX=make
|
||||
unamestr=$(uname)
|
||||
if [ "$unamestr" = 'FreeBSD' ]; then
|
||||
makeX=gmake
|
||||
fi
|
||||
nCPU=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || 1)
|
||||
which $makeX && echo_run $makeX -C csources -j $((nCPU + 2)) -l $nCPU || build_nim_csources_via_script
|
||||
fi
|
||||
)
|
||||
# keep $nim_csources in case needed to investigate bootstrap issues
|
||||
# without having to rebuild from csources
|
||||
echo_run cp bin/nim $nim_csources
|
||||
}
|
||||
|
||||
[ -f $nim_csources ] || echo_run build_nim_csources $@
|
||||
|
||||
# Note: if fails, may need to `cd csources && git pull`
|
||||
echo_run bin/nim c --skipUserCfg --skipParentCfg koch
|
||||
|
||||
echo_run ./koch boot -d:release --skipUserCfg --skipParentCfg
|
||||
echo_run ./koch tools --skipUserCfg --skipParentCfg # Compile Nimble and other tools.
|
||||
echo_run bin/nim c --noNimblePath --skipUserCfg --skipParentCfg --hints:off koch
|
||||
echo_run ./koch boot -d:release --skipUserCfg --skipParentCfg --hints:off
|
||||
echo_run ./koch tools --skipUserCfg --skipParentCfg --hints:off
|
||||
|
||||
|
||||
363
changelog.md
363
changelog.md
@@ -1,313 +1,110 @@
|
||||
# v1.6.x - yyyy-mm-dd
|
||||
# v2.2.0 - yyyy-mm-dd
|
||||
|
||||
|
||||
## Changes affecting backward compatibility
|
||||
|
||||
- `-d:nimStrictDelete` becomes the default. An index error is produced when the index passed to `system.delete` was out of bounds. Use `-d:nimAuditDelete` to mimic the old behavior for backwards compatibility.
|
||||
- The default user-agent in `std/httpclient` has been changed to `Nim-httpclient/<version>` instead of `Nim httpclient/<version>` which was incorrect according to the HTTP spec.
|
||||
- Methods now support implementations based on a VTable by using `--experimental:vtables`. Methods are then confined to be in the same module where their type has been defined.
|
||||
- With `-d:nimPreviewNonVarDestructor`, non-var destructors become the default.
|
||||
- A bug where tuple unpacking assignment with a longer tuple on the RHS than the LHS was allowed has been fixed, i.e. code like:
|
||||
```nim
|
||||
var a, b: int
|
||||
(a, b) = (1, 2, 3, 4)
|
||||
```
|
||||
will no longer compile.
|
||||
- `internalNew` is removed from system, use `new` instead.
|
||||
|
||||
- `bindMethod` in `std/jsffi` is deprecated, don't use it with closures.
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
- Make custom op in macros.quote work for all statements.
|
||||
[//]: # "Changes:"
|
||||
|
||||
- On Windows the SSL library now checks for valid certificates.
|
||||
It uses the `cacert.pem` file for this purpose which was extracted
|
||||
from `https://curl.se/ca/cacert.pem`. Besides
|
||||
the OpenSSL DLLs (e.g. libssl-1_1-x64.dll, libcrypto-1_1-x64.dll) you
|
||||
now also need to ship `cacert.pem` with your `.exe` file.
|
||||
- Changed `std/osfiles.copyFile` to allow to specify `bufferSize` instead of a hardcoded one.
|
||||
- Changed `std/osfiles.copyFile` to use `POSIX_FADV_SEQUENTIAL` hints for kernel-level aggressive sequential read-aheads.
|
||||
- `std/htmlparser` has been moved to a nimble package, use `nimble` or `atlas` to install it.
|
||||
|
||||
[//]: # "Additions:"
|
||||
|
||||
- Make `{.requiresInit.}` pragma to work for `distinct` types.
|
||||
- Added `newStringUninit` to system, which creates a new string of length `len` like `newString` but with uninitialized content.
|
||||
- Added `setLenUninit` to system, which doesn't initalize
|
||||
slots when enlarging a sequence.
|
||||
- Added `hasDefaultValue` to `std/typetraits` to check if a type has a valid default value.
|
||||
- Added Viewport API for the JavaScript targets in the `dom` module.
|
||||
- Added `toSinglyLinkedRing` and `toDoublyLinkedRing` to `std/lists` to convert from `openArray`s.
|
||||
- ORC: To be enabled via `nimOrcStats` there is a new API called `GC_orcStats` that can be used to query how many
|
||||
objects the cyclic collector did free. If the number is zero that is a strong indicator that you can use `--mm:arc`
|
||||
instead of `--mm:orc`.
|
||||
- A `$` template is provided for `Path` in `std/paths`.
|
||||
|
||||
- Added a macros `enumLen` for returning the number of items in an enum to the
|
||||
`typetraits.nim` module.
|
||||
[//]: # "Deprecations:"
|
||||
|
||||
- `prelude` now works with the JavaScript target.
|
||||
Added `sequtils` import to `prelude`.
|
||||
`prelude` can now be used via `include std/prelude`, but `include prelude` still works.
|
||||
- Deprecates `system.newSeqUninitialized`, which is replaced by `newSeqUninit`.
|
||||
|
||||
- Added `almostEqual` in `math` for comparing two float values using a machine epsilon.
|
||||
|
||||
- Added `clamp` in `math` which allows using a `Slice` to clamp to a value.
|
||||
|
||||
- The JSON module can now handle integer literals and floating point literals of
|
||||
arbitrary length and precision.
|
||||
Numbers that do not fit the underlying `BiggestInt` or `BiggestFloat` fields are
|
||||
kept as string literals and one can use external BigNum libraries to handle these.
|
||||
The `parseFloat` family of functions also has now optional `rawIntegers` and
|
||||
`rawFloats` parameters that can be used to enforce that all integer or float
|
||||
literals remain in the "raw" string form so that client code can easily treat
|
||||
small and large numbers uniformly.
|
||||
|
||||
- Added `BackwardsIndex` overload for `JsonNode`.
|
||||
|
||||
- added `jsonutils.jsonTo` overload with `opt = Joptions()` param.
|
||||
|
||||
- `json.%`,`json.to`, `jsonutils.formJson`,`jsonutils.toJson` now work with `uint|uint64`
|
||||
instead of raising (as in 1.4) or giving wrong results (as in 1.2).
|
||||
|
||||
- Added an overload for the `collect` macro that inferes the container type based
|
||||
on the syntax of the last expression. Works with std seqs, tables and sets.
|
||||
|
||||
- Added `randState` template that exposes the default random number generator.
|
||||
Useful for library authors.
|
||||
|
||||
- Added `std/enumutils` module. Added `genEnumCaseStmt` macro that generates case statement to parse string to enum.
|
||||
Added `items` for enums with holes.
|
||||
Added `symbolName` to return the enum symbol name ignoring the human readable name.
|
||||
|
||||
- Added `typetraits.HoleyEnum` for enums with holes, `OrdinalEnum` for enums without holes.
|
||||
|
||||
- Removed deprecated `iup` module from stdlib, it has already moved to
|
||||
[nimble](https://github.com/nim-lang/iup).
|
||||
|
||||
- various functions in `httpclient` now accept `url` of type `Uri`. Moreover `request` function's
|
||||
`httpMethod` argument of type `string` was deprecated in favor of `HttpMethod` enum type.
|
||||
|
||||
- `nodejs` backend now supports osenv: `getEnv`, `putEnv`, `envPairs`, `delEnv`, `existsEnv`.
|
||||
|
||||
- Added `cmpMem` to `system`.
|
||||
|
||||
- `doAssertRaises` now correctly handles foreign exceptions.
|
||||
|
||||
- Added `asyncdispatch.activeDescriptors` that returns the number of currently
|
||||
active async event handles/file descriptors.
|
||||
|
||||
- `--gc:orc` is now 10% faster than previously for common workloads. If
|
||||
you have trouble with its changed behavior, compile with `-d:nimOldOrc`.
|
||||
|
||||
|
||||
- `os.FileInfo` (returned by `getFileInfo`) now contains `blockSize`,
|
||||
determining preferred I/O block size for this file object.
|
||||
|
||||
- Added a simpler to use `io.readChars` overload.
|
||||
|
||||
- `repr` now doesn't insert trailing newline; previous behavior was very inconsistent,
|
||||
see #16034. Use `-d:nimLegacyReprWithNewline` for previous behavior.
|
||||
|
||||
- Added `**` to jsffi.
|
||||
|
||||
- `writeStackTrace` is available in JS backend now.
|
||||
|
||||
- Added `decodeQuery` to `std/uri`.
|
||||
|
||||
- `strscans.scanf` now supports parsing single characters.
|
||||
|
||||
- `strscans.scanTuple` added which uses `strscans.scanf` internally,
|
||||
returning a tuple which can be unpacked for easier usage of `scanf`.
|
||||
|
||||
- Added `setutils.toSet` that can take any iterable and convert it to a built-in `set`,
|
||||
if the iterable yields a built-in settable type.
|
||||
|
||||
- Added `setutils.fullSet` which returns a full built-in `set` for a valid type.
|
||||
|
||||
- Added `setutils.complement` which returns the complement of a built-in `set`.
|
||||
|
||||
- Added `setutils.[]=`.
|
||||
|
||||
- Added `math.isNaN`.
|
||||
|
||||
- `echo` and `debugEcho` will now raise `IOError` if writing to stdout fails. Previous behavior
|
||||
silently ignored errors. See #16366. Use `-d:nimLegacyEchoNoRaise` for previous behavior.
|
||||
|
||||
- Added `jsbigints` module, arbitrary precision integers for JavaScript target.
|
||||
|
||||
- Added `math.copySign`.
|
||||
|
||||
- Added new operations for singly- and doubly linked lists: `lists.toSinglyLinkedList`
|
||||
and `lists.toDoublyLinkedList` convert from `openArray`s; `lists.copy` implements
|
||||
shallow copying; `lists.add` concatenates two lists - an O(1) variation that consumes
|
||||
its argument, `addMoved`, is also supplied.
|
||||
|
||||
- Added `euclDiv` and `euclMod` to `math`.
|
||||
|
||||
- Added `httpcore.is1xx` and missing HTTP codes.
|
||||
|
||||
- Added `jsconsole.jsAssert` for JavaScript target.
|
||||
|
||||
- Added `posix_utils.osReleaseFile` to get system identification from `os-release` file on Linux and the BSDs.
|
||||
https://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
|
||||
- `math.round` now is rounded "away from zero" in JS backend which is consistent
|
||||
with other backends. See #9125. Use `-d:nimLegacyJsRound` for previous behavior.
|
||||
|
||||
- Added `socketstream` module that wraps sockets in the stream interface
|
||||
|
||||
- Changed the behavior of `uri.decodeQuery` when there are unencoded `=`
|
||||
characters in the decoded values. Prior versions would raise an error. This is
|
||||
no longer the case to comply with the HTML spec and other languages
|
||||
implementations. Old behavior can be obtained with
|
||||
`-d:nimLegacyParseQueryStrict`. `cgi.decodeData` which uses the same
|
||||
underlying code is also updated the same way.
|
||||
|
||||
- Added `sugar.dumpToString` which improves on `sugar.dump`.
|
||||
|
||||
- Added `math.signbit`.
|
||||
|
||||
- Removed the optional `longestMatch` parameter of the `critbits._WithPrefix` iterators (it never worked reliably)
|
||||
|
||||
- In `lists`: renamed `append` to `add` and retained `append` as an alias;
|
||||
added `prepend` and `prependMoved` analogously to `add` and `addMoved`;
|
||||
added `remove` for `SinglyLinkedList`s.
|
||||
|
||||
- Deprecated `any`. See https://github.com/nim-lang/RFCs/issues/281
|
||||
|
||||
- Added `std/sysrand` module to get random numbers from a secure source
|
||||
provided by the operating system.
|
||||
|
||||
- Added optional `options` argument to `copyFile`, `copyFileToDir`, and
|
||||
`copyFileWithPermissions`. By default, on non-Windows OSes, symlinks are
|
||||
followed (copy files symlinks point to); on Windows, `options` argument is
|
||||
ignored and symlinks are skipped.
|
||||
|
||||
- On non-Windows OSes, `copyDir` and `copyDirWithPermissions` copy symlinks as
|
||||
symlinks (instead of skipping them as it was before); on Windows symlinks are
|
||||
skipped.
|
||||
|
||||
- On non-Windows OSes, `moveFile` and `moveDir` move symlinks as symlinks
|
||||
(instead of skipping them sometimes as it was before).
|
||||
|
||||
- Added optional `followSymlinks` argument to `setFilePermissions`.
|
||||
|
||||
- Added `os.isAdmin` to tell whether the caller's process is a member of the
|
||||
Administrators local group (on Windows) or a root (on POSIX).
|
||||
|
||||
- Added `random.initRand()` overload with no argument which uses the current time as a seed.
|
||||
|
||||
- Added experimental `linenoise.readLineStatus` to get line and status (e.g. ctrl-D or ctrl-C).
|
||||
|
||||
- Added `compilesettings.SingleValueSetting.libPath`.
|
||||
|
||||
- `std/wrapnils` doesn't use `experimental:dotOperators` anymore, avoiding
|
||||
issues like https://github.com/nim-lang/Nim/issues/13063 (which affected error messages)
|
||||
for modules importing `std/wrapnils`.
|
||||
Added `??.` macro which returns an `Option`.
|
||||
|
||||
- Added `math.frexp` overload procs. Deprecated `c_frexp`, use `frexp` instead.
|
||||
|
||||
- `parseopt.initOptParser` has been made available and `parseopt` has been
|
||||
added back to `prelude` for all backends. Previously `initOptParser` was
|
||||
unavailable if the `os` module did not have `paramCount` or `paramStr`,
|
||||
but the use of these in `initOptParser` were conditionally to the runtime
|
||||
arguments passed to it, so `initOptParser` has been changed to raise
|
||||
`ValueError` when the real command line is not available. `parseopt` was
|
||||
previously excluded from `prelude` for JS, as it could not be imported.
|
||||
|
||||
- On POSIX systems, the default signal handlers used for Nim programs (it's
|
||||
used for printing the stacktrace on fatal signals) will now re-raise the
|
||||
signal for the OS default handlers to handle.
|
||||
|
||||
This lets the OS perform its default actions, which might include core
|
||||
dumping (on select signals) and notifying the parent process about the cause
|
||||
of termination.
|
||||
|
||||
- Added `system.prepareStrMutation` for better support of low
|
||||
level `moveMem`, `copyMem` operations for Orc's copy-on-write string
|
||||
implementation.
|
||||
|
||||
- `hashes.hash` now supports `object`, but can be overloaded.
|
||||
|
||||
- Added `std/strbasics` for high performance string operations.
|
||||
Added `strip`, `setSlice`, `add(a: var string, b: openArray[char])`.
|
||||
|
||||
- `hashes.hash` now supports `object`, but can be overloaded.
|
||||
|
||||
- Added to `wrapnils` an option-like API via `??.`, `isSome`, `get`.
|
||||
|
||||
- `std/options` changed `$some(3)` to `"some(3)"` instead of `"Some(3)"`
|
||||
and `$none(int)` to `"none(int)"` instead of `"None[int]"`.
|
||||
|
||||
- Added `std/jsfetch` module [Fetch](https://developer.mozilla.org/docs/Web/API/Fetch_API) wrapper for JavaScript target.
|
||||
|
||||
- Added `std/jsheaders` module [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) wrapper for JavaScript target.
|
||||
|
||||
- Added `std/jsformdata` module [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) wrapper for JavaScript target.
|
||||
|
||||
- `system.addEscapedChar` now renders `\r` as `\r` instead of `\c`, to be compatible
|
||||
with most other languages.
|
||||
|
||||
- Removed support for named procs in `sugar.=>`.
|
||||
|
||||
- Added `jscore.debugger` to [call any available debugging functionality, such as breakpoints.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger).
|
||||
|
||||
|
||||
- Added `std/channels`.
|
||||
|
||||
- Added `htmlgen.portal` for [making "SPA style" pages using HTML only](https://web.dev/hands-on-portals).
|
||||
|
||||
- Added `ZZZ` and `ZZZZ` patterns to `times.nim` `DateTime` parsing, to match time
|
||||
zone offsets without colons, e.g. `UTC+7 -> +0700`.
|
||||
|
||||
- In `std/os`, `getHomeDir`, `expandTilde`, `getTempDir`, `getConfigDir` now do not include trailing `DirSep`,
|
||||
unless `-d:nimLegacyHomeDir` is specified (for a transition period).
|
||||
|
||||
- Added `jsconsole.dir`, `jsconsole.dirxml`, `jsconsole.timeStamp`.
|
||||
[//]: # "Removals:"
|
||||
|
||||
|
||||
## Language changes
|
||||
|
||||
- `nimscript` now handles `except Exception as e`.
|
||||
- `noInit` can be used in types and fields to disable member initializers in the C++ backend.
|
||||
- C++ custom constructors initializers see https://nim-lang.org/docs/manual_experimental.htm#constructor-initializer
|
||||
- `member` can be used to attach a procedure to a C++ type.
|
||||
- C++ `constructor` now reuses `result` instead creating `this`.
|
||||
|
||||
- The `cstring` doesn't support `[]=` operator in JS backend.
|
||||
- Tuple unpacking changes:
|
||||
- Tuple unpacking assignment now supports using underscores to discard values.
|
||||
```nim
|
||||
var a, c: int
|
||||
(a, _, c) = (1, 2, 3)
|
||||
```
|
||||
- Tuple unpacking variable declarations now support type annotations, but
|
||||
only for the entire tuple.
|
||||
```nim
|
||||
let (a, b): (int, int) = (1, 2)
|
||||
let (a, (b, c)): (byte, (float, cstring)) = (1, (2, "abc"))
|
||||
```
|
||||
|
||||
- nil dereference is not allowed at compile time. `cast[ptr int](nil)[]` is rejected at compile time.
|
||||
- An experimental option `genericsOpenSym` has been added to allow captured
|
||||
symbols in generic routine bodies to be replaced by symbols injected locally
|
||||
by templates/macros at instantiation time. `bind` may be used to keep the
|
||||
captured symbols over the injected ones regardless of enabling the option.
|
||||
|
||||
- `typetraits.distinctBase` now is identity instead of error for non distinct types.
|
||||
Since this change may affect runtime behavior, the experimental switch
|
||||
`genericsOpenSym` needs to be enabled, and a warning is given in the case
|
||||
where an injected symbol would replace a captured symbol not bound by `bind`
|
||||
and the experimental switch isn't enabled.
|
||||
|
||||
- `os.copyFile` is now 2.5x faster on OSX, by using `copyfile` from `copyfile.h`;
|
||||
use `-d:nimLegacyCopyFile` for OSX < 10.5.
|
||||
```nim
|
||||
const value = "captured"
|
||||
template foo(x: int, body: untyped) =
|
||||
let value {.inject.} = "injected"
|
||||
body
|
||||
|
||||
- The required name of case statement macros for the experimental
|
||||
`caseStmtMacros` feature has changed from `match` to `` `case` ``.
|
||||
proc old[T](): string =
|
||||
foo(123):
|
||||
return value # warning: a new `value` has been injected, use `bind` or turn on `experimental:genericsOpenSym`
|
||||
echo old[int]() # "captured"
|
||||
|
||||
- `typedesc[Foo]` now renders as such instead of `type Foo` in compiler messages.
|
||||
{.experimental: "genericsOpenSym".}
|
||||
|
||||
proc bar[T](): string =
|
||||
foo(123):
|
||||
return value
|
||||
assert bar[int]() == "injected" # previously it would be "captured"
|
||||
|
||||
proc baz[T](): string =
|
||||
bind value
|
||||
foo(123):
|
||||
return value
|
||||
assert baz[int]() == "captured"
|
||||
```
|
||||
|
||||
## Compiler changes
|
||||
|
||||
- Added `--declaredlocs` to show symbol declaration location in messages.
|
||||
|
||||
- Deprecated `TaintedString` and `--taintmode`.
|
||||
|
||||
- Deprecated `--nilseqs` which is now a noop.
|
||||
|
||||
- Added `--spellSuggest` to show spelling suggestions on typos.
|
||||
|
||||
- Source+Edit links now appear on top of every docgen'd page when
|
||||
`nim doc --git.url:url ...` is given.
|
||||
|
||||
- Added `nim --eval:cmd` to evaluate a command directly, see `nim --help`.
|
||||
|
||||
- VM now supports `addr(mystring[ind])` (index + index assignment)
|
||||
|
||||
- Type mismatch errors now show more context, use `-d:nimLegacyTypeMismatch` for previous
|
||||
behavior.
|
||||
|
||||
- Added `--hintAsError` with similar semantics as `--warningAsError`.
|
||||
|
||||
- TLS: OSX now uses native TLS (`--tlsEmulation:off`), TLS now works with importcpp non-POD types,
|
||||
such types must use `.cppNonPod` and `--tlsEmulation:off`should be used.
|
||||
|
||||
- Now array literals(JS backend) uses JS typed arrays when the corresponding js typed array exists, for example `[byte(1), 2, 3]` generates `new Uint8Array([1, 2, 3])`.
|
||||
|
||||
- docgen: rst files can now use single backticks instead of double backticks and correctly render
|
||||
in both rst2html (as before) as well as common tools rendering rst directly (e.g. github), by
|
||||
adding: `default-role:: code` directive inside the rst file, which is now handled by rst2html.
|
||||
|
||||
- Added `-d:nimStrictMode` in CI in several places to ensure code doesn't have certain hints/warnings
|
||||
|
||||
- Added `then`, `catch` to `asyncjs`, for now hidden behind `-d:nimExperimentalAsyncjsThen`.
|
||||
|
||||
- `--newruntime` and `--refchecks` are deprecated.
|
||||
|
||||
- Added `unsafeIsolate` and `extract` to `std/isolation`.
|
||||
|
||||
|
||||
- `--nimcache` using a relative path as the argument in a config file is now relative to the config file instead of the current directory.
|
||||
|
||||
## Tool changes
|
||||
|
||||
- The rst parser now supports markdown table syntax.
|
||||
Known limitations:
|
||||
- cell alignment is not supported, i.e. alignment annotations in a delimiter
|
||||
row (`:---`, `:--:`, `---:`) are ignored,
|
||||
- every table row must start with `|`, e.g. `| cell 1 | cell 2 |`.
|
||||
- koch now allows bootstrapping with `-d:nimHasLibFFI`, replacing the older option of building the compiler directly w/ the `libffi` nimble package in tow.
|
||||
|
||||
- `fusion` is now un-bundled from nim, `./koch fusion` will
|
||||
install it via nimble at a fixed hash.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
at runtime. Use ``ref object`` with inheritance rather than ``object`` with
|
||||
inheritance to prevent this issue.
|
||||
- The ``not nil`` type annotation now has to be enabled explicitly
|
||||
via ``{.experimental: "notnil"}`` as we are still not pleased with how this
|
||||
via ``{.experimental: "notnil".}`` as we are still not pleased with how this
|
||||
feature works with Nim's containers.
|
||||
- The parser now warns about inconsistent spacing around binary operators as
|
||||
these can easily be confused with unary operators. This warning will likely
|
||||
|
||||
@@ -282,7 +282,7 @@
|
||||
- Removed the deprecated `asyncdispatch.newAsyncNativeSocket`.
|
||||
- Removed the deprecated `dom.releaseEvents` and `dom.captureEvents`.
|
||||
|
||||
- Removed `sharedlists.initSharedList`, was deprecated and produces undefined behaviour.
|
||||
- Removed `sharedlist.initSharedList`, was deprecated and produces undefined behaviour.
|
||||
|
||||
- There is a new experimental feature called "strictFuncs" which makes the definition of
|
||||
`.noSideEffect` stricter. [See here](manual_experimental.html#stricts-funcs)
|
||||
|
||||
957
changelogs/changelog_1_6_0.md
Normal file
957
changelogs/changelog_1_6_0.md
Normal file
@@ -0,0 +1,957 @@
|
||||
# v1.6.0 - 2021-10-14
|
||||
|
||||
|
||||
|
||||
# Major new features
|
||||
|
||||
With so many new features, pinpointing the most salient ones is a subjective exercise,
|
||||
but here are a select few:
|
||||
|
||||
|
||||
## `iterable[T]`
|
||||
|
||||
The `iterable[T]` type class was added to match called iterators,
|
||||
which solves a number of long-standing issues related to iterators.
|
||||
Example:
|
||||
|
||||
```nim
|
||||
iterator iota(n: int): int =
|
||||
for i in 0..<n: yield i
|
||||
|
||||
# previously, you'd need `untyped`, which caused other problems such as lack
|
||||
# of type inference, overloading issues, and MCS.
|
||||
template sumOld(a: untyped): untyped = # no type inference possible
|
||||
var result: typeof(block:(for ai in a: ai))
|
||||
for ai in a: result += ai
|
||||
result
|
||||
|
||||
assert sumOld(iota(3)) == 0 + 1 + 2
|
||||
|
||||
# now, you can write:
|
||||
template sum[T](a: iterable[T]): T =
|
||||
# `template sum(a: iterable): auto =` would also be possible
|
||||
var result: T
|
||||
for ai in a: result += ai
|
||||
result
|
||||
|
||||
assert sum(iota(3)) == 0 + 1 + 2 # or `iota(3).sum`
|
||||
```
|
||||
|
||||
In particular iterable arguments can now be used with the method call syntax. For example:
|
||||
```nim
|
||||
import std/[sequtils, os]
|
||||
echo walkFiles("*").toSeq # now works
|
||||
```
|
||||
See PR [#17196](https://github.com/nim-lang/Nim/pull/17196) for additional details.
|
||||
|
||||
|
||||
## Strict effects
|
||||
|
||||
The effect system was refined and there is a new `.effectsOf` annotation that does
|
||||
explicitly what was previously done implicitly. See the
|
||||
[manual](https://nim-lang.github.io/Nim/manual.html#effect-system-effectsof-annotation)
|
||||
for more details.
|
||||
To write code that is portable with older Nim versions, use this idiom:
|
||||
|
||||
```nim
|
||||
when defined(nimHasEffectsOf):
|
||||
{.experimental: "strictEffects".}
|
||||
else:
|
||||
{.pragma: effectsOf.}
|
||||
|
||||
proc mysort(s: seq; cmp: proc(a, b: T): int) {.effectsOf: cmp.}
|
||||
```
|
||||
|
||||
To enable the new effect system, compile with `--experimental:strictEffects`.
|
||||
See also [#18777](https://github.com/nim-lang/Nim/pull/18777) and RFC
|
||||
[#408](https://github.com/nim-lang/RFCs/issues/408).
|
||||
|
||||
|
||||
## Private imports and private field access
|
||||
|
||||
A new import syntax `import foo {.all.}` now allows importing all symbols
|
||||
(public or private) from `foo`.
|
||||
This can be useful for testing purposes or for more flexibility in project organization.
|
||||
|
||||
Example:
|
||||
```nim
|
||||
from system {.all.} as system2 import nil
|
||||
echo system2.ThisIsSystem # ThisIsSystem is private in `system`
|
||||
import os {.all.} # weirdTarget is private in `os`
|
||||
echo weirdTarget # or `os.weirdTarget`
|
||||
```
|
||||
|
||||
Added a new module `std/importutils`, and an API `privateAccess`, which allows access
|
||||
to private fields for an object type in the current scope.
|
||||
|
||||
Example:
|
||||
```nim
|
||||
import times
|
||||
from std/importutils import privateAccess
|
||||
block:
|
||||
let t = now()
|
||||
# echo t.monthdayZero # Error: undeclared field: 'monthdayZero' for type times.DateTime
|
||||
privateAccess(typeof(t)) # enables private access in this scope
|
||||
echo t.monthdayZero # ok
|
||||
```
|
||||
|
||||
See PR [#17706](https://github.com/nim-lang/Nim/pull/17706) for additional details.
|
||||
|
||||
|
||||
## `nim --eval:cmd`
|
||||
|
||||
Added `nim --eval:cmd` to evaluate a command directly, e.g.: `nim --eval:"echo 1"`.
|
||||
It defaults to `e` (nimscript) but can also work with other commands, e.g.:
|
||||
```bash
|
||||
find . | nim r --eval:'import strutils; for a in stdin.lines: echo a.toUpper'
|
||||
```
|
||||
|
||||
```bash
|
||||
# use as a calculator:
|
||||
nim --eval:'echo 3.1 / (1.2+7)'
|
||||
# explore a module's APIs, including private symbols:
|
||||
nim --eval:'import os {.all.}; echo weirdTarget'
|
||||
# use a custom backend:
|
||||
nim r -b:js --eval:"import std/jsbigints; echo 2'big ** 64'big"
|
||||
```
|
||||
|
||||
See PR [#15687](https://github.com/nim-lang/Nim/pull/15687) for more details.
|
||||
|
||||
|
||||
## Round-trip float to string
|
||||
|
||||
`system.addFloat` and `system.$` now can produce string representations of
|
||||
floating point numbers that are minimal in size and possess round-trip and correct
|
||||
rounding guarantees (via the
|
||||
[Dragonbox](https://raw.githubusercontent.com/jk-jeon/dragonbox/master/other_files/Dragonbox.pdf) algorithm).
|
||||
This currently has to be enabled via `-d:nimPreviewFloatRoundtrip`.
|
||||
It is expected that this behavior becomes the new default in upcoming versions,
|
||||
as with other `nimPreviewX` define flags.
|
||||
|
||||
Example:
|
||||
```nim
|
||||
from math import round
|
||||
let a = round(9.779999999999999, 2)
|
||||
assert a == 9.78
|
||||
echo a # with `-d:nimPreviewFloatRoundtrip`: 9.78, like in python3 (instead of 9.779999999999999)
|
||||
```
|
||||
|
||||
|
||||
## New `std/jsbigints` module
|
||||
|
||||
Provides arbitrary precision integers for the JS target. See PR
|
||||
[#16409](https://github.com/nim-lang/Nim/pull/16409).
|
||||
Example:
|
||||
```nim
|
||||
import std/jsbigints
|
||||
assert 2'big ** 65'big == 36893488147419103232'big
|
||||
echo 0xdeadbeef'big shl 4'big # 59774856944n
|
||||
```
|
||||
|
||||
|
||||
## New `std/sysrand` module
|
||||
Cryptographically secure pseudorandom number generator,
|
||||
allows generating random numbers from a secure source provided by the operating system.
|
||||
Example:
|
||||
```nim
|
||||
import std/sysrand
|
||||
assert urandom(1234) != urandom(1234) # unlikely to fail in practice
|
||||
```
|
||||
See PR [#16459](https://github.com/nim-lang/Nim/pull/16459).
|
||||
|
||||
|
||||
## New module: `std/tempfiles`
|
||||
|
||||
Allows creating temporary files and directories, see PR
|
||||
[#17361](https://github.com/nim-lang/Nim/pull/17361) and followups.
|
||||
```nim
|
||||
import std/tempfiles
|
||||
let tmpPath = genTempPath("prefix", "suffix.log", "/tmp/")
|
||||
# tmpPath looks like: /tmp/prefixpmW1P2KLsuffix.log
|
||||
|
||||
let dir = createTempDir("tmpprefix_", "_end")
|
||||
# created dir looks like: getTempDir() / "tmpprefix_YEl9VuVj_end"
|
||||
|
||||
let (cfile, path) = createTempFile("tmpprefix_", "_end.tmp")
|
||||
# path looks like: getTempDir() / "tmpprefix_FDCIRZA0_end.tmp"
|
||||
cfile.write "foo"
|
||||
cfile.setFilePos 0
|
||||
assert readAll(cfile) == "foo"
|
||||
close cfile
|
||||
assert readFile(path) == "foo"
|
||||
```
|
||||
|
||||
|
||||
## User-defined literals
|
||||
|
||||
Custom numeric literals (e.g. `-128'bignum`) are now supported.
|
||||
Additionally, the unary minus in `-1` is now part of the integer literal, i.e.
|
||||
it is now parsed as a single token.
|
||||
This implies that edge cases like `-128'i8` finally work correctly.
|
||||
Example:
|
||||
```nim
|
||||
func `'big`*(num: cstring): JsBigInt {.importjs: "BigInt(#)".}
|
||||
assert 0xffffffffffffffff'big == (1'big shl 64'big) - 1'big
|
||||
```
|
||||
|
||||
|
||||
## Dot-like operators
|
||||
|
||||
With `-d:nimPreviewDotLikeOps`, dot-like operators (operators starting with `.`,
|
||||
but not with `..`) now have the same precedence as `.`, so that `a.?b.c` is now
|
||||
parsed as `(a.?b).c` instead of `a.?(b.c)`.
|
||||
A warning is generated when a dot-like operator is used without `-d:nimPreviewDotLikeOps`.
|
||||
|
||||
An important use case is to enable dynamic fields without affecting the
|
||||
built-in `.` operator, e.g. for `std/jsffi`, `std/json`, `pkg/nimpy`. Example:
|
||||
```nim
|
||||
import std/json
|
||||
template `.?`(a: JsonNode, b: untyped{ident}): JsonNode =
|
||||
a[astToStr(b)]
|
||||
let j = %*{"a1": {"a2": 10}}
|
||||
assert j.?a1.?a2.getInt == 10
|
||||
```
|
||||
|
||||
|
||||
## Block arguments now support optional parameters
|
||||
|
||||
This solves a major pain point for routines accepting block parameters,
|
||||
see PR [#18631](https://github.com/nim-lang/Nim/pull/18631) for details:
|
||||
|
||||
```nim
|
||||
template fn(a = 1, b = 2, body) = discard
|
||||
fn(1, 2): # already works
|
||||
bar
|
||||
fn(a = 1): # now works
|
||||
bar
|
||||
```
|
||||
|
||||
Likewise with multiple block arguments via `do`:
|
||||
```nim
|
||||
template fn(a = 1, b = 2, body1, body2) = discard
|
||||
fn(a = 1): # now works
|
||||
bar1
|
||||
do:
|
||||
bar2
|
||||
```
|
||||
|
||||
|
||||
# Other new features
|
||||
|
||||
## New and deprecated modules
|
||||
|
||||
The following modules were added (they are discussed in the rest of the text):
|
||||
- `std/enumutils`
|
||||
- `std/genasts`
|
||||
- `std/importutils`
|
||||
- `std/jsbigints`
|
||||
- `std/jsfetch`
|
||||
- `std/jsformdata`
|
||||
- `std/jsheaders`
|
||||
- `std/packedsets`
|
||||
- `std/setutils`
|
||||
- `std/socketstreams`
|
||||
- `std/strbasics`
|
||||
- `std/sysrand`
|
||||
- `std/tasks`
|
||||
- `std/tempfiles`
|
||||
- `std/vmutils`
|
||||
|
||||
- Deprecated `std/mersenne`.
|
||||
- Removed deprecated `std/iup` module from stdlib; it has already moved to
|
||||
[nimble](https://github.com/nim-lang/iup).
|
||||
|
||||
|
||||
## New `std/jsfetch` module
|
||||
|
||||
Provides a wrapper for JS Fetch API.
|
||||
Example:
|
||||
```nim
|
||||
# requires -d:nimExperimentalAsyncjsThen
|
||||
import std/[jsfetch, asyncjs, jsconsole, jsffi, sugar]
|
||||
proc fn {.async.} =
|
||||
await fetch("https://api.github.com/users/torvalds".cstring)
|
||||
.then((response: Response) => response.json())
|
||||
.then((json: JsObject) => console.log(json))
|
||||
.catch((err: Error) => console.log("Request Failed", err))
|
||||
discard fn()
|
||||
```
|
||||
|
||||
|
||||
## New `std/tasks` module
|
||||
|
||||
Provides basic primitives for creating parallel programs.
|
||||
Example:
|
||||
```nim
|
||||
import std/tasks
|
||||
var num = 0
|
||||
proc hello(a: int) = num+=a
|
||||
|
||||
let b = toTask hello(13) # arguments must be isolated, see `std/isolation`
|
||||
b.invoke()
|
||||
assert num == 13
|
||||
b.invoke() # can be called again
|
||||
assert num == 26
|
||||
```
|
||||
|
||||
|
||||
## New module: `std/genasts`
|
||||
|
||||
Provides an API `genAst` that avoids the problems inherent with `quote do` and can
|
||||
be used as a replacement.
|
||||
Example showing how this could be used for writing a simplified version of `unittest.check`:
|
||||
```nim
|
||||
import std/[genasts, macros, strutils]
|
||||
macro check2(cond: bool): untyped =
|
||||
assert cond.kind == nnkInfix, "$# not implemented" % $cond.kind
|
||||
result = genAst(cond, s = repr(cond), lhs = cond[1], rhs = cond[2]):
|
||||
# each local symbol we access must be explicitly captured
|
||||
if not cond:
|
||||
doAssert false, "'$#'' failed: lhs: '$#', rhs: '$#'" % [s, $lhs, $rhs]
|
||||
let a = 3
|
||||
check2 a*2 == a+3
|
||||
if false: check2 a*2 < a+1 # would error with: 'a * 2 < a + 1'' failed: lhs: '6', rhs: '4'
|
||||
```
|
||||
|
||||
See PR [#17426](https://github.com/nim-lang/Nim/pull/17426) for details.
|
||||
|
||||
|
||||
## New module: `std/setutils`
|
||||
|
||||
- Added `setutils.toSet` that can take any iterable and convert it to a built-in `set`,
|
||||
if the iterable yields a built-in settable type.
|
||||
- Added `setutils.fullSet` which returns a full built-in `set` for a valid type.
|
||||
- Added `setutils.complement` which returns the complement of a built-in `set`.
|
||||
- Added `setutils.[]=`.
|
||||
|
||||
|
||||
## New module: `std/enumutils`
|
||||
|
||||
- Added `genEnumCaseStmt` macro that generates
|
||||
case statement to parse string to enum.
|
||||
- Added `items` for enums with holes.
|
||||
- Added `symbolName` to return the `enum` symbol name ignoring the human-readable name.
|
||||
- Added `symbolRank` to return the index in which an `enum` member is listed in an enum.
|
||||
|
||||
|
||||
## `system`
|
||||
|
||||
- Added `system.prepareMutation` for better support of low
|
||||
level `moveMem`, `copyMem` operations for `gc:orc`'s copy-on-write string
|
||||
implementation.
|
||||
- `system.addEscapedChar` now renders `\r` as `\r` instead of `\c`, to be compatible
|
||||
with most other languages.
|
||||
- Added `cmpMem` to `system`.
|
||||
- `doAssertRaises` now correctly handles foreign exceptions.
|
||||
- `addInt` now supports unsigned integers.
|
||||
|
||||
Compatibility notes:
|
||||
- `system.delete` had surprising behavior when the index passed to it was out of
|
||||
bounds (it would delete the last entry then). Compile with `-d:nimStrictDelete` so
|
||||
that an index error is produced instead. Be aware however that your code might depend on
|
||||
this quirky behavior so a review process is required on your part before you can
|
||||
use `-d:nimStrictDelete`. To make this review easier, use the `-d:nimAuditDelete`
|
||||
switch, which pretends that `system.delete` is deprecated so that it is easier
|
||||
to see where it was used in your code.
|
||||
`-d:nimStrictDelete` will become the default in upcoming versions.
|
||||
- `cuchar` is now deprecated as it aliased `char` where arguably it should have aliased `uint8`.
|
||||
Please use `char` or `uint8` instead.
|
||||
- `repr` now doesn't insert trailing newlines; the previous behavior was very inconsistent,
|
||||
see [#16034](https://github.com/nim-lang/Nim/pull/16034).
|
||||
Use `-d:nimLegacyReprWithNewline` for the previous behavior.
|
||||
`repr` now also renders ASTs correctly for user defined literals, setters, `do`, etc.
|
||||
- Deprecated `any`. See RFC [#281](https://github.com/nim-lang/RFCs/issues/281).
|
||||
- The unary slice `..b` was deprecated, use `0..b` instead.
|
||||
|
||||
|
||||
## `std/math`
|
||||
|
||||
- Added `almostEqual` for comparing two float values using a machine epsilon.
|
||||
- Added `clamp` which allows using a `Slice` to clamp to a value.
|
||||
- Added `ceilDiv` for integer division that rounds up.
|
||||
- Added `isNaN`.
|
||||
- Added `copySign`.
|
||||
- Added `euclDiv` and `euclMod`.
|
||||
- Added `signbit`.
|
||||
- Added `frexp` overload procs. Deprecated `c_frexp`, use `frexp` instead.
|
||||
|
||||
Compatibility notes:
|
||||
- `math.round` now rounds "away from zero" in the JS backend, which is consistent
|
||||
with other backends. See [#9125](https://github.com/nim-lang/Nim/pull/9125).
|
||||
Use `-d:nimLegacyJsRound` for the previous behavior.
|
||||
|
||||
|
||||
## Random number generators: `std/random`, `std/sysrand`, `std/oids`
|
||||
|
||||
- Added `std/sysrand` module (see details above).
|
||||
- Added `randState` template that exposes the default random number generator.
|
||||
Useful for library authors.
|
||||
- Added `initRand()` overload with no argument which uses the current time as a seed.
|
||||
- `initRand(seed)` now allows `seed == 0`.
|
||||
- Fixed overflow bugs.
|
||||
- Fix `initRand` to avoid random number sequences overlapping, refs
|
||||
[#18744](https://github.com/nim-lang/Nim/pull/18744).
|
||||
- `std/oids` now uses `std/random`.
|
||||
|
||||
Compatibility notes:
|
||||
- Deprecated `std/mersenne`.
|
||||
- `random.initRand(seed)` now produces non-skewed values for the first call to
|
||||
`rand()` after initialization with a small (< 30000) seed.
|
||||
Use `-d:nimLegacyRandomInitRand` to restore previous behavior for a transition
|
||||
time, see PR [#17467](https://github.com/nim-lang/Nim/pull/17467).
|
||||
|
||||
|
||||
## `std/json`, `std/jsonutils`
|
||||
|
||||
- With `-d:nimPreviewJsonutilsHoleyEnum`, `jsonutils` now can serialize/deserialize
|
||||
holey enums as regular enums (via `ord`) instead of as strings.
|
||||
It is expected that this behavior becomes the new default in upcoming versions.
|
||||
`toJson` now serializes `JsonNode` as is via reference (without a deep copy)
|
||||
instead of treating `JsonNode` as a regular ref object,
|
||||
this can be customized via `jsonNodeMode`.
|
||||
- `std/json` and `std/jsonutils` now serialize `NaN`, `Inf`, `-Inf` as strings,
|
||||
so that `%[NaN, -Inf]` is the string `["nan","-inf"]` instead of `[nan,-inf]`
|
||||
which was invalid JSON.
|
||||
- `std/json` can now handle integer literals and floating point literals of
|
||||
arbitrary length and precision.
|
||||
Numbers that do not fit the underlying `BiggestInt` or `BiggestFloat` fields are
|
||||
kept as string literals and one can use external BigNum libraries to handle these.
|
||||
The `parseFloat` family of functions also has now optional `rawIntegers` and
|
||||
`rawFloats` parameters that can be used to enforce that all integer or float
|
||||
literals remain in the "raw" string form so that client code can easily treat
|
||||
small and large numbers uniformly.
|
||||
- Added `BackwardsIndex` overload for `JsonNode`.
|
||||
- `json.%`,`json.to`, `jsonutils.fromJson`,`jsonutils.toJson` now work with `uint|uint64`
|
||||
instead of raising (as in 1.4) or giving wrong results (as in 1.2).
|
||||
- `std/jsonutils` now handles `cstring` (including as Table key), and `set`.
|
||||
- Added `jsonutils.jsonTo` overload with `opt = Joptions()` param.
|
||||
- `jsonutils.toJson` now supports customization via `ToJsonOptions`.
|
||||
- `std/json`, `std/jsonutils` now support round-trip serialization when
|
||||
`-d:nimPreviewFloatRoundtrip` is used.
|
||||
|
||||
|
||||
## `std/typetraits`, `std/compilesettings`
|
||||
|
||||
- `distinctBase` now is identity instead of error for non distinct types.
|
||||
- `distinctBase` now allows controlling whether to be recursive or not.
|
||||
- Added `enumLen` to return the number of elements in an enum.
|
||||
- Added `HoleyEnum` for enums with holes, `OrdinalEnum` for enums without holes.
|
||||
- Added `hasClosure`.
|
||||
- Added `pointerBase` to return `T` for `ref T | ptr T`.
|
||||
- Added `compilesettings.SingleValueSetting.libPath`.
|
||||
|
||||
|
||||
## networking: `std/net`, `std/asyncnet`, `std/htmlgen`, `std/httpclient`, `std/asyncdispatch`, `std/asynchttpserver`, `std/httpcore`
|
||||
|
||||
- Fixed buffer overflow bugs in `std/net`.
|
||||
- Exported `sslHandle` from `std/net` and `std/asyncnet`.
|
||||
- Added `hasDataBuffered` to `std/asyncnet`.
|
||||
- Various functions in `std/httpclient` now accept `url` of type `Uri`.
|
||||
Moreover, the `request` function's `httpMethod` argument of type `string` was
|
||||
deprecated in favor of `HttpMethod` `enum` type; see
|
||||
[#15919](https://github.com/nim-lang/Nim/pull/15919).
|
||||
- Added `asyncdispatch.activeDescriptors` that returns the number of currently
|
||||
active async event handles/file descriptors.
|
||||
- Added `getPort` to `std/asynchttpserver` to resolve OS-assigned `Port(0)`;
|
||||
this is usually recommended instead of hardcoding ports which can lead to
|
||||
"Address already in use" errors.
|
||||
- Fixed premature garbage collection in `std/asyncdispatch`, when a stacktrace
|
||||
override is in place.
|
||||
- Added `httpcore.is1xx` and missing HTTP codes.
|
||||
- Added `htmlgen.portal` for [making "SPA style" pages using HTML only](https://web.dev/hands-on-portals).
|
||||
|
||||
Compatibility notes:
|
||||
- On Windows, the SSL library now checks for valid certificates.
|
||||
For this purpose it uses the `cacert.pem` file, which was extracted
|
||||
from `https://curl.se/ca/cacert.pem`. Besides
|
||||
the OpenSSL DLLs (e.g. `libssl-1_1-x64.dll`, `libcrypto-1_1-x64.dll`) you
|
||||
now also need to ship `cacert.pem` with your `.exe` file.
|
||||
|
||||
|
||||
## `std/hashes`
|
||||
|
||||
- `hashes.hash` can now support `object` and `ref` (can be overloaded in user code),
|
||||
if `-d:nimPreviewHashRef` is used. It is expected that this behavior
|
||||
becomes the new default in upcoming versions.
|
||||
- `hashes.hash(proc|ptr|ref|pointer)` now calls `hash(int)` and honors `-d:nimIntHash1`.
|
||||
`hashes.hash(closure)` has also been improved.
|
||||
|
||||
|
||||
## OS: `std/os`, `std/io`, `std/socketstream`, `std/linenoise`, `std/tempfiles`
|
||||
|
||||
- `os.FileInfo` (returned by `getFileInfo`) now contains `blockSize`,
|
||||
determining preferred I/O block size for this file object.
|
||||
- Added `os.getCacheDir()` to return platform specific cache directory.
|
||||
- Improved `os.getTempDir()`, see PR [#16914](https://github.com/nim-lang/Nim/pull/16914).
|
||||
- Added `os.isAdmin` to tell whether the caller's process is a member of the
|
||||
Administrators local group (on Windows) or a root (on POSIX).
|
||||
- Added optional `options` argument to `copyFile`, `copyFileToDir`, and
|
||||
`copyFileWithPermissions`. By default, on non-Windows OSes, symlinks are
|
||||
followed (copy files symlinks point to); on Windows, `options` argument is
|
||||
ignored and symlinks are skipped.
|
||||
- On non-Windows OSes, `copyDir` and `copyDirWithPermissions` copy symlinks as
|
||||
symlinks (instead of skipping them as it was before); on Windows symlinks are
|
||||
skipped.
|
||||
- On non-Windows OSes, `moveFile` and `moveDir` move symlinks as symlinks
|
||||
(instead of skipping them sometimes as it was before).
|
||||
- Added optional `followSymlinks` argument to `setFilePermissions`.
|
||||
- Added a simpler to use `io.readChars` overload.
|
||||
- Added `socketstream` module that wraps sockets in the stream interface.
|
||||
- Added experimental `linenoise.readLineStatus` to get line and status (e.g. ctrl-D or ctrl-C).
|
||||
|
||||
|
||||
## Environment variable handling
|
||||
|
||||
- Empty environment variable values are now supported across OS's and backends.
|
||||
- Environment variable APIs now work in multithreaded scenarios, by delegating to
|
||||
direct OS calls instead of trying to keep track of the environment.
|
||||
- `putEnv`, `delEnv` now work at compile time.
|
||||
- NodeJS backend now supports osenv: `getEnv`, `putEnv`, `envPairs`, `delEnv`, `existsEnv`.
|
||||
|
||||
Compatibility notes:
|
||||
- `std/os`: `putEnv` now raises if the first argument contains a `=`.
|
||||
|
||||
|
||||
## POSIX
|
||||
|
||||
- On POSIX systems, the default signal handlers used for Nim programs (it's
|
||||
used for printing the stacktrace on fatal signals) will now re-raise the
|
||||
signal for the OS default handlers to handle.
|
||||
This lets the OS perform its default actions, which might include core
|
||||
dumping (on select signals) and notifying the parent process about the cause
|
||||
of termination.
|
||||
- On POSIX systems, we now ignore `SIGPIPE` signals, use `-d:nimLegacySigpipeHandler`
|
||||
for previous behavior.
|
||||
- Added `posix_utils.osReleaseFile` to get system identification from `os-release`
|
||||
file on Linux and the BSDs.
|
||||
([link](https://www.freedesktop.org/software/systemd/man/os-release.html))
|
||||
- Removed undefined behavior for `posix.open`.
|
||||
|
||||
|
||||
## `std/prelude`
|
||||
|
||||
- `std/strformat` is now part of `include std/prelude`.
|
||||
- Added `std/sequtils` import to `std/prelude`.
|
||||
- `std/prelude` now works with the JS target.
|
||||
- `std/prelude` can now be used via `include std/prelude`, but `include prelude` still works.
|
||||
|
||||
|
||||
## String manipulation: `std/strformat`, `std/strbasics`
|
||||
|
||||
- Added support for parenthesized expressions.
|
||||
- Added support for const strings instead of just string literals.
|
||||
- Added `std/strbasics` for high-performance string operations.
|
||||
- Added `strip`, `setSlice`, `add(a: var string, b: openArray[char])`.
|
||||
|
||||
|
||||
## `std/wrapnils`
|
||||
|
||||
- `std/wrapnils` doesn't use `experimental:dotOperators` anymore, avoiding
|
||||
issues like bug [#13063](https://github.com/nim-lang/Nim/issues/13063)
|
||||
(which affected error messages) for modules importing `std/wrapnils`.
|
||||
- Added `??.` macro which returns an `Option`.
|
||||
- `std/wrapnils` can now be used to protect against `FieldDefect` errors in
|
||||
case objects, generates optimal code (no overhead compared to manual
|
||||
if-else branches), and preserves lvalue semantics which allows modifying
|
||||
an expression.
|
||||
|
||||
|
||||
## Containers: `std/algorithm`, `std/lists`, `std/sequtils`, `std/options`, `std/packedsets`
|
||||
|
||||
- Removed the optional `longestMatch` parameter of the `critbits._WithPrefix`
|
||||
iterators (it never worked reliably).
|
||||
- Added `algorithm.merge`.
|
||||
- In `std/lists`: renamed `append` to `add` and retained `append` as an alias;
|
||||
added `prepend` and `prependMoved` analogously to `add` and `addMoved`;
|
||||
added `remove` for `SinglyLinkedList`s.
|
||||
- Added new operations for singly- and doubly linked lists: `lists.toSinglyLinkedList`
|
||||
and `lists.toDoublyLinkedList` convert from `openArray`s; `lists.copy` implements
|
||||
shallow copying; `lists.add` concatenates two lists - an O(1) variation that consumes
|
||||
its argument, `addMoved`, is also supplied.
|
||||
See PRs [#16362](https://github.com/nim-lang/Nim/pull/16362),
|
||||
[#16536](https://github.com/nim-lang/Nim/pull/16536).
|
||||
- New module: `std/packedsets`.
|
||||
Generalizes `std/intsets`, see PR [#15564](https://github.com/nim-lang/Nim/pull/15564).
|
||||
|
||||
Compatibility notes:
|
||||
- Deprecated `sequtils.delete` and added an overload taking a `Slice` that raises
|
||||
a defect if the slice is out of bounds, likewise with `strutils.delete`.
|
||||
- Deprecated `proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T]`
|
||||
in `std/algorithm`.
|
||||
- `std/options` changed `$some(3)` to `"some(3)"` instead of `"Some(3)"`
|
||||
and `$none(int)` to `"none(int)"` instead of `"None[int]"`.
|
||||
|
||||
|
||||
## `std/times`
|
||||
|
||||
- Added `ZZZ` and `ZZZZ` patterns to `times.nim` `DateTime` parsing, to match time
|
||||
zone offsets without colons, e.g. `UTC+7 -> +0700`.
|
||||
- Added `dateTime` and deprecated `initDateTime`.
|
||||
|
||||
|
||||
## `std/macros` and AST
|
||||
|
||||
- New module `std/genasts`, see description above.
|
||||
- The required name of case statement macros for the experimental
|
||||
`caseStmtMacros` feature has changed from `match` to `` `case` ``.
|
||||
- Tuple expressions are now parsed consistently as
|
||||
`nnkTupleConstr` node. Will affect macros expecting nodes to be of `nnkPar`.
|
||||
- In `std/macros`, `treeRepr,lispRepr,astGenRepr` now represent SymChoice nodes
|
||||
in a collapsed way.
|
||||
Use `-d:nimLegacyMacrosCollapseSymChoice` to get the previous behavior.
|
||||
- Made custom op in `macros.quote` work for all statements.
|
||||
|
||||
|
||||
## `std/sugar`
|
||||
|
||||
- Added `sugar.dumpToString` which improves on `sugar.dump`.
|
||||
- Added an overload for the `collect` macro that infers the container type based
|
||||
on the syntax of the last expression. Works with std seqs, tables and sets.
|
||||
|
||||
Compatibility notes:
|
||||
- Removed support for named procs in `sugar.=>`.
|
||||
|
||||
|
||||
## Parsing: `std/parsecfg`, `std/strscans`, `std/uri`
|
||||
|
||||
- Added `sections` iterator in `parsecfg`.
|
||||
- `strscans.scanf` now supports parsing single characters.
|
||||
- Added `strscans.scanTuple` which uses `strscans.scanf` internally,
|
||||
returning a tuple which can be unpacked for easier usage of `scanf`.
|
||||
- Added `decodeQuery` to `std/uri`.
|
||||
- `parseopt.initOptParser` has been made available and `parseopt` has been
|
||||
added back to `std/prelude` for all backends. Previously `initOptParser` was
|
||||
unavailable if the `std/os` module did not have `paramCount` or `paramStr`,
|
||||
but the use of these in `initOptParser` were conditionally to the runtime
|
||||
arguments passed to it, so `initOptParser` has been changed to raise
|
||||
`ValueError` when the real command line is not available. `parseopt` was
|
||||
previously excluded from `std/prelude` for JS, as it could not be imported.
|
||||
|
||||
Compatibility notes:
|
||||
- Changed the behavior of `uri.decodeQuery` when there are unencoded `=`
|
||||
characters in the decoded values. Prior versions would raise an error. This is
|
||||
no longer the case to comply with the HTML spec and other languages'
|
||||
implementations. Old behavior can be obtained with
|
||||
`-d:nimLegacyParseQueryStrict`. `cgi.decodeData` which uses the same
|
||||
underlying code is also updated the same way.
|
||||
|
||||
|
||||
## JS stdlib changes
|
||||
|
||||
- Added `std/jsbigints` module, which provides arbitrary precision integers for the JS target.
|
||||
- Added `setCurrentException` for the JS backend.
|
||||
- `writeStackTrace` is available in the JS backend now.
|
||||
- Added `then`, `catch` to `std/asyncjs` for promise pipelining, for now hidden
|
||||
behind `-d:nimExperimentalAsyncjsThen`.
|
||||
- Added `std/jsfetch` module [Fetch](https://developer.mozilla.org/docs/Web/API/Fetch_API)
|
||||
wrapper for the JS target.
|
||||
- Added `std/jsheaders` module [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers)
|
||||
wrapper for the JS target.
|
||||
- Added `std/jsformdata` module [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
|
||||
wrapper for the JS target.
|
||||
- Added `jscore.debugger` to [call any available debugging functionality, such as breakpoints](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger).
|
||||
- Added `jsconsole.dir`, `jsconsole.dirxml`, `jsconsole.timeStamp`.
|
||||
- Added dollar `$` and `len` for `jsre.RegExp`.
|
||||
- Added `jsconsole.jsAssert` for the JS target.
|
||||
- Added `**` to `std/jsffi`.
|
||||
- Added `copyWithin` [for `seq` and `array` for JS targets](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin).
|
||||
- In `std/dom`, `Interval` is now a `ref object`, same as `Timeout`.
|
||||
Definitions of `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` were updated.
|
||||
- Added `dom.scrollIntoView` proc with options.
|
||||
- Added `dom.setInterval`, `dom.clearInterval` overloads.
|
||||
- Merged `std/dom_extensions` into the `std/dom` module,
|
||||
as it was a module with a single line, see RFC [#413](https://github.com/nim-lang/RFCs/issues/413).
|
||||
- `$` now gives more correct results on the JS backend.
|
||||
|
||||
|
||||
## JS compiler changes
|
||||
|
||||
- `cstring` doesn't support the `[]=` operator anymore in the JS backend.
|
||||
- Array literals now use JS typed arrays when the corresponding JS typed array exists,
|
||||
for example `[byte(1), 2, 3]` generates `new Uint8Array([1, 2, 3])`.
|
||||
|
||||
|
||||
## VM and nimscript backend
|
||||
|
||||
- VM now supports `addr(mystring[ind])` (index + index assignment).
|
||||
- `nimscript` now handles `except Exception as e`.
|
||||
- `nil` dereference is not allowed at compile time. `cast[ptr int](nil)[]` is rejected at compile time.
|
||||
- `static[T]` now works better, refs [#17590](https://github.com/nim-lang/Nim/pull/17590),
|
||||
[#15853](https://github.com/nim-lang/Nim/pull/15853).
|
||||
- `distinct T` conversions now work in VM.
|
||||
- `items(cstring)` now works in VM.
|
||||
- Fix `addr`, `len`, `high` in VM ([#16002](https://github.com/nim-lang/Nim/pull/16002),
|
||||
[#16610](https://github.com/nim-lang/Nim/pull/16610)).
|
||||
- `std/cstrutils` now works in VM.
|
||||
|
||||
|
||||
## OS/platform-specific notes
|
||||
|
||||
- Support for Apple silicon/M1.
|
||||
- Support for 32-bit RISC-V, refs [#16231](https://github.com/nim-lang/Nim/pull/16231).
|
||||
- Support for armv8l, refs [#18901](https://github.com/nim-lang/Nim/pull/18901).
|
||||
- Support for CROSSOS, refs [#18889](https://github.com/nim-lang/Nim/pull/18889).
|
||||
- The allocator for Nintendo Switch, which was nonfunctional because
|
||||
of breaking changes in libnx, was removed, in favor of the new `-d:nimAllocPagesViaMalloc` option.
|
||||
- Allow reading parameters when compiling for Nintendo Switch.
|
||||
- `--nimcache` now correctly works in a cross-compilation setting.
|
||||
- Cross compilation targeting Windows was improved.
|
||||
- This now works from macOS/Linux: `nim r -d:mingw main`
|
||||
|
||||
|
||||
|
||||
## Performance / memory optimizations
|
||||
|
||||
- The comment field in PNode AST was moved to a side channel, reducing overall
|
||||
memory usage during compilation by a factor 1.25x
|
||||
- `std/jsonutils` deserialization is now up to 20x faster.
|
||||
- `os.copyFile` is now 2.5x faster on macOS, by using `copyfile` from `copyfile.h`;
|
||||
use `-d:nimLegacyCopyFile` for macOS < 10.5.
|
||||
- Float to string conversion is now 10x faster thanks to the Dragonbox algorithm,
|
||||
with `-d:nimPreviewFloatRoundtrip`.
|
||||
- `newSeqWith` is 3x faster.
|
||||
- CI now supports batching (making Windows CI 2.3X faster).
|
||||
- Sets now uses the optimized `countSetBits` proc, see PR
|
||||
[#17334](https://github.com/nim-lang/Nim/pull/17334).
|
||||
|
||||
|
||||
## Debugging
|
||||
|
||||
- You can now enable/disable VM tracing in user code via `vmutils.vmTrace`.
|
||||
- `koch tools` now builds `bin/nim_dbg` which allows easy access to a debug version
|
||||
of Nim without recompiling.
|
||||
- Added new module `compiler/debugutils` to help with debugging Nim compiler.
|
||||
- Renamed `-d:nimCompilerStackraceHints` to `-d:nimCompilerStacktraceHints` and
|
||||
used it in more contexts; this flag which works in tandem with `--stackTraceMsgs`
|
||||
to show user code context in compiler stacktraces.
|
||||
|
||||
|
||||
## Type system
|
||||
|
||||
- `typeof(voidStmt)` now works and returns `void`.
|
||||
- `enum` values can now be overloaded. This needs to be enabled
|
||||
via `{.experimental: "overloadableEnums".}`. We hope that this feature allows
|
||||
for the development of more fluent (less ugly) APIs.
|
||||
See RFC [#373](https://github.com/nim-lang/RFCs/issues/373) for more details.
|
||||
- A type conversion from one `enum` type to another now produces an `[EnumConv]` warning.
|
||||
You should use `ord` (or `cast`, but the compiler won't help, if you misuse it) instead.
|
||||
```nim
|
||||
type A = enum a1, a2
|
||||
type B = enum b1, b2
|
||||
echo a1.B # produces a warning
|
||||
echo a1.ord.B # produces no warning
|
||||
```
|
||||
- A dangerous implicit conversion to `cstring` now triggers a `[CStringConv]` warning.
|
||||
This warning will become an error in future versions! Use an explicit conversion
|
||||
like `cstring(x)` in order to silence the warning.
|
||||
- There is a new warning for *any* type conversion to `enum` that can be enabled via
|
||||
`.warning[AnyEnumConv]:on` or `--warning:AnyEnumConv:on`.
|
||||
- Reusing a type name in a different scope now works, refs [#17710](https://github.com/nim-lang/Nim/pull/17710).
|
||||
- Fixed implicit and explicit generics in procedures, refs [#18808](https://github.com/nim-lang/Nim/pull/18808).
|
||||
|
||||
|
||||
## New-style concepts
|
||||
|
||||
Example:
|
||||
```nim
|
||||
type
|
||||
Comparable = concept # no T, an atom
|
||||
proc cmp(a, b: Self): int
|
||||
```
|
||||
The new design does not rely on `system.compiles` and may compile faster.
|
||||
See PR [#15251](https://github.com/nim-lang/Nim/pull/15251)
|
||||
and RFC [#168](https://github.com/nim-lang/RFCs/issues/168) for details.
|
||||
|
||||
|
||||
## Lexical / syntactic
|
||||
|
||||
- Nim now supports a small subset of Unicode operators as operator symbols.
|
||||
The supported symbols are: "∙ ∘ × ★ ⊗ ⊘ ⊙ ⊛ ⊠ ⊡ ∩ ∧ ⊓ ± ⊕ ⊖ ⊞ ⊟ ∪ ∨ ⊔".
|
||||
To enable this feature, use `--experimental:unicodeOperators`. Note that due
|
||||
to parser limitations you **cannot** enable this feature via a
|
||||
pragma `{.experimental: "unicodeOperators".}` reliably, you need to enable
|
||||
it via the command line or in a configuration file.
|
||||
- `var a {.foo.} = expr` now works inside templates (except when `foo` is overloaded).
|
||||
|
||||
|
||||
## Compiler messages, error messages, hints, warnings
|
||||
|
||||
- Significant improvement to error messages involving effect mismatches,
|
||||
see PRs [#18384](https://github.com/nim-lang/Nim/pull/18384),
|
||||
[#18418](https://github.com/nim-lang/Nim/pull/18418).
|
||||
- Added `--declaredLocs` to show symbol declaration location in error messages.
|
||||
- Added `--spellSuggest` to show spelling suggestions on typos.
|
||||
- Added `--processing:dots|filenames|off` which customizes `hintProcessing`;
|
||||
`--processing:filenames` shows which include/import modules are being compiled as an import stack.
|
||||
- `FieldDefect` messages now shows discriminant value + lineinfo, in all backends (C, JS, VM)
|
||||
- Added `--hintAsError` with similar semantics as `--warningAsError`.
|
||||
- Added `--unitsep:on|off` to control whether to add ASCII unit separator `\31`
|
||||
before a newline for every generated message (potentially multiline),
|
||||
so tooling can tell when messages start and end.
|
||||
- Added `--filenames:abs|canonical|legacyRelProj` which replaces `--listFullPaths:on|off`
|
||||
- `--hint:all:on|off` is now supported to select or deselect all hints; it
|
||||
differs from `--hints:on|off` which acts as a (reversible) gate.
|
||||
Likewise with `--warning:all:on|off`.
|
||||
- The style checking of the compiler now supports a `--styleCheck:usages` switch.
|
||||
This switch enforces that every symbol is written as it was declared, not enforcing
|
||||
the official Nim style guide. To be enabled, this has to be combined either
|
||||
with `--styleCheck:error` or `--styleCheck:hint`.
|
||||
- Type mismatch errors now show more context, use `-d:nimLegacyTypeMismatch` for
|
||||
previous behavior.
|
||||
- `typedesc[Foo]` now renders as such instead of `type Foo` in compiler messages.
|
||||
- `runnableExamples` now show originating location in stacktraces on failure.
|
||||
- `SuccessX` message now shows more useful information.
|
||||
- New `DuplicateModuleImport` warning; improved `UnusedImport` and
|
||||
`XDeclaredButNotUsed` accuracy.
|
||||
|
||||
Compatibility notes:
|
||||
- `--hint:CC` now prints to stderr (like all other hints) instead of stdout.
|
||||
|
||||
|
||||
## Building and running Nim programs, configuration system
|
||||
|
||||
- JSON build instructions are now generated in `$nimcache/outFileBasename.json`
|
||||
instead of `$nimcache/projectName.json`. This allows avoiding recompiling a
|
||||
given project compiled with different options if the output file differs.
|
||||
- `--usenimcache` (implied by `nim r main`) now generates an output file that includes
|
||||
a hash of some of the compilation options, which allows caching generated binaries:
|
||||
```bash
|
||||
nim r main # recompiles
|
||||
nim r -d:foo main # recompiles
|
||||
nim r main # uses cached binary
|
||||
nim r main arg1 arg2 # likewise (runtime arguments are irrelevant)
|
||||
```
|
||||
- `nim r` now supports cross compilation from unix to windows when specifying
|
||||
`-d:mingw` by using Wine, e.g.:
|
||||
`nim r --eval:'import os; echo "a" / "b"'` prints `a\b`.
|
||||
- `nim` can compile version 1.4.0 as follows:
|
||||
`nim c --lib:lib --stylecheck:off -d:nimVersion140 compiler/nim`.
|
||||
`-d:nimVersion140` is not needed for bootstrapping, only for building 1.4.0 from devel.
|
||||
- `nim e` now accepts arbitrary file extensions for the nimscript file,
|
||||
although `.nims` is still the preferred extension in general.
|
||||
- The configuration subsystem now allows for `-d:release` and `-d:danger` to work as expected.
|
||||
The downside is that these defines now have custom logic that doesn't apply for
|
||||
other defines.
|
||||
|
||||
|
||||
## Multithreading
|
||||
|
||||
- TLS: macOS now uses native TLS (`--tlsEmulation:off`). TLS now works with
|
||||
`importcpp` non-POD types; such types must use `.cppNonPod` and
|
||||
`--tlsEmulation:off`should be used.
|
||||
- Added `unsafeIsolate` and `extract` to `std/isolation`.
|
||||
- Added `std/tasks`, see description above.
|
||||
|
||||
|
||||
## Memory management
|
||||
|
||||
- `--gc:arc` now bootstraps (PR [#17342](https://github.com/nim-lang/Nim/pull/17342)).
|
||||
- Lots of improvements to `gc:arc`, `gc:orc`, see PR
|
||||
[#15697](https://github.com/nim-lang/Nim/pull/15697),
|
||||
[#16849](https://github.com/nim-lang/Nim/pull/16849),
|
||||
[#17993](https://github.com/nim-lang/Nim/pull/17993).
|
||||
- `--gc:orc` is now 10% faster than previously for common workloads.
|
||||
If you have trouble with its changed behavior, compile with `-d:nimOldOrc`.
|
||||
- The `--gc:orc` algorithm was refined so that custom container types can participate in the
|
||||
cycle collection process. See the documentation of `=trace` for more details.
|
||||
- On embedded devices `malloc` can now be used instead of `mmap` via `-d:nimAllocPagesViaMalloc`.
|
||||
This is only supported for `--gc:orc` or `--gc:arc`.
|
||||
|
||||
Compatibility notes:
|
||||
- `--newruntime` and `--refchecks` are deprecated,
|
||||
use `--gc:arc`, `--gc:orc`, or `--gc:none` as appropriate instead.
|
||||
|
||||
|
||||
## Docgen
|
||||
|
||||
- docgen: RST files can now use single backticks instead of double backticks and
|
||||
correctly render in both `nim rst2html` (as before) as well as common tools rendering
|
||||
RST directly (e.g. GitHub).
|
||||
This is done by adding the `default-role:: code` directive inside the RST file
|
||||
(which is now handled by `nim rst2html`).
|
||||
- Source+Edit links now appear on top of every docgen'd page when
|
||||
`nim doc --git.url:url ...` is given.
|
||||
- Latex doc generation is revised: output `.tex` files should be compiled
|
||||
by `xelatex` (not by `pdflatex` as before). Now default Latex settings
|
||||
provide support for Unicode and better avoid margin overflows.
|
||||
The minimum required version is TeXLive 2018 (or an equivalent MikTeX version).
|
||||
- The RST parser now supports footnotes, citations, admonitions, and short style
|
||||
references with symbols.
|
||||
- The RST parser now supports Markdown table syntax.
|
||||
Known limitations:
|
||||
- cell alignment is not supported, i.e. alignment annotations in a delimiter
|
||||
row (`:---`, `:--:`, `---:`) are ignored
|
||||
- every table row must start with `|`, e.g. `| cell 1 | cell 2 |`.
|
||||
- Implemented `doc2tex` compiler command which converts documentation in
|
||||
`.nim` files to Latex.
|
||||
- docgen now supports syntax highlighting for inline code.
|
||||
- docgen now supports same-line doc comments:
|
||||
```nim
|
||||
func fn*(a: int): int = 42 ## Doc comment
|
||||
```
|
||||
- docgen now renders `deprecated` and other pragmas.
|
||||
- `runnableExamples` now works with templates and nested templates.
|
||||
- `runnableExamples: "-r:off"` now works for examples that should compile but not run.
|
||||
- `runnableExamples` now renders code verbatim, and produces correct code in all cases.
|
||||
- docgen now shows correct, canonical import paths in docs.
|
||||
- docgen now shows all routines in sidebar, and the proc signature is now shown in sidebar.
|
||||
|
||||
|
||||
## Effects and checks
|
||||
|
||||
- Significant improvement to error messages involving effect mismatches
|
||||
- There is a new `cast` section `{.cast(uncheckedAssign).}: body` that disables some
|
||||
compiler checks regarding `case objects`. This allows serialization libraries
|
||||
to avoid ugly, non-portable solutions. See RFC
|
||||
[#407](https://github.com/nim-lang/RFCs/issues/407) for more details.
|
||||
|
||||
Compatibility notes:
|
||||
- Fixed effect tracking for borrowed procs (see [#18882](https://github.com/nim-lang/Nim/pull/18882)).
|
||||
One consequence is that, under some circumstances, Nim could previously permit a procedure with side effects to be written with `func` - you may need to change some occurrences of `func` to `proc`.
|
||||
To illustrate, Nim versions before 1.6.0 compile the below without error
|
||||
```nim
|
||||
proc print(s: string) =
|
||||
echo s
|
||||
|
||||
type
|
||||
MyString = distinct string
|
||||
|
||||
proc print(s: MyString) {.borrow.}
|
||||
|
||||
func foo(s: MyString) =
|
||||
print(s)
|
||||
```
|
||||
but Nim 1.6.0 produces the error
|
||||
```
|
||||
Error: 'foo' can have side effects
|
||||
```
|
||||
similar to how we expect that
|
||||
```nim
|
||||
func print(s: string) =
|
||||
echo s
|
||||
```
|
||||
produces
|
||||
```
|
||||
Error: 'print' can have side effects
|
||||
```
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
- Major improvements to `nimgrep`, see PR [#15612
|
||||
](https://github.com/nim-lang/Nim/pull/15612).
|
||||
- `fusion` is now un-bundled from Nim, `./koch fusion` will
|
||||
install it via Nimble at a fixed hash.
|
||||
- `testament`: added `nimoutFull: bool` spec to compare full output of compiler
|
||||
instead of a subset; many bugfixes to testament.
|
||||
|
||||
|
||||
## Misc/cleanups
|
||||
|
||||
- Deprecated `TaintedString` and `--taintmode`.
|
||||
- Deprecated `--nilseqs` which is now a noop.
|
||||
- Added `-d:nimStrictMode` in CI in several places to ensure code doesn't have
|
||||
certain hints/warnings.
|
||||
- Removed `.travis.yml`, `appveyor.yml.disabled`, `.github/workflows/ci.yml.disabled`.
|
||||
- `[skip ci]` now works in azure and CI pipelines, see detail in PR
|
||||
[#17561](https://github.com/nim-lang/Nim/pull/17561).
|
||||
330
changelogs/changelog_2_0_0.md
Normal file
330
changelogs/changelog_2_0_0.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# v2.0.0 - 2023-08-01
|
||||
|
||||
Version 2.0 is a big milestone with too many changes to list them all here.
|
||||
|
||||
For a full list see [details](changelog_2_0_0_details.html).
|
||||
|
||||
|
||||
## New features
|
||||
|
||||
### Better tuple unpacking
|
||||
|
||||
Tuple unpacking for variables is now treated as syntax sugar that directly
|
||||
expands into multiple assignments. Along with this, tuple unpacking for
|
||||
variables can now be nested.
|
||||
|
||||
```nim
|
||||
proc returnsNestedTuple(): (int, (int, int), int, int) = (4, (5, 7), 2, 3)
|
||||
|
||||
# Now nesting is supported!
|
||||
let (x, (_, y), _, z) = returnsNestedTuple()
|
||||
|
||||
```
|
||||
|
||||
### Improved type inference
|
||||
|
||||
A new form of type inference called [top-down inference](https://nim-lang.github.io/Nim/manual_experimental.html#topminusdown-type-inference) has been implemented for a variety of basic cases.
|
||||
|
||||
For example, code like the following now compiles:
|
||||
|
||||
```nim
|
||||
let foo: seq[(float, byte, cstring)] = @[(1, 2, "abc")]
|
||||
```
|
||||
|
||||
### Forbidden Tags
|
||||
|
||||
[Tag tracking](https://nim-lang.github.io/Nim/manual.html#effect-system-tag-tracking) now supports the definition
|
||||
of forbidden tags by the `.forbids` pragma which can be used to disable certain effects in proc types.
|
||||
|
||||
For example:
|
||||
|
||||
```nim
|
||||
|
||||
type IO = object ## input/output effect
|
||||
proc readLine(): string {.tags: [IO].} = discard
|
||||
proc echoLine(): void = discard
|
||||
|
||||
proc no_IO_please() {.forbids: [IO].} =
|
||||
# this is OK because it didn't define any tag:
|
||||
echoLine()
|
||||
# the compiler prevents this:
|
||||
let y = readLine()
|
||||
|
||||
```
|
||||
|
||||
### New standard library modules
|
||||
|
||||
The famous `os` module got an overhaul. Several of its features are available
|
||||
under a new interface that introduces a `Path` abstraction. A `Path` is
|
||||
a `distinct string`, which improves the type safety when dealing with paths, files
|
||||
and directories.
|
||||
|
||||
Use:
|
||||
|
||||
- `std/oserrors` for OS error reporting.
|
||||
- `std/envvars` for environment variables handling.
|
||||
- `std/paths` for path handling.
|
||||
- `std/dirs` for directory creation/deletion/traversal.
|
||||
- `std/files` for file existence checking, file deletions and moves.
|
||||
- `std/symlinks` for symlink handling.
|
||||
- `std/appdirs` for accessing configuration/home/temp directories.
|
||||
- `std/cmdline` for reading command line parameters.
|
||||
|
||||
### Consistent underscore handling
|
||||
|
||||
The underscore identifier (`_`) is now generally not added to scope when
|
||||
used as the name of a definition. While this was already the case for
|
||||
variables, it is now also the case for routine parameters, generic
|
||||
parameters, routine declarations, type declarations, etc. This means that the following code now does not compile:
|
||||
|
||||
```nim
|
||||
proc foo(_: int): int = _ + 1
|
||||
echo foo(1)
|
||||
|
||||
proc foo[_](t: typedesc[_]): seq[_] = @[default(_)]
|
||||
echo foo[int]()
|
||||
|
||||
proc _() = echo "_"
|
||||
_()
|
||||
|
||||
type _ = int
|
||||
let x: _ = 3
|
||||
```
|
||||
|
||||
Whereas the following code now compiles:
|
||||
|
||||
```nim
|
||||
proc foo(_, _: int): int = 123
|
||||
echo foo(1, 2)
|
||||
|
||||
proc foo[_, _](): int = 123
|
||||
echo foo[int, bool]()
|
||||
|
||||
proc foo[T, U](_: typedesc[T], _: typedesc[U]): (T, U) = (default(T), default(U))
|
||||
echo foo(int, bool)
|
||||
|
||||
proc _() = echo "one"
|
||||
proc _() = echo "two"
|
||||
|
||||
type _ = int
|
||||
type _ = float
|
||||
```
|
||||
|
||||
### JavaScript codegen improvement
|
||||
|
||||
The JavaScript backend now uses [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
|
||||
for 64-bit integer types (`int64` and `uint64`) by default. As this affects
|
||||
JS code generation, code using these types to interface with the JS backend
|
||||
may need to be updated. Note that `int` and `uint` are not affected.
|
||||
|
||||
For compatibility with [platforms that do not support BigInt](https://caniuse.com/bigint)
|
||||
and in the case of potential bugs with the new implementation, the
|
||||
old behavior is currently still supported with the command line option
|
||||
`--jsbigint64:off`.
|
||||
|
||||
|
||||
## Docgen improvements
|
||||
|
||||
`Markdown` is now the default markup language of doc comments (instead
|
||||
of the legacy `RstMarkdown` mode). In this release we begin to separate
|
||||
RST and Markdown features to better follow specification of each
|
||||
language, with the focus on Markdown development.
|
||||
See also [the docs](https://nim-lang.github.io/Nim/markdown_rst.html).
|
||||
|
||||
* Added a `{.doctype: Markdown | RST | RstMarkdown.}` pragma allowing to
|
||||
select the markup language mode in the doc comments of the current `.nim`
|
||||
file for processing by `nim doc`:
|
||||
|
||||
1. `Markdown` (default) is basically CommonMark (standard Markdown) +
|
||||
some Pandoc Markdown features + some RST features that are missing
|
||||
in our current implementation of CommonMark and Pandoc Markdown.
|
||||
2. `RST` closely follows the RST spec with few additional Nim features.
|
||||
3. `RstMarkdown` is a maximum mix of RST and Markdown features, which
|
||||
is kept for the sake of compatibility and ease of migration.
|
||||
|
||||
* Added separate `md2html` and `rst2html` commands for processing
|
||||
standalone `.md` and `.rst` files respectively (and also `md2tex`/`rst2tex`).
|
||||
|
||||
* Added Pandoc Markdown bracket syntax `[...]` for making anchor-less links.
|
||||
* Docgen now supports concise syntax for referencing Nim symbols:
|
||||
instead of specifying HTML anchors directly one can use original
|
||||
Nim symbol declarations (adding the aforementioned link brackets
|
||||
`[...]` around them).
|
||||
* To use this feature across modules, a new `importdoc` directive was added.
|
||||
Using this feature for referencing also helps to ensure that links
|
||||
(inside one module or the whole project) are not broken.
|
||||
* Added support for RST & Markdown quote blocks (blocks starting with `>`).
|
||||
* Added a popular Markdown definition lists extension.
|
||||
* Added Markdown indented code blocks (blocks indented by >= 4 spaces).
|
||||
* Added syntax for additional parameters to Markdown code blocks:
|
||||
|
||||
```nim test="nim c $1"
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## C++ interop enhancements
|
||||
|
||||
Nim 2.0 takes C++ interop to the next level. With the new [virtual](https://nim-lang.github.io/Nim/manual_experimental.html#virtual-pragma) pragma and the extended [constructor](https://nim-lang.github.io/Nim/manual_experimental.html#constructor-pragma) pragma.
|
||||
Now one can define constructors and virtual procs that maps to C++ constructors and virtual methods, allowing one to further customize
|
||||
the interoperability. There is also extended support for the [codeGenDecl](https://nim-lang.org/docs/manual.html#implementation-specific-pragmas-codegendecl-pragma) pragma, so that it works on types.
|
||||
|
||||
It's a common pattern in C++ to use inheritance to extend a library. Some even use multiple inheritance as a mechanism to make interfaces.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
```cpp
|
||||
|
||||
struct Base {
|
||||
int someValue;
|
||||
Base(int inValue) {
|
||||
someValue = inValue;
|
||||
};
|
||||
};
|
||||
|
||||
class IPrinter {
|
||||
public:
|
||||
virtual void print() = 0;
|
||||
};
|
||||
```
|
||||
|
||||
```nim
|
||||
|
||||
type
|
||||
Base* {.importcpp, inheritable.} = object
|
||||
someValue*: int32
|
||||
IPrinter* {.importcpp.} = object
|
||||
|
||||
const objTemplate = """
|
||||
struct $1 : public $3, public IPrinter {
|
||||
$2
|
||||
};
|
||||
""";
|
||||
|
||||
type NimChild {.codegenDecl: objTemplate .} = object of Base
|
||||
|
||||
proc makeNimChild(val: int32): NimChild {.constructor: "NimClass('1 #1) : Base(#1)".} =
|
||||
echo "It calls the base constructor passing " & $this.someValue
|
||||
this.someValue = val * 2 # Notice how we can access `this` inside the constructor. It's of the type `ptr NimChild`.
|
||||
|
||||
proc print*(self: NimChild) {.virtual.} =
|
||||
echo "Some value is " & $self.someValue
|
||||
|
||||
let child = makeNimChild(10)
|
||||
child.print()
|
||||
```
|
||||
|
||||
It outputs:
|
||||
|
||||
```
|
||||
It calls the base constructor passing 10
|
||||
Some value is 20
|
||||
```
|
||||
|
||||
|
||||
## ARC/ORC refinements
|
||||
|
||||
With the 2.0 release, the ARC/ORC model got refined once again and is now finally complete:
|
||||
|
||||
1. Programmers now have control over the "item was moved from" state as `=wasMoved` is overridable.
|
||||
2. There is a new `=dup` hook which is more efficient than the old combination of `=wasMoved(tmp); =copy(tmp, x)` operations.
|
||||
3. Destructors now take a parameter of the attached object type `T` directly and don't have to take a `var T` parameter.
|
||||
|
||||
With these important optimizations we improved the runtime of the compiler and important benchmarks by 0%! Wait ... what?
|
||||
Yes, unfortunately it turns out that for a modern optimizer like in GCC or LLVM there is no difference.
|
||||
|
||||
But! This refined model is more efficient once separate compilation enters the picture. In other words, as we think of
|
||||
providing a stable ABI it is important not to lose any efficiency in the calling conventions.
|
||||
|
||||
|
||||
## Tool changes
|
||||
|
||||
- Nim now ships Nimble version 0.14 which added support for lock-files. Libraries are stored in `$nimbleDir/pkgs2` (it was `$nimbleDir/pkgs` before). Use `nimble develop --global` to create an old style link file in the special links directory documented at https://github.com/nim-lang/nimble#nimble-develop.
|
||||
- nimgrep now offers the option `--inContext` (and `--notInContext`), which
|
||||
allows to filter only matches with the context block containing a given pattern.
|
||||
- nimgrep: names of options containing "include/exclude" are deprecated,
|
||||
e.g. instead of `--includeFile` and `--excludeFile` we have
|
||||
`--filename` and `--notFilename` respectively.
|
||||
Also, the semantics are now consistent for such positive/negative filters.
|
||||
- Nim now ships with an alternative package manager called Atlas. More on this in upcoming versions.
|
||||
|
||||
|
||||
## Porting guide
|
||||
|
||||
### Block and Break
|
||||
|
||||
Using an unnamed break in a block is deprecated. This warning will become an error in future versions! Use a named block with a named break instead. In other words, turn:
|
||||
|
||||
```nim
|
||||
|
||||
block:
|
||||
a()
|
||||
if cond:
|
||||
break
|
||||
b()
|
||||
|
||||
```
|
||||
|
||||
Into:
|
||||
|
||||
```nim
|
||||
|
||||
block maybePerformB:
|
||||
a()
|
||||
if cond:
|
||||
break maybePerformB
|
||||
b()
|
||||
|
||||
```
|
||||
|
||||
### Strict funcs
|
||||
|
||||
The definition of `"strictFuncs"` was changed.
|
||||
The old definition was roughly: "A store to a ref/ptr deref is forbidden unless it's coming from a `var T` parameter".
|
||||
The new definition is: "A store to a ref/ptr deref is forbidden."
|
||||
|
||||
This new definition is much easier to understand, the price is some expressitivity. The following code used to be
|
||||
accepted:
|
||||
|
||||
```nim
|
||||
|
||||
{.experimental: "strictFuncs".}
|
||||
|
||||
type Node = ref object
|
||||
s: string
|
||||
|
||||
func create(s: string): Node =
|
||||
result = Node()
|
||||
result.s = s # store to result[]
|
||||
|
||||
```
|
||||
|
||||
Now it has to be rewritten to:
|
||||
|
||||
```nim
|
||||
|
||||
{.experimental: "strictFuncs".}
|
||||
|
||||
type Node = ref object
|
||||
s: string
|
||||
|
||||
func create(s: string): Node =
|
||||
result = Node(s: s)
|
||||
|
||||
```
|
||||
|
||||
### Standard library
|
||||
|
||||
Several standard library modules have been moved to nimble packages, use `nimble` or `atlas` to install them:
|
||||
|
||||
- `std/punycode` => `punycode`
|
||||
- `std/asyncftpclient` => `asyncftpclient`
|
||||
- `std/smtp` => `smtp`
|
||||
- `std/db_common` => `db_connector/db_common`
|
||||
- `std/db_sqlite` => `db_connector/db_sqlite`
|
||||
- `std/db_mysql` => `db_connector/db_mysql`
|
||||
- `std/db_postgres` => `db_connector/db_postgres`
|
||||
- `std/db_odbc` => `db_connector/db_odbc`
|
||||
- `std/md5` => `checksums/md5`
|
||||
- `std/sha1` => `checksums/sha1`
|
||||
- `std/sums` => `sums`
|
||||
560
changelogs/changelog_2_0_0_details.md
Normal file
560
changelogs/changelog_2_0_0_details.md
Normal file
@@ -0,0 +1,560 @@
|
||||
# v2.0.0 - 2023-08-01
|
||||
|
||||
|
||||
## Changes affecting backward compatibility
|
||||
|
||||
- ORC is now the default memory management strategy. Use
|
||||
`--mm:refc` for a transition period.
|
||||
|
||||
- The `threads:on` option is now the default.
|
||||
|
||||
- `httpclient.contentLength` default to `-1` if the Content-Length header is not set in the response. It follows Apache's `HttpClient` (Java), `http` (go) and .NET `HttpWebResponse` (C#) behaviors. Previously it raised a `ValueError`.
|
||||
|
||||
- `addr` is now available for all addressable locations,
|
||||
`unsafeAddr` is now deprecated and an alias for `addr`.
|
||||
|
||||
- Certain definitions from the default `system` module have been moved to
|
||||
the following new modules:
|
||||
|
||||
- `std/syncio`
|
||||
- `std/assertions`
|
||||
- `std/formatfloat`
|
||||
- `std/objectdollar`
|
||||
- `std/widestrs`
|
||||
- `std/typedthreads`
|
||||
- `std/sysatomics`
|
||||
|
||||
In the future, these definitions will be removed from the `system` module,
|
||||
and their respective modules will have to be imported to use them.
|
||||
Currently, to make these imports required, the `-d:nimPreviewSlimSystem` option
|
||||
may be used.
|
||||
|
||||
- Enabling `-d:nimPreviewSlimSystem` also removes the following deprecated
|
||||
symbols in the `system` module:
|
||||
- Aliases with an `Error` suffix to exception types that have a `Defect` suffix
|
||||
(see [exceptions](https://nim-lang.github.io/Nim/exceptions.html)):
|
||||
`ArithmeticError`, `DivByZeroError`, `OverflowError`,
|
||||
`AccessViolationError`, `AssertionError`, `OutOfMemError`, `IndexError`,
|
||||
`FieldError`, `RangeError`, `StackOverflowError`, `ReraiseError`,
|
||||
`ObjectAssignmentError`, `ObjectConversionError`, `FloatingPointError`,
|
||||
`FloatOverflowError`, `FloatUnderflowError`, `FloatInexactError`,
|
||||
`DeadThreadError`, `NilAccessError`
|
||||
- `addQuitProc`, replaced by `exitprocs.addExitProc`
|
||||
- Legacy unsigned conversion operations: `ze`, `ze64`, `toU8`, `toU16`, `toU32`
|
||||
- `TaintedString`, formerly a distinct alias to `string`
|
||||
- `PInt32`, `PInt64`, `PFloat32`, `PFloat64`, aliases to
|
||||
`ptr int32`, `ptr int64`, `ptr float32`, `ptr float64`
|
||||
|
||||
- Enabling `-d:nimPreviewSlimSystem` removes the import of `channels_builtin` in
|
||||
in the `system` module, which is replaced by [threading/channels](https://github.com/nim-lang/threading/blob/master/threading/channels.nim). Use the command `nimble install threading` and import `threading/channels`.
|
||||
|
||||
- Enabling `-d:nimPreviewCstringConversion` causes `ptr char`, `ptr array[N, char]` and `ptr UncheckedArray[N, char]` to not support conversion to `cstring` anymore.
|
||||
|
||||
- Enabling `-d:nimPreviewProcConversion` causes `proc` to not support conversion to
|
||||
`pointer` anymore. `cast` may be used instead.
|
||||
|
||||
- The `gc:v2` option is removed.
|
||||
|
||||
- The `mainmodule` and `m` options are removed.
|
||||
|
||||
- Optional parameters in combination with `: body` syntax ([RFC #405](https://github.com/nim-lang/RFCs/issues/405))
|
||||
are now opt-in via `experimental:flexibleOptionalParams`.
|
||||
|
||||
- Automatic dereferencing (experimental feature) is removed.
|
||||
|
||||
- The `Math.trunc` polyfill for targeting Internet Explorer was
|
||||
previously included in most JavaScript output files.
|
||||
Now, it is only included with `-d:nimJsMathTruncPolyfill`.
|
||||
If you are targeting Internet Explorer, you may choose to enable this option
|
||||
or define your own `Math.trunc` polyfill using the [`emit` pragma](https://nim-lang.org/docs/manual.html#implementation-specific-pragmas-emit-pragma).
|
||||
Nim uses `Math.trunc` for the division and modulo operators for integers.
|
||||
|
||||
- `shallowCopy` and `shallow` are removed for ARC/ORC. Use `move` when possible or combine assignment and
|
||||
`sink` for optimization purposes.
|
||||
|
||||
- The experimental `nimPreviewDotLikeOps` switch is going to be removed or deprecated because it didn't fulfill its promises.
|
||||
|
||||
- The `{.this.}` pragma, deprecated since 0.19, has been removed.
|
||||
- `nil` literals can no longer be directly assigned to variables or fields of `distinct` pointer types. They must be converted instead.
|
||||
```nim
|
||||
type Foo = distinct ptr int
|
||||
|
||||
# Before:
|
||||
var x: Foo = nil
|
||||
# After:
|
||||
var x: Foo = Foo(nil)
|
||||
```
|
||||
- Removed two type pragma syntaxes deprecated since 0.20, namely
|
||||
`type Foo = object {.final.}`, and `type Foo {.final.} [T] = object`. Instead,
|
||||
use `type Foo[T] {.final.} = object`.
|
||||
|
||||
- `foo a = b` now means `foo(a = b)` rather than `foo(a) = b`. This is consistent
|
||||
with the existing behavior of `foo a, b = c` meaning `foo(a, b = c)`.
|
||||
This decision was made with the assumption that the old syntax was used rarely;
|
||||
if your code used the old syntax, please be aware of this change.
|
||||
|
||||
- [Overloadable enums](https://nim-lang.github.io/Nim/manual.html#overloadable-enum-value-names) and Unicode Operators
|
||||
are no longer experimental.
|
||||
|
||||
- `macros.getImpl` for `const` symbols now returns the full definition node
|
||||
(as `nnkConstDef`) rather than the AST of the constant value.
|
||||
|
||||
- Lock levels are deprecated, now a noop.
|
||||
|
||||
- `strictEffects` are no longer experimental.
|
||||
Use `legacy:laxEffects` to keep backward compatibility.
|
||||
|
||||
- The `gorge`/`staticExec` calls will now return a descriptive message in the output
|
||||
if the execution fails for whatever reason. To get back legacy behaviour, use `-d:nimLegacyGorgeErrors`.
|
||||
|
||||
- Pointer to `cstring` conversions now trigger a `[PtrToCstringConv]` warning.
|
||||
This warning will become an error in future versions! Use a `cast` operation
|
||||
like `cast[cstring](x)` instead.
|
||||
|
||||
- `logging` will default to flushing all log level messages. To get the legacy behaviour of only flushing Error and Fatal messages, use `-d:nimV1LogFlushBehavior`.
|
||||
|
||||
- Redefining templates with the same signature was previously
|
||||
allowed to support certain macro code. To do this explicitly, the
|
||||
`{.redefine.}` pragma has been added. Note that this is only for templates.
|
||||
Implicit redefinition of templates is now deprecated and will give an error in the future.
|
||||
|
||||
- Using an unnamed break in a block is deprecated. This warning will become an error in future versions! Use a named block with a named break instead.
|
||||
|
||||
- Several Standard libraries have been moved to nimble packages, use `nimble` to install them:
|
||||
- `std/punycode` => `punycode`
|
||||
- `std/asyncftpclient` => `asyncftpclient`
|
||||
- `std/smtp` => `smtp`
|
||||
- `std/db_common` => `db_connector/db_common`
|
||||
- `std/db_sqlite` => `db_connector/db_sqlite`
|
||||
- `std/db_mysql` => `db_connector/db_mysql`
|
||||
- `std/db_postgres` => `db_connector/db_postgres`
|
||||
- `std/db_odbc` => `db_connector/db_odbc`
|
||||
- `std/md5` => `checksums/md5`
|
||||
- `std/sha1` => `checksums/sha1`
|
||||
- `std/sums` => `std/sums`
|
||||
|
||||
- Previously, calls like `foo(a, b): ...` or `foo(a, b) do: ...` where the final argument of
|
||||
`foo` had type `proc ()` were assumed by the compiler to mean `foo(a, b, proc () = ...)`.
|
||||
This behavior is now deprecated. Use `foo(a, b) do (): ...` or `foo(a, b, proc () = ...)` instead.
|
||||
|
||||
- When `--warning[BareExcept]:on` is enabled, if an `except` specifies no exception or any exception not inheriting from `Defect` or `CatchableError`, a `warnBareExcept` warning will be triggered. For example, the following code will emit a warning:
|
||||
```nim
|
||||
try:
|
||||
discard
|
||||
except: # Warning: The bare except clause is deprecated; use `except CatchableError:` instead [BareExcept]
|
||||
discard
|
||||
```
|
||||
|
||||
- The experimental `strictFuncs` feature now disallows a store to the heap via a `ref` or `ptr` indirection.
|
||||
|
||||
- The underscore identifier (`_`) is now generally not added to scope when
|
||||
used as the name of a definition. While this was already the case for
|
||||
variables, it is now also the case for routine parameters, generic
|
||||
parameters, routine declarations, type declarations, etc. This means that the following code now does not compile:
|
||||
|
||||
```nim
|
||||
proc foo(_: int): int = _ + 1
|
||||
echo foo(1)
|
||||
|
||||
proc foo[_](t: typedesc[_]): seq[_] = @[default(_)]
|
||||
echo foo[int]()
|
||||
|
||||
proc _() = echo "_"
|
||||
_()
|
||||
|
||||
type _ = int
|
||||
let x: _ = 3
|
||||
```
|
||||
|
||||
Whereas the following code now compiles:
|
||||
|
||||
```nim
|
||||
proc foo(_, _: int): int = 123
|
||||
echo foo(1, 2)
|
||||
|
||||
proc foo[_, _](): int = 123
|
||||
echo foo[int, bool]()
|
||||
|
||||
proc foo[T, U](_: typedesc[T], _: typedesc[U]): (T, U) = (default(T), default(U))
|
||||
echo foo(int, bool)
|
||||
|
||||
proc _() = echo "one"
|
||||
proc _() = echo "two"
|
||||
|
||||
type _ = int
|
||||
type _ = float
|
||||
```
|
||||
|
||||
- Added the `--legacy:verboseTypeMismatch` switch to get legacy type mismatch error messages.
|
||||
|
||||
- The JavaScript backend now uses [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
|
||||
for 64-bit integer types (`int64` and `uint64`) by default. As this affects
|
||||
JS code generation, code using these types to interface with the JS backend
|
||||
may need to be updated. Note that `int` and `uint` are not affected.
|
||||
|
||||
For compatibility with [platforms that do not support BigInt](https://caniuse.com/bigint)
|
||||
and in the case of potential bugs with the new implementation, the
|
||||
old behavior is currently still supported with the command line option
|
||||
`--jsbigint64:off`.
|
||||
|
||||
- The `proc` and `iterator` type classes now respectively only match
|
||||
procs and iterators. Previously both type classes matched any of
|
||||
procs or iterators.
|
||||
|
||||
```nim
|
||||
proc prc(): int =
|
||||
123
|
||||
|
||||
iterator iter(): int {.closure.} =
|
||||
yield 123
|
||||
|
||||
proc takesProc[T: proc](x: T) = discard
|
||||
proc takesIter[T: iterator](x: T) = discard
|
||||
|
||||
# always compiled:
|
||||
takesProc(prc)
|
||||
takesIter(iter)
|
||||
# no longer compiles:
|
||||
takesProc(iter)
|
||||
takesIter(prc)
|
||||
```
|
||||
|
||||
- The `proc` and `iterator` type classes now accept a calling convention pragma
|
||||
(i.e. `proc {.closure.}`) that must be shared by matching proc or iterator
|
||||
types. Previously, pragmas were parsed but discarded if no parameter list
|
||||
was given.
|
||||
|
||||
This is represented in the AST by an `nnkProcTy`/`nnkIteratorTy` node with
|
||||
an `nnkEmpty` node in the place of the `nnkFormalParams` node, and the pragma
|
||||
node in the same place as in a concrete `proc` or `iterator` type node. This
|
||||
state of the AST may be unexpected to existing code, both due to the
|
||||
replacement of the `nnkFormalParams` node as well as having child nodes
|
||||
unlike other type class AST.
|
||||
|
||||
- Signed integer literals in `set` literals now default to a range type of
|
||||
`0..255` instead of `0..65535` (the maximum size of sets).
|
||||
|
||||
- `case` statements with `else` branches put before `elif`/`of` branches in macros
|
||||
are rejected with "invalid order of case branches".
|
||||
|
||||
- Destructors now default to `.raises: []` (i.e. destructors must not raise
|
||||
unlisted exceptions) and explicitly raising destructors are implementation
|
||||
defined behavior.
|
||||
|
||||
- The very old, undocumented `deprecated` pragma statement syntax for
|
||||
deprecated aliases is now a no-op. The regular deprecated pragma syntax is
|
||||
generally sufficient instead.
|
||||
|
||||
```nim
|
||||
# now does nothing:
|
||||
{.deprecated: [OldName: NewName].}
|
||||
|
||||
# instead use:
|
||||
type OldName* {.deprecated: "use NewName instead".} = NewName
|
||||
const oldName* {.deprecated: "use newName instead".} = newName
|
||||
```
|
||||
|
||||
`defined(nimalias)` can be used to check for versions when this syntax was
|
||||
available; however since code that used this syntax is usually very old,
|
||||
these deprecated aliases are likely not used anymore and it may make sense
|
||||
to simply remove these statements.
|
||||
|
||||
- `getProgramResult` and `setProgramResult` in `std/exitprocs` are no longer
|
||||
declared when they are not available on the backend. Previously it would call
|
||||
`doAssert false` at runtime despite the condition being checkable at compile-time.
|
||||
|
||||
- Custom destructors now supports non-var parameters, e.g. ``proc `=destroy`[T: object](x: T)`` is valid. ``proc `=destroy`[T: object](x: var T)`` is deprecated.
|
||||
|
||||
- Relative imports will not resolve to searched paths anymore, e.g. `import ./tables` now reports an error properly.
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
[//]: # "Changes:"
|
||||
- OpenSSL 3 is now supported.
|
||||
- `macros.parseExpr` and `macros.parseStmt` now accept an optional
|
||||
`filename` argument for more informative errors.
|
||||
- The `colors` module is expanded with missing colors from the CSS color standard.
|
||||
`colPaleVioletRed` and `colMediumPurple` have also been changed to match the CSS color standard.
|
||||
- Fixed `lists.SinglyLinkedList` being broken after removing the last node ([#19353](https://github.com/nim-lang/Nim/pull/19353)).
|
||||
- The `md5` module now works at compile time and in JavaScript.
|
||||
- Changed `mimedb` to use an `OrderedTable` instead of `OrderedTableRef`, to support `const` tables.
|
||||
- `strutils.find` now uses and defaults to `last = -1` for whole string searches,
|
||||
making limiting it to just the first char (`last = 0`) valid.
|
||||
- `strutils.split` and `strutils.rsplit` now return the source string as a single element for an empty separator.
|
||||
- `random.rand` now works with `Ordinal`s.
|
||||
- Undeprecated `os.isvalidfilename`.
|
||||
- `std/oids` now uses `int64` to store time internally (before, it was int32).
|
||||
- `std/uri.Uri` dollar (`$`) improved, precalculates the `string` result length from the `Uri`.
|
||||
- `std/uri.Uri.isIpv6` is now exported.
|
||||
- `std/logging.ConsoleLogger` and `FileLogger` now have a `flushThreshold` attribute to set what log message levels are automatically flushed. For Nim v1 use `-d:nimFlushAllLogs` to automatically flush all message levels. Flushing all logs is the default behavior for Nim v2.
|
||||
|
||||
|
||||
- `std/jsfetch.newFetchOptions` now has default values for all parameters.
|
||||
- `std/jsformdata` now accepts the `Blob` data type.
|
||||
|
||||
- `std/sharedlist` and `std/sharedtables` are now deprecated, see [RFC #433](https://github.com/nim-lang/RFCs/issues/433).
|
||||
|
||||
- There is a new compile flag (`-d:nimNoGetRandom`) when building `std/sysrand` to remove the dependency on the Linux `getrandom` syscall.
|
||||
|
||||
This compile flag only affects Linux builds and is necessary if either compiling on a Linux kernel version < 3.17, or if code built will be executing on kernel < 3.17.
|
||||
|
||||
On Linux kernels < 3.17 (such as kernel 3.10 in RHEL7 and CentOS7), the `getrandom` syscall was not yet introduced. Without this, the `std/sysrand` module will not build properly, and if code is built on a kernel >= 3.17 without the flag, any usage of the `std/sysrand` module will fail to execute on a kernel < 3.17 (since it attempts to perform a syscall to `getrandom`, which isn't present in the current kernel). A compile flag has been added to force the `std/sysrand` module to use /dev/urandom (available since Linux kernel 1.3.30), rather than the `getrandom` syscall. This allows for use of a cryptographically secure PRNG, regardless of kernel support for the `getrandom` syscall.
|
||||
|
||||
When building for RHEL7/CentOS7 for example, the entire build process for nim from a source package would then be:
|
||||
```sh
|
||||
$ yum install devtoolset-8 # Install GCC version 8 vs the standard 4.8.5 on RHEL7/CentOS7. Alternatively use -d:nimEmulateOverflowChecks. See issue #13692 for details
|
||||
$ scl enable devtoolset-8 bash # Run bash shell with default toolchain of gcc 8
|
||||
$ sh build.sh # per unix install instructions
|
||||
$ bin/nim c koch # per unix install instructions
|
||||
$ ./koch boot -d:release # per unix install instructions
|
||||
$ ./koch tools -d:nimNoGetRandom # pass the nimNoGetRandom flag to compile std/sysrand without support for getrandom syscall
|
||||
```
|
||||
|
||||
This is necessary to pass when building Nim on kernel versions < 3.17 in particular to avoid an error of "SYS_getrandom undeclared" during the build process for the stdlib (`sysrand` in particular).
|
||||
|
||||
[//]: # "Additions:"
|
||||
- Added ISO 8601 week date utilities in `times`:
|
||||
- Added `IsoWeekRange`, a range type for weeks in a week-based year.
|
||||
- Added `IsoYear`, a distinct type for a week-based year in contrast to a regular year.
|
||||
- Added an `initDateTime` overload to create a `DateTime` from an ISO week date.
|
||||
- Added `getIsoWeekAndYear` to get an ISO week number and week-based year from a datetime.
|
||||
- Added `getIsoWeeksInYear` to return the number of weeks in a week-based year.
|
||||
- Added new modules which were previously part of `std/os`:
|
||||
- Added `std/oserrors` for OS error reporting.
|
||||
- Added `std/envvars` for environment variables handling.
|
||||
- Added `std/cmdline` for reading command line parameters.
|
||||
- Added `std/paths`, `std/dirs`, `std/files`, `std/symlinks` and `std/appdirs`.
|
||||
- Added `sep` parameter in `std/uri` to specify the query separator.
|
||||
- Added `UppercaseLetters`, `LowercaseLetters`, `PunctuationChars`, `PrintableChars` sets to `std/strutils`.
|
||||
- Added `complex.sgn` for obtaining the phase of complex numbers.
|
||||
- Added `insertAdjacentText`, `insertAdjacentElement`, `insertAdjacentHTML`,
|
||||
`after`, `before`, `closest`, `append`, `hasAttributeNS`, `removeAttributeNS`,
|
||||
`hasPointerCapture`, `releasePointerCapture`, `requestPointerLock`,
|
||||
`replaceChildren`, `replaceWith`, `scrollIntoViewIfNeeded`, `setHTML`,
|
||||
`toggleAttribute`, and `matches` to `std/dom`.
|
||||
- Added [`jsre.hasIndices`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/hasIndices).
|
||||
- Added `capacity` for `string` and `seq` to return the current capacity, see [RFC #460](https://github.com/nim-lang/RFCs/issues/460).
|
||||
- Added `openArray[char]` overloads for `std/parseutils` and `std/unicode`, allowing for more code reuse.
|
||||
- Added a `safe` parameter to `base64.encodeMime`.
|
||||
- Added `parseutils.parseSize` - inverse to `strutils.formatSize` - to parse human readable sizes.
|
||||
- Added `minmax` to `sequtils`, as a more efficient `(min(_), max(_))` over sequences.
|
||||
- `std/jscore` for the JavaScript target:
|
||||
+ Added bindings to [`Array.shift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift)
|
||||
and [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask).
|
||||
+ Added `toDateString`, `toISOString`, `toJSON`, `toTimeString`, `toUTCString` converters for `DateTime`.
|
||||
- Added `BackwardsIndex` overload for `CacheSeq`.
|
||||
- Added support for nested `with` blocks in `std/with`.
|
||||
- Added `ensureMove` to the system module. It ensures that the passed argument is moved, otherwise an error is given at the compile time.
|
||||
|
||||
|
||||
[//]: # "Deprecations:"
|
||||
- Deprecated `selfExe` for Nimscript.
|
||||
- Deprecated `std/base64.encode` for collections of arbitrary integer element type.
|
||||
Now only `byte` and `char` are supported.
|
||||
|
||||
[//]: # "Removals:"
|
||||
- Removed deprecated module `parseopt2`.
|
||||
- Removed deprecated module `sharedstrings`.
|
||||
- Removed deprecated module `dom_extensions`.
|
||||
- Removed deprecated module `LockFreeHash`.
|
||||
- Removed deprecated module `events`.
|
||||
- Removed deprecated `oids.oidToString`.
|
||||
- Removed define `nimExperimentalAsyncjsThen` for `std/asyncjs.then` and `std/jsfetch`.
|
||||
- Removed deprecated `jsre.test` and `jsre.toString`.
|
||||
- Removed deprecated `math.c_frexp`.
|
||||
- Removed deprecated `` httpcore.`==` ``.
|
||||
- Removed deprecated `std/posix.CMSG_SPACE` and `std/posix.CMSG_LEN` that take wrong argument types.
|
||||
- Removed deprecated `osproc.poDemon`, symbol with typo.
|
||||
- Removed deprecated `tables.rightSize`.
|
||||
|
||||
|
||||
- Removed deprecated `posix.CLONE_STOPPED`.
|
||||
|
||||
|
||||
## Language changes
|
||||
|
||||
- [Tag tracking](https://nim-lang.github.io/Nim/manual.html#effect-system-tag-tracking) now supports the definition of forbidden tags by the `.forbids` pragma
|
||||
which can be used to disable certain effects in proc types.
|
||||
- [Case statement macros](https://nim-lang.github.io/Nim/manual.html#macros-case-statement-macros) are no longer experimental,
|
||||
meaning you no longer need to enable the experimental switch `caseStmtMacros` to use them.
|
||||
- Full command syntax and block arguments i.e. `foo a, b: c` are now allowed
|
||||
for the right-hand side of type definitions in type sections. Previously
|
||||
they would error with "invalid indentation".
|
||||
|
||||
- Compile-time define changes:
|
||||
- `defined` now accepts identifiers separated by dots, i.e. `defined(a.b.c)`.
|
||||
In the command line, this is defined as `-d:a.b.c`. Older versions can
|
||||
use backticks as in ``defined(`a.b.c`)`` to access such defines.
|
||||
- [Define pragmas for constants](https://nim-lang.github.io/Nim/manual.html#implementation-specific-pragmas-compileminustime-define-pragmas)
|
||||
now support a string argument for qualified define names.
|
||||
|
||||
```nim
|
||||
# -d:package.FooBar=42
|
||||
const FooBar {.intdefine: "package.FooBar".}: int = 5
|
||||
echo FooBar # 42
|
||||
```
|
||||
|
||||
This was added to help disambiguate similar define names for different packages.
|
||||
In older versions, this could only be achieved with something like the following:
|
||||
|
||||
```nim
|
||||
const FooBar = block:
|
||||
const `package.FooBar` {.intdefine.}: int = 5
|
||||
`package.FooBar`
|
||||
```
|
||||
- A generic `define` pragma for constants has been added that interprets
|
||||
the value of the define based on the type of the constant value.
|
||||
See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#generic-nimdefine-pragma)
|
||||
for a list of supported types.
|
||||
|
||||
- [Macro pragmas](https://nim-lang.github.io/Nim/manual.html#userminusdefined-pragmas-macro-pragmas) changes:
|
||||
- Templates now accept macro pragmas.
|
||||
- Macro pragmas for var/let/const sections have been redesigned in a way that works
|
||||
similarly to routine macro pragmas. The new behavior is documented in the
|
||||
[experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#extended-macro-pragmas).
|
||||
- Pragma macros on type definitions can now return `nnkTypeSection` nodes as well as `nnkTypeDef`,
|
||||
allowing multiple type definitions to be injected in place of the original type definition.
|
||||
|
||||
```nim
|
||||
import macros
|
||||
|
||||
macro multiply(amount: static int, s: untyped): untyped =
|
||||
let name = $s[0].basename
|
||||
result = newNimNode(nnkTypeSection)
|
||||
for i in 1 .. amount:
|
||||
result.add(newTree(nnkTypeDef, ident(name & $i), s[1], s[2]))
|
||||
|
||||
type
|
||||
Foo = object
|
||||
Bar {.multiply: 3.} = object
|
||||
x, y, z: int
|
||||
Baz = object
|
||||
# becomes
|
||||
type
|
||||
Foo = object
|
||||
Bar1 = object
|
||||
x, y, z: int
|
||||
Bar2 = object
|
||||
x, y, z: int
|
||||
Bar3 = object
|
||||
x, y, z: int
|
||||
Baz = object
|
||||
```
|
||||
|
||||
- A new form of type inference called [top-down inference](https://nim-lang.github.io/Nim/manual_experimental.html#topminusdown-type-inference)
|
||||
has been implemented for a variety of basic cases. For example, code like the following now compiles:
|
||||
|
||||
```nim
|
||||
let foo: seq[(float, byte, cstring)] = @[(1, 2, "abc")]
|
||||
```
|
||||
|
||||
- `cstring` is now accepted as a selector in `case` statements, removing the
|
||||
need to convert to `string`. On the JS backend, this is translated directly
|
||||
to a `switch` statement.
|
||||
|
||||
- Nim now supports `out` parameters and ["strict definitions"](https://nim-lang.github.io/Nim/manual_experimental.html#strict-definitions-and-nimout-parameters).
|
||||
- Nim now offers a [strict mode](https://nim-lang.github.io/Nim/manual_experimental.html#strict-case-objects) for `case objects`.
|
||||
|
||||
- IBM Z architecture and macOS m1 arm64 architecture are supported.
|
||||
|
||||
- `=wasMoved` can now be overridden by users.
|
||||
|
||||
- There is a new pragma called [quirky](https://nim-lang.github.io/Nim/manual_experimental.html#quirky-routines) that can be used to affect the code
|
||||
generation of goto based exception handling. It can improve the produced code size but its effects can be subtle so use it with care.
|
||||
|
||||
- Tuple unpacking for variables is now treated as syntax sugar that directly
|
||||
expands into multiple assignments. Along with this, tuple unpacking for
|
||||
variables can now be nested.
|
||||
|
||||
```nim
|
||||
proc returnsNestedTuple(): (int, (int, int), int, int) = (4, (5, 7), 2, 3)
|
||||
|
||||
let (x, (_, y), _, z) = returnsNestedTuple()
|
||||
# roughly becomes
|
||||
let
|
||||
tmpTup1 = returnsNestedTuple()
|
||||
x = tmpTup1[0]
|
||||
tmpTup2 = tmpTup1[1]
|
||||
y = tmpTup2[1]
|
||||
z = tmpTup1[3]
|
||||
```
|
||||
|
||||
As a result `nnkVarTuple` nodes in variable sections will no longer be
|
||||
reflected in `typed` AST.
|
||||
|
||||
- C++ interoperability:
|
||||
- New [`virtual`](https://nim-lang.github.io/Nim/manual_experimental.html#virtual-pragma) pragma added.
|
||||
- Improvements to [`constructor`](https://nim-lang.github.io/Nim/manual_experimental.html#constructor-pragma) pragma.
|
||||
|
||||
## Compiler changes
|
||||
|
||||
- The `gc` switch has been renamed to `mm` ("memory management") in order to reflect the
|
||||
reality better. (Nim moved away from all techniques based on "tracing".)
|
||||
|
||||
- Defines the `gcRefc` symbol which allows writing specific code for the refc GC.
|
||||
|
||||
- `nim` can now compile version 1.4.0 as follows: `nim c --lib:lib --stylecheck:off compiler/nim`,
|
||||
without requiring `-d:nimVersion140` which is now a noop.
|
||||
|
||||
- `--styleCheck`, `--hintAsError` and `--warningAsError` now only apply to the current package.
|
||||
|
||||
- The switch `--nimMainPrefix:prefix` has been added to add a prefix to the names of `NimMain` and
|
||||
related functions produced on the backend. This prevents conflicts with other Nim
|
||||
static libraries.
|
||||
|
||||
- When compiling for release, the flag `-fno-math-errno` is used for GCC.
|
||||
- Removed deprecated `LineTooLong` hint.
|
||||
- Line numbers and file names of source files work correctly inside templates for JavaScript targets.
|
||||
|
||||
- Removed support for LCC (Local C), Pelles C, Digital Mars and Watcom compilers.
|
||||
|
||||
|
||||
## Docgen
|
||||
|
||||
- `Markdown` is now the default markup language of doc comments (instead
|
||||
of the legacy `RstMarkdown` mode). In this release we begin to separate
|
||||
RST and Markdown features to better follow specification of each
|
||||
language, with the focus on Markdown development.
|
||||
See also [the docs](https://nim-lang.github.io/Nim/markdown_rst.html).
|
||||
|
||||
* Added a `{.doctype: Markdown | RST | RstMarkdown.}` pragma allowing to
|
||||
select the markup language mode in the doc comments of the current `.nim`
|
||||
file for processing by `nim doc`:
|
||||
|
||||
1. `Markdown` (default) is basically CommonMark (standard Markdown) +
|
||||
some Pandoc Markdown features + some RST features that are missing
|
||||
in our current implementation of CommonMark and Pandoc Markdown.
|
||||
2. `RST` closely follows the RST spec with few additional Nim features.
|
||||
3. `RstMarkdown` is a maximum mix of RST and Markdown features, which
|
||||
is kept for the sake of compatibility and ease of migration.
|
||||
|
||||
* Added separate `md2html` and `rst2html` commands for processing
|
||||
standalone `.md` and `.rst` files respectively (and also `md2tex`/`rst2tex`).
|
||||
|
||||
- Added Pandoc Markdown bracket syntax `[...]` for making anchor-less links.
|
||||
- Docgen now supports concise syntax for referencing Nim symbols:
|
||||
instead of specifying HTML anchors directly one can use original
|
||||
Nim symbol declarations (adding the aforementioned link brackets
|
||||
`[...]` around them).
|
||||
* To use this feature across modules, a new `importdoc` directive was added.
|
||||
Using this feature for referencing also helps to ensure that links
|
||||
(inside one module or the whole project) are not broken.
|
||||
- Added support for RST & Markdown quote blocks (blocks starting with `>`).
|
||||
- Added a popular Markdown definition lists extension.
|
||||
- Added Markdown indented code blocks (blocks indented by >= 4 spaces).
|
||||
- Added syntax for additional parameters to Markdown code blocks:
|
||||
|
||||
```nim test="nim c $1"
|
||||
...
|
||||
```
|
||||
|
||||
## Tool changes
|
||||
|
||||
- Nim now ships Nimble version 0.14 which added support for lock-files. Libraries are stored in `$nimbleDir/pkgs2` (it was `$nimbleDir/pkgs` before). Use `nimble develop --global` to create an old style link file in the special links directory documented at https://github.com/nim-lang/nimble#nimble-develop.
|
||||
- nimgrep added the option `--inContext` (and `--notInContext`), which
|
||||
allows to filter only matches with the context block containing a given pattern.
|
||||
- nimgrep: names of options containing "include/exclude" are deprecated,
|
||||
e.g. instead of `--includeFile` and `--excludeFile` we have
|
||||
`--filename` and `--notFilename` respectively.
|
||||
Also the semantics are now consistent for such positive/negative filters.
|
||||
- koch now supports the `--skipIntegrityCheck` option. The command `koch --skipIntegrityCheck boot -d:release` always builds the compiler twice.
|
||||
12
changelogs/changelog_2_2_0.md
Normal file
12
changelogs/changelog_2_2_0.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# v2.2.0 - 2023-mm-dd
|
||||
|
||||
## Changes affecting backward compatibility
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
## Language changes
|
||||
|
||||
## Compiler changes
|
||||
|
||||
## Tool changes
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
This is an example file.
|
||||
The changes should go to changelog.md!
|
||||
|
||||
## Changes affecting backward compatibility
|
||||
|
||||
- `foo` now behaves differently, use `-d:nimLegacyFoo` for previous behavior.
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
- Added `example.exampleProc`.
|
||||
- Changed `example.foo` to take additional `bar` parameter.
|
||||
|
||||
- Changed `example.foo` to take additional `bar` parameter.
|
||||
|
||||
## Language changes
|
||||
|
||||
|
||||
26
ci/action.nim
Normal file
26
ci/action.nim
Normal file
@@ -0,0 +1,26 @@
|
||||
import std/[strutils, os, osproc, parseutils, strformat]
|
||||
|
||||
|
||||
proc main() =
|
||||
var msg = ""
|
||||
const cmd = "./koch boot --mm:orc -d:release"
|
||||
|
||||
let (output, exitCode) = execCmdEx(cmd)
|
||||
|
||||
doAssert exitCode == 0, output
|
||||
|
||||
let start = rfind(output, "Hint: mm")
|
||||
doAssert parseUntil(output, msg, "; proj", start) > 0, output
|
||||
|
||||
let (commitHash, _) = execCmdEx("""git log --format="%H" -n 1""")
|
||||
|
||||
let welcomeMessage = fmt"""Thanks for your hard work on this PR!
|
||||
The lines below are statistics of the Nim compiler built from {commitHash}
|
||||
|
||||
{msg}
|
||||
"""
|
||||
createDir "ci/nimcache"
|
||||
writeFile "ci/nimcache/results.txt", welcomeMessage
|
||||
|
||||
when isMainModule:
|
||||
main()
|
||||
14
ci/build.bat
14
ci/build.bat
@@ -1,14 +0,0 @@
|
||||
REM Some debug info
|
||||
echo "Running on %CI_RUNNER_ID% (%CI_RUNNER_DESCRIPTION%) with tags %CI_RUNNER_TAGS%."
|
||||
gcc -v
|
||||
|
||||
git clone --depth 1 https://github.com/nim-lang/csources.git
|
||||
cd csources
|
||||
call build64.bat
|
||||
cd ..
|
||||
set PATH=%CD%\bin;%PATH%
|
||||
nim -v
|
||||
nim c koch
|
||||
koch.exe boot
|
||||
copy bin/nim bin/nimd
|
||||
koch.exe boot -d:release
|
||||
15
ci/build.sh
15
ci/build.sh
@@ -1,15 +0,0 @@
|
||||
sh ci/deps.sh
|
||||
|
||||
# Build from C sources.
|
||||
git clone --depth 1 https://github.com/nim-lang/csources.git
|
||||
cd csources
|
||||
sh build.sh
|
||||
cd ..
|
||||
# Add Nim to the PATH
|
||||
export PATH=$(pwd)/bin${PATH:+:$PATH}
|
||||
# Bootstrap.
|
||||
nim -v
|
||||
nim c koch
|
||||
./koch boot
|
||||
cp bin/nim bin/nimd
|
||||
./koch boot -d:release
|
||||
26
ci/build_autogen.bat
Normal file
26
ci/build_autogen.bat
Normal file
@@ -0,0 +1,26 @@
|
||||
@echo off
|
||||
rem DO NO EDIT DIRECTLY! auto-generated by `nim r tools/ci_generate.nim`
|
||||
rem Build development version of the compiler; can be rerun safely
|
||||
rem bare bones version of ci/funs.sh adapted for windows.
|
||||
|
||||
rem Read in some common shared variables (shared with other tools),
|
||||
rem see https://stackoverflow.com/questions/3068929/how-to-read-file-contents-into-a-variable-in-a-batch-file
|
||||
for /f "delims== tokens=1,2" %%G in (config/build_config.txt) do set %%G=%%H
|
||||
SET nim_csources=bin\nim_csources_%nim_csourcesHash%.exe
|
||||
echo "building from csources: %nim_csources%"
|
||||
|
||||
if not exist %nim_csourcesDir% (
|
||||
git clone -q --depth 1 -b %nim_csourcesBranch% %nim_csourcesUrl% %nim_csourcesDir%
|
||||
)
|
||||
|
||||
if not exist %nim_csources% (
|
||||
cd %nim_csourcesDir%
|
||||
git checkout %nim_csourcesHash%
|
||||
echo "%PROCESSOR_ARCHITECTURE%"
|
||||
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
|
||||
SET ARCH=64
|
||||
)
|
||||
CALL build.bat
|
||||
cd ..
|
||||
copy /y bin\nim.exe %nim_csources%
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
nim e install_nimble.nims
|
||||
nim e tests/test_nimscript.nims
|
||||
nimble update
|
||||
nimble install -y zip opengl sdl1 jester@#head niminst
|
||||
16
ci/deps.sh
16
ci/deps.sh
@@ -1,16 +0,0 @@
|
||||
# Some debug info
|
||||
echo "Running on $CI_RUNNER_ID ($CI_RUNNER_DESCRIPTION) with tags $CI_RUNNER_TAGS."
|
||||
|
||||
# Packages
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq build-essential git libcurl4-openssl-dev libsdl1.2-dev libgc-dev nodejs
|
||||
|
||||
gcc -v
|
||||
|
||||
export PATH=$(pwd)/bin${PATH:+:$PATH}
|
||||
|
||||
# Nimble deps
|
||||
nim e install_nimble.nims
|
||||
nim e tests/test_nimscript.nims
|
||||
nimble update
|
||||
nimble install zip opengl sdl1 jester@#head niminst
|
||||
143
ci/funs.sh
143
ci/funs.sh
@@ -1,8 +1,147 @@
|
||||
# utilities used in CI pipelines to avoid duplication.
|
||||
# Utilities used in CI pipelines and tooling to avoid duplication.
|
||||
# Avoid top-level statements.
|
||||
# Prefer nim scripts whenever possible.
|
||||
# functions starting with `_` are considered internal, less stable.
|
||||
|
||||
echo_run () {
|
||||
# echo's a command before running it, which helps understanding logs
|
||||
echo ""
|
||||
echo "$@"
|
||||
echo "cmd: $@" # in azure we could also use this: echo '##[section]"$@"'
|
||||
"$@"
|
||||
}
|
||||
|
||||
nimGetLastCommit() {
|
||||
git log --no-merges -1 --pretty=format:"%s"
|
||||
}
|
||||
|
||||
nimIsCiSkip(){
|
||||
# D20210329T004830:here refs https://github.com/microsoft/azure-pipelines-agent/issues/2944
|
||||
# `--no-merges` is needed to avoid merge commits which occur for PR's.
|
||||
# $(Build.SourceVersionMessage) is not helpful
|
||||
# nor is `github.event.head_commit.message` for github actions.
|
||||
# Note: `[skip ci]` is now handled automatically for github actions, see https://github.blog/changelog/2021-02-08-github-actions-skip-pull-request-and-push-workflows-with-skip-ci/
|
||||
commitMsg=$(nimGetLastCommit)
|
||||
echo commitMsg: "$commitMsg"
|
||||
if [[ $commitMsg == *"[skip ci]"* ]]; then
|
||||
echo "skipci: true"
|
||||
return 0
|
||||
else
|
||||
echo "skipci: false"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
nimInternalInstallDepsWindows(){
|
||||
echo_run mkdir dist
|
||||
echo_run curl -L https://nim-lang.org/download/mingw64.7z -o dist/mingw64.7z
|
||||
echo_run curl -L https://nim-lang.org/download/dlls.zip -o dist/dlls.zip
|
||||
echo_run 7z x dist/mingw64.7z -odist
|
||||
echo_run 7z x dist/dlls.zip -obin
|
||||
}
|
||||
|
||||
nimInternalBuildKochAndRunCI(){
|
||||
echo_run nim c koch
|
||||
if ! echo_run ./koch runCI; then
|
||||
echo_run echo "runCI failed"
|
||||
echo_run nim r tools/ci_testresults.nim
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
nimDefineVars(){
|
||||
. config/build_config.txt
|
||||
nim_csources=bin/nim_csources_$nim_csourcesHash
|
||||
}
|
||||
|
||||
_nimNumCpu(){
|
||||
# linux: $(nproc)
|
||||
# FreeBSD | macOS: $(sysctl -n hw.ncpu)
|
||||
# OpenBSD: $(sysctl -n hw.ncpuonline)
|
||||
# windows: $NUMBER_OF_PROCESSORS ?
|
||||
if env | grep -q '^NIMCORES='; then
|
||||
echo $NIMCORES
|
||||
else
|
||||
echo $(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || 1)
|
||||
fi
|
||||
}
|
||||
|
||||
_nimBuildCsourcesIfNeeded(){
|
||||
# if some systems cannot use make or gmake, we could add support for calling `build.sh`
|
||||
# but this is slower (not parallel jobs) and would require making build.sh
|
||||
# understand the arguments passed to the makefile (e.g. `CC=gcc ucpu=amd64 uos=darwin`),
|
||||
# instead of `--cpu amd64 --os darwin`.
|
||||
unamestr=$(uname)
|
||||
# uname values: https://en.wikipedia.org/wiki/Uname
|
||||
if [ "$unamestr" = 'FreeBSD' ]; then
|
||||
makeX=gmake
|
||||
elif [ "$unamestr" = 'OpenBSD' ]; then
|
||||
makeX=gmake
|
||||
elif [ "$unamestr" = 'NetBSD' ]; then
|
||||
makeX=gmake
|
||||
elif [ "$unamestr" = 'CROSSOS' ]; then
|
||||
makeX=gmake
|
||||
elif [ "$unamestr" = 'SunOS' ]; then
|
||||
makeX=gmake
|
||||
else
|
||||
makeX=make
|
||||
fi
|
||||
nCPU=$(_nimNumCpu)
|
||||
echo_run which $makeX
|
||||
# parallel jobs (5X faster on 16 cores: 10s instead of 50s)
|
||||
echo_run $makeX -C $nim_csourcesDir -j $((nCPU + 2)) -l $nCPU "$@"
|
||||
# keep $nim_csources in case needed to investigate bootstrap issues
|
||||
# without having to rebuild
|
||||
echo_run cp bin/nim $nim_csources
|
||||
}
|
||||
|
||||
nimCiSystemInfo(){
|
||||
nimDefineVars
|
||||
echo_run eval echo '$'nim_csources
|
||||
echo_run pwd
|
||||
echo_run date
|
||||
echo_run uname -a
|
||||
echo_run git log --no-merges -1 --pretty=oneline
|
||||
echo_run eval echo '$'PATH
|
||||
echo_run gcc -v
|
||||
echo_run node -v
|
||||
echo_run make -v
|
||||
}
|
||||
|
||||
nimCsourcesHash(){
|
||||
nimDefineVars
|
||||
echo $nim_csourcesHash
|
||||
}
|
||||
|
||||
nimBuildCsourcesIfNeeded(){
|
||||
# goal: allow cachine each tagged version independently
|
||||
# to avoid rebuilding, so that tools like `git bisect`
|
||||
# can grab a cached past version without rebuilding.
|
||||
nimDefineVars
|
||||
(
|
||||
set -e
|
||||
# avoid polluting caller scope with internal variable definitions.
|
||||
if test -f "$nim_csources"; then
|
||||
echo "$nim_csources exists."
|
||||
else
|
||||
if test -d "$nim_csourcesDir"; then
|
||||
echo "$nim_csourcesDir exists."
|
||||
else
|
||||
# Note: using git tags would allow fetching just what's needed, unlike git hashes, e.g.
|
||||
# via `git clone -q --depth 1 --branch $tag $nim_csourcesUrl`.
|
||||
echo_run git clone -q --depth 1 -b $nim_csourcesBranch \
|
||||
$nim_csourcesUrl "$nim_csourcesDir"
|
||||
# old `git` versions don't support -C option, using `cd` explicitly:
|
||||
echo_run cd "$nim_csourcesDir"
|
||||
echo_run git checkout $nim_csourcesHash
|
||||
echo_run cd "$OLDPWD"
|
||||
# if needed we could also add: `git reset --hard $nim_csourcesHash`
|
||||
fi
|
||||
_nimBuildCsourcesIfNeeded "$@"
|
||||
fi
|
||||
|
||||
echo_run rm -f bin/nim
|
||||
# fixes bug #17913, but it's unclear why it's needed, maybe specific to MacOS Big Sur 11.3 on M1 arch?
|
||||
echo_run cp $nim_csources bin/nim
|
||||
echo_run $nim_csources -v
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
REM - Run the full testsuite; testament\tester all
|
||||
|
||||
REM - Uncomment the list of changes in news.txt
|
||||
REM - write a news ticker entry
|
||||
REM - Update the version
|
||||
|
||||
REM - Generate the full docs; koch web0
|
||||
REM - Generate the installers;
|
||||
REM - Update the version in system.nim
|
||||
REM - Test the installers
|
||||
REM - Tag the release
|
||||
REM - Merge devel into master
|
||||
REM - Update csources
|
||||
|
||||
set NIMVER=%1
|
||||
|
||||
Rem Build -docs file:
|
||||
koch web0
|
||||
cd web\upload
|
||||
7z a -tzip docs-%NIMVER%.zip *.html
|
||||
move /y docs-%NIMVER%.zip download
|
||||
cd ..\..
|
||||
|
||||
Rem Build csources
|
||||
koch csources -d:release || exit /b
|
||||
|
||||
rem Grab C sources and nimsuggest
|
||||
git clone --depth 1 https://github.com/nim-lang/csources.git
|
||||
|
||||
set PATH=%CD%\bin;%PATH%
|
||||
|
||||
ReM Build Win32 version:
|
||||
|
||||
set PATH=C:\Users\araq\projects\mingw32\bin;%PATH%
|
||||
cd csources
|
||||
call build.bat
|
||||
cd ..
|
||||
ReM Rebuilding koch is necessary because it uses its pointer size to determine
|
||||
ReM which mingw link to put in the NSIS installer.
|
||||
nim c --out:koch_temp koch || exit /b
|
||||
koch_temp boot -d:release || exit /b
|
||||
koch_temp nsis -d:release || exit /b
|
||||
koch_temp zip -d:release || exit /b
|
||||
dir build
|
||||
move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x32.exe || exit /b
|
||||
move /y build\nim-%NIMVER%.zip build\nim-%NIMVER%_x32.zip || exit /b
|
||||
|
||||
|
||||
ReM Build Win64 version:
|
||||
set PATH=C:\Users\araq\projects\mingw64\bin;%PATH%
|
||||
cd csources
|
||||
call build64.bat
|
||||
cd ..
|
||||
nim c --out:koch_temp koch || exit /b
|
||||
koch_temp boot -d:release || exit /b
|
||||
koch_temp nsis -d:release || exit /b
|
||||
koch_temp zip -d:release || exit /b
|
||||
move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x64.exe || exit /b
|
||||
move /y build\nim-%NIMVER%.zip build\nim-%NIMVER%_x64.zip || exit /b
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
version = system.NimVersion
|
||||
author = "Andreas Rumpf"
|
||||
description = "Compiler package providing the compiler sources as a library."
|
||||
license = "MIT"
|
||||
|
||||
installDirs = @["compiler", "nimsuggest"]
|
||||
|
||||
requires "nim >= 0.14.0"
|
||||
128
compiler/aliasanalysis.nim
Normal file
128
compiler/aliasanalysis.nim
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
import ast
|
||||
|
||||
import std / assertions
|
||||
|
||||
const
|
||||
PathKinds0* = {nkDotExpr, nkCheckedFieldExpr,
|
||||
nkBracketExpr, nkDerefExpr, nkHiddenDeref,
|
||||
nkAddr, nkHiddenAddr,
|
||||
nkObjDownConv, nkObjUpConv}
|
||||
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv}
|
||||
|
||||
proc skipConvDfa*(n: PNode): PNode =
|
||||
result = n
|
||||
while true:
|
||||
case result.kind
|
||||
of nkObjDownConv, nkObjUpConv:
|
||||
result = result[0]
|
||||
of PathKinds1:
|
||||
result = result[1]
|
||||
else: break
|
||||
|
||||
proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool =
|
||||
var n = orig
|
||||
while true:
|
||||
case n.kind
|
||||
of PathKinds0 - {nkHiddenDeref, nkDerefExpr}:
|
||||
n = n[0]
|
||||
of PathKinds1:
|
||||
n = n[1]
|
||||
of nkHiddenDeref, nkDerefExpr:
|
||||
# We "own" sinkparam[].loc but not ourVar[].location as it is a nasty
|
||||
# pointer indirection.
|
||||
# bug #14159, we cannot reason about sinkParam[].location as it can
|
||||
# still be shared for tyRef.
|
||||
n = n[0]
|
||||
return n.kind == nkSym and n.sym.owner == owner and
|
||||
(n.sym.typ.skipTypes(abstractInst-{tyOwned}).kind in {tyOwned})
|
||||
else: break
|
||||
# XXX Allow closure deref operations here if we know
|
||||
# the owner controlled the closure allocation?
|
||||
result = n.kind == nkSym and n.sym.owner == owner and
|
||||
{sfGlobal, sfThread, sfCursor} * n.sym.flags == {} and
|
||||
(n.sym.kind != skParam or isSinkParam(n.sym)) # or n.sym.typ.kind == tyVar)
|
||||
# Note: There is a different move analyzer possible that checks for
|
||||
# consume(param.key); param.key = newValue for all paths. Then code like
|
||||
#
|
||||
# let splited = split(move self.root, x)
|
||||
# self.root = merge(splited.lower, splited.greater)
|
||||
#
|
||||
# could be written without the ``move self.root``. However, this would be
|
||||
# wrong! Then the write barrier for the ``self.root`` assignment would
|
||||
# free the old data and all is lost! Lesson: Don't be too smart, trust the
|
||||
# lower level C++ optimizer to specialize this code.
|
||||
|
||||
type AliasKind* = enum
|
||||
yes, no, maybe
|
||||
|
||||
proc aliases*(obj, field: PNode): AliasKind =
|
||||
# obj -> field:
|
||||
# x -> x: true
|
||||
# x -> x.f: true
|
||||
# x.f -> x: false
|
||||
# x.f -> x.f: true
|
||||
# x.f -> x.v: false
|
||||
# x -> x[]: true
|
||||
# x[] -> x: false
|
||||
# x -> x[0]: true
|
||||
# x[0] -> x: false
|
||||
# x[0] -> x[0]: true
|
||||
# x[0] -> x[1]: false
|
||||
# x -> x[i]: true
|
||||
# x[i] -> x: false
|
||||
# x[i] -> x[i]: maybe; Further analysis could make this return true when i is a runtime-constant
|
||||
# x[i] -> x[j]: maybe; also returns maybe if only one of i or j is a compiletime-constant
|
||||
template collectImportantNodes(result, n) =
|
||||
var result: seq[PNode] = @[]
|
||||
var n = n
|
||||
while true:
|
||||
case n.kind
|
||||
of PathKinds0 - {nkDotExpr, nkBracketExpr, nkDerefExpr, nkHiddenDeref}:
|
||||
n = n[0]
|
||||
of PathKinds1:
|
||||
n = n[1]
|
||||
of nkDotExpr, nkBracketExpr, nkDerefExpr, nkHiddenDeref:
|
||||
result.add n
|
||||
n = n[0]
|
||||
of nkSym:
|
||||
result.add n
|
||||
break
|
||||
else: return no
|
||||
|
||||
collectImportantNodes(objImportantNodes, obj)
|
||||
collectImportantNodes(fieldImportantNodes, field)
|
||||
|
||||
# If field is less nested than obj, then it cannot be part of/aliased by obj
|
||||
if fieldImportantNodes.len < objImportantNodes.len: return no
|
||||
|
||||
result = yes
|
||||
for i in 1..objImportantNodes.len:
|
||||
# We compare the nodes leading to the location of obj and field
|
||||
# with each other.
|
||||
# We continue until they diverge, in which case we return no, or
|
||||
# until we reach the location of obj, in which case we do not need
|
||||
# to look further, since field must be part of/aliased by obj now.
|
||||
# If we encounter an element access using an index which is a runtime value,
|
||||
# we simply return maybe instead of yes; should further nodes not diverge.
|
||||
let currFieldPath = fieldImportantNodes[^i]
|
||||
let currObjPath = objImportantNodes[^i]
|
||||
|
||||
if currFieldPath.kind != currObjPath.kind:
|
||||
return no
|
||||
|
||||
case currFieldPath.kind
|
||||
of nkSym:
|
||||
if currFieldPath.sym != currObjPath.sym: return no
|
||||
of nkDotExpr:
|
||||
if currFieldPath[1].sym != currObjPath[1].sym: return no
|
||||
of nkDerefExpr, nkHiddenDeref:
|
||||
discard
|
||||
of nkBracketExpr:
|
||||
if currFieldPath[1].kind in nkLiterals and currObjPath[1].kind in nkLiterals:
|
||||
if currFieldPath[1].intVal != currObjPath[1].intVal:
|
||||
return no
|
||||
else:
|
||||
result = maybe
|
||||
else: assert false # unreachable
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
## Simple alias analysis for the HLO and the code generators.
|
||||
|
||||
import
|
||||
ast, astalgo, types, trees, intsets
|
||||
ast, astalgo, types, trees
|
||||
|
||||
import std/intsets
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
TAnalysisResult* = enum
|
||||
@@ -46,14 +51,16 @@ proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult =
|
||||
if compareTypes(a, b, dcEqIgnoreDistinct): return arYes
|
||||
case a.kind
|
||||
of tyObject:
|
||||
if a[0] != nil:
|
||||
result = isPartOfAux(a[0].skipTypes(skipPtrs), b, marker)
|
||||
if a.baseClass != nil:
|
||||
result = isPartOfAux(a.baseClass.skipTypes(skipPtrs), b, marker)
|
||||
if result == arNo: result = isPartOfAux(a.n, b, marker)
|
||||
of tyGenericInst, tyDistinct, tyAlias, tySink:
|
||||
result = isPartOfAux(lastSon(a), b, marker)
|
||||
of tyArray, tySet, tyTuple:
|
||||
for i in 0..<a.len:
|
||||
result = isPartOfAux(a[i], b, marker)
|
||||
result = isPartOfAux(skipModifier(a), b, marker)
|
||||
of tySet, tyArray:
|
||||
result = isPartOfAux(a.elementType, b, marker)
|
||||
of tyTuple:
|
||||
for aa in a.kids:
|
||||
result = isPartOfAux(aa, b, marker)
|
||||
if result == arYes: return
|
||||
else: discard
|
||||
|
||||
@@ -74,24 +81,29 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
|
||||
## cases:
|
||||
##
|
||||
## YES-cases:
|
||||
## ```
|
||||
## x <| x # for general trees
|
||||
## x[] <| x
|
||||
## x[i] <| x
|
||||
## x.f <| x
|
||||
## ```
|
||||
##
|
||||
## NO-cases:
|
||||
## ```
|
||||
## x !<| y # depending on type and symbol kind
|
||||
## x[constA] !<| x[constB]
|
||||
## x.f !<| x.g
|
||||
## x.f !<| y.f iff x !<= y
|
||||
## ```
|
||||
##
|
||||
## MAYBE-cases:
|
||||
##
|
||||
## ```
|
||||
## x[] ?<| y[] iff compatible type
|
||||
##
|
||||
##
|
||||
## x[] ?<| y depending on type
|
||||
##
|
||||
## ```
|
||||
if a.kind == b.kind:
|
||||
case a.kind
|
||||
of nkSym:
|
||||
@@ -106,6 +118,8 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
|
||||
# use expensive type check:
|
||||
if isPartOf(a.sym.typ, b.sym.typ) != arNo:
|
||||
result = arMaybe
|
||||
else:
|
||||
result = arNo
|
||||
of nkBracketExpr:
|
||||
result = isPartOf(a[0], b[0])
|
||||
if a.len >= 2 and b.len >= 2:
|
||||
@@ -141,7 +155,7 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
|
||||
result = isPartOf(a[1], b[1])
|
||||
of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
|
||||
result = isPartOf(a[0], b[0])
|
||||
else: discard
|
||||
else: result = arNo
|
||||
# Calls return a new location, so a default of ``arNo`` is fine.
|
||||
else:
|
||||
# go down recursively; this is quite demanding:
|
||||
@@ -157,6 +171,7 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
|
||||
|
||||
of DerefKinds:
|
||||
# a* !<| b[] iff
|
||||
result = arNo
|
||||
if isPartOf(a.typ, b.typ) != arNo:
|
||||
result = isPartOf(a, b[0])
|
||||
if result == arNo: result = arMaybe
|
||||
@@ -178,7 +193,9 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
|
||||
if isPartOf(a.typ, b.typ) != arNo:
|
||||
result = isPartOf(a[0], b)
|
||||
if result == arNo: result = arMaybe
|
||||
else: discard
|
||||
else:
|
||||
result = arNo
|
||||
else: result = arNo
|
||||
of nkObjConstr:
|
||||
result = arNo
|
||||
for i in 1..<b.len:
|
||||
@@ -196,4 +213,6 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
|
||||
of nkBracket:
|
||||
if b.len > 0:
|
||||
result = isPartOf(a, b[0])
|
||||
else: discard
|
||||
else:
|
||||
result = arNo
|
||||
else: result = arNo
|
||||
|
||||
1008
compiler/ast.nim
1008
compiler/ast.nim
File diff suppressed because it is too large
Load Diff
@@ -12,18 +12,18 @@
|
||||
# the data structures here are used in various places of the compiler.
|
||||
|
||||
import
|
||||
ast, hashes, intsets, strutils, options, lineinfos, ropes, idents, rodutils,
|
||||
ast, astyaml, options, lineinfos, idents, rodutils,
|
||||
msgs
|
||||
|
||||
proc hashNode*(p: RootRef): Hash
|
||||
proc treeToYaml*(conf: ConfigRef; n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope
|
||||
# Convert a tree into its YAML representation; this is used by the
|
||||
# YAML code generator and it is invaluable for debugging purposes.
|
||||
# If maxRecDepht <> -1 then it won't print the whole graph.
|
||||
proc typeToYaml*(conf: ConfigRef; n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope
|
||||
proc symToYaml*(conf: ConfigRef; n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope
|
||||
proc lineInfoToStr*(conf: ConfigRef; info: TLineInfo): Rope
|
||||
import std/[hashes, intsets]
|
||||
import std/strutils except addf
|
||||
|
||||
export astyaml.treeToYaml, astyaml.typeToYaml, astyaml.symToYaml, astyaml.lineInfoToStr
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
proc hashNode*(p: RootRef): Hash
|
||||
|
||||
# these are for debugging only: They are not really deprecated, but I want
|
||||
# the warning so that release versions do not contain debugging statements:
|
||||
@@ -31,15 +31,6 @@ proc debug*(n: PSym; conf: ConfigRef = nil) {.exportc: "debugSym", deprecated.}
|
||||
proc debug*(n: PType; conf: ConfigRef = nil) {.exportc: "debugType", deprecated.}
|
||||
proc debug*(n: PNode; conf: ConfigRef = nil) {.exportc: "debugNode", deprecated.}
|
||||
|
||||
proc typekinds*(t: PType) {.deprecated.} =
|
||||
var t = t
|
||||
var s = ""
|
||||
while t != nil and t.len > 0:
|
||||
s.add $t.kind
|
||||
s.add " "
|
||||
t = t.lastSon
|
||||
echo s
|
||||
|
||||
template debug*(x: PSym|PType|PNode) {.deprecated.} =
|
||||
when compiles(c.config):
|
||||
debug(c.config, x)
|
||||
@@ -74,16 +65,6 @@ template mdbg*: bool {.deprecated.} =
|
||||
else:
|
||||
error()
|
||||
|
||||
# --------------------------- ident tables ----------------------------------
|
||||
proc idTableGet*(t: TIdTable, key: PIdObj): RootRef
|
||||
proc idTableGet*(t: TIdTable, key: int): RootRef
|
||||
proc idTablePut*(t: var TIdTable, key: PIdObj, val: RootRef)
|
||||
proc idTableHasObjectAsKey*(t: TIdTable, key: PIdObj): bool
|
||||
# checks if `t` contains the `key` (compared by the pointer value, not only
|
||||
# `key`'s id)
|
||||
proc idNodeTableGet*(t: TIdNodeTable, key: PIdObj): PNode
|
||||
proc idNodeTablePut*(t: var TIdNodeTable, key: PIdObj, val: PNode)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc lookupInRecord*(n: PNode, field: PIdent): PSym
|
||||
@@ -104,7 +85,7 @@ type
|
||||
data*: TIIPairSeq
|
||||
|
||||
|
||||
proc initIiTable*(x: var TIITable)
|
||||
proc initIITable*(x: var TIITable)
|
||||
proc iiTableGet*(t: TIITable, key: int): int
|
||||
proc iiTablePut*(t: var TIITable, key, val: int)
|
||||
|
||||
@@ -192,6 +173,7 @@ proc getSymFromList*(list: PNode, ident: PIdent, start: int = 0): PSym =
|
||||
result = nil
|
||||
|
||||
proc sameIgnoreBacktickGensymInfo(a, b: string): bool =
|
||||
result = false
|
||||
if a[0] != b[0]: return false
|
||||
var alen = a.len - 1
|
||||
while alen > 0 and a[alen] != '`': dec(alen)
|
||||
@@ -221,11 +203,11 @@ proc getNamedParamFromList*(list: PNode, ident: PIdent): PSym =
|
||||
## Named parameters are special because a named parameter can be
|
||||
## gensym'ed and then they have '\`<number>' suffix that we need to
|
||||
## ignore, see compiler / evaltempl.nim, snippet:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
##
|
||||
## ```nim
|
||||
## result.add newIdentNode(getIdent(c.ic, x.name.s & "\`gensym" & $x.id),
|
||||
## if c.instLines: actual.info else: templ.info)
|
||||
## ```
|
||||
result = nil
|
||||
for i in 1..<list.len:
|
||||
let it = list[i].sym
|
||||
if it.name.id == ident.id or
|
||||
@@ -238,169 +220,7 @@ proc mustRehash(length, counter: int): bool =
|
||||
assert(length > counter)
|
||||
result = (length * 2 < counter * 3) or (length - counter < 4)
|
||||
|
||||
proc rspaces(x: int): Rope =
|
||||
# returns x spaces
|
||||
result = rope(spaces(x))
|
||||
|
||||
proc toYamlChar(c: char): string =
|
||||
case c
|
||||
of '\0'..'\x1F', '\x7F'..'\xFF': result = "\\u" & strutils.toHex(ord(c), 4)
|
||||
of '\'', '\"', '\\': result = '\\' & c
|
||||
else: result = $c
|
||||
|
||||
proc makeYamlString*(s: string): Rope =
|
||||
# We have to split long strings into many ropes. Otherwise
|
||||
# this could trigger InternalError(111). See the ropes module for
|
||||
# further information.
|
||||
const MaxLineLength = 64
|
||||
result = nil
|
||||
var res = "\""
|
||||
for i in 0..<s.len:
|
||||
if (i + 1) mod MaxLineLength == 0:
|
||||
res.add('\"')
|
||||
res.add("\n")
|
||||
result.add(rope(res))
|
||||
res = "\"" # reset
|
||||
res.add(toYamlChar(s[i]))
|
||||
res.add('\"')
|
||||
result.add(rope(res))
|
||||
|
||||
proc flagsToStr[T](flags: set[T]): Rope =
|
||||
if flags == {}:
|
||||
result = rope("[]")
|
||||
else:
|
||||
result = nil
|
||||
for x in items(flags):
|
||||
if result != nil: result.add(", ")
|
||||
result.add(makeYamlString($x))
|
||||
result = "[" & result & "]"
|
||||
|
||||
proc lineInfoToStr(conf: ConfigRef; info: TLineInfo): Rope =
|
||||
result = "[$1, $2, $3]" % [makeYamlString(toFilename(conf, info)),
|
||||
rope(toLinenumber(info)),
|
||||
rope(toColumn(info))]
|
||||
|
||||
proc treeToYamlAux(conf: ConfigRef; n: PNode, marker: var IntSet,
|
||||
indent, maxRecDepth: int): Rope
|
||||
proc symToYamlAux(conf: ConfigRef; n: PSym, marker: var IntSet,
|
||||
indent, maxRecDepth: int): Rope
|
||||
proc typeToYamlAux(conf: ConfigRef; n: PType, marker: var IntSet,
|
||||
indent, maxRecDepth: int): Rope
|
||||
|
||||
proc symToYamlAux(conf: ConfigRef; n: PSym, marker: var IntSet, indent: int,
|
||||
maxRecDepth: int): Rope =
|
||||
if n == nil:
|
||||
result = rope("null")
|
||||
elif containsOrIncl(marker, n.id):
|
||||
result = "\"$1\"" % [rope(n.name.s)]
|
||||
else:
|
||||
var ast = treeToYamlAux(conf, n.ast, marker, indent + 2, maxRecDepth - 1)
|
||||
#rope("typ"), typeToYamlAux(conf, n.typ, marker,
|
||||
# indent + 2, maxRecDepth - 1),
|
||||
let istr = rspaces(indent + 2)
|
||||
result = rope("{")
|
||||
result.addf("$N$1\"kind\": $2", [istr, makeYamlString($n.kind)])
|
||||
result.addf("$N$1\"name\": $2", [istr, makeYamlString(n.name.s)])
|
||||
result.addf("$N$1\"typ\": $2", [istr, typeToYamlAux(conf, n.typ, marker, indent + 2, maxRecDepth - 1)])
|
||||
if conf != nil:
|
||||
# if we don't pass the config, we probably don't care about the line info
|
||||
result.addf("$N$1\"info\": $2", [istr, lineInfoToStr(conf, n.info)])
|
||||
if card(n.flags) > 0:
|
||||
result.addf("$N$1\"flags\": $2", [istr, flagsToStr(n.flags)])
|
||||
result.addf("$N$1\"magic\": $2", [istr, makeYamlString($n.magic)])
|
||||
result.addf("$N$1\"ast\": $2", [istr, ast])
|
||||
result.addf("$N$1\"options\": $2", [istr, flagsToStr(n.options)])
|
||||
result.addf("$N$1\"position\": $2", [istr, rope(n.position)])
|
||||
result.addf("$N$1\"k\": $2", [istr, makeYamlString($n.loc.k)])
|
||||
result.addf("$N$1\"storage\": $2", [istr, makeYamlString($n.loc.storage)])
|
||||
if card(n.loc.flags) > 0:
|
||||
result.addf("$N$1\"flags\": $2", [istr, makeYamlString($n.loc.flags)])
|
||||
result.addf("$N$1\"r\": $2", [istr, n.loc.r])
|
||||
result.addf("$N$1\"lode\": $2", [istr, treeToYamlAux(conf, n.loc.lode, marker, indent + 2, maxRecDepth - 1)])
|
||||
result.addf("$N$1}", [rspaces(indent)])
|
||||
|
||||
proc typeToYamlAux(conf: ConfigRef; n: PType, marker: var IntSet, indent: int,
|
||||
maxRecDepth: int): Rope =
|
||||
var sonsRope: Rope
|
||||
if n == nil:
|
||||
sonsRope = rope("null")
|
||||
elif containsOrIncl(marker, n.id):
|
||||
sonsRope = "\"$1 @$2\"" % [rope($n.kind), rope(
|
||||
strutils.toHex(cast[ByteAddress](n), sizeof(n) * 2))]
|
||||
else:
|
||||
if n.len > 0:
|
||||
sonsRope = rope("[")
|
||||
for i in 0..<n.len:
|
||||
if i > 0: sonsRope.add(",")
|
||||
sonsRope.addf("$N$1$2", [rspaces(indent + 4), typeToYamlAux(conf, n[i],
|
||||
marker, indent + 4, maxRecDepth - 1)])
|
||||
sonsRope.addf("$N$1]", [rspaces(indent + 2)])
|
||||
else:
|
||||
sonsRope = rope("null")
|
||||
|
||||
let istr = rspaces(indent + 2)
|
||||
result = rope("{")
|
||||
result.addf("$N$1\"kind\": $2", [istr, makeYamlString($n.kind)])
|
||||
result.addf("$N$1\"sym\": $2", [istr, symToYamlAux(conf, n.sym, marker, indent + 2, maxRecDepth - 1)])
|
||||
result.addf("$N$1\"n\": $2", [istr, treeToYamlAux(conf, n.n, marker, indent + 2, maxRecDepth - 1)])
|
||||
if card(n.flags) > 0:
|
||||
result.addf("$N$1\"flags\": $2", [istr, flagsToStr(n.flags)])
|
||||
result.addf("$N$1\"callconv\": $2", [istr, makeYamlString($n.callConv)])
|
||||
result.addf("$N$1\"size\": $2", [istr, rope(n.size)])
|
||||
result.addf("$N$1\"align\": $2", [istr, rope(n.align)])
|
||||
result.addf("$N$1\"sons\": $2", [istr, sonsRope])
|
||||
|
||||
proc treeToYamlAux(conf: ConfigRef; n: PNode, marker: var IntSet, indent: int,
|
||||
maxRecDepth: int): Rope =
|
||||
if n == nil:
|
||||
result = rope("null")
|
||||
else:
|
||||
var istr = rspaces(indent + 2)
|
||||
result = "{$N$1\"kind\": $2" % [istr, makeYamlString($n.kind)]
|
||||
if maxRecDepth != 0:
|
||||
if conf != nil:
|
||||
result.addf(",$N$1\"info\": $2", [istr, lineInfoToStr(conf, n.info)])
|
||||
case n.kind
|
||||
of nkCharLit..nkInt64Lit:
|
||||
result.addf(",$N$1\"intVal\": $2", [istr, rope(n.intVal)])
|
||||
of nkFloatLit, nkFloat32Lit, nkFloat64Lit:
|
||||
result.addf(",$N$1\"floatVal\": $2",
|
||||
[istr, rope(n.floatVal.toStrMaxPrecision)])
|
||||
of nkStrLit..nkTripleStrLit:
|
||||
result.addf(",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)])
|
||||
of nkSym:
|
||||
result.addf(",$N$1\"sym\": $2",
|
||||
[istr, symToYamlAux(conf, n.sym, marker, indent + 2, maxRecDepth)])
|
||||
of nkIdent:
|
||||
if n.ident != nil:
|
||||
result.addf(",$N$1\"ident\": $2", [istr, makeYamlString(n.ident.s)])
|
||||
else:
|
||||
result.addf(",$N$1\"ident\": null", [istr])
|
||||
else:
|
||||
if n.len > 0:
|
||||
result.addf(",$N$1\"sons\": [", [istr])
|
||||
for i in 0..<n.len:
|
||||
if i > 0: result.add(",")
|
||||
result.addf("$N$1$2", [rspaces(indent + 4), treeToYamlAux(conf, n[i],
|
||||
marker, indent + 4, maxRecDepth - 1)])
|
||||
result.addf("$N$1]", [istr])
|
||||
result.addf(",$N$1\"typ\": $2",
|
||||
[istr, typeToYamlAux(conf, n.typ, marker, indent + 2, maxRecDepth)])
|
||||
result.addf("$N$1}", [rspaces(indent)])
|
||||
|
||||
proc treeToYaml(conf: ConfigRef; n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope =
|
||||
var marker = initIntSet()
|
||||
result = treeToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
|
||||
proc typeToYaml(conf: ConfigRef; n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope =
|
||||
var marker = initIntSet()
|
||||
result = typeToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
|
||||
proc symToYaml(conf: ConfigRef; n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope =
|
||||
var marker = initIntSet()
|
||||
result = symToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
|
||||
import tables
|
||||
import std/tables
|
||||
|
||||
const backrefStyle = "\e[90m"
|
||||
const enumStyle = "\e[34m"
|
||||
@@ -564,14 +384,12 @@ proc value(this: var DebugPrinter; value: PType) =
|
||||
this.key "n"
|
||||
this.value value.n
|
||||
|
||||
if value.len > 0:
|
||||
this.key "sons"
|
||||
this.openBracket
|
||||
for i in 0..<value.len:
|
||||
this.value value[i]
|
||||
if i != value.len - 1:
|
||||
this.comma
|
||||
this.closeBracket
|
||||
this.key "sons"
|
||||
this.openBracket
|
||||
for i, a in value.ikids:
|
||||
if i > 0: this.comma
|
||||
this.value a
|
||||
this.closeBracket
|
||||
|
||||
if value.n != nil:
|
||||
this.key "n"
|
||||
@@ -585,6 +403,9 @@ proc value(this: var DebugPrinter; value: PNode) =
|
||||
this.openCurly
|
||||
this.key "kind"
|
||||
this.value value.kind
|
||||
if value.comment.len > 0:
|
||||
this.key "comment"
|
||||
this.value value.comment
|
||||
when defined(useNodeIds):
|
||||
this.key "id"
|
||||
this.value value.id
|
||||
@@ -637,30 +458,33 @@ proc value(this: var DebugPrinter; value: PNode) =
|
||||
|
||||
|
||||
proc debug(n: PSym; conf: ConfigRef) =
|
||||
var this: DebugPrinter
|
||||
this.visited = initTable[pointer, int]()
|
||||
this.renderSymType = true
|
||||
this.useColor = not defined(windows)
|
||||
var this = DebugPrinter(
|
||||
visited: initTable[pointer, int](),
|
||||
renderSymType: true,
|
||||
useColor: not defined(windows)
|
||||
)
|
||||
this.value(n)
|
||||
echo($this.res)
|
||||
|
||||
proc debug(n: PType; conf: ConfigRef) =
|
||||
var this: DebugPrinter
|
||||
this.visited = initTable[pointer, int]()
|
||||
this.renderSymType = true
|
||||
this.useColor = not defined(windows)
|
||||
var this = DebugPrinter(
|
||||
visited: initTable[pointer, int](),
|
||||
renderSymType: true,
|
||||
useColor: not defined(windows)
|
||||
)
|
||||
this.value(n)
|
||||
echo($this.res)
|
||||
|
||||
proc debug(n: PNode; conf: ConfigRef) =
|
||||
var this: DebugPrinter
|
||||
this.visited = initTable[pointer, int]()
|
||||
#this.renderSymType = true
|
||||
this.useColor = not defined(windows)
|
||||
var this = DebugPrinter(
|
||||
visited: initTable[pointer, int](),
|
||||
renderSymType: false,
|
||||
useColor: not defined(windows)
|
||||
)
|
||||
this.value(n)
|
||||
echo($this.res)
|
||||
|
||||
proc nextTry(h, maxHash: Hash): Hash =
|
||||
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
|
||||
result = ((5 * h) + 1) and maxHash
|
||||
# For any initial h in range(maxHash), repeating that maxHash times
|
||||
# generates each int in range(maxHash) exactly once (see any text on
|
||||
@@ -759,9 +583,9 @@ proc strTableAdd*(t: var TStrTable, n: PSym) =
|
||||
|
||||
proc strTableInclReportConflict*(t: var TStrTable, n: PSym;
|
||||
onConflictKeepOld = false): PSym =
|
||||
# returns true if n is already in the string table:
|
||||
# It is essential that `n` is written nevertheless!
|
||||
# This way the newest redefinition is picked by the semantic analyses!
|
||||
# if `t` has a conflicting symbol (same identifier as `n`), return it
|
||||
# otherwise return `nil`. Incl `n` to `t` unless `onConflictKeepOld = true`
|
||||
# and a conflict was found.
|
||||
assert n.name != nil
|
||||
var h: Hash = n.name.h and high(t.data)
|
||||
var replaceSlot = -1
|
||||
@@ -777,9 +601,10 @@ proc strTableInclReportConflict*(t: var TStrTable, n: PSym;
|
||||
replaceSlot = h
|
||||
h = nextTry(h, high(t.data))
|
||||
if replaceSlot >= 0:
|
||||
result = t.data[replaceSlot] # found it
|
||||
if not onConflictKeepOld:
|
||||
t.data[replaceSlot] = n # overwrite it with newer definition!
|
||||
return t.data[replaceSlot] # found it
|
||||
return result # but return the old one
|
||||
elif mustRehash(t.data.len, t.counter):
|
||||
strTableEnlarge(t)
|
||||
strTableRawInsert(t.data, n)
|
||||
@@ -808,16 +633,21 @@ type
|
||||
name*: PIdent
|
||||
|
||||
proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
|
||||
# hot spots
|
||||
var h = ti.h and high(tab.data)
|
||||
var start = h
|
||||
result = tab.data[h]
|
||||
while result != nil:
|
||||
if result.name.id == ti.name.id: break
|
||||
var p {.cursor.} = tab.data[h]
|
||||
while p != nil:
|
||||
if p.name.id == ti.name.id: break
|
||||
h = nextTry(h, high(tab.data))
|
||||
if h == start:
|
||||
result = nil
|
||||
p = nil
|
||||
break
|
||||
result = tab.data[h]
|
||||
p = tab.data[h]
|
||||
if p != nil:
|
||||
result = p # increase the count
|
||||
else:
|
||||
result = nil
|
||||
ti.h = nextTry(h, high(tab.data))
|
||||
|
||||
proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym =
|
||||
@@ -877,125 +707,12 @@ proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym =
|
||||
result = nextIter(ti, tab)
|
||||
|
||||
iterator items*(tab: TStrTable): PSym =
|
||||
var it: TTabIter
|
||||
var it: TTabIter = default(TTabIter)
|
||||
var s = initTabIter(it, tab)
|
||||
while s != nil:
|
||||
yield s
|
||||
s = nextIter(it, tab)
|
||||
|
||||
proc hasEmptySlot(data: TIdPairSeq): bool =
|
||||
for h in 0..high(data):
|
||||
if data[h].key == nil:
|
||||
return true
|
||||
result = false
|
||||
|
||||
proc idTableRawGet(t: TIdTable, key: int): int =
|
||||
var h: Hash
|
||||
h = key and high(t.data) # start with real hash value
|
||||
while t.data[h].key != nil:
|
||||
if t.data[h].key.id == key:
|
||||
return h
|
||||
h = nextTry(h, high(t.data))
|
||||
result = - 1
|
||||
|
||||
proc idTableHasObjectAsKey(t: TIdTable, key: PIdObj): bool =
|
||||
var index = idTableRawGet(t, key.id)
|
||||
if index >= 0: result = t.data[index].key == key
|
||||
else: result = false
|
||||
|
||||
proc idTableGet(t: TIdTable, key: PIdObj): RootRef =
|
||||
var index = idTableRawGet(t, key.id)
|
||||
if index >= 0: result = t.data[index].val
|
||||
else: result = nil
|
||||
|
||||
proc idTableGet(t: TIdTable, key: int): RootRef =
|
||||
var index = idTableRawGet(t, key)
|
||||
if index >= 0: result = t.data[index].val
|
||||
else: result = nil
|
||||
|
||||
iterator pairs*(t: TIdTable): tuple[key: int, value: RootRef] =
|
||||
for i in 0..high(t.data):
|
||||
if t.data[i].key != nil:
|
||||
yield (t.data[i].key.id, t.data[i].val)
|
||||
|
||||
proc idTableRawInsert(data: var TIdPairSeq, key: PIdObj, val: RootRef) =
|
||||
var h: Hash
|
||||
h = key.id and high(data)
|
||||
while data[h].key != nil:
|
||||
assert(data[h].key.id != key.id)
|
||||
h = nextTry(h, high(data))
|
||||
assert(data[h].key == nil)
|
||||
data[h].key = key
|
||||
data[h].val = val
|
||||
|
||||
proc idTablePut(t: var TIdTable, key: PIdObj, val: RootRef) =
|
||||
var
|
||||
index: int
|
||||
n: TIdPairSeq
|
||||
index = idTableRawGet(t, key.id)
|
||||
if index >= 0:
|
||||
assert(t.data[index].key != nil)
|
||||
t.data[index].val = val
|
||||
else:
|
||||
if mustRehash(t.data.len, t.counter):
|
||||
newSeq(n, t.data.len * GrowthFactor)
|
||||
for i in 0..high(t.data):
|
||||
if t.data[i].key != nil:
|
||||
idTableRawInsert(n, t.data[i].key, t.data[i].val)
|
||||
assert(hasEmptySlot(n))
|
||||
swap(t.data, n)
|
||||
idTableRawInsert(t.data, key, val)
|
||||
inc(t.counter)
|
||||
|
||||
iterator idTablePairs*(t: TIdTable): tuple[key: PIdObj, val: RootRef] =
|
||||
for i in 0..high(t.data):
|
||||
if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val)
|
||||
|
||||
proc idNodeTableRawGet(t: TIdNodeTable, key: PIdObj): int =
|
||||
var h: Hash
|
||||
h = key.id and high(t.data) # start with real hash value
|
||||
while t.data[h].key != nil:
|
||||
if t.data[h].key.id == key.id:
|
||||
return h
|
||||
h = nextTry(h, high(t.data))
|
||||
result = - 1
|
||||
|
||||
proc idNodeTableGet(t: TIdNodeTable, key: PIdObj): PNode =
|
||||
var index: int
|
||||
index = idNodeTableRawGet(t, key)
|
||||
if index >= 0: result = t.data[index].val
|
||||
else: result = nil
|
||||
|
||||
proc idNodeTableRawInsert(data: var TIdNodePairSeq, key: PIdObj, val: PNode) =
|
||||
var h: Hash
|
||||
h = key.id and high(data)
|
||||
while data[h].key != nil:
|
||||
assert(data[h].key.id != key.id)
|
||||
h = nextTry(h, high(data))
|
||||
assert(data[h].key == nil)
|
||||
data[h].key = key
|
||||
data[h].val = val
|
||||
|
||||
proc idNodeTablePut(t: var TIdNodeTable, key: PIdObj, val: PNode) =
|
||||
var index = idNodeTableRawGet(t, key)
|
||||
if index >= 0:
|
||||
assert(t.data[index].key != nil)
|
||||
t.data[index].val = val
|
||||
else:
|
||||
if mustRehash(t.data.len, t.counter):
|
||||
var n: TIdNodePairSeq
|
||||
newSeq(n, t.data.len * GrowthFactor)
|
||||
for i in 0..high(t.data):
|
||||
if t.data[i].key != nil:
|
||||
idNodeTableRawInsert(n, t.data[i].key, t.data[i].val)
|
||||
swap(t.data, n)
|
||||
idNodeTableRawInsert(t.data, key, val)
|
||||
inc(t.counter)
|
||||
|
||||
iterator pairs*(t: TIdNodeTable): tuple[key: PIdObj, val: PNode] =
|
||||
for i in 0..high(t.data):
|
||||
if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val)
|
||||
|
||||
proc initIITable(x: var TIITable) =
|
||||
x.counter = 0
|
||||
newSeq(x.data, StartSize)
|
||||
@@ -1041,15 +758,8 @@ proc iiTablePut(t: var TIITable, key, val: int) =
|
||||
iiTableRawInsert(t.data, key, val)
|
||||
inc(t.counter)
|
||||
|
||||
proc isAddrNode*(n: PNode): bool =
|
||||
case n.kind
|
||||
of nkAddr, nkHiddenAddr: true
|
||||
of nkCallKinds:
|
||||
if n[0].kind == nkSym and n[0].sym.magic == mAddr: true
|
||||
else: false
|
||||
else: false
|
||||
|
||||
proc listSymbolNames*(symbols: openArray[PSym]): string =
|
||||
result = ""
|
||||
for sym in symbols:
|
||||
if result.len > 0:
|
||||
result.add ", "
|
||||
|
||||
45
compiler/astmsgs.nim
Normal file
45
compiler/astmsgs.nim
Normal file
@@ -0,0 +1,45 @@
|
||||
# this module avoids ast depending on msgs or vice versa
|
||||
import std/strutils
|
||||
import options, ast, msgs
|
||||
|
||||
proc typSym*(t: PType): PSym =
|
||||
result = t.sym
|
||||
if result == nil and t.kind == tyGenericInst: # this might need to be refined
|
||||
result = t.genericHead.sym
|
||||
|
||||
proc addDeclaredLoc*(result: var string, conf: ConfigRef; sym: PSym) =
|
||||
result.add " [$1 declared in $2]" % [sym.kind.toHumanStr, toFileLineCol(conf, sym.info)]
|
||||
|
||||
proc addDeclaredLocMaybe*(result: var string, conf: ConfigRef; sym: PSym) =
|
||||
if optDeclaredLocs in conf.globalOptions and sym != nil:
|
||||
addDeclaredLoc(result, conf, sym)
|
||||
|
||||
proc addDeclaredLoc*(result: var string, conf: ConfigRef; typ: PType) =
|
||||
# xxx figure out how to resolve `tyGenericParam`, e.g. for
|
||||
# proc fn[T](a: T, b: T) = discard
|
||||
# fn(1.1, "a")
|
||||
let typ = typ.skipTypes(abstractInst + {tyStatic, tySequence, tyArray, tySet, tyUserTypeClassInst, tyVar, tyRef, tyPtr} - {tyRange})
|
||||
result.add " [$1" % typ.kind.toHumanStr
|
||||
if typ.sym != nil:
|
||||
result.add " declared in " & toFileLineCol(conf, typ.sym.info)
|
||||
result.add "]"
|
||||
|
||||
proc addTypeNodeDeclaredLoc*(result: var string, conf: ConfigRef; typ: PType) =
|
||||
result.add " [$1" % typ.kind.toHumanStr
|
||||
if typ.sym != nil:
|
||||
result.add " declared in " & toFileLineCol(conf, typ.sym.info)
|
||||
result.add "]"
|
||||
|
||||
proc addDeclaredLocMaybe*(result: var string, conf: ConfigRef; typ: PType) =
|
||||
if optDeclaredLocs in conf.globalOptions: addDeclaredLoc(result, conf, typ)
|
||||
|
||||
template quoteExpr*(a: string): untyped =
|
||||
## can be used for quoting expressions in error msgs.
|
||||
"'" & a & "'"
|
||||
|
||||
proc genFieldDefect*(conf: ConfigRef, field: string, disc: PSym): string =
|
||||
let obj = disc.owner.name.s # `types.typeToString` might be better, eg for generics
|
||||
result = "field '$#' is not accessible for type '$#'" % [field, obj]
|
||||
if optDeclaredLocs in conf.globalOptions:
|
||||
result.add " [discriminant declared in $#]" % toFileLineCol(conf, disc.info)
|
||||
result.add " using '$# = " % disc.name.s
|
||||
154
compiler/astyaml.nim
Normal file
154
compiler/astyaml.nim
Normal file
@@ -0,0 +1,154 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2012 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# AST YAML printing
|
||||
|
||||
import "."/[ast, lineinfos, msgs, options, rodutils]
|
||||
import std/[intsets, strutils]
|
||||
|
||||
proc addYamlString*(res: var string; s: string) =
|
||||
res.add "\""
|
||||
for c in s:
|
||||
case c
|
||||
of '\0' .. '\x1F', '\x7F' .. '\xFF':
|
||||
res.add("\\u" & strutils.toHex(ord(c), 4))
|
||||
of '\"', '\\':
|
||||
res.add '\\' & c
|
||||
else:
|
||||
res.add c
|
||||
|
||||
res.add('\"')
|
||||
|
||||
proc makeYamlString(s: string): string =
|
||||
result = ""
|
||||
result.addYamlString(s)
|
||||
|
||||
proc flagsToStr[T](flags: set[T]): string =
|
||||
if flags == {}:
|
||||
result = "[]"
|
||||
else:
|
||||
result = ""
|
||||
for x in items(flags):
|
||||
if result != "":
|
||||
result.add(", ")
|
||||
result.addYamlString($x)
|
||||
result = "[" & result & "]"
|
||||
|
||||
proc lineInfoToStr*(conf: ConfigRef; info: TLineInfo): string =
|
||||
result = "["
|
||||
result.addYamlString(toFilename(conf, info))
|
||||
result.addf ", $1, $2]", [toLinenumber(info), toColumn(info)]
|
||||
|
||||
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; indent, maxRecDepth: int)
|
||||
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; indent, maxRecDepth: int)
|
||||
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; indent, maxRecDepth: int)
|
||||
|
||||
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; indent: int; maxRecDepth: int) =
|
||||
if n == nil:
|
||||
res.add("null")
|
||||
elif containsOrIncl(marker, n.id):
|
||||
res.addYamlString(n.name.s)
|
||||
else:
|
||||
let istr = spaces(indent * 4)
|
||||
|
||||
res.addf("kind: $1", [makeYamlString($n.kind)])
|
||||
res.addf("\n$1name: $2", [istr, makeYamlString(n.name.s)])
|
||||
res.addf("\n$1typ: ", [istr])
|
||||
res.typeToYamlAux(conf, n.typ, marker, indent + 1, maxRecDepth - 1)
|
||||
if conf != nil:
|
||||
# if we don't pass the config, we probably don't care about the line info
|
||||
res.addf("\n$1info: $2", [istr, lineInfoToStr(conf, n.info)])
|
||||
if card(n.flags) > 0:
|
||||
res.addf("\n$1flags: $2", [istr, flagsToStr(n.flags)])
|
||||
res.addf("\n$1magic: $2", [istr, makeYamlString($n.magic)])
|
||||
res.addf("\n$1ast: ", [istr])
|
||||
res.treeToYamlAux(conf, n.ast, marker, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1options: $2", [istr, flagsToStr(n.options)])
|
||||
res.addf("\n$1position: $2", [istr, $n.position])
|
||||
res.addf("\n$1k: $2", [istr, makeYamlString($n.loc.k)])
|
||||
res.addf("\n$1storage: $2", [istr, makeYamlString($n.loc.storage)])
|
||||
if card(n.loc.flags) > 0:
|
||||
res.addf("\n$1flags: $2", [istr, makeYamlString($n.loc.flags)])
|
||||
res.addf("\n$1r: $2", [istr, n.loc.r])
|
||||
res.addf("\n$1lode: $2", [istr])
|
||||
res.treeToYamlAux(conf, n.loc.lode, marker, indent + 1, maxRecDepth - 1)
|
||||
|
||||
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; indent: int; maxRecDepth: int) =
|
||||
if n == nil:
|
||||
res.add("null")
|
||||
elif containsOrIncl(marker, n.id):
|
||||
res.addf "\"$1 @$2\"" % [$n.kind, strutils.toHex(cast[uint](n), sizeof(n) * 2)]
|
||||
else:
|
||||
let istr = spaces(indent * 4)
|
||||
res.addf("kind: $2", [istr, makeYamlString($n.kind)])
|
||||
res.addf("\n$1sym: ")
|
||||
res.symToYamlAux(conf, n.sym, marker, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1n: ")
|
||||
res.treeToYamlAux(conf, n.n, marker, indent + 1, maxRecDepth - 1)
|
||||
if card(n.flags) > 0:
|
||||
res.addf("\n$1flags: $2", [istr, flagsToStr(n.flags)])
|
||||
res.addf("\n$1callconv: $2", [istr, makeYamlString($n.callConv)])
|
||||
res.addf("\n$1size: $2", [istr, $(n.size)])
|
||||
res.addf("\n$1align: $2", [istr, $(n.align)])
|
||||
if n.hasElementType:
|
||||
res.addf("\n$1sons:")
|
||||
for a in n.kids:
|
||||
res.addf("\n - ")
|
||||
res.typeToYamlAux(conf, a, marker, indent + 1, maxRecDepth - 1)
|
||||
|
||||
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; indent: int;
|
||||
maxRecDepth: int) =
|
||||
if n == nil:
|
||||
res.add("null")
|
||||
else:
|
||||
var istr = spaces(indent * 4)
|
||||
res.addf("kind: $1" % [makeYamlString($n.kind)])
|
||||
|
||||
if maxRecDepth != 0:
|
||||
if conf != nil:
|
||||
res.addf("\n$1info: $2", [istr, lineInfoToStr(conf, n.info)])
|
||||
case n.kind
|
||||
of nkCharLit .. nkInt64Lit:
|
||||
res.addf("\n$1intVal: $2", [istr, $(n.intVal)])
|
||||
of nkFloatLit, nkFloat32Lit, nkFloat64Lit:
|
||||
res.addf("\n$1floatVal: $2", [istr, n.floatVal.toStrMaxPrecision])
|
||||
of nkStrLit .. nkTripleStrLit:
|
||||
res.addf("\n$1strVal: $2", [istr, makeYamlString(n.strVal)])
|
||||
of nkSym:
|
||||
res.addf("\n$1sym: ", [istr])
|
||||
res.symToYamlAux(conf, n.sym, marker, indent + 1, maxRecDepth)
|
||||
of nkIdent:
|
||||
if n.ident != nil:
|
||||
res.addf("\n$1ident: $2", [istr, makeYamlString(n.ident.s)])
|
||||
else:
|
||||
res.addf("\n$1ident: null", [istr])
|
||||
else:
|
||||
if n.len > 0:
|
||||
res.addf("\n$1sons: ", [istr])
|
||||
for i in 0 ..< n.len:
|
||||
res.addf("\n$1 - ", [istr])
|
||||
res.treeToYamlAux(conf, n[i], marker, indent + 1, maxRecDepth - 1)
|
||||
if n.typ != nil:
|
||||
res.addf("\n$1typ: ", [istr])
|
||||
res.typeToYamlAux(conf, n.typ, marker, indent + 1, maxRecDepth)
|
||||
|
||||
proc treeToYaml*(conf: ConfigRef; n: PNode; indent: int = 0; maxRecDepth: int = -1): string =
|
||||
var marker = initIntSet()
|
||||
result = newStringOfCap(1024)
|
||||
result.treeToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
|
||||
proc typeToYaml*(conf: ConfigRef; n: PType; indent: int = 0; maxRecDepth: int = -1): string =
|
||||
var marker = initIntSet()
|
||||
result = newStringOfCap(1024)
|
||||
result.typeToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
|
||||
proc symToYaml*(conf: ConfigRef; n: PSym; indent: int = 0; maxRecDepth: int = -1): string =
|
||||
var marker = initIntSet()
|
||||
result = newStringOfCap(1024)
|
||||
result.symToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
25
compiler/backendpragmas.nim
Normal file
25
compiler/backendpragmas.nim
Normal file
@@ -0,0 +1,25 @@
|
||||
import pragmas, options, ast, trees
|
||||
|
||||
proc pushBackendOption(optionsStack: var seq[TOptions], options: var TOptions) =
|
||||
optionsStack.add options
|
||||
|
||||
proc popBackendOption(optionsStack: var seq[TOptions], options: var TOptions) =
|
||||
options = optionsStack[^1]
|
||||
optionsStack.setLen(optionsStack.len-1)
|
||||
|
||||
proc processPushBackendOption*(optionsStack: var seq[TOptions], options: var TOptions,
|
||||
n: PNode, start: int) =
|
||||
pushBackendOption(optionsStack, options)
|
||||
for i in start..<n.len:
|
||||
let it = n[i]
|
||||
if it.kind in nkPragmaCallKinds and it.len == 2 and it[1].kind == nkIntLit:
|
||||
let sw = whichPragma(it[0])
|
||||
let opts = pragmaToOptions(sw)
|
||||
if opts != {}:
|
||||
if it[1].intVal != 0:
|
||||
options.incl opts
|
||||
else:
|
||||
options.excl opts
|
||||
|
||||
template processPopBackendOption*(optionsStack: var seq[TOptions], options: var TOptions) =
|
||||
popBackendOption(optionsStack, options)
|
||||
@@ -10,6 +10,9 @@
|
||||
# this unit handles Nim sets; it implements bit sets
|
||||
# the code here should be reused in the Nim standard library
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
ElemType = byte
|
||||
TBitSet* = seq[ElemType] # we use byte here to avoid issues with
|
||||
@@ -23,53 +26,40 @@ const
|
||||
template modElemSize(arg: untyped): untyped = arg and 7
|
||||
template divElemSize(arg: untyped): untyped = arg shr 3
|
||||
|
||||
proc bitSetInit*(b: var TBitSet, length: int)
|
||||
proc bitSetUnion*(x: var TBitSet, y: TBitSet)
|
||||
proc bitSetDiff*(x: var TBitSet, y: TBitSet)
|
||||
proc bitSetSymDiff*(x: var TBitSet, y: TBitSet)
|
||||
proc bitSetIntersect*(x: var TBitSet, y: TBitSet)
|
||||
proc bitSetIncl*(x: var TBitSet, elem: BiggestInt)
|
||||
proc bitSetExcl*(x: var TBitSet, elem: BiggestInt)
|
||||
proc bitSetIn*(x: TBitSet, e: BiggestInt): bool
|
||||
proc bitSetEquals*(x, y: TBitSet): bool
|
||||
proc bitSetContains*(x, y: TBitSet): bool
|
||||
proc bitSetCard*(x: TBitSet): BiggestInt
|
||||
# implementation
|
||||
|
||||
proc bitSetIn(x: TBitSet, e: BiggestInt): bool =
|
||||
proc bitSetIn*(x: TBitSet, e: BiggestInt): bool =
|
||||
result = (x[int(e.divElemSize)] and (One shl e.modElemSize)) != Zero
|
||||
|
||||
proc bitSetIncl(x: var TBitSet, elem: BiggestInt) =
|
||||
proc bitSetIncl*(x: var TBitSet, elem: BiggestInt) =
|
||||
assert(elem >= 0)
|
||||
x[int(elem.divElemSize)] = x[int(elem.divElemSize)] or
|
||||
(One shl elem.modElemSize)
|
||||
|
||||
proc bitSetExcl(x: var TBitSet, elem: BiggestInt) =
|
||||
proc bitSetExcl*(x: var TBitSet, elem: BiggestInt) =
|
||||
x[int(elem.divElemSize)] = x[int(elem.divElemSize)] and
|
||||
not(One shl elem.modElemSize)
|
||||
|
||||
proc bitSetInit(b: var TBitSet, length: int) =
|
||||
proc bitSetInit*(b: var TBitSet, length: int) =
|
||||
newSeq(b, length)
|
||||
|
||||
proc bitSetUnion(x: var TBitSet, y: TBitSet) =
|
||||
proc bitSetUnion*(x: var TBitSet, y: TBitSet) =
|
||||
for i in 0..high(x): x[i] = x[i] or y[i]
|
||||
|
||||
proc bitSetDiff(x: var TBitSet, y: TBitSet) =
|
||||
proc bitSetDiff*(x: var TBitSet, y: TBitSet) =
|
||||
for i in 0..high(x): x[i] = x[i] and not y[i]
|
||||
|
||||
proc bitSetSymDiff(x: var TBitSet, y: TBitSet) =
|
||||
proc bitSetSymDiff*(x: var TBitSet, y: TBitSet) =
|
||||
for i in 0..high(x): x[i] = x[i] xor y[i]
|
||||
|
||||
proc bitSetIntersect(x: var TBitSet, y: TBitSet) =
|
||||
proc bitSetIntersect*(x: var TBitSet, y: TBitSet) =
|
||||
for i in 0..high(x): x[i] = x[i] and y[i]
|
||||
|
||||
proc bitSetEquals(x, y: TBitSet): bool =
|
||||
proc bitSetEquals*(x, y: TBitSet): bool =
|
||||
for i in 0..high(x):
|
||||
if x[i] != y[i]:
|
||||
return false
|
||||
result = true
|
||||
|
||||
proc bitSetContains(x, y: TBitSet): bool =
|
||||
proc bitSetContains*(x, y: TBitSet): bool =
|
||||
for i in 0..high(x):
|
||||
if (x[i] and not y[i]) != Zero:
|
||||
return false
|
||||
@@ -96,6 +86,12 @@ const populationCount: array[uint8, uint8] = block:
|
||||
|
||||
arr
|
||||
|
||||
proc bitSetCard(x: TBitSet): BiggestInt =
|
||||
proc bitSetCard*(x: TBitSet): BiggestInt =
|
||||
result = 0
|
||||
for it in x:
|
||||
result.inc int(populationCount[it])
|
||||
|
||||
proc bitSetToWord*(s: TBitSet; size: int): BiggestUInt =
|
||||
result = 0
|
||||
for j in 0..<size:
|
||||
if j < s.len: result = result or (BiggestUInt(s[j]) shl (j * 8))
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
## BTree implementation with few features, but good enough for the
|
||||
## Nim compiler's needs.
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
const
|
||||
M = 512 # max children per B-tree node = M-1
|
||||
# (must be even and greater than 2)
|
||||
Mhalf = M div 2
|
||||
|
||||
type
|
||||
Node[Key, Val] = ref object
|
||||
Node[Key, Val] {.acyclic.} = ref object
|
||||
entries: int
|
||||
keys: array[M, Key]
|
||||
case isInternal: bool
|
||||
@@ -35,6 +38,7 @@ template less(a, b): bool = cmp(a, b) < 0
|
||||
template eq(a, b): bool = cmp(a, b) == 0
|
||||
|
||||
proc getOrDefault*[Key, Val](b: BTree[Key, Val], key: Key): Val =
|
||||
result = default(Val)
|
||||
var x = b.root
|
||||
while x.isInternal:
|
||||
for j in 0..<x.entries:
|
||||
@@ -65,7 +69,10 @@ proc copyHalf[Key, Val](h, result: Node[Key, Val]) =
|
||||
result.links[j] = h.links[Mhalf + j]
|
||||
else:
|
||||
for j in 0..<Mhalf:
|
||||
shallowCopy(result.vals[j], h.vals[Mhalf + j])
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result.vals[j] = move h.vals[Mhalf + j]
|
||||
else:
|
||||
shallowCopy(result.vals[j], h.vals[Mhalf + j])
|
||||
|
||||
proc split[Key, Val](h: Node[Key, Val]): Node[Key, Val] =
|
||||
## split node in half
|
||||
@@ -85,7 +92,10 @@ proc insert[Key, Val](h: Node[Key, Val], key: Key, val: Val): Node[Key, Val] =
|
||||
if less(key, h.keys[j]): break
|
||||
inc j
|
||||
for i in countdown(h.entries, j+1):
|
||||
shallowCopy(h.vals[i], h.vals[i-1])
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
h.vals[i] = move h.vals[i-1]
|
||||
else:
|
||||
shallowCopy(h.vals[i], h.vals[i-1])
|
||||
h.vals[j] = val
|
||||
else:
|
||||
var newLink: Node[Key, Val] = nil
|
||||
|
||||
@@ -14,15 +14,17 @@ proc canRaiseDisp(p: BProc; n: PNode): bool =
|
||||
if n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
|
||||
result = false
|
||||
elif optPanics in p.config.globalOptions or
|
||||
(n.kind == nkSym and sfSystemModule in getModule(n.sym).flags):
|
||||
(n.kind == nkSym and sfSystemModule in getModule(n.sym).flags and
|
||||
sfSystemRaisesDefect notin n.sym.flags):
|
||||
# we know we can be strict:
|
||||
result = canRaise(n)
|
||||
else:
|
||||
# we have to be *very* conservative:
|
||||
result = canRaiseConservative(n)
|
||||
|
||||
proc preventNrvo(p: BProc; le, ri: PNode): bool =
|
||||
proc preventNrvo(p: BProc; dest, le, ri: PNode): bool =
|
||||
proc locationEscapes(p: BProc; le: PNode; inTryStmt: bool): bool =
|
||||
result = false
|
||||
var n = le
|
||||
while true:
|
||||
# do NOT follow nkHiddenDeref here!
|
||||
@@ -45,6 +47,7 @@ proc preventNrvo(p: BProc; le, ri: PNode): bool =
|
||||
# cannot analyse the location; assume the worst
|
||||
return true
|
||||
|
||||
result = false
|
||||
if le != nil:
|
||||
for i in 1..<ri.len:
|
||||
let r = ri[i]
|
||||
@@ -54,6 +57,11 @@ proc preventNrvo(p: BProc; le, ri: PNode): bool =
|
||||
if canRaise(ri[0]) and
|
||||
locationEscapes(p, le, p.nestedTryStmts.len > 0):
|
||||
message(p.config, le.info, warnObservableStores, $le)
|
||||
# bug #19613 prevent dangerous aliasing too:
|
||||
if dest != nil and dest != le:
|
||||
for i in 1..<ri.len:
|
||||
let r = ri[i]
|
||||
if isPartOf(dest, r) != arNo: return true
|
||||
|
||||
proc hasNoInit(call: PNode): bool {.inline.} =
|
||||
result = call[0].kind == nkSym and sfNoInit in call[0].sym.flags
|
||||
@@ -68,36 +76,56 @@ proc isHarmlessStore(p: BProc; canRaise: bool; d: TLoc): bool =
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc cleanupTemp(p: BProc; returnType: PType, tmp: TLoc): bool =
|
||||
if returnType.kind in {tyVar, tyLent}:
|
||||
# we don't need to worry about var/lent return types
|
||||
result = false
|
||||
elif hasDestructor(returnType) and getAttachedOp(p.module.g.graph, returnType, attachedDestructor) != nil:
|
||||
let dtor = getAttachedOp(p.module.g.graph, returnType, attachedDestructor)
|
||||
var op = initLocExpr(p, newSymNode(dtor))
|
||||
var callee = rdLoc(op)
|
||||
let destroy = if dtor.typ.firstParamType.kind == tyVar:
|
||||
callee & "(&" & rdLoc(tmp) & ")"
|
||||
else:
|
||||
callee & "(" & rdLoc(tmp) & ")"
|
||||
raiseExitCleanup(p, destroy)
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
|
||||
callee, params: Rope) =
|
||||
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri[0])
|
||||
genLineDir(p, ri)
|
||||
var pl = callee & ~"(" & params
|
||||
var pl = callee & "(" & params
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
if typ[0] != nil:
|
||||
if isInvalidReturnType(p.config, typ[0]):
|
||||
if params != nil: pl.add(~", ")
|
||||
if typ.returnType != nil:
|
||||
var flags: TAssignmentFlags = {}
|
||||
if typ.returnType.kind in {tyOpenArray, tyVarargs}:
|
||||
# perhaps generate no temp if the call doesn't have side effects
|
||||
flags.incl needTempForOpenArray
|
||||
if isInvalidReturnType(p.config, typ):
|
||||
if params.len != 0: pl.add(", ")
|
||||
# beware of 'result = p(result)'. We may need to allocate a temporary:
|
||||
if d.k in {locTemp, locNone} or not preventNrvo(p, le, ri):
|
||||
if d.k in {locTemp, locNone} or not preventNrvo(p, d.lode, le, ri):
|
||||
# Great, we can use 'd':
|
||||
if d.k == locNone: getTemp(p, typ[0], d, needsInit=true)
|
||||
if d.k == locNone: d = getTemp(p, typ.returnType, needsInit=true)
|
||||
elif d.k notin {locTemp} and not hasNoInit(ri):
|
||||
# reset before pass as 'result' var:
|
||||
discard "resetLoc(p, d)"
|
||||
pl.add(addrLoc(p.config, d))
|
||||
pl.add(~");$n")
|
||||
pl.add(");\n")
|
||||
line(p, cpsStmts, pl)
|
||||
else:
|
||||
var tmp: TLoc
|
||||
getTemp(p, typ[0], tmp, needsInit=true)
|
||||
var tmp: TLoc = getTemp(p, typ.returnType, needsInit=true)
|
||||
pl.add(addrLoc(p.config, tmp))
|
||||
pl.add(~");$n")
|
||||
pl.add(");\n")
|
||||
line(p, cpsStmts, pl)
|
||||
genAssignment(p, d, tmp, {}) # no need for deep copying
|
||||
if canRaise: raiseExit(p)
|
||||
else:
|
||||
pl.add(~")")
|
||||
pl.add(")")
|
||||
if p.module.compileToCpp:
|
||||
if lfSingleUse in d.flags:
|
||||
# do not generate spurious temporaries for C++! For C we're better off
|
||||
@@ -108,34 +136,37 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
|
||||
excl d.flags, lfSingleUse
|
||||
else:
|
||||
if d.k == locNone and p.splitDecls == 0:
|
||||
getTempCpp(p, typ[0], d, pl)
|
||||
d = getTempCpp(p, typ.returnType, pl)
|
||||
else:
|
||||
if d.k == locNone: getTemp(p, typ[0], d)
|
||||
var list: TLoc
|
||||
initLoc(list, locCall, d.lode, OnUnknown)
|
||||
if d.k == locNone: d = getTemp(p, typ.returnType)
|
||||
var list = initLoc(locCall, d.lode, OnUnknown)
|
||||
list.r = pl
|
||||
genAssignment(p, d, list, {}) # no need for deep copying
|
||||
if canRaise: raiseExit(p)
|
||||
|
||||
elif isHarmlessStore(p, canRaise, d):
|
||||
if d.k == locNone: getTemp(p, typ[0], d)
|
||||
var useTemp = false
|
||||
if d.k == locNone:
|
||||
useTemp = true
|
||||
d = getTemp(p, typ.returnType)
|
||||
assert(d.t != nil) # generate an assignment to d:
|
||||
var list: TLoc
|
||||
initLoc(list, locCall, d.lode, OnUnknown)
|
||||
var list = initLoc(locCall, d.lode, OnUnknown)
|
||||
list.r = pl
|
||||
genAssignment(p, d, list, {}) # no need for deep copying
|
||||
if canRaise: raiseExit(p)
|
||||
genAssignment(p, d, list, flags) # no need for deep copying
|
||||
if canRaise:
|
||||
if not (useTemp and cleanupTemp(p, typ.returnType, d)):
|
||||
raiseExit(p)
|
||||
else:
|
||||
var tmp: TLoc
|
||||
getTemp(p, typ[0], tmp, needsInit=true)
|
||||
var list: TLoc
|
||||
initLoc(list, locCall, d.lode, OnUnknown)
|
||||
var tmp: TLoc = getTemp(p, typ.returnType, needsInit=true)
|
||||
var list = initLoc(locCall, d.lode, OnUnknown)
|
||||
list.r = pl
|
||||
genAssignment(p, tmp, list, {}) # no need for deep copying
|
||||
if canRaise: raiseExit(p)
|
||||
genAssignment(p, tmp, list, flags) # no need for deep copying
|
||||
if canRaise:
|
||||
if not cleanupTemp(p, typ.returnType, tmp):
|
||||
raiseExit(p)
|
||||
genAssignment(p, d, tmp, {})
|
||||
else:
|
||||
pl.add(~");$n")
|
||||
pl.add(");\n")
|
||||
line(p, cpsStmts, pl)
|
||||
if canRaise: raiseExit(p)
|
||||
|
||||
@@ -143,22 +174,32 @@ proc genBoundsCheck(p: BProc; arr, a, b: TLoc)
|
||||
|
||||
proc reifiedOpenArray(n: PNode): bool {.inline.} =
|
||||
var x = n
|
||||
while x.kind in {nkAddr, nkHiddenAddr, nkHiddenStdConv, nkHiddenDeref}:
|
||||
x = x[0]
|
||||
while true:
|
||||
case x.kind
|
||||
of {nkAddr, nkHiddenAddr, nkHiddenDeref}:
|
||||
x = x[0]
|
||||
of nkHiddenStdConv:
|
||||
x = x[1]
|
||||
else:
|
||||
break
|
||||
if x.kind == nkSym and x.sym.kind == skParam:
|
||||
result = false
|
||||
else:
|
||||
result = true
|
||||
|
||||
proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType): (Rope, Rope) =
|
||||
var a, b, c: TLoc
|
||||
initLocExpr(p, q[1], a)
|
||||
initLocExpr(p, q[2], b)
|
||||
initLocExpr(p, q[3], c)
|
||||
proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareForMutation = false): (Rope, Rope) =
|
||||
var a = initLocExpr(p, q[1])
|
||||
var b = initLocExpr(p, q[2])
|
||||
var c = initLocExpr(p, q[3])
|
||||
# but first produce the required index checks:
|
||||
if optBoundsCheck in p.options:
|
||||
genBoundsCheck(p, a, b, c)
|
||||
let ty = skipTypes(a.t, abstractVar+{tyPtr})
|
||||
if prepareForMutation:
|
||||
linefmt(p, cpsStmts, "#nimPrepareStrMutationV2($1);$n", [byRefLoc(p, a)])
|
||||
# bug #23321: In the function mapType, ptrs (tyPtr, tyVar, tyLent, tyRef)
|
||||
# are mapped into ctPtrToArray, the dereference of which is skipped
|
||||
# in the `genref`. We need to skip these ptrs here
|
||||
let ty = skipTypes(a.t, abstractVar+{tyPtr, tyRef})
|
||||
let dest = getTypeDesc(p.module, destType)
|
||||
let lengthExpr = "($1)-($2)+1" % [rdLoc(c), rdLoc(b)]
|
||||
case ty.kind
|
||||
@@ -168,8 +209,10 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType): (Rope,
|
||||
result = ("($3*)(($1)+($2))" % [rdLoc(a), rdLoc(b), dest],
|
||||
lengthExpr)
|
||||
else:
|
||||
var lit = newRopeAppender()
|
||||
intLiteral(first, lit)
|
||||
result = ("($4*)($1)+(($2)-($3))" %
|
||||
[rdLoc(a), rdLoc(b), intLiteral(first), dest],
|
||||
[rdLoc(a), rdLoc(b), lit, dest],
|
||||
lengthExpr)
|
||||
of tyOpenArray, tyVarargs:
|
||||
if reifiedOpenArray(q[1]):
|
||||
@@ -178,7 +221,7 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType): (Rope,
|
||||
else:
|
||||
result = ("($3*)($1)+($2)" % [rdLoc(a), rdLoc(b), dest],
|
||||
lengthExpr)
|
||||
of tyUncheckedArray, tyCString:
|
||||
of tyUncheckedArray, tyCstring:
|
||||
result = ("($3*)($1)+($2)" % [rdLoc(a), rdLoc(b), dest],
|
||||
lengthExpr)
|
||||
of tyString, tySequence:
|
||||
@@ -187,15 +230,18 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType): (Rope,
|
||||
optSeqDestructors in p.config.globalOptions:
|
||||
linefmt(p, cpsStmts, "#nimPrepareStrMutationV2($1);$n", [byRefLoc(p, a)])
|
||||
if atyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
result = ("($4*)(*$1)$3+($2)" % [rdLoc(a), rdLoc(b), dataField(p), dest],
|
||||
result = ("(($5) ? (($4*)(*$1)$3+($2)) : NIM_NIL)" %
|
||||
[rdLoc(a), rdLoc(b), dataField(p), dest, dataFieldAccessor(p, "*" & rdLoc(a))],
|
||||
lengthExpr)
|
||||
else:
|
||||
result = ("($4*)$1$3+($2)" % [rdLoc(a), rdLoc(b), dataField(p), dest],
|
||||
result = ("(($5) ? (($4*)$1$3+($2)) : NIM_NIL)" %
|
||||
[rdLoc(a), rdLoc(b), dataField(p), dest, dataFieldAccessor(p, rdLoc(a))],
|
||||
lengthExpr)
|
||||
else:
|
||||
result = ("", "")
|
||||
internalError(p.config, "openArrayLoc: " & typeToString(a.t))
|
||||
|
||||
proc openArrayLoc(p: BProc, formalType: PType, n: PNode): Rope =
|
||||
proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Rope) =
|
||||
var q = skipConv(n)
|
||||
var skipped = false
|
||||
while q.kind == nkStmtListExpr and q.len > 0:
|
||||
@@ -209,41 +255,43 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode): Rope =
|
||||
for i in 0..<q.len-1:
|
||||
genStmts(p, q[i])
|
||||
q = q.lastSon
|
||||
let (x, y) = genOpenArraySlice(p, q, formalType, n.typ[0])
|
||||
result = x & ", " & y
|
||||
let (x, y) = genOpenArraySlice(p, q, formalType, n.typ.elementType)
|
||||
result.add x & ", " & y
|
||||
else:
|
||||
var a: TLoc
|
||||
initLocExpr(p, if n.kind == nkHiddenStdConv: n[1] else: n, a)
|
||||
case skipTypes(a.t, abstractVar).kind
|
||||
var a = initLocExpr(p, if n.kind == nkHiddenStdConv: n[1] else: n)
|
||||
case skipTypes(a.t, abstractVar+{tyStatic}).kind
|
||||
of tyOpenArray, tyVarargs:
|
||||
if reifiedOpenArray(n):
|
||||
if a.t.kind in {tyVar, tyLent}:
|
||||
result = "$1->Field0, $1->Field1" % [rdLoc(a)]
|
||||
result.add "$1->Field0, $1->Field1" % [rdLoc(a)]
|
||||
else:
|
||||
result = "$1.Field0, $1.Field1" % [rdLoc(a)]
|
||||
result.add "$1.Field0, $1.Field1" % [rdLoc(a)]
|
||||
else:
|
||||
result = "$1, $1Len_0" % [rdLoc(a)]
|
||||
result.add "$1, $1Len_0" % [rdLoc(a)]
|
||||
of tyString, tySequence:
|
||||
let ntyp = skipTypes(n.typ, abstractInst)
|
||||
if formalType.skipTypes(abstractInst).kind in {tyVar} and ntyp.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions:
|
||||
linefmt(p, cpsStmts, "#nimPrepareStrMutationV2($1);$n", [byRefLoc(p, a)])
|
||||
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
var t: TLoc
|
||||
t.r = "(*$1)" % [a.rdLoc]
|
||||
result = "(*$1)$3, $2" % [a.rdLoc, lenExpr(p, t), dataField(p)]
|
||||
var t = TLoc(r: "(*$1)" % [a.rdLoc])
|
||||
result.add "($4) ? ((*$1)$3) : NIM_NIL, $2" %
|
||||
[a.rdLoc, lenExpr(p, t), dataField(p),
|
||||
dataFieldAccessor(p, "*" & a.rdLoc)]
|
||||
else:
|
||||
result = "$1$3, $2" % [a.rdLoc, lenExpr(p, a), dataField(p)]
|
||||
result.add "($4) ? ($1$3) : NIM_NIL, $2" %
|
||||
[a.rdLoc, lenExpr(p, a), dataField(p), dataFieldAccessor(p, a.rdLoc)]
|
||||
of tyArray:
|
||||
result = "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, a.t))]
|
||||
result.add "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, a.t))]
|
||||
of tyPtr, tyRef:
|
||||
case lastSon(a.t).kind
|
||||
case elementType(a.t).kind
|
||||
of tyString, tySequence:
|
||||
var t: TLoc
|
||||
t.r = "(*$1)" % [a.rdLoc]
|
||||
result = "(*$1)$3, $2" % [a.rdLoc, lenExpr(p, t), dataField(p)]
|
||||
var t = TLoc(r: "(*$1)" % [a.rdLoc])
|
||||
result.add "($4) ? ((*$1)$3) : NIM_NIL, $2" %
|
||||
[a.rdLoc, lenExpr(p, t), dataField(p),
|
||||
dataFieldAccessor(p, "*" & a.rdLoc)]
|
||||
of tyArray:
|
||||
result = "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, lastSon(a.t)))]
|
||||
result.add "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, elementType(a.t)))]
|
||||
else:
|
||||
internalError(p.config, "openArrayLoc: " & typeToString(a.t))
|
||||
else: internalError(p.config, "openArrayLoc: " & typeToString(a.t))
|
||||
@@ -252,56 +300,69 @@ proc withTmpIfNeeded(p: BProc, a: TLoc, needsTmp: bool): TLoc =
|
||||
# Bug https://github.com/status-im/nimbus-eth2/issues/1549
|
||||
# Aliasing is preferred over stack overflows.
|
||||
# Also don't regress for non ARC-builds, too risky.
|
||||
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcOrc} and
|
||||
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and
|
||||
getSize(p.config, a.lode.typ) < 1024:
|
||||
getTemp(p, a.lode.typ, result, needsInit=false)
|
||||
result = getTemp(p, a.lode.typ, needsInit=false)
|
||||
genAssignment(p, result, a, {})
|
||||
else:
|
||||
result = a
|
||||
|
||||
proc genArgStringToCString(p: BProc, n: PNode, needsTmp: bool): Rope {.inline.} =
|
||||
var a: TLoc
|
||||
initLocExpr(p, n[0], a)
|
||||
ropecg(p.module, "#nimToCStringConv($1)", [withTmpIfNeeded(p, a, needsTmp).rdLoc])
|
||||
proc literalsNeedsTmp(p: BProc, a: TLoc): TLoc =
|
||||
result = getTemp(p, a.lode.typ, needsInit=false)
|
||||
genAssignment(p, result, a, {})
|
||||
|
||||
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode, needsTmp = false): Rope =
|
||||
proc genArgStringToCString(p: BProc, n: PNode; result: var Rope; needsTmp: bool) {.inline.} =
|
||||
var a = initLocExpr(p, n[0])
|
||||
appcg(p.module, result, "#nimToCStringConv($1)", [withTmpIfNeeded(p, a, needsTmp).rdLoc])
|
||||
|
||||
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Rope; needsTmp = false) =
|
||||
var a: TLoc
|
||||
if n.kind == nkStringToCString:
|
||||
result = genArgStringToCString(p, n, needsTmp)
|
||||
genArgStringToCString(p, n, result, needsTmp)
|
||||
elif skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs}:
|
||||
var n = if n.kind != nkHiddenAddr: n else: n[0]
|
||||
result = openArrayLoc(p, param.typ, n)
|
||||
elif ccgIntroducedPtr(p.config, param, call[0].typ[0]):
|
||||
initLocExpr(p, n, a)
|
||||
result = addrLoc(p.config, withTmpIfNeeded(p, a, needsTmp))
|
||||
openArrayLoc(p, param.typ, n, result)
|
||||
elif ccgIntroducedPtr(p.config, param, call[0].typ.returnType) and
|
||||
(optByRef notin param.options or not p.module.compileToCpp):
|
||||
a = initLocExpr(p, n)
|
||||
if n.kind in {nkCharLit..nkNilLit}:
|
||||
addAddrLoc(p.config, literalsNeedsTmp(p, a), result)
|
||||
else:
|
||||
addAddrLoc(p.config, withTmpIfNeeded(p, a, needsTmp), result)
|
||||
elif p.module.compileToCpp and param.typ.kind in {tyVar} and
|
||||
n.kind == nkHiddenAddr:
|
||||
initLocExprSingleUse(p, n[0], a)
|
||||
a = initLocExprSingleUse(p, n[0])
|
||||
# if the proc is 'importc'ed but not 'importcpp'ed then 'var T' still
|
||||
# means '*T'. See posix.nim for lots of examples that do that in the wild.
|
||||
let callee = call[0]
|
||||
if callee.kind == nkSym and
|
||||
{sfImportc, sfInfixCall, sfCompilerProc} * callee.sym.flags == {sfImportc} and
|
||||
{lfHeader, lfNoDecl} * callee.sym.loc.flags != {}:
|
||||
result = addrLoc(p.config, a)
|
||||
addAddrLoc(p.config, a, result)
|
||||
else:
|
||||
result = rdLoc(a)
|
||||
addRdLoc(a, result)
|
||||
else:
|
||||
initLocExprSingleUse(p, n, a)
|
||||
result = rdLoc(withTmpIfNeeded(p, a, needsTmp))
|
||||
a = initLocExprSingleUse(p, n)
|
||||
if param.typ.kind in abstractPtrs:
|
||||
let typ = skipTypes(param.typ, abstractPtrs)
|
||||
if typ.sym != nil and sfImportc in typ.sym.flags:
|
||||
a.r = "(($1) ($2))" %
|
||||
[getTypeDesc(p.module, param.typ), rdCharLoc(a)]
|
||||
addRdLoc(withTmpIfNeeded(p, a, needsTmp), result)
|
||||
#assert result != nil
|
||||
|
||||
proc genArgNoParam(p: BProc, n: PNode, needsTmp = false): Rope =
|
||||
proc genArgNoParam(p: BProc, n: PNode; result: var Rope; needsTmp = false) =
|
||||
var a: TLoc
|
||||
if n.kind == nkStringToCString:
|
||||
result = genArgStringToCString(p, n, needsTmp)
|
||||
genArgStringToCString(p, n, result, needsTmp)
|
||||
else:
|
||||
initLocExprSingleUse(p, n, a)
|
||||
result = rdLoc(withTmpIfNeeded(p, a, needsTmp))
|
||||
a = initLocExprSingleUse(p, n)
|
||||
addRdLoc(withTmpIfNeeded(p, a, needsTmp), result)
|
||||
|
||||
from dfa import aliases, AliasKind
|
||||
import aliasanalysis
|
||||
|
||||
proc potentialAlias(n: PNode, potentialWrites: seq[PNode]): bool =
|
||||
result = false
|
||||
for p in potentialWrites:
|
||||
if p.aliases(n) != no or n.aliases(p) != no:
|
||||
return true
|
||||
@@ -310,64 +371,84 @@ proc skipTrivialIndirections(n: PNode): PNode =
|
||||
result = n
|
||||
while true:
|
||||
case result.kind
|
||||
of {nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr, nkObjDownConv, nkObjUpConv}:
|
||||
of nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr, nkObjDownConv, nkObjUpConv:
|
||||
result = result[0]
|
||||
of {nkHiddenStdConv, nkHiddenSubConv}:
|
||||
of nkHiddenStdConv, nkHiddenSubConv:
|
||||
result = result[1]
|
||||
else: break
|
||||
|
||||
proc getPotentialWrites(n: PNode, mutate = false): seq[PNode] =
|
||||
proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
|
||||
case n.kind:
|
||||
of nkLiterals, nkIdent: discard
|
||||
of nkLiterals, nkIdent, nkFormalParams: discard
|
||||
of nkSym:
|
||||
if mutate: result.add n
|
||||
of nkAsgn, nkFastAsgn:
|
||||
result.add getPotentialWrites(n[0], true)
|
||||
result.add getPotentialWrites(n[1], mutate)
|
||||
of nkAsgn, nkFastAsgn, nkSinkAsgn:
|
||||
getPotentialWrites(n[0], true, result)
|
||||
getPotentialWrites(n[1], mutate, result)
|
||||
of nkAddr, nkHiddenAddr:
|
||||
result.add getPotentialWrites(n[0], true)
|
||||
of nkCallKinds: #TODO: Find out why in f += 1, f is a nkSym and not a nkHiddenAddr
|
||||
for s in n.sons:
|
||||
result.add getPotentialWrites(s, true)
|
||||
getPotentialWrites(n[0], true, result)
|
||||
of nkBracketExpr, nkDotExpr, nkCheckedFieldExpr:
|
||||
getPotentialWrites(n[0], mutate, result)
|
||||
of nkCallKinds:
|
||||
case n.getMagic:
|
||||
of mIncl, mExcl, mInc, mDec, mAppendStrCh, mAppendStrStr, mAppendSeqElem,
|
||||
mAddr, mNew, mNewFinalize, mWasMoved, mDestroy:
|
||||
getPotentialWrites(n[1], true, result)
|
||||
for i in 2..<n.len:
|
||||
getPotentialWrites(n[i], mutate, result)
|
||||
of mSwap:
|
||||
for i in 1..<n.len:
|
||||
getPotentialWrites(n[i], true, result)
|
||||
else:
|
||||
for i in 1..<n.len:
|
||||
getPotentialWrites(n[i], mutate, result)
|
||||
else:
|
||||
for s in n.sons:
|
||||
result.add getPotentialWrites(s, mutate)
|
||||
for s in n:
|
||||
getPotentialWrites(s, mutate, result)
|
||||
|
||||
proc getPotentialReads(n: PNode): seq[PNode] =
|
||||
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
|
||||
case n.kind:
|
||||
of nkLiterals, nkIdent: discard
|
||||
of nkLiterals, nkIdent, nkFormalParams: discard
|
||||
of nkSym: result.add n
|
||||
else:
|
||||
for s in n.sons:
|
||||
result.add getPotentialReads(s)
|
||||
for s in n:
|
||||
getPotentialReads(s, result)
|
||||
|
||||
proc genParams(p: BProc, ri: PNode, typ: PType): Rope =
|
||||
proc genParams(p: BProc, ri: PNode, typ: PType; result: var Rope) =
|
||||
# We must generate temporaries in cases like #14396
|
||||
# to keep the strict Left-To-Right evaluation
|
||||
var needTmp = newSeq[bool](ri.len - 1)
|
||||
var potentialWrites: seq[PNode]
|
||||
var potentialWrites: seq[PNode] = @[]
|
||||
for i in countdown(ri.len - 1, 1):
|
||||
if ri[i].skipTrivialIndirections.kind == nkSym:
|
||||
needTmp[i - 1] = potentialAlias(ri[i], potentialWrites)
|
||||
else:
|
||||
for n in getPotentialReads(ri[i]):
|
||||
#if not ri[i].typ.isCompileTimeOnly:
|
||||
var potentialReads: seq[PNode] = @[]
|
||||
getPotentialReads(ri[i], potentialReads)
|
||||
for n in potentialReads:
|
||||
if not needTmp[i - 1]:
|
||||
needTmp[i - 1] = potentialAlias(n, potentialWrites)
|
||||
potentialWrites.add getPotentialWrites(ri[i])
|
||||
if ri[i].kind == nkHiddenAddr:
|
||||
# Optimization: don't use a temp, if we would only take the adress anyway
|
||||
getPotentialWrites(ri[i], false, potentialWrites)
|
||||
if ri[i].kind in {nkHiddenAddr, nkAddr}:
|
||||
# Optimization: don't use a temp, if we would only take the address anyway
|
||||
needTmp[i - 1] = false
|
||||
|
||||
var oldLen = result.len
|
||||
for i in 1..<ri.len:
|
||||
if i < typ.len:
|
||||
if i < typ.n.len:
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
let paramType = typ.n[i]
|
||||
if not paramType.typ.isCompileTimeOnly:
|
||||
if result != nil: result.add(~", ")
|
||||
result.add(genArg(p, ri[i], paramType.sym, ri, needTmp[i-1]))
|
||||
if oldLen != result.len:
|
||||
result.add(", ")
|
||||
oldLen = result.len
|
||||
genArg(p, ri[i], paramType.sym, ri, result, needTmp[i-1])
|
||||
else:
|
||||
if result != nil: result.add(~", ")
|
||||
result.add(genArgNoParam(p, ri[i], needTmp[i-1]))
|
||||
if oldLen != result.len:
|
||||
result.add(", ")
|
||||
oldLen = result.len
|
||||
genArgNoParam(p, ri[i], result, needTmp[i-1])
|
||||
|
||||
proc addActualSuffixForHCR(res: var Rope, module: PSym, sym: PSym) =
|
||||
if sym.flags * {sfImportc, sfNonReloadable} == {} and sym.loc.k == locProc and
|
||||
@@ -375,15 +456,14 @@ proc addActualSuffixForHCR(res: var Rope, module: PSym, sym: PSym) =
|
||||
res = res & "_actual".rope
|
||||
|
||||
proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
var op: TLoc
|
||||
# this is a hotspot in the compiler
|
||||
initLocExpr(p, ri[0], op)
|
||||
var op = initLocExpr(p, ri[0])
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri[0].typ, abstractInstOwned)
|
||||
assert(typ.kind == tyProc)
|
||||
assert(typ.len == typ.n.len)
|
||||
|
||||
var params = genParams(p, ri, typ)
|
||||
var params = newRopeAppender()
|
||||
genParams(p, ri, typ, params)
|
||||
|
||||
var callee = rdLoc(op)
|
||||
if p.hcrOn and ri[0].kind == nkSym:
|
||||
@@ -393,20 +473,19 @@ proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
|
||||
proc addComma(r: Rope): Rope =
|
||||
if r == nil: r else: r & ~", "
|
||||
if r.len == 0: r else: r & ", "
|
||||
|
||||
const PatProc = "$1.ClE_0? $1.ClP_0($3$1.ClE_0):(($4)($1.ClP_0))($2)"
|
||||
const PatIter = "$1.ClP_0($3$1.ClE_0)" # we know the env exists
|
||||
|
||||
var op: TLoc
|
||||
initLocExpr(p, ri[0], op)
|
||||
var op = initLocExpr(p, ri[0])
|
||||
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri[0].typ, abstractInstOwned)
|
||||
assert(typ.kind == tyProc)
|
||||
assert(typ.len == typ.n.len)
|
||||
|
||||
var pl = genParams(p, ri, typ)
|
||||
var pl = newRopeAppender()
|
||||
genParams(p, ri, typ, pl)
|
||||
|
||||
template genCallPattern {.dirty.} =
|
||||
if tfIterator in typ.flags:
|
||||
@@ -416,31 +495,30 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
|
||||
let rawProc = getClosureType(p.module, typ, clHalf)
|
||||
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri[0])
|
||||
if typ[0] != nil:
|
||||
if isInvalidReturnType(p.config, typ[0]):
|
||||
if ri.len > 1: pl.add(~", ")
|
||||
if typ.returnType != nil:
|
||||
if isInvalidReturnType(p.config, typ):
|
||||
if ri.len > 1: pl.add(", ")
|
||||
# beware of 'result = p(result)'. We may need to allocate a temporary:
|
||||
if d.k in {locTemp, locNone} or not preventNrvo(p, le, ri):
|
||||
if d.k in {locTemp, locNone} or not preventNrvo(p, d.lode, le, ri):
|
||||
# Great, we can use 'd':
|
||||
if d.k == locNone:
|
||||
getTemp(p, typ[0], d, needsInit=true)
|
||||
d = getTemp(p, typ.returnType, needsInit=true)
|
||||
elif d.k notin {locTemp} and not hasNoInit(ri):
|
||||
# reset before pass as 'result' var:
|
||||
discard "resetLoc(p, d)"
|
||||
pl.add(addrLoc(p.config, d))
|
||||
genCallPattern()
|
||||
if canRaise: raiseExit(p)
|
||||
else:
|
||||
var tmp: TLoc
|
||||
getTemp(p, typ[0], tmp, needsInit=true)
|
||||
var tmp: TLoc = getTemp(p, typ.returnType, needsInit=true)
|
||||
pl.add(addrLoc(p.config, tmp))
|
||||
genCallPattern()
|
||||
if canRaise: raiseExit(p)
|
||||
genAssignment(p, d, tmp, {}) # no need for deep copying
|
||||
elif isHarmlessStore(p, canRaise, d):
|
||||
if d.k == locNone: getTemp(p, typ[0], d)
|
||||
if d.k == locNone: d = getTemp(p, typ.returnType)
|
||||
assert(d.t != nil) # generate an assignment to d:
|
||||
var list: TLoc
|
||||
initLoc(list, locCall, d.lode, OnUnknown)
|
||||
var list: TLoc = initLoc(locCall, d.lode, OnUnknown)
|
||||
if tfIterator in typ.flags:
|
||||
list.r = PatIter % [rdLoc(op), pl, pl.addComma, rawProc]
|
||||
else:
|
||||
@@ -448,11 +526,9 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
genAssignment(p, d, list, {}) # no need for deep copying
|
||||
if canRaise: raiseExit(p)
|
||||
else:
|
||||
var tmp: TLoc
|
||||
getTemp(p, typ[0], tmp)
|
||||
var tmp: TLoc = getTemp(p, typ.returnType)
|
||||
assert(d.t != nil) # generate an assignment to d:
|
||||
var list: TLoc
|
||||
initLoc(list, locCall, d.lode, OnUnknown)
|
||||
var list: TLoc = initLoc(locCall, d.lode, OnUnknown)
|
||||
if tfIterator in typ.flags:
|
||||
list.r = PatIter % [rdLoc(op), pl, pl.addComma, rawProc]
|
||||
else:
|
||||
@@ -464,24 +540,30 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
genCallPattern()
|
||||
if canRaise: raiseExit(p)
|
||||
|
||||
proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope =
|
||||
if i < typ.len:
|
||||
proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Rope;
|
||||
argsCounter: var int) =
|
||||
if i < typ.n.len:
|
||||
# 'var T' is 'T&' in C++. This means we ignore the request of
|
||||
# any nkHiddenAddr when it's a 'var T'.
|
||||
let paramType = typ.n[i]
|
||||
assert(paramType.kind == nkSym)
|
||||
if paramType.typ.isCompileTimeOnly:
|
||||
result = nil
|
||||
elif typ[i].kind in {tyVar} and ri[i].kind == nkHiddenAddr:
|
||||
result = genArgNoParam(p, ri[i][0])
|
||||
discard
|
||||
elif paramType.typ.kind in {tyVar} and ri[i].kind == nkHiddenAddr:
|
||||
if argsCounter > 0: result.add ", "
|
||||
genArgNoParam(p, ri[i][0], result)
|
||||
inc argsCounter
|
||||
else:
|
||||
result = genArgNoParam(p, ri[i]) #, typ.n[i].sym)
|
||||
if argsCounter > 0: result.add ", "
|
||||
genArgNoParam(p, ri[i], result) #, typ.n[i].sym)
|
||||
inc argsCounter
|
||||
else:
|
||||
if tfVarargs notin typ.flags:
|
||||
localError(p.config, ri.info, "wrong argument count")
|
||||
result = nil
|
||||
else:
|
||||
result = genArgNoParam(p, ri[i])
|
||||
if argsCounter > 0: result.add ", "
|
||||
genArgNoParam(p, ri[i], result)
|
||||
inc argsCounter
|
||||
|
||||
discard """
|
||||
Dot call syntax in C++
|
||||
@@ -538,11 +620,11 @@ proc skipAddrDeref(node: PNode): PNode =
|
||||
else:
|
||||
result = node
|
||||
|
||||
proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType): Rope =
|
||||
proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Rope) =
|
||||
# for better or worse c2nim translates the 'this' argument to a 'var T'.
|
||||
# However manual wrappers may also use 'ptr T'. In any case we support both
|
||||
# for convenience.
|
||||
internalAssert p.config, i < typ.len
|
||||
internalAssert p.config, i < typ.n.len
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
# if the parameter is lying (tyVar) and thus we required an additional deref,
|
||||
# skip the deref:
|
||||
@@ -552,75 +634,72 @@ proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType): Rope =
|
||||
if t.kind in {tyVar}:
|
||||
let x = if ri.kind == nkHiddenAddr: ri[0] else: ri
|
||||
if x.typ.kind == tyPtr:
|
||||
result = genArgNoParam(p, x)
|
||||
genArgNoParam(p, x, result)
|
||||
result.add("->")
|
||||
elif x.kind in {nkHiddenDeref, nkDerefExpr} and x[0].typ.kind == tyPtr:
|
||||
result = genArgNoParam(p, x[0])
|
||||
genArgNoParam(p, x[0], result)
|
||||
result.add("->")
|
||||
else:
|
||||
result = genArgNoParam(p, x)
|
||||
genArgNoParam(p, x, result)
|
||||
result.add(".")
|
||||
elif t.kind == tyPtr:
|
||||
if ri.kind in {nkAddr, nkHiddenAddr}:
|
||||
result = genArgNoParam(p, ri[0])
|
||||
genArgNoParam(p, ri[0], result)
|
||||
result.add(".")
|
||||
else:
|
||||
result = genArgNoParam(p, ri)
|
||||
genArgNoParam(p, ri, result)
|
||||
result.add("->")
|
||||
else:
|
||||
ri = skipAddrDeref(ri)
|
||||
if ri.kind in {nkAddr, nkHiddenAddr}: ri = ri[0]
|
||||
result = genArgNoParam(p, ri) #, typ.n[i].sym)
|
||||
genArgNoParam(p, ri, result) #, typ.n[i].sym)
|
||||
result.add(".")
|
||||
|
||||
proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType): Rope =
|
||||
proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Rope) =
|
||||
var i = 0
|
||||
var j = 1
|
||||
while i < pat.len:
|
||||
case pat[i]
|
||||
of '@':
|
||||
var first = true
|
||||
var argsCounter = 0
|
||||
for k in j..<ri.len:
|
||||
let arg = genOtherArg(p, ri, k, typ)
|
||||
if arg.len > 0:
|
||||
if not first:
|
||||
result.add(~", ")
|
||||
first = false
|
||||
result.add arg
|
||||
genOtherArg(p, ri, k, typ, result, argsCounter)
|
||||
inc i
|
||||
of '#':
|
||||
if i+1 < pat.len and pat[i+1] in {'+', '@'}:
|
||||
let ri = ri[j]
|
||||
if ri.kind in nkCallKinds:
|
||||
let typ = skipTypes(ri[0].typ, abstractInst)
|
||||
if pat[i+1] == '+': result.add genArgNoParam(p, ri[0])
|
||||
result.add(~"(")
|
||||
if pat[i+1] == '+': genArgNoParam(p, ri[0], result)
|
||||
result.add("(")
|
||||
if 1 < ri.len:
|
||||
result.add genOtherArg(p, ri, 1, typ)
|
||||
var argsCounterB = 0
|
||||
genOtherArg(p, ri, 1, typ, result, argsCounterB)
|
||||
for k in j+1..<ri.len:
|
||||
result.add(~", ")
|
||||
result.add genOtherArg(p, ri, k, typ)
|
||||
result.add(~")")
|
||||
var argsCounterB = 0
|
||||
genOtherArg(p, ri, k, typ, result, argsCounterB)
|
||||
result.add(")")
|
||||
else:
|
||||
localError(p.config, ri.info, "call expression expected for C++ pattern")
|
||||
inc i
|
||||
elif i+1 < pat.len and pat[i+1] == '.':
|
||||
result.add genThisArg(p, ri, j, typ)
|
||||
genThisArg(p, ri, j, typ, result)
|
||||
inc i
|
||||
elif i+1 < pat.len and pat[i+1] == '[':
|
||||
var arg = ri[j].skipAddrDeref
|
||||
while arg.kind in {nkAddr, nkHiddenAddr, nkObjDownConv}: arg = arg[0]
|
||||
result.add genArgNoParam(p, arg)
|
||||
genArgNoParam(p, arg, result)
|
||||
#result.add debugTree(arg, 0, 10)
|
||||
else:
|
||||
result.add genOtherArg(p, ri, j, typ)
|
||||
var argsCounter = 0
|
||||
genOtherArg(p, ri, j, typ, result, argsCounter)
|
||||
inc j
|
||||
inc i
|
||||
of '\'':
|
||||
var idx, stars: int
|
||||
var idx, stars: int = 0
|
||||
if scanCppGenericSlot(pat, i, idx, stars):
|
||||
var t = resolveStarsInCppType(typ, idx, stars)
|
||||
if t == nil: result.add(~"void")
|
||||
if t == nil: result.add("void")
|
||||
else: result.add(getTypeDesc(p.module, t))
|
||||
else:
|
||||
let start = i
|
||||
@@ -631,20 +710,19 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType): Rope =
|
||||
result.add(substr(pat, start, i - 1))
|
||||
|
||||
proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
var op: TLoc
|
||||
initLocExpr(p, ri[0], op)
|
||||
var op = initLocExpr(p, ri[0])
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
assert(typ.kind == tyProc)
|
||||
assert(typ.len == typ.n.len)
|
||||
# don't call '$' here for efficiency:
|
||||
let pat = ri[0].sym.loc.r.data
|
||||
let pat = $ri[0].sym.loc.r
|
||||
internalAssert p.config, pat.len > 0
|
||||
if pat.contains({'#', '(', '@', '\''}):
|
||||
var pl = genPatternCall(p, ri, pat, typ)
|
||||
var pl = newRopeAppender()
|
||||
genPatternCall(p, ri, pat, typ, pl)
|
||||
# simpler version of 'fixupCall' that works with the pl+params combination:
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
if typ[0] != nil:
|
||||
if typ.returnType != nil:
|
||||
if p.module.compileToCpp and lfSingleUse in d.flags:
|
||||
# do not generate spurious temporaries for C++! For C we're better off
|
||||
# with them to prevent undefined behaviour and because the codegen
|
||||
@@ -653,95 +731,87 @@ proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
d.r = pl
|
||||
excl d.flags, lfSingleUse
|
||||
else:
|
||||
if d.k == locNone: getTemp(p, typ[0], d)
|
||||
if d.k == locNone: d = getTemp(p, typ.returnType)
|
||||
assert(d.t != nil) # generate an assignment to d:
|
||||
var list: TLoc
|
||||
initLoc(list, locCall, d.lode, OnUnknown)
|
||||
var list: TLoc = initLoc(locCall, d.lode, OnUnknown)
|
||||
list.r = pl
|
||||
genAssignment(p, d, list, {}) # no need for deep copying
|
||||
else:
|
||||
pl.add(~";$n")
|
||||
pl.add(";\n")
|
||||
line(p, cpsStmts, pl)
|
||||
else:
|
||||
var pl: Rope = nil
|
||||
#var param = typ.n[1].sym
|
||||
var pl = newRopeAppender()
|
||||
var argsCounter = 0
|
||||
if 1 < ri.len:
|
||||
pl.add(genThisArg(p, ri, 1, typ))
|
||||
genThisArg(p, ri, 1, typ, pl)
|
||||
pl.add(op.r)
|
||||
var params: Rope
|
||||
var params = newRopeAppender()
|
||||
for i in 2..<ri.len:
|
||||
if params != nil: params.add(~", ")
|
||||
assert(typ.len == typ.n.len)
|
||||
params.add(genOtherArg(p, ri, i, typ))
|
||||
genOtherArg(p, ri, i, typ, params, argsCounter)
|
||||
fixupCall(p, le, ri, d, pl, params)
|
||||
|
||||
proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
|
||||
# generates a crappy ObjC call
|
||||
var op: TLoc
|
||||
initLocExpr(p, ri[0], op)
|
||||
var pl = ~"["
|
||||
var op = initLocExpr(p, ri[0])
|
||||
var pl = "["
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
assert(typ.kind == tyProc)
|
||||
assert(typ.len == typ.n.len)
|
||||
|
||||
# don't call '$' here for efficiency:
|
||||
let pat = ri[0].sym.loc.r.data
|
||||
let pat = $ri[0].sym.loc.r
|
||||
internalAssert p.config, pat.len > 0
|
||||
var start = 3
|
||||
if ' ' in pat:
|
||||
start = 1
|
||||
pl.add(op.r)
|
||||
if ri.len > 1:
|
||||
pl.add(~": ")
|
||||
pl.add(genArg(p, ri[1], typ.n[1].sym, ri))
|
||||
pl.add(": ")
|
||||
genArg(p, ri[1], typ.n[1].sym, ri, pl)
|
||||
start = 2
|
||||
else:
|
||||
if ri.len > 1:
|
||||
pl.add(genArg(p, ri[1], typ.n[1].sym, ri))
|
||||
pl.add(~" ")
|
||||
genArg(p, ri[1], typ.n[1].sym, ri, pl)
|
||||
pl.add(" ")
|
||||
pl.add(op.r)
|
||||
if ri.len > 2:
|
||||
pl.add(~": ")
|
||||
pl.add(genArg(p, ri[2], typ.n[2].sym, ri))
|
||||
pl.add(": ")
|
||||
genArg(p, ri[2], typ.n[2].sym, ri, pl)
|
||||
for i in start..<ri.len:
|
||||
assert(typ.len == typ.n.len)
|
||||
if i >= typ.len:
|
||||
if i >= typ.n.len:
|
||||
internalError(p.config, ri.info, "varargs for objective C method?")
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
var param = typ.n[i].sym
|
||||
pl.add(~" ")
|
||||
pl.add(" ")
|
||||
pl.add(param.name.s)
|
||||
pl.add(~": ")
|
||||
pl.add(genArg(p, ri[i], param, ri))
|
||||
if typ[0] != nil:
|
||||
if isInvalidReturnType(p.config, typ[0]):
|
||||
if ri.len > 1: pl.add(~" ")
|
||||
pl.add(": ")
|
||||
genArg(p, ri[i], param, ri, pl)
|
||||
if typ.returnType != nil:
|
||||
if isInvalidReturnType(p.config, typ):
|
||||
if ri.len > 1: pl.add(" ")
|
||||
# beware of 'result = p(result)'. We always allocate a temporary:
|
||||
if d.k in {locTemp, locNone}:
|
||||
# We already got a temp. Great, special case it:
|
||||
if d.k == locNone: getTemp(p, typ[0], d, needsInit=true)
|
||||
pl.add(~"Result: ")
|
||||
if d.k == locNone: d = getTemp(p, typ.returnType, needsInit=true)
|
||||
pl.add("Result: ")
|
||||
pl.add(addrLoc(p.config, d))
|
||||
pl.add(~"];$n")
|
||||
pl.add("];\n")
|
||||
line(p, cpsStmts, pl)
|
||||
else:
|
||||
var tmp: TLoc
|
||||
getTemp(p, typ[0], tmp, needsInit=true)
|
||||
var tmp: TLoc = getTemp(p, typ.returnType, needsInit=true)
|
||||
pl.add(addrLoc(p.config, tmp))
|
||||
pl.add(~"];$n")
|
||||
pl.add("];\n")
|
||||
line(p, cpsStmts, pl)
|
||||
genAssignment(p, d, tmp, {}) # no need for deep copying
|
||||
else:
|
||||
pl.add(~"]")
|
||||
if d.k == locNone: getTemp(p, typ[0], d)
|
||||
pl.add("]")
|
||||
if d.k == locNone: d = getTemp(p, typ.returnType)
|
||||
assert(d.t != nil) # generate an assignment to d:
|
||||
var list: TLoc
|
||||
initLoc(list, locCall, ri, OnUnknown)
|
||||
var list: TLoc = initLoc(locCall, ri, OnUnknown)
|
||||
list.r = pl
|
||||
genAssignment(p, d, list, {}) # no need for deep copying
|
||||
else:
|
||||
pl.add(~"];$n")
|
||||
pl.add("];\n")
|
||||
line(p, cpsStmts, pl)
|
||||
|
||||
proc notYetAlive(n: PNode): bool {.inline.} =
|
||||
@@ -779,6 +849,5 @@ proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
genNamedParamCall(p, ri, d)
|
||||
else:
|
||||
genPrefixCall(p, le, ri, d)
|
||||
postStmtActions(p)
|
||||
|
||||
proc genCall(p: BProc, e: PNode, d: var TLoc) = genAsgnCall(p, nil, e, d)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ template detectVersion(field, corename) =
|
||||
if core == nil or core.kind != skConst:
|
||||
m.g.field = 1
|
||||
else:
|
||||
m.g.field = toInt(ast.getInt(core.ast))
|
||||
m.g.field = toInt(ast.getInt(core.astdef))
|
||||
result = m.g.field
|
||||
|
||||
proc detectStrVersion(m: BModule): int =
|
||||
@@ -32,82 +32,87 @@ proc detectSeqVersion(m: BModule): int =
|
||||
|
||||
# ----- Version 1: GC'ed strings and seqs --------------------------------
|
||||
|
||||
proc genStringLiteralDataOnlyV1(m: BModule, s: string): Rope =
|
||||
discard cgsym(m, "TGenericSeq")
|
||||
result = getTempName(m)
|
||||
m.s[cfsData].addf("STRING_LITERAL($1, $2, $3);$n",
|
||||
[result, makeCString(s), rope(s.len)])
|
||||
proc genStringLiteralDataOnlyV1(m: BModule, s: string; result: var Rope) =
|
||||
cgsym(m, "TGenericSeq")
|
||||
let tmp = getTempName(m)
|
||||
result.add tmp
|
||||
m.s[cfsStrData].addf("STRING_LITERAL($1, $2, $3);$n",
|
||||
[tmp, makeCString(s), rope(s.len)])
|
||||
|
||||
proc genStringLiteralV1(m: BModule; n: PNode): Rope =
|
||||
proc genStringLiteralV1(m: BModule; n: PNode; result: var Rope) =
|
||||
if s.isNil:
|
||||
result = ropecg(m, "((#NimStringDesc*) NIM_NIL)", [])
|
||||
appcg(m, result, "((#NimStringDesc*) NIM_NIL)", [])
|
||||
else:
|
||||
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
|
||||
if id == m.labels:
|
||||
# string literal not found in the cache:
|
||||
result = ropecg(m, "((#NimStringDesc*) &$1)",
|
||||
[genStringLiteralDataOnlyV1(m, n.strVal)])
|
||||
appcg(m, result, "((#NimStringDesc*) &", [])
|
||||
genStringLiteralDataOnlyV1(m, n.strVal, result)
|
||||
result.add ")"
|
||||
else:
|
||||
result = ropecg(m, "((#NimStringDesc*) &$1$2)",
|
||||
appcg(m, result, "((#NimStringDesc*) &$1$2)",
|
||||
[m.tmpBase, id])
|
||||
|
||||
# ------ Version 2: destructor based strings and seqs -----------------------
|
||||
|
||||
proc genStringLiteralDataOnlyV2(m: BModule, s: string; result: Rope; isConst: bool) =
|
||||
m.s[cfsData].addf("static $4 struct {$n" &
|
||||
m.s[cfsStrData].addf("static $4 struct {$n" &
|
||||
" NI cap; NIM_CHAR data[$2+1];$n" &
|
||||
"} $1 = { $2 | NIM_STRLIT_FLAG, $3 };$n",
|
||||
[result, rope(s.len), makeCString(s),
|
||||
rope(if isConst: "const" else: "")])
|
||||
|
||||
proc genStringLiteralV2(m: BModule; n: PNode; isConst: bool): Rope =
|
||||
proc genStringLiteralV2(m: BModule; n: PNode; isConst: bool; result: var Rope) =
|
||||
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
|
||||
if id == m.labels:
|
||||
let pureLit = getTempName(m)
|
||||
genStringLiteralDataOnlyV2(m, n.strVal, pureLit, isConst)
|
||||
result = getTempName(m)
|
||||
discard cgsym(m, "NimStrPayload")
|
||||
discard cgsym(m, "NimStringV2")
|
||||
let tmp = getTempName(m)
|
||||
result.add tmp
|
||||
cgsym(m, "NimStrPayload")
|
||||
cgsym(m, "NimStringV2")
|
||||
# string literal not found in the cache:
|
||||
m.s[cfsData].addf("static $4 NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n",
|
||||
[result, rope(n.strVal.len), pureLit, rope(if isConst: "const" else: "")])
|
||||
m.s[cfsStrData].addf("static $4 NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n",
|
||||
[tmp, rope(n.strVal.len), pureLit, rope(if isConst: "const" else: "")])
|
||||
else:
|
||||
result = getTempName(m)
|
||||
m.s[cfsData].addf("static $4 NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n",
|
||||
[result, rope(n.strVal.len), m.tmpBase & rope(id),
|
||||
let tmp = getTempName(m)
|
||||
result.add tmp
|
||||
m.s[cfsStrData].addf("static $4 NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n",
|
||||
[tmp, rope(n.strVal.len), m.tmpBase & rope(id),
|
||||
rope(if isConst: "const" else: "")])
|
||||
|
||||
proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool): Rope =
|
||||
proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Rope) =
|
||||
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
|
||||
var pureLit: Rope
|
||||
if id == m.labels:
|
||||
pureLit = getTempName(m)
|
||||
discard cgsym(m, "NimStrPayload")
|
||||
discard cgsym(m, "NimStringV2")
|
||||
cgsym(m, "NimStrPayload")
|
||||
cgsym(m, "NimStringV2")
|
||||
# string literal not found in the cache:
|
||||
genStringLiteralDataOnlyV2(m, n.strVal, pureLit, isConst)
|
||||
else:
|
||||
pureLit = m.tmpBase & rope(id)
|
||||
result = "{$1, (NimStrPayload*)&$2}" % [rope(n.strVal.len), pureLit]
|
||||
result.addf "{$1, (NimStrPayload*)&$2}", [rope(n.strVal.len), pureLit]
|
||||
|
||||
# ------ Version selector ---------------------------------------------------
|
||||
|
||||
proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo;
|
||||
isConst: bool): Rope =
|
||||
isConst: bool; result: var Rope) =
|
||||
case detectStrVersion(m)
|
||||
of 0, 1: result = genStringLiteralDataOnlyV1(m, s)
|
||||
of 0, 1: genStringLiteralDataOnlyV1(m, s, result)
|
||||
of 2:
|
||||
result = getTempName(m)
|
||||
genStringLiteralDataOnlyV2(m, s, result, isConst)
|
||||
let tmp = getTempName(m)
|
||||
genStringLiteralDataOnlyV2(m, s, tmp, isConst)
|
||||
result.add tmp
|
||||
else:
|
||||
localError(m.config, info, "cannot determine how to produce code for string literal")
|
||||
|
||||
proc genNilStringLiteral(m: BModule; info: TLineInfo): Rope =
|
||||
result = ropecg(m, "((#NimStringDesc*) NIM_NIL)", [])
|
||||
proc genNilStringLiteral(m: BModule; info: TLineInfo; result: var Rope) =
|
||||
appcg(m, result, "((#NimStringDesc*) NIM_NIL)", [])
|
||||
|
||||
proc genStringLiteral(m: BModule; n: PNode): Rope =
|
||||
proc genStringLiteral(m: BModule; n: PNode; result: var Rope) =
|
||||
case detectStrVersion(m)
|
||||
of 0, 1: result = genStringLiteralV1(m, n)
|
||||
of 2: result = genStringLiteralV2(m, n, isConst = true)
|
||||
of 0, 1: genStringLiteralV1(m, n, result)
|
||||
of 2: genStringLiteralV2(m, n, isConst = true, result)
|
||||
else:
|
||||
localError(m.config, n.info, "cannot determine how to produce code for string literal")
|
||||
|
||||
@@ -19,13 +19,11 @@ import
|
||||
|
||||
const
|
||||
CFileSectionNames: array[TCFileSection, string] = [
|
||||
cfsMergeInfo: "",
|
||||
cfsHeaders: "NIM_merge_HEADERS",
|
||||
cfsFrameDefines: "NIM_merge_FRAME_DEFINES",
|
||||
cfsForwardTypes: "NIM_merge_FORWARD_TYPES",
|
||||
cfsTypes: "NIM_merge_TYPES",
|
||||
cfsSeqTypes: "NIM_merge_SEQ_TYPES",
|
||||
cfsFieldInfo: "NIM_merge_FIELD_INFO",
|
||||
cfsTypeInfo: "NIM_merge_TYPE_INFO",
|
||||
cfsProcHeaders: "NIM_merge_PROC_HEADERS",
|
||||
cfsData: "NIM_merge_DATA",
|
||||
@@ -34,11 +32,8 @@ const
|
||||
cfsInitProc: "NIM_merge_INIT_PROC",
|
||||
cfsDatInitProc: "NIM_merge_DATINIT_PROC",
|
||||
cfsTypeInit1: "NIM_merge_TYPE_INIT1",
|
||||
cfsTypeInit2: "NIM_merge_TYPE_INIT2",
|
||||
cfsTypeInit3: "NIM_merge_TYPE_INIT3",
|
||||
cfsDebugInit: "NIM_merge_DEBUG_INIT",
|
||||
cfsDynLibInit: "NIM_merge_DYNLIB_INIT",
|
||||
cfsDynLibDeinit: "NIM_merge_DYNLIB_DEINIT",
|
||||
cfsDynLibInit: "NIM_merge_DYNLIB_INIT"
|
||||
]
|
||||
CProcSectionNames: array[TCProcSection, string] = [
|
||||
cpsLocals: "NIM_merge_PROC_LOCALS",
|
||||
|
||||
@@ -24,7 +24,7 @@ proc specializeResetN(p: BProc, accessor: Rope, n: PNode;
|
||||
of nkRecCase:
|
||||
if (n[0].kind != nkSym): internalError(p.config, n.info, "specializeResetN")
|
||||
let disc = n[0].sym
|
||||
if disc.loc.r == nil: fillObjectFields(p.module, typ)
|
||||
if disc.loc.r == "": fillObjectFields(p.module, typ)
|
||||
if disc.loc.t == nil:
|
||||
internalError(p.config, n.info, "specializeResetN()")
|
||||
lineF(p, cpsStmts, "switch ($1.$2) {$n", [accessor, disc.loc.r])
|
||||
@@ -42,7 +42,7 @@ proc specializeResetN(p: BProc, accessor: Rope, n: PNode;
|
||||
of nkSym:
|
||||
let field = n.sym
|
||||
if field.typ.kind == tyVoid: return
|
||||
if field.loc.r == nil: fillObjectFields(p.module, typ)
|
||||
if field.loc.r == "": fillObjectFields(p.module, typ)
|
||||
if field.loc.t == nil:
|
||||
internalError(p.config, n.info, "specializeResetN()")
|
||||
specializeResetT(p, "$1.$2" % [accessor, field.loc.r], field.loc.t)
|
||||
@@ -54,25 +54,23 @@ proc specializeResetT(p: BProc, accessor: Rope, typ: PType) =
|
||||
case typ.kind
|
||||
of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred,
|
||||
tySink, tyOwned:
|
||||
specializeResetT(p, accessor, lastSon(typ))
|
||||
specializeResetT(p, accessor, skipModifier(typ))
|
||||
of tyArray:
|
||||
let arraySize = lengthOrd(p.config, typ[0])
|
||||
var i: TLoc
|
||||
getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt), i)
|
||||
let arraySize = lengthOrd(p.config, typ.indexType)
|
||||
var i: TLoc = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt))
|
||||
linefmt(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",
|
||||
[i.r, arraySize])
|
||||
specializeResetT(p, ropecg(p.module, "$1[$2]", [accessor, i.r]), typ[1])
|
||||
specializeResetT(p, ropecg(p.module, "$1[$2]", [accessor, i.r]), typ.elementType)
|
||||
lineF(p, cpsStmts, "}$n", [])
|
||||
of tyObject:
|
||||
for i in 0..<typ.len:
|
||||
var x = typ[i]
|
||||
if x != nil: x = x.skipTypes(skipPtrs)
|
||||
specializeResetT(p, accessor.parentObj(p.module), x)
|
||||
var x = typ.baseClass
|
||||
if x != nil: x = x.skipTypes(skipPtrs)
|
||||
specializeResetT(p, accessor.parentObj(p.module), x)
|
||||
if typ.n != nil: specializeResetN(p, accessor, typ.n, typ)
|
||||
of tyTuple:
|
||||
let typ = getUniqueType(typ)
|
||||
for i in 0..<typ.len:
|
||||
specializeResetT(p, ropecg(p.module, "$1.Field$2", [accessor, i]), typ[i])
|
||||
for i, a in typ.ikids:
|
||||
specializeResetT(p, ropecg(p.module, "$1.Field$2", [accessor, i]), a)
|
||||
|
||||
of tyString, tyRef, tySequence:
|
||||
lineCg(p, cpsStmts, "#unsureAsgnRef((void**)&$1, NIM_NIL);$n", [accessor])
|
||||
@@ -83,11 +81,24 @@ proc specializeResetT(p: BProc, accessor: Rope, typ: PType) =
|
||||
lineCg(p, cpsStmts, "$1.ClP_0 = NIM_NIL;$n", [accessor])
|
||||
else:
|
||||
lineCg(p, cpsStmts, "$1 = NIM_NIL;$n", [accessor])
|
||||
of tyChar, tyBool, tyEnum, tyInt..tyUInt64:
|
||||
of tyChar, tyBool, tyEnum, tyRange, tyInt..tyUInt64:
|
||||
lineCg(p, cpsStmts, "$1 = 0;$n", [accessor])
|
||||
of tyCString, tyPointer, tyPtr, tyVar, tyLent:
|
||||
of tyCstring, tyPointer, tyPtr, tyVar, tyLent:
|
||||
lineCg(p, cpsStmts, "$1 = NIM_NIL;$n", [accessor])
|
||||
else:
|
||||
of tySet:
|
||||
case mapSetType(p.config, typ)
|
||||
of ctArray:
|
||||
lineCg(p, cpsStmts, "#nimZeroMem($1, sizeof($2));$n",
|
||||
[accessor, getTypeDesc(p.module, typ)])
|
||||
of ctInt8, ctInt16, ctInt32, ctInt64:
|
||||
lineCg(p, cpsStmts, "$1 = 0;$n", [accessor])
|
||||
else:
|
||||
raiseAssert "unexpected set type kind"
|
||||
of tyNone, tyEmpty, tyNil, tyUntyped, tyTyped, tyGenericInvocation,
|
||||
tyGenericParam, tyOrdinal, tyOpenArray, tyForward, tyVarargs,
|
||||
tyUncheckedArray, tyProxy, tyBuiltInTypeClass, tyUserTypeClass,
|
||||
tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot,
|
||||
tyAnything, tyStatic, tyFromExpr, tyConcept, tyVoid, tyIterable:
|
||||
discard
|
||||
|
||||
proc specializeReset(p: BProc, a: TLoc) =
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,13 +44,13 @@ proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) =
|
||||
m.s[cfsVars].addf(" $1;$n", [s.loc.r])
|
||||
|
||||
proc generateThreadLocalStorage(m: BModule) =
|
||||
if m.g.nimtv != nil and (usesThreadVars in m.flags or sfMainModule in m.module.flags):
|
||||
if m.g.nimtv != "" and (usesThreadVars in m.flags or sfMainModule in m.module.flags):
|
||||
for t in items(m.g.nimtvDeps): discard getTypeDesc(m, t)
|
||||
finishTypeDescriptions(m)
|
||||
m.s[cfsSeqTypes].addf("typedef struct {$1} NimThreadVars;$n", [m.g.nimtv])
|
||||
|
||||
proc generateThreadVarsSize(m: BModule) =
|
||||
if m.g.nimtv != nil:
|
||||
if m.g.nimtv != "":
|
||||
let externc = if m.config.backend == backendCpp or
|
||||
sfCompileToCpp in m.module.flags: "extern \"C\" "
|
||||
else: ""
|
||||
|
||||
@@ -21,7 +21,7 @@ const
|
||||
|
||||
proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType)
|
||||
proc genCaseRange(p: BProc, branch: PNode)
|
||||
proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false)
|
||||
proc getTemp(p: BProc, t: PType, needsInit=false): TLoc
|
||||
|
||||
proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode;
|
||||
typ: PType) =
|
||||
@@ -34,7 +34,7 @@ proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode;
|
||||
if (n[0].kind != nkSym): internalError(c.p.config, n.info, "genTraverseProc")
|
||||
var p = c.p
|
||||
let disc = n[0].sym
|
||||
if disc.loc.r == nil: fillObjectFields(c.p.module, typ)
|
||||
if disc.loc.r == "": fillObjectFields(c.p.module, typ)
|
||||
if disc.loc.t == nil:
|
||||
internalError(c.p.config, n.info, "genTraverseProc()")
|
||||
lineF(p, cpsStmts, "switch ($1.$2) {$n", [accessor, disc.loc.r])
|
||||
@@ -51,7 +51,7 @@ proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode;
|
||||
of nkSym:
|
||||
let field = n.sym
|
||||
if field.typ.kind == tyVoid: return
|
||||
if field.loc.r == nil: fillObjectFields(c.p.module, typ)
|
||||
if field.loc.r == "": fillObjectFields(c.p.module, typ)
|
||||
if field.loc.t == nil:
|
||||
internalError(c.p.config, n.info, "genTraverseProc()")
|
||||
genTraverseProc(c, "$1.$2" % [accessor, field.loc.r], field.loc.t)
|
||||
@@ -71,37 +71,36 @@ proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType) =
|
||||
case typ.kind
|
||||
of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred,
|
||||
tySink, tyOwned:
|
||||
genTraverseProc(c, accessor, lastSon(typ))
|
||||
genTraverseProc(c, accessor, skipModifier(typ))
|
||||
of tyArray:
|
||||
let arraySize = lengthOrd(c.p.config, typ[0])
|
||||
var i: TLoc
|
||||
getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo, tyInt), i)
|
||||
let oldCode = p.s(cpsStmts)
|
||||
let arraySize = lengthOrd(c.p.config, typ.indexType)
|
||||
var i: TLoc = getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo, tyInt))
|
||||
var oldCode = p.s(cpsStmts)
|
||||
freeze oldCode
|
||||
linefmt(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",
|
||||
[i.r, arraySize])
|
||||
let oldLen = p.s(cpsStmts).len
|
||||
genTraverseProc(c, ropecg(c.p.module, "$1[$2]", [accessor, i.r]), typ[1])
|
||||
genTraverseProc(c, ropecg(c.p.module, "$1[$2]", [accessor, i.r]), typ.elementType)
|
||||
if p.s(cpsStmts).len == oldLen:
|
||||
# do not emit dummy long loops for faster debug builds:
|
||||
p.s(cpsStmts) = oldCode
|
||||
else:
|
||||
lineF(p, cpsStmts, "}$n", [])
|
||||
of tyObject:
|
||||
for i in 0..<typ.len:
|
||||
var x = typ[i]
|
||||
if x != nil: x = x.skipTypes(skipPtrs)
|
||||
genTraverseProc(c, accessor.parentObj(c.p.module), x)
|
||||
var x = typ.baseClass
|
||||
if x != nil: x = x.skipTypes(skipPtrs)
|
||||
genTraverseProc(c, accessor.parentObj(c.p.module), x)
|
||||
if typ.n != nil: genTraverseProc(c, accessor, typ.n, typ)
|
||||
of tyTuple:
|
||||
let typ = getUniqueType(typ)
|
||||
for i in 0..<typ.len:
|
||||
genTraverseProc(c, ropecg(c.p.module, "$1.Field$2", [accessor, i]), typ[i])
|
||||
for i, a in typ.ikids:
|
||||
genTraverseProc(c, ropecg(c.p.module, "$1.Field$2", [accessor, i]), a)
|
||||
of tyRef:
|
||||
lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
|
||||
of tySequence:
|
||||
if optSeqDestructors notin c.p.module.config.globalOptions:
|
||||
lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
|
||||
elif containsGarbageCollectedRef(typ.lastSon):
|
||||
elif containsGarbageCollectedRef(typ.elementType):
|
||||
# destructor based seqs are themselves not traced but their data is, if
|
||||
# they contain a GC'ed type:
|
||||
lineCg(p, cpsStmts, "#nimGCvisitSeq((void*)$1, $2);$n", [accessor, c.visitorFrmt])
|
||||
@@ -118,16 +117,15 @@ proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType) =
|
||||
proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType) =
|
||||
var p = c.p
|
||||
assert typ.kind == tySequence
|
||||
var i: TLoc
|
||||
getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo, tyInt), i)
|
||||
let oldCode = p.s(cpsStmts)
|
||||
var a: TLoc
|
||||
a.r = accessor
|
||||
var i = getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo, tyInt))
|
||||
var oldCode = p.s(cpsStmts)
|
||||
freeze oldCode
|
||||
var a = TLoc(r: accessor)
|
||||
|
||||
lineF(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",
|
||||
[i.r, lenExpr(c.p, a)])
|
||||
let oldLen = p.s(cpsStmts).len
|
||||
genTraverseProc(c, "$1$3[$2]" % [accessor, i.r, dataField(c.p)], typ[0])
|
||||
genTraverseProc(c, "$1$3[$2]" % [accessor, i.r, dataField(c.p)], typ.elementType)
|
||||
if p.s(cpsStmts).len == oldLen:
|
||||
# do not emit dummy long loops for faster debug builds:
|
||||
p.s(cpsStmts) = oldCode
|
||||
@@ -135,7 +133,6 @@ proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType) =
|
||||
lineF(p, cpsStmts, "}$n", [])
|
||||
|
||||
proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash): Rope =
|
||||
var c: TTraversalClosure
|
||||
var p = newProc(nil, m)
|
||||
result = "Marker_" & getTypeName(m, origTyp, sig)
|
||||
let
|
||||
@@ -148,18 +145,19 @@ proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash): Rope =
|
||||
lineF(p, cpsLocals, "$1 a;$n", [t])
|
||||
lineF(p, cpsInit, "a = ($1)p;$n", [t])
|
||||
|
||||
c.p = p
|
||||
c.visitorFrmt = "op" # "#nimGCvisit((void*)$1, op);$n"
|
||||
var c = TTraversalClosure(p: p,
|
||||
visitorFrmt: "op" # "#nimGCvisit((void*)$1, op);$n"
|
||||
)
|
||||
|
||||
assert typ.kind != tyTypeDesc
|
||||
if typ.kind == tySequence:
|
||||
genTraverseProcSeq(c, "a".rope, typ)
|
||||
else:
|
||||
if skipTypes(typ[0], typedescInst+{tyOwned}).kind == tyArray:
|
||||
if skipTypes(typ.elementType, typedescInst+{tyOwned}).kind == tyArray:
|
||||
# C's arrays are broken beyond repair:
|
||||
genTraverseProc(c, "a".rope, typ[0])
|
||||
genTraverseProc(c, "a".rope, typ.elementType)
|
||||
else:
|
||||
genTraverseProc(c, "(*a)".rope, typ[0])
|
||||
genTraverseProc(c, "(*a)".rope, typ.elementType)
|
||||
|
||||
let generatedProc = "$1 {$n$2$3$4}\n" %
|
||||
[header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)]
|
||||
@@ -175,7 +173,6 @@ proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash): Rope =
|
||||
proc genTraverseProcForGlobal(m: BModule, s: PSym; info: TLineInfo): Rope =
|
||||
discard genTypeInfoV1(m, s.loc.t, info)
|
||||
|
||||
var c: TTraversalClosure
|
||||
var p = newProc(nil, m)
|
||||
var sLoc = rdLoc(s.loc)
|
||||
result = getTempName(m)
|
||||
@@ -184,8 +181,10 @@ proc genTraverseProcForGlobal(m: BModule, s: PSym; info: TLineInfo): Rope =
|
||||
accessThreadLocalVar(p, s)
|
||||
sLoc = "NimTV_->" & sLoc
|
||||
|
||||
c.visitorFrmt = "0" # "#nimGCvisit((void*)$1, 0);$n"
|
||||
c.p = p
|
||||
var c = TTraversalClosure(p: p,
|
||||
visitorFrmt: "0" # "#nimGCvisit((void*)$1, 0);$n"
|
||||
)
|
||||
|
||||
let header = "static N_NIMCALL(void, $1)(void)" % [result]
|
||||
genTraverseProc(c, sLoc, s.loc.t)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,19 +10,27 @@
|
||||
# This module declares some helpers for the C code generator.
|
||||
|
||||
import
|
||||
ast, hashes, strutils, msgs, wordrecg,
|
||||
platform, trees, options
|
||||
ast, types, msgs, wordrecg,
|
||||
platform, trees, options, cgendata, mangleutils
|
||||
|
||||
import std/[hashes, strutils, formatfloat]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode =
|
||||
case n.kind
|
||||
of nkStmtList:
|
||||
result = nil
|
||||
for i in 0..<n.len:
|
||||
result = getPragmaStmt(n[i], w)
|
||||
if result != nil: break
|
||||
of nkPragma:
|
||||
result = nil
|
||||
for i in 0..<n.len:
|
||||
if whichPragma(n[i]) == w: return n[i]
|
||||
else: discard
|
||||
else:
|
||||
result = nil
|
||||
|
||||
proc stmtsContainPragma*(n: PNode, w: TSpecialWord): bool =
|
||||
result = getPragmaStmt(n, w) != nil
|
||||
@@ -50,7 +58,7 @@ proc hashString*(conf: ConfigRef; s: string): BiggestInt =
|
||||
a = a + (a shl 3)
|
||||
a = a xor (a shr 11)
|
||||
a = a + (a shl 15)
|
||||
result = cast[Hash](a)
|
||||
result = cast[Hash](uint(a))
|
||||
|
||||
template getUniqueType*(key: PType): PType = key
|
||||
|
||||
@@ -60,49 +68,103 @@ proc makeSingleLineCString*(s: string): string =
|
||||
c.toCChar(result)
|
||||
result.add('\"')
|
||||
|
||||
proc mangle*(name: string): string =
|
||||
result = newStringOfCap(name.len)
|
||||
var start = 0
|
||||
if name[0] in Digits:
|
||||
result.add("X" & name[0])
|
||||
start = 1
|
||||
var requiresUnderscore = false
|
||||
template special(x) =
|
||||
result.add x
|
||||
requiresUnderscore = true
|
||||
for i in start..<name.len:
|
||||
let c = name[i]
|
||||
case c
|
||||
of 'a'..'z', '0'..'9', 'A'..'Z':
|
||||
result.add(c)
|
||||
of '_':
|
||||
# we generate names like 'foo_9' for scope disambiguations and so
|
||||
# disallow this here:
|
||||
if i > 0 and i < name.len-1 and name[i+1] in Digits:
|
||||
discard
|
||||
else:
|
||||
result.add(c)
|
||||
of '$': special "dollar"
|
||||
of '%': special "percent"
|
||||
of '&': special "amp"
|
||||
of '^': special "roof"
|
||||
of '!': special "emark"
|
||||
of '?': special "qmark"
|
||||
of '*': special "star"
|
||||
of '+': special "plus"
|
||||
of '-': special "minus"
|
||||
of '/': special "slash"
|
||||
of '\\': special "backslash"
|
||||
of '=': special "eq"
|
||||
of '<': special "lt"
|
||||
of '>': special "gt"
|
||||
of '~': special "tilde"
|
||||
of ':': special "colon"
|
||||
of '.': special "dot"
|
||||
of '@': special "at"
|
||||
of '|': special "bar"
|
||||
proc mapSetType(conf: ConfigRef; typ: PType): TCTypeKind =
|
||||
case int(getSize(conf, typ))
|
||||
of 1: result = ctInt8
|
||||
of 2: result = ctInt16
|
||||
of 4: result = ctInt32
|
||||
of 8: result = ctInt64
|
||||
else: result = ctArray
|
||||
|
||||
proc ccgIntroducedPtr*(conf: ConfigRef; s: PSym, retType: PType): bool =
|
||||
var pt = skipTypes(s.typ, typedescInst)
|
||||
assert skResult != s.kind
|
||||
|
||||
#note precedence: params override types
|
||||
if optByRef in s.options: return true
|
||||
elif sfByCopy in s.flags: return false
|
||||
elif tfByRef in pt.flags: return true
|
||||
elif tfByCopy in pt.flags: return false
|
||||
case pt.kind
|
||||
of tyObject:
|
||||
if s.typ.sym != nil and sfForward in s.typ.sym.flags:
|
||||
# forwarded objects are *always* passed by pointers for consistency!
|
||||
result = true
|
||||
elif s.typ.kind == tySink and conf.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcHooks}:
|
||||
# bug #23354:
|
||||
result = false
|
||||
elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3):
|
||||
result = true # requested anyway
|
||||
elif (tfFinal in pt.flags) and (pt[0] == nil):
|
||||
result = false # no need, because no subtyping possible
|
||||
else:
|
||||
result.add("X" & toHex(ord(c), 2))
|
||||
requiresUnderscore = true
|
||||
if requiresUnderscore:
|
||||
result.add "_"
|
||||
result = true # ordinary objects are always passed by reference,
|
||||
# otherwise casting doesn't work
|
||||
of tyTuple:
|
||||
result = (getSize(conf, pt) > conf.target.floatSize*3) or (optByRef in s.options)
|
||||
else:
|
||||
result = false
|
||||
# first parameter and return type is 'lent T'? --> use pass by pointer
|
||||
if s.position == 0 and retType != nil and retType.kind == tyLent:
|
||||
result = not (pt.kind in {tyVar, tyArray, tyOpenArray, tyVarargs, tyRef, tyPtr, tyPointer} or
|
||||
pt.kind == tySet and mapSetType(conf, pt) == ctArray)
|
||||
|
||||
proc encodeName*(name: string): string =
|
||||
result = mangle(name)
|
||||
result = $result.len & result
|
||||
|
||||
proc makeUnique(m: BModule; s: PSym, name: string = ""): string =
|
||||
result = if name == "": s.name.s else: name
|
||||
result.add "__"
|
||||
result.add m.g.graph.ifaces[s.itemId.module].uniqueName
|
||||
result.add "_u"
|
||||
result.add $s.itemId.item
|
||||
|
||||
proc encodeSym*(m: BModule; s: PSym; makeUnique: bool = false): string =
|
||||
#Module::Type
|
||||
var name = s.name.s
|
||||
if makeUnique:
|
||||
name = makeUnique(m, s, name)
|
||||
"N" & encodeName(s.skipGenericOwner.name.s) & encodeName(name) & "E"
|
||||
|
||||
proc encodeType*(m: BModule; t: PType): string =
|
||||
result = ""
|
||||
var kindName = ($t.kind)[2..^1]
|
||||
kindName[0] = toLower($kindName[0])[0]
|
||||
case t.kind
|
||||
of tyObject, tyEnum, tyDistinct, tyUserTypeClass, tyGenericParam:
|
||||
result = encodeSym(m, t.sym)
|
||||
of tyGenericInst, tyUserTypeClassInst, tyGenericBody:
|
||||
result = encodeName(t[0].sym.name.s)
|
||||
result.add "I"
|
||||
for i in 1..<t.len - 1:
|
||||
result.add encodeType(m, t[i])
|
||||
result.add "E"
|
||||
of tySequence, tyOpenArray, tyArray, tyVarargs, tyTuple, tyProc, tySet, tyTypeDesc,
|
||||
tyPtr, tyRef, tyVar, tyLent, tySink, tyStatic, tyUncheckedArray, tyOr, tyAnd, tyBuiltInTypeClass:
|
||||
result =
|
||||
case t.kind:
|
||||
of tySequence: encodeName("seq")
|
||||
else: encodeName(kindName)
|
||||
result.add "I"
|
||||
for i in 0..<t.len:
|
||||
let s = t[i]
|
||||
if s.isNil: continue
|
||||
result.add encodeType(m, s)
|
||||
result.add "E"
|
||||
of tyRange:
|
||||
var val = "range_"
|
||||
if t.n[0].typ.kind in {tyFloat..tyFloat128}:
|
||||
val.addFloat t.n[0].floatVal
|
||||
val.add "_"
|
||||
val.addFloat t.n[1].floatVal
|
||||
else:
|
||||
val.add $t.n[0].intVal & "_" & $t.n[1].intVal
|
||||
result = encodeName(val)
|
||||
of tyString..tyUInt64, tyPointer, tyBool, tyChar, tyVoid, tyAnything, tyNil, tyEmpty:
|
||||
result = encodeName(kindName)
|
||||
of tyAlias, tyInferred, tyOwned:
|
||||
result = encodeType(m, t.elementType)
|
||||
else:
|
||||
assert false, "encodeType " & $t.kind
|
||||
|
||||
|
||||
1020
compiler/cgen.nim
1020
compiler/cgen.nim
File diff suppressed because it is too large
Load Diff
@@ -10,13 +10,14 @@
|
||||
## This module contains the data structures for the C code generation phase.
|
||||
|
||||
import
|
||||
ast, ropes, options, intsets,
|
||||
tables, ndi, lineinfos, pathutils, modulegraphs, sets
|
||||
ast, ropes, options,
|
||||
ndi, lineinfos, pathutils, modulegraphs
|
||||
|
||||
import std/[intsets, tables, sets]
|
||||
|
||||
type
|
||||
TLabel* = Rope # for the C generator a label is just a rope
|
||||
TCFileSection* = enum # the sections a generated C file consists of
|
||||
cfsMergeInfo, # section containing merge information
|
||||
cfsHeaders, # section for C include file headers
|
||||
cfsFrameDefines # section for nim frame macros
|
||||
cfsForwardTypes, # section for C forward typedefs
|
||||
@@ -24,21 +25,17 @@ type
|
||||
cfsSeqTypes, # section for sequence types only
|
||||
# this is needed for strange type generation
|
||||
# reasons
|
||||
cfsFieldInfo, # section for field information
|
||||
cfsTypeInfo, # section for type information (ag ABI checks)
|
||||
cfsProcHeaders, # section for C procs prototypes
|
||||
cfsStrData, # section for constant string literals
|
||||
cfsData, # section for C constant data
|
||||
cfsVars, # section for C variable declarations
|
||||
cfsProcs, # section for C procs that are not inline
|
||||
cfsInitProc, # section for the C init proc
|
||||
cfsDatInitProc, # section for the C datInit proc
|
||||
cfsTypeInit1, # section 1 for declarations of type information
|
||||
cfsTypeInit2, # section 2 for init of type information
|
||||
cfsTypeInit3, # section 3 for init of type information
|
||||
cfsDebugInit, # section for init of debug information
|
||||
cfsDynLibInit, # section for init of dynamic library binding
|
||||
cfsDynLibDeinit # section for deinitialization of dynamic
|
||||
# libraries
|
||||
TCTypeKind* = enum # describes the type kind of a C type
|
||||
ctVoid, ctChar, ctBool,
|
||||
ctInt, ctInt8, ctInt16, ctInt32, ctInt64,
|
||||
@@ -91,6 +88,7 @@ type
|
||||
options*: TOptions # options that should be used for code
|
||||
# generation; this is the same as prc.options
|
||||
# unless prc == nil
|
||||
optionsStack*: seq[TOptions]
|
||||
module*: BModule # used to prevent excessive parameter passing
|
||||
withinLoop*: int # > 0 if we are within a loop
|
||||
splitDecls*: int # > 0 if we are in some context for C++ that
|
||||
@@ -99,6 +97,7 @@ type
|
||||
withinTryWithExcept*: int # required for goto based exception handling
|
||||
withinBlockLeaveActions*: int # complex to explain
|
||||
sigConflicts*: CountTable[string]
|
||||
inUncheckedAssignSection*: int
|
||||
|
||||
TTypeSeq* = seq[PType]
|
||||
TypeCache* = Table[SigHash, Rope]
|
||||
@@ -138,6 +137,7 @@ type
|
||||
# unconditionally...
|
||||
# nimtvDeps is VERY hard to cache because it's
|
||||
# not a list of IDs nor can it be made to be one.
|
||||
mangledPrcs*: HashSet[string]
|
||||
|
||||
TCGen = object of PPassContext # represents a C source file
|
||||
s*: TCFileSections # sections of the C file
|
||||
@@ -151,7 +151,7 @@ type
|
||||
typeABICache*: HashSet[SigHash] # cache for ABI checks; reusing typeCache
|
||||
# would be ideal but for some reason enums
|
||||
# don't seem to get cached so it'd generate
|
||||
# 1 ABI check per occurence in code
|
||||
# 1 ABI check per occurrence in code
|
||||
forwTypeCache*: TypeCache # cache for forward declarations of types
|
||||
declaredThings*: IntSet # things we have declared in this .c file
|
||||
declaredProtos*: IntSet # prototypes we have declared in this .c file
|
||||
@@ -170,13 +170,13 @@ type
|
||||
labels*: Natural # for generating unique module-scope names
|
||||
extensionLoaders*: array['0'..'9', Rope] # special procs for the
|
||||
# OpenGL wrapper
|
||||
injectStmt*: Rope
|
||||
sigConflicts*: CountTable[SigHash]
|
||||
g*: BModuleList
|
||||
ndi*: NdiFile
|
||||
|
||||
template config*(m: BModule): ConfigRef = m.g.config
|
||||
template config*(p: BProc): ConfigRef = p.module.g.config
|
||||
template vccAndC*(p: BProc): bool = p.module.config.cCompiler == ccVcc and p.module.config.backend == backendC
|
||||
|
||||
proc includeHeader*(this: BModule; header: string) =
|
||||
if not this.headerFiles.contains header:
|
||||
@@ -190,16 +190,23 @@ proc procSec*(p: BProc, s: TCProcSection): var Rope {.inline.} =
|
||||
# top level proc sections
|
||||
result = p.blocks[0].sections[s]
|
||||
|
||||
proc initBlock*(): TBlock =
|
||||
result = TBlock()
|
||||
for i in low(result.sections)..high(result.sections):
|
||||
result.sections[i] = newRopeAppender()
|
||||
|
||||
proc newProc*(prc: PSym, module: BModule): BProc =
|
||||
new(result)
|
||||
result.prc = prc
|
||||
result.module = module
|
||||
result.options = if prc != nil: prc.options
|
||||
else: module.config.options
|
||||
newSeq(result.blocks, 1)
|
||||
result.nestedTryStmts = @[]
|
||||
result.finallySafePoints = @[]
|
||||
result.sigConflicts = initCountTable[string]()
|
||||
result = BProc(
|
||||
prc: prc,
|
||||
module: module,
|
||||
optionsStack: if module.initProc != nil: module.initProc.optionsStack
|
||||
else: @[],
|
||||
options: if prc != nil: prc.options
|
||||
else: module.config.options,
|
||||
blocks: @[initBlock()],
|
||||
sigConflicts: initCountTable[string]())
|
||||
if optQuirky in result.options:
|
||||
result.flags = {nimErrorFlagDisabled}
|
||||
|
||||
proc newModuleList*(g: ModuleGraph): BModuleList =
|
||||
BModuleList(typeInfoMarker: initTable[SigHash, tuple[str: Rope, owner: int32]](),
|
||||
|
||||
@@ -10,8 +10,15 @@
|
||||
## This module implements code generation for methods.
|
||||
|
||||
import
|
||||
intsets, options, ast, msgs, idents, renderer, types, magicsys,
|
||||
sempass2, strutils, modulegraphs, lineinfos
|
||||
options, ast, msgs, idents, renderer, types, magicsys,
|
||||
sempass2, modulegraphs, lineinfos, astalgo
|
||||
|
||||
import std/intsets
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import std/[tables]
|
||||
|
||||
proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
|
||||
var dest = skipTypes(d, abstractPtrs)
|
||||
@@ -40,12 +47,15 @@ proc getDispatcher*(s: PSym): PSym =
|
||||
if dispatcherPos < s.ast.len:
|
||||
result = s.ast[dispatcherPos].sym
|
||||
doAssert sfDispatcher in result.flags
|
||||
else:
|
||||
result = nil
|
||||
|
||||
proc methodCall*(n: PNode; conf: ConfigRef): PNode =
|
||||
result = n
|
||||
# replace ordinary method by dispatcher method:
|
||||
let disp = getDispatcher(result[0].sym)
|
||||
if disp != nil:
|
||||
result[0].typ = disp.typ
|
||||
result[0].sym = disp
|
||||
# change the arguments to up/downcasts to fit the dispatcher's parameters:
|
||||
for i in 1..<result.len:
|
||||
@@ -57,22 +67,25 @@ type
|
||||
MethodResult = enum No, Invalid, Yes
|
||||
|
||||
proc sameMethodBucket(a, b: PSym; multiMethods: bool): MethodResult =
|
||||
result = No
|
||||
if a.name.id != b.name.id: return
|
||||
if a.typ.len != b.typ.len:
|
||||
if a.typ.signatureLen != b.typ.signatureLen:
|
||||
return
|
||||
|
||||
for i in 1..<a.typ.len:
|
||||
var aa = a.typ[i]
|
||||
var bb = b.typ[i]
|
||||
var i = 0
|
||||
for x, y in paramTypePairs(a.typ, b.typ):
|
||||
inc i
|
||||
var aa = x
|
||||
var bb = y
|
||||
while true:
|
||||
aa = skipTypes(aa, {tyGenericInst, tyAlias})
|
||||
bb = skipTypes(bb, {tyGenericInst, tyAlias})
|
||||
if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef, tyLent, tySink}:
|
||||
aa = aa.lastSon
|
||||
bb = bb.lastSon
|
||||
aa = aa.elementType
|
||||
bb = bb.elementType
|
||||
else:
|
||||
break
|
||||
if sameType(a.typ[i], b.typ[i]):
|
||||
if sameType(x, y):
|
||||
if aa.kind == tyObject and result != Invalid:
|
||||
result = Yes
|
||||
elif aa.kind == tyObject and bb.kind == tyObject and (i == 1 or multiMethods):
|
||||
@@ -90,10 +103,11 @@ proc sameMethodBucket(a, b: PSym; multiMethods: bool): MethodResult =
|
||||
return No
|
||||
if result == Yes:
|
||||
# check for return type:
|
||||
if not sameTypeOrNil(a.typ[0], b.typ[0]):
|
||||
if b.typ[0] != nil and b.typ[0].kind == tyUntyped:
|
||||
# ignore flags of return types; # bug #22673
|
||||
if not sameTypeOrNil(a.typ.returnType, b.typ.returnType, {IgnoreFlags}):
|
||||
if b.typ.returnType != nil and b.typ.returnType.kind == tyUntyped:
|
||||
# infer 'auto' from the base to make it consistent:
|
||||
b.typ[0] = a.typ[0]
|
||||
b.typ.setReturnType a.typ.returnType
|
||||
else:
|
||||
return No
|
||||
|
||||
@@ -108,21 +122,21 @@ proc attachDispatcher(s: PSym, dispatcher: PNode) =
|
||||
s.ast[dispatcherPos] = dispatcher
|
||||
|
||||
proc createDispatcher(s: PSym; g: ModuleGraph; idgen: IdGenerator): PSym =
|
||||
var disp = copySym(s, nextSymId(idgen))
|
||||
var disp = copySym(s, idgen)
|
||||
incl(disp.flags, sfDispatcher)
|
||||
excl(disp.flags, sfExported)
|
||||
let old = disp.typ
|
||||
disp.typ = copyType(disp.typ, nextTypeId(idgen), disp.typ.owner)
|
||||
disp.typ = copyType(disp.typ, idgen, disp.typ.owner)
|
||||
copyTypeProps(g, idgen.module, disp.typ, old)
|
||||
|
||||
# we can't inline the dispatcher itself (for now):
|
||||
if disp.typ.callConv == ccInline: disp.typ.callConv = ccNimCall
|
||||
disp.ast = copyTree(s.ast)
|
||||
disp.ast[bodyPos] = newNodeI(nkEmpty, s.info)
|
||||
disp.loc.r = nil
|
||||
if s.typ[0] != nil:
|
||||
disp.loc.r = ""
|
||||
if s.typ.returnType != nil:
|
||||
if disp.ast.len > resultPos:
|
||||
disp.ast[resultPos].sym = copySym(s.ast[resultPos].sym, nextSymId(idgen))
|
||||
disp.ast[resultPos].sym = copySym(s.ast[resultPos].sym, idgen)
|
||||
else:
|
||||
# We've encountered a method prototype without a filled-in
|
||||
# resultPos slot. We put a placeholder in there that will
|
||||
@@ -143,25 +157,16 @@ proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
|
||||
disp.ast[resultPos].kind == nkEmpty:
|
||||
disp.ast[resultPos] = copyTree(meth.ast[resultPos])
|
||||
|
||||
# The following code works only with lock levels, so we disable
|
||||
# it when they're not available.
|
||||
when declared(TLockLevel):
|
||||
proc `<`(a, b: TLockLevel): bool {.borrow.}
|
||||
proc `==`(a, b: TLockLevel): bool {.borrow.}
|
||||
if disp.typ.lockLevel == UnspecifiedLockLevel:
|
||||
disp.typ.lockLevel = meth.typ.lockLevel
|
||||
elif meth.typ.lockLevel != UnspecifiedLockLevel and
|
||||
meth.typ.lockLevel != disp.typ.lockLevel:
|
||||
message(conf, meth.info, warnLockLevel,
|
||||
"method has lock level $1, but another method has $2" %
|
||||
[$meth.typ.lockLevel, $disp.typ.lockLevel])
|
||||
# XXX The following code silences a duplicate warning in
|
||||
# checkMethodeffects() in sempass2.nim for now.
|
||||
if disp.typ.lockLevel < meth.typ.lockLevel:
|
||||
disp.typ.lockLevel = meth.typ.lockLevel
|
||||
|
||||
proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
var witness: PSym
|
||||
var witness: PSym = nil
|
||||
if s.typ.firstParamType.owner.getModule != s.getModule and vtables in g.config.features and not
|
||||
g.config.isDefined("nimInternalNonVtablesTesting"):
|
||||
localError(g.config, s.info, errGenerated, "method `" & s.name.s &
|
||||
"` can be defined only in the same module with its type (" & s.typ.firstParamType.typeToString() & ")")
|
||||
if sfImportc in s.flags:
|
||||
localError(g.config, s.info, errGenerated, "method `" & s.name.s &
|
||||
"` is not allowed to have 'importc' pragmas")
|
||||
|
||||
for i in 0..<g.methods.len:
|
||||
let disp = g.methods[i].dispatcher
|
||||
case sameMethodBucket(disp, s, multimethods = optMultiMethods in g.config.globalOptions)
|
||||
@@ -180,6 +185,11 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
of Invalid:
|
||||
if witness.isNil: witness = g.methods[i].methods[0]
|
||||
# create a new dispatcher:
|
||||
# stores the id and the position
|
||||
if s.typ.firstParamType.skipTypes(skipPtrs).itemId notin g.bucketTable:
|
||||
g.bucketTable[s.typ.firstParamType.skipTypes(skipPtrs).itemId] = 1
|
||||
else:
|
||||
g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).itemId)
|
||||
g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen)))
|
||||
#echo "adding ", s.info
|
||||
if witness != nil:
|
||||
@@ -188,8 +198,9 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
elif sfBase notin s.flags:
|
||||
message(g.config, s.info, warnUseBase)
|
||||
|
||||
proc relevantCol(methods: seq[PSym], col: int): bool =
|
||||
proc relevantCol*(methods: seq[PSym], col: int): bool =
|
||||
# returns true iff the position is relevant
|
||||
result = false
|
||||
var t = methods[0].typ[col].skipTypes(skipPtrs)
|
||||
if t.kind == tyObject:
|
||||
for i in 1..high(methods):
|
||||
@@ -198,7 +209,8 @@ proc relevantCol(methods: seq[PSym], col: int): bool =
|
||||
return true
|
||||
|
||||
proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
|
||||
for col in 1..<a.typ.len:
|
||||
result = 0
|
||||
for col in FirstParamAt..<a.typ.signatureLen:
|
||||
if contains(relevantCols, col):
|
||||
var aa = skipTypes(a.typ[col], skipPtrs)
|
||||
var bb = skipTypes(b.typ[col], skipPtrs)
|
||||
@@ -206,7 +218,7 @@ proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
|
||||
if (d != high(int)) and d != 0:
|
||||
return d
|
||||
|
||||
proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
|
||||
proc sortBucket*(a: var seq[PSym], relevantCols: IntSet) =
|
||||
# we use shellsort here; fast and simple
|
||||
var n = a.len
|
||||
var h = 1
|
||||
@@ -225,16 +237,16 @@ proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
|
||||
a[j] = v
|
||||
if h == 1: break
|
||||
|
||||
proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PSym =
|
||||
proc genIfDispatcher*(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idgen: IdGenerator): PSym =
|
||||
var base = methods[0].ast[dispatcherPos].sym
|
||||
result = base
|
||||
var paramLen = base.typ.len
|
||||
var paramLen = base.typ.signatureLen
|
||||
var nilchecks = newNodeI(nkStmtList, base.info)
|
||||
var disp = newNodeI(nkIfStmt, base.info)
|
||||
var ands = getSysMagic(g, unknownLineInfo, "and", mAnd)
|
||||
var iss = getSysMagic(g, unknownLineInfo, "of", mOf)
|
||||
let boolType = getSysType(g, unknownLineInfo, tyBool)
|
||||
for col in 1..<paramLen:
|
||||
for col in FirstParamAt..<paramLen:
|
||||
if contains(relevantCols, col):
|
||||
let param = base.typ.n[col].sym
|
||||
if param.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
|
||||
@@ -243,7 +255,7 @@ proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PS
|
||||
for meth in 0..high(methods):
|
||||
var curr = methods[meth] # generate condition:
|
||||
var cond: PNode = nil
|
||||
for col in 1..<paramLen:
|
||||
for col in FirstParamAt..<paramLen:
|
||||
if contains(relevantCols, col):
|
||||
var isn = newNodeIT(nkCall, base.info, boolType)
|
||||
isn.add newSymNode(iss)
|
||||
@@ -258,7 +270,7 @@ proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PS
|
||||
cond = a
|
||||
else:
|
||||
cond = isn
|
||||
let retTyp = base.typ[0]
|
||||
let retTyp = base.typ.returnType
|
||||
let call = newNodeIT(nkCall, base.info, retTyp)
|
||||
call.add newSymNode(curr)
|
||||
for col in 1..<paramLen:
|
||||
@@ -284,14 +296,13 @@ proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PS
|
||||
nilchecks.flags.incl nfTransf # should not be further transformed
|
||||
result.ast[bodyPos] = nilchecks
|
||||
|
||||
proc generateMethodDispatchers*(g: ModuleGraph): PNode =
|
||||
result = newNode(nkStmtList)
|
||||
proc generateIfMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator) =
|
||||
for bucket in 0..<g.methods.len:
|
||||
var relevantCols = initIntSet()
|
||||
for col in 1..<g.methods[bucket].methods[0].typ.len:
|
||||
for col in FirstParamAt..<g.methods[bucket].methods[0].typ.signatureLen:
|
||||
if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col)
|
||||
if optMultiMethods notin g.config.globalOptions:
|
||||
# if multi-methods are not enabled, we are interested only in the first field
|
||||
break
|
||||
sortBucket(g.methods[bucket].methods, relevantCols)
|
||||
result.add newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols))
|
||||
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, idgen)
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
# dec a
|
||||
#
|
||||
# Should be transformed to:
|
||||
# STATE0:
|
||||
# case :state
|
||||
# of 0:
|
||||
# if a > 0:
|
||||
# echo "hi"
|
||||
# :state = 1 # Next state
|
||||
@@ -26,12 +27,14 @@
|
||||
# else:
|
||||
# :state = 2 # Next state
|
||||
# break :stateLoop # Proceed to the next state
|
||||
# STATE1:
|
||||
# of 1:
|
||||
# dec a
|
||||
# :state = 0 # Next state
|
||||
# break :stateLoop # Proceed to the next state
|
||||
# STATE2:
|
||||
# of 2:
|
||||
# :state = -1 # End of execution
|
||||
# else:
|
||||
# return
|
||||
|
||||
# The transformation should play well with lambdalifting, however depending
|
||||
# on situation, it can be called either before or after lambdalifting
|
||||
@@ -104,12 +107,13 @@
|
||||
# Is transformed to (yields are left in place for example simplicity,
|
||||
# in reality the code is subdivided even more, as described above):
|
||||
#
|
||||
# STATE0: # Try
|
||||
# case :state
|
||||
# of 0: # Try
|
||||
# yield 0
|
||||
# raise ...
|
||||
# :state = 2 # What would happen should we not raise
|
||||
# break :stateLoop
|
||||
# STATE1: # Except
|
||||
# of 1: # Except
|
||||
# yield 1
|
||||
# :tmpResult = 3 # Return
|
||||
# :unrollFinally = true # Return
|
||||
@@ -117,21 +121,31 @@
|
||||
# break :stateLoop
|
||||
# :state = 2 # What would happen should we not return
|
||||
# break :stateLoop
|
||||
# STATE2: # Finally
|
||||
# of 2: # Finally
|
||||
# yield 2
|
||||
# if :unrollFinally: # This node is created by `newEndFinallyNode`
|
||||
# if :curExc.isNil:
|
||||
# return :tmpResult
|
||||
# if nearestFinally == 0:
|
||||
# return :tmpResult
|
||||
# else:
|
||||
# :state = nearestFinally # bubble up
|
||||
# else:
|
||||
# closureIterSetupExc(nil)
|
||||
# raise
|
||||
# state = -1 # Goto next state. In this case we just exit
|
||||
# break :stateLoop
|
||||
# else:
|
||||
# return
|
||||
|
||||
import
|
||||
ast, msgs, idents,
|
||||
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos,
|
||||
tables, options
|
||||
options
|
||||
|
||||
import std/tables
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
Ctx = object
|
||||
@@ -142,7 +156,7 @@ type
|
||||
unrollFinallySym: PSym # Indicates that we're unrolling finally states (either exception happened or premature return)
|
||||
curExcSym: PSym # Current exception
|
||||
|
||||
states: seq[PNode] # The resulting states. Every state is an nkState node.
|
||||
states: seq[tuple[label: int, body: PNode]] # The resulting states.
|
||||
blockLevel: int # Temp used to transform break and continue stmts
|
||||
stateLoopLabel: PSym # Label to break on, when jumping between states.
|
||||
exitStateIdx: int # index of the last state
|
||||
@@ -158,6 +172,7 @@ type
|
||||
const
|
||||
nkSkip = {nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt,
|
||||
nkCommentStmt, nkMixinStmt, nkBindStmt} + procDefs
|
||||
emptyStateLabel = -1
|
||||
|
||||
proc newStateAccess(ctx: var Ctx): PNode =
|
||||
if ctx.stateVarSym.isNil:
|
||||
@@ -177,8 +192,9 @@ proc newStateAssgn(ctx: var Ctx, stateNo: int = -2): PNode =
|
||||
ctx.newStateAssgn(newIntTypeNode(stateNo, ctx.g.getSysType(TLineInfo(), tyInt)))
|
||||
|
||||
proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym =
|
||||
result = newSym(skVar, getIdent(ctx.g.cache, name), nextSymId(ctx.idgen), ctx.fn, ctx.fn.info)
|
||||
result = newSym(skVar, getIdent(ctx.g.cache, name), ctx.idgen, ctx.fn, ctx.fn.info)
|
||||
result.typ = typ
|
||||
result.flags.incl sfNoInit
|
||||
assert(not typ.isNil)
|
||||
|
||||
if not ctx.stateVarSym.isNil:
|
||||
@@ -190,7 +206,7 @@ proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym =
|
||||
else:
|
||||
let envParam = getEnvParam(ctx.fn)
|
||||
# let obj = envParam.typ.lastSon
|
||||
result = addUniqueField(envParam.typ.lastSon, result, ctx.g.cache, ctx.idgen)
|
||||
result = addUniqueField(envParam.typ.elementType, result, ctx.g.cache, ctx.idgen)
|
||||
|
||||
proc newEnvVarAccess(ctx: Ctx, s: PSym): PNode =
|
||||
if ctx.stateVarSym.isNil:
|
||||
@@ -200,7 +216,7 @@ proc newEnvVarAccess(ctx: Ctx, s: PSym): PNode =
|
||||
|
||||
proc newTmpResultAccess(ctx: var Ctx): PNode =
|
||||
if ctx.tmpResultSym.isNil:
|
||||
ctx.tmpResultSym = ctx.newEnvVar(":tmpResult", ctx.fn.typ[0])
|
||||
ctx.tmpResultSym = ctx.newEnvVar(":tmpResult", ctx.fn.typ.returnType)
|
||||
ctx.newEnvVarAccess(ctx.tmpResultSym)
|
||||
|
||||
proc newUnrollFinallyAccess(ctx: var Ctx, info: TLineInfo): PNode =
|
||||
@@ -220,10 +236,7 @@ proc newState(ctx: var Ctx, n, gotoOut: PNode): int =
|
||||
|
||||
result = ctx.states.len
|
||||
let resLit = ctx.g.newIntLit(n.info, result)
|
||||
let s = newNodeI(nkState, n.info)
|
||||
s.add(resLit)
|
||||
s.add(n)
|
||||
ctx.states.add(s)
|
||||
ctx.states.add((result, n))
|
||||
ctx.exceptionTable.add(ctx.curExcHandlingState)
|
||||
|
||||
if not gotoOut.isNil:
|
||||
@@ -252,10 +265,11 @@ proc hasYields(n: PNode): bool =
|
||||
of nkYieldStmt:
|
||||
result = true
|
||||
of nkSkip:
|
||||
discard
|
||||
result = false
|
||||
else:
|
||||
for c in n:
|
||||
if c.hasYields:
|
||||
result = false
|
||||
for i in ord(n.kind == nkCast)..<n.len:
|
||||
if n[i].hasYields:
|
||||
result = true
|
||||
break
|
||||
|
||||
@@ -319,7 +333,7 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
var ifBranch: PNode
|
||||
|
||||
if c.len > 1:
|
||||
var cond: PNode
|
||||
var cond: PNode = nil
|
||||
for i in 0..<c.len - 1:
|
||||
assert(c[i].kind == nkType)
|
||||
let nextCond = newTree(nkCall,
|
||||
@@ -382,26 +396,29 @@ proc getFinallyNode(ctx: var Ctx, n: PNode): PNode =
|
||||
proc hasYieldsInExpressions(n: PNode): bool =
|
||||
case n.kind
|
||||
of nkSkip:
|
||||
discard
|
||||
result = false
|
||||
of nkStmtListExpr:
|
||||
if isEmptyType(n.typ):
|
||||
result = false
|
||||
for c in n:
|
||||
if c.hasYieldsInExpressions:
|
||||
return true
|
||||
else:
|
||||
result = n.hasYields
|
||||
of nkCast:
|
||||
result = false
|
||||
for i in 1..<n.len:
|
||||
if n[i].hasYieldsInExpressions:
|
||||
return true
|
||||
else:
|
||||
result = false
|
||||
for c in n:
|
||||
if c.hasYieldsInExpressions:
|
||||
return true
|
||||
|
||||
proc exprToStmtList(n: PNode): tuple[s, res: PNode] =
|
||||
assert(n.kind == nkStmtListExpr)
|
||||
result.s = newNodeI(nkStmtList, n.info)
|
||||
result = (newNodeI(nkStmtList, n.info), nil)
|
||||
result.s.sons = @[]
|
||||
|
||||
var n = n
|
||||
@@ -414,8 +431,11 @@ proc exprToStmtList(n: PNode): tuple[s, res: PNode] =
|
||||
|
||||
|
||||
proc newEnvVarAsgn(ctx: Ctx, s: PSym, v: PNode): PNode =
|
||||
result = newTree(nkFastAsgn, ctx.newEnvVarAccess(s), v)
|
||||
result.info = v.info
|
||||
if isEmptyType(v.typ):
|
||||
result = v
|
||||
else:
|
||||
result = newTree(nkFastAsgn, ctx.newEnvVarAccess(s), v)
|
||||
result.info = v.info
|
||||
|
||||
proc addExprAssgn(ctx: Ctx, output, input: PNode, sym: PSym) =
|
||||
if input.kind == nkStmtListExpr:
|
||||
@@ -427,13 +447,16 @@ proc addExprAssgn(ctx: Ctx, output, input: PNode, sym: PSym) =
|
||||
|
||||
proc convertExprBodyToAsgn(ctx: Ctx, exprBody: PNode, res: PSym): PNode =
|
||||
result = newNodeI(nkStmtList, exprBody.info)
|
||||
if exprBody.typ != nil:
|
||||
ctx.addExprAssgn(result, exprBody, res)
|
||||
ctx.addExprAssgn(result, exprBody, res)
|
||||
|
||||
proc newNotCall(g: ModuleGraph; e: PNode): PNode =
|
||||
result = newTree(nkCall, newSymNode(g.getSysMagic(e.info, "not", mNot), e.info), e)
|
||||
result.typ = g.getSysType(e.info, tyBool)
|
||||
|
||||
proc boolLit(g: ModuleGraph; info: TLineInfo; value: bool): PNode =
|
||||
result = newIntLit(g, info, ord value)
|
||||
result.typ = getSysType(g, info, tyBool)
|
||||
|
||||
proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
result = n
|
||||
case n.kind
|
||||
@@ -487,7 +510,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
|
||||
if ns:
|
||||
needsSplit = true
|
||||
var tmp: PSym
|
||||
var tmp: PSym = nil
|
||||
let isExpr = not isEmptyType(n.typ)
|
||||
if isExpr:
|
||||
tmp = ctx.newTempVar(n.typ)
|
||||
@@ -606,6 +629,12 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
internalError(ctx.g.config, "lowerStmtListExpr(nkCaseStmt): " & $branch.kind)
|
||||
result.add(n)
|
||||
result.add(ctx.newEnvVarAccess(tmp))
|
||||
elif n[0].kind == nkStmtListExpr:
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
let (st, ex) = exprToStmtList(n[0])
|
||||
result.add(st)
|
||||
n[0] = ex
|
||||
result.add(n)
|
||||
|
||||
of nkCallKinds, nkChckRange, nkChckRangeF, nkChckRange64:
|
||||
var ns = false
|
||||
@@ -704,7 +733,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
n[^1] = ex
|
||||
result.add(n)
|
||||
|
||||
of nkAsgn, nkFastAsgn:
|
||||
of nkAsgn, nkFastAsgn, nkSinkAsgn:
|
||||
var ns = false
|
||||
for i in 0..<n.len:
|
||||
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
||||
@@ -759,7 +788,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
let check = newTree(nkIfStmt, branch)
|
||||
let newBody = newTree(nkStmtList, st, check, n[1])
|
||||
|
||||
n[0] = newSymNode(ctx.g.getSysSym(n[0].info, "true"))
|
||||
n[0] = ctx.g.boolLit(n[0].info, true)
|
||||
n[1] = newBody
|
||||
|
||||
of nkDotExpr, nkCheckedFieldExpr:
|
||||
@@ -796,7 +825,10 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
|
||||
# Generate the following code:
|
||||
# if :unrollFinally:
|
||||
# if :curExc.isNil:
|
||||
# return :tmpResult
|
||||
# if nearestFinally == 0:
|
||||
# return :tmpResult
|
||||
# else:
|
||||
# :state = nearestFinally # bubble up
|
||||
# else:
|
||||
# raise
|
||||
let curExc = ctx.newCurExcAccess()
|
||||
@@ -805,11 +837,20 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
|
||||
let cmp = newTree(nkCall, newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info), curExc, nilnode)
|
||||
cmp.typ = ctx.g.getSysType(info, tyBool)
|
||||
|
||||
let asgn = newTree(nkFastAsgn,
|
||||
newSymNode(getClosureIterResult(ctx.g, ctx.fn, ctx.idgen), info),
|
||||
ctx.newTmpResultAccess())
|
||||
let retStmt =
|
||||
if ctx.nearestFinally == 0:
|
||||
# last finally, we can return
|
||||
let retValue = if ctx.fn.typ.returnType.isNil:
|
||||
ctx.g.emptyNode
|
||||
else:
|
||||
newTree(nkFastAsgn,
|
||||
newSymNode(getClosureIterResult(ctx.g, ctx.fn, ctx.idgen), info),
|
||||
ctx.newTmpResultAccess())
|
||||
newTree(nkReturnStmt, retValue)
|
||||
else:
|
||||
# bubble up to next finally
|
||||
newTree(nkGotoState, ctx.g.newIntLit(info, ctx.nearestFinally))
|
||||
|
||||
let retStmt = newTree(nkReturnStmt, asgn)
|
||||
let branch = newTree(nkElifBranch, cmp, retStmt)
|
||||
|
||||
let nullifyExc = newTree(nkCall, newSymNode(ctx.g.getCompilerProc("closureIterSetupExc")), nilnode)
|
||||
@@ -829,7 +870,9 @@ proc transformReturnsInTry(ctx: var Ctx, n: PNode): PNode =
|
||||
case n.kind
|
||||
of nkReturnStmt:
|
||||
# We're somewhere in try, transform to finally unrolling
|
||||
assert(ctx.nearestFinally != 0)
|
||||
if ctx.nearestFinally == 0:
|
||||
# return is within the finally
|
||||
return
|
||||
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
|
||||
@@ -842,7 +885,7 @@ proc transformReturnsInTry(ctx: var Ctx, n: PNode): PNode =
|
||||
if n[0].kind != nkEmpty:
|
||||
let asgnTmpResult = newNodeI(nkAsgn, n.info)
|
||||
asgnTmpResult.add(ctx.newTmpResultAccess())
|
||||
let x = if n[0].kind in {nkAsgn, nkFastAsgn}: n[0][1] else: n[0]
|
||||
let x = if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}: n[0][1] else: n[0]
|
||||
asgnTmpResult.add(x)
|
||||
result.add(asgnTmpResult)
|
||||
|
||||
@@ -853,6 +896,13 @@ proc transformReturnsInTry(ctx: var Ctx, n: PNode): PNode =
|
||||
|
||||
of nkSkip:
|
||||
discard
|
||||
of nkTryStmt:
|
||||
if n.hasYields:
|
||||
# the inner try will handle these transformations
|
||||
discard
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
n[i] = ctx.transformReturnsInTry(n[i])
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
n[i] = ctx.transformReturnsInTry(n[i])
|
||||
@@ -1092,10 +1142,10 @@ proc skipEmptyStates(ctx: Ctx, stateIdx: int): int =
|
||||
let label = stateIdx
|
||||
if label == ctx.exitStateIdx: break
|
||||
var newLabel = label
|
||||
if label == -1:
|
||||
if label == emptyStateLabel:
|
||||
newLabel = ctx.exitStateIdx
|
||||
else:
|
||||
let fs = skipStmtList(ctx, ctx.states[label][1])
|
||||
let fs = skipStmtList(ctx, ctx.states[label].body)
|
||||
if fs.kind == nkGotoState:
|
||||
newLabel = fs[0].intVal.int
|
||||
if label == newLabel: break
|
||||
@@ -1104,7 +1154,7 @@ proc skipEmptyStates(ctx: Ctx, stateIdx: int): int =
|
||||
if maxJumps == 0:
|
||||
assert(false, "Internal error")
|
||||
|
||||
result = ctx.states[stateIdx][0].intVal.int
|
||||
result = ctx.states[stateIdx].label
|
||||
|
||||
proc skipThroughEmptyStates(ctx: var Ctx, n: PNode): PNode=
|
||||
result = n
|
||||
@@ -1119,10 +1169,10 @@ proc skipThroughEmptyStates(ctx: var Ctx, n: PNode): PNode=
|
||||
n[i] = ctx.skipThroughEmptyStates(n[i])
|
||||
|
||||
proc newArrayType(g: ModuleGraph; n: int, t: PType; idgen: IdGenerator; owner: PSym): PType =
|
||||
result = newType(tyArray, nextTypeId(idgen), owner)
|
||||
result = newType(tyArray, idgen, owner)
|
||||
|
||||
let rng = newType(tyRange, nextTypeId(idgen), owner)
|
||||
rng.n = newTree(nkRange, g.newIntLit(owner.info, 0), g.newIntLit(owner.info, n))
|
||||
let rng = newType(tyRange, idgen, owner)
|
||||
rng.n = newTree(nkRange, g.newIntLit(owner.info, 0), g.newIntLit(owner.info, n - 1))
|
||||
rng.rawAddSon(t)
|
||||
|
||||
result.rawAddSon(rng)
|
||||
@@ -1222,11 +1272,10 @@ proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
|
||||
# while true:
|
||||
# block :stateLoop:
|
||||
# gotoState :state
|
||||
# local vars decl (if needed)
|
||||
# body # Might get wrapped in try-except
|
||||
let loopBody = newNodeI(nkStmtList, n.info)
|
||||
result = newTree(nkWhileStmt, newSymNode(ctx.g.getSysSym(n.info, "true")), loopBody)
|
||||
result = newTree(nkWhileStmt, ctx.g.boolLit(n.info, true), loopBody)
|
||||
result.info = n.info
|
||||
|
||||
let localVars = newNodeI(nkStmtList, n.info)
|
||||
@@ -1241,11 +1290,7 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
|
||||
let blockStmt = newNodeI(nkBlockStmt, n.info)
|
||||
blockStmt.add(newSymNode(ctx.stateLoopLabel))
|
||||
|
||||
let gs = newNodeI(nkGotoState, n.info)
|
||||
gs.add(ctx.newStateAccess())
|
||||
gs.add(ctx.g.newIntLit(n.info, ctx.states.len - 1))
|
||||
|
||||
var blockBody = newTree(nkStmtList, gs, localVars, n)
|
||||
var blockBody = newTree(nkStmtList, localVars, n)
|
||||
if ctx.hasExceptions:
|
||||
blockBody = ctx.wrapIntoTryExcept(blockBody)
|
||||
|
||||
@@ -1258,29 +1303,28 @@ proc deleteEmptyStates(ctx: var Ctx) =
|
||||
|
||||
# Apply new state indexes and mark unused states with -1
|
||||
var iValid = 0
|
||||
for i, s in ctx.states:
|
||||
let body = skipStmtList(ctx, s[1])
|
||||
for i, s in ctx.states.mpairs:
|
||||
let body = skipStmtList(ctx, s.body)
|
||||
if body.kind == nkGotoState and i != ctx.states.len - 1 and i != 0:
|
||||
# This is an empty state. Mark with -1.
|
||||
s[0].intVal = -1
|
||||
s.label = emptyStateLabel
|
||||
else:
|
||||
s[0].intVal = iValid
|
||||
s.label = iValid
|
||||
inc iValid
|
||||
|
||||
for i, s in ctx.states:
|
||||
let body = skipStmtList(ctx, s[1])
|
||||
let body = skipStmtList(ctx, s.body)
|
||||
if body.kind != nkGotoState or i == 0:
|
||||
discard ctx.skipThroughEmptyStates(s)
|
||||
discard ctx.skipThroughEmptyStates(s.body)
|
||||
let excHandlState = ctx.exceptionTable[i]
|
||||
if excHandlState < 0:
|
||||
ctx.exceptionTable[i] = -ctx.skipEmptyStates(-excHandlState)
|
||||
elif excHandlState != 0:
|
||||
ctx.exceptionTable[i] = ctx.skipEmptyStates(excHandlState)
|
||||
|
||||
var i = 0
|
||||
var i = 1 # ignore the entry and the exit
|
||||
while i < ctx.states.len - 1:
|
||||
let fs = skipStmtList(ctx, ctx.states[i][1])
|
||||
if fs.kind == nkGotoState and i != 0:
|
||||
if ctx.states[i].label == emptyStateLabel:
|
||||
ctx.states.delete(i)
|
||||
ctx.exceptionTable.delete(i)
|
||||
else:
|
||||
@@ -1315,7 +1359,7 @@ proc freshVars(n: PNode; c: var FreshVarsContext): PNode =
|
||||
let idefs = copyNode(it)
|
||||
for v in 0..it.len-3:
|
||||
if it[v].kind == nkSym:
|
||||
let x = copySym(it[v].sym, nextSymId(c.idgen))
|
||||
let x = copySym(it[v].sym, c.idgen)
|
||||
c.tab[it[v].sym.id] = x
|
||||
idefs.add newSymNode(x)
|
||||
else:
|
||||
@@ -1326,6 +1370,7 @@ proc freshVars(n: PNode; c: var FreshVarsContext): PNode =
|
||||
else:
|
||||
result.add it
|
||||
of nkRaiseStmt:
|
||||
result = nil
|
||||
localError(c.config, c.info, "unsupported control flow: 'finally: ... raise' duplicated because of 'break'")
|
||||
else:
|
||||
result = n
|
||||
@@ -1339,20 +1384,24 @@ proc preprocess(c: var PreprocessContext; n: PNode): PNode =
|
||||
# detect: 'finally: raises X' which is currently not supported. We produce
|
||||
# an error for this case for now. All this will be done properly with Yuriy's
|
||||
# patch.
|
||||
|
||||
result = n
|
||||
case n.kind
|
||||
of nkTryStmt:
|
||||
let f = n.lastSon
|
||||
var didAddSomething = false
|
||||
if f.kind == nkFinally:
|
||||
c.finallys.add f.lastSon
|
||||
didAddSomething = true
|
||||
|
||||
for i in 0 ..< n.len:
|
||||
result[i] = preprocess(c, n[i])
|
||||
|
||||
if f.kind == nkFinally:
|
||||
if didAddSomething:
|
||||
discard c.finallys.pop()
|
||||
|
||||
of nkWhileStmt, nkBlockStmt:
|
||||
if not n.hasYields: return n
|
||||
c.blocks.add((n, c.finallys.len))
|
||||
for i in 0 ..< n.len:
|
||||
result[i] = preprocess(c, n[i])
|
||||
@@ -1376,7 +1425,7 @@ proc preprocess(c: var PreprocessContext; n: PNode): PNode =
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
for i in countdown(c.finallys.high, fin):
|
||||
var vars = FreshVarsContext(tab: initTable[int, PSym](), config: c.config, info: n.info, idgen: c.idgen)
|
||||
result.add freshVars(preprocess(c, c.finallys[i]), vars)
|
||||
result.add freshVars(copyTree(c.finallys[i]), vars)
|
||||
c.idgen = vars.idgen
|
||||
result.add n
|
||||
of nkSkip: discard
|
||||
@@ -1385,18 +1434,15 @@ proc preprocess(c: var PreprocessContext; n: PNode): PNode =
|
||||
result[i] = preprocess(c, n[i])
|
||||
|
||||
proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n: PNode): PNode =
|
||||
var ctx: Ctx
|
||||
ctx.g = g
|
||||
ctx.fn = fn
|
||||
ctx.idgen = idgen
|
||||
var ctx = Ctx(g: g, fn: fn, idgen: idgen)
|
||||
|
||||
if getEnvParam(fn).isNil:
|
||||
# Lambda lifting was not done yet. Use temporary :state sym, which will
|
||||
# be handled specially by lambda lifting. Local temp vars (if needed)
|
||||
# should follow the same logic.
|
||||
ctx.stateVarSym = newSym(skVar, getIdent(ctx.g.cache, ":state"), nextSymId(idgen), fn, fn.info)
|
||||
ctx.stateVarSym = newSym(skVar, getIdent(ctx.g.cache, ":state"), idgen, fn, fn.info)
|
||||
ctx.stateVarSym.typ = g.createClosureIterStateType(fn, idgen)
|
||||
ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), nextSymId(idgen), fn, fn.info)
|
||||
ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), idgen, fn, fn.info)
|
||||
var pc = PreprocessContext(finallys: @[], config: g.config, idgen: idgen)
|
||||
var n = preprocess(pc, n.toStmtList)
|
||||
#echo "transformed into ", n
|
||||
@@ -1417,21 +1463,21 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
|
||||
# Optimize empty states away
|
||||
ctx.deleteEmptyStates()
|
||||
|
||||
# Make new body by concatenating the list of states
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
let caseDispatcher = newTreeI(nkCaseStmt, n.info,
|
||||
ctx.newStateAccess())
|
||||
|
||||
for s in ctx.states:
|
||||
assert(s.len == 2)
|
||||
let body = s[1]
|
||||
s.sons.del(1)
|
||||
result.add(s)
|
||||
result.add(body)
|
||||
let body = ctx.transformStateAssignments(s.body)
|
||||
caseDispatcher.add newTreeI(nkOfBranch, body.info, g.newIntLit(body.info, s.label), body)
|
||||
|
||||
result = ctx.transformStateAssignments(result)
|
||||
result = ctx.wrapIntoStateLoop(result)
|
||||
caseDispatcher.add newTreeI(nkElse, n.info, newTreeI(nkReturnStmt, n.info, g.emptyNode))
|
||||
|
||||
# echo "TRANSFORM TO STATES: "
|
||||
# echo renderTree(result)
|
||||
result = wrapIntoStateLoop(ctx, caseDispatcher)
|
||||
|
||||
# echo "exception table:"
|
||||
# for i, e in ctx.exceptionTable:
|
||||
# echo i, " -> ", e
|
||||
when false:
|
||||
echo "TRANSFORM TO STATES: "
|
||||
echo renderTree(result)
|
||||
|
||||
echo "exception table:"
|
||||
for i, e in ctx.exceptionTable:
|
||||
echo i, " -> ", e
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Helpers for binaries that use compiler passes, e.g.: nim, nimsuggest, nimfix
|
||||
## Helpers for binaries that use compiler passes, e.g.: nim, nimsuggest
|
||||
|
||||
import
|
||||
options, idents, nimconf, extccomp, commands, msgs,
|
||||
lineinfos, modulegraphs, condsyms, os, pathutils, parseopt
|
||||
lineinfos, modulegraphs, condsyms, pathutils
|
||||
|
||||
import std/[os, parseopt]
|
||||
|
||||
proc prependCurDir*(f: AbsoluteFile): AbsoluteFile =
|
||||
when defined(unix):
|
||||
@@ -44,14 +46,7 @@ proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) =
|
||||
elif self.supportsStdinFile and conf.projectName == "-":
|
||||
handleStdinInput(conf)
|
||||
elif conf.projectName != "":
|
||||
try:
|
||||
conf.projectFull = canonicalizePath(conf, AbsoluteFile conf.projectName)
|
||||
except OSError:
|
||||
conf.projectFull = AbsoluteFile conf.projectName
|
||||
let p = splitFile(conf.projectFull)
|
||||
let dir = if p.dir.isEmpty: AbsoluteDir getCurrentDir() else: p.dir
|
||||
conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile dir)
|
||||
conf.projectName = p.name
|
||||
setFromProjectName(conf, conf.projectName)
|
||||
else:
|
||||
conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile getCurrentDir())
|
||||
|
||||
@@ -62,6 +57,11 @@ proc loadConfigsAndProcessCmdLine*(self: NimProg, cache: IdentCache; conf: Confi
|
||||
if conf.cmd == cmdNimscript:
|
||||
incl(conf.globalOptions, optWasNimscript)
|
||||
loadConfigs(DefaultConfig, cache, conf, graph.idgen) # load all config files
|
||||
# restores `conf.notes` after loading config files
|
||||
# because it has overwrites the notes when compiling the system module which
|
||||
# is a foreign module compared to the project
|
||||
if conf.cmd in cmdBackends:
|
||||
conf.notes = conf.mainPackageNotes
|
||||
|
||||
if not self.suggestMode:
|
||||
let scriptFile = conf.projectFull.changeFileExt("nims")
|
||||
@@ -71,7 +71,8 @@ proc loadConfigsAndProcessCmdLine*(self: NimProg, cache: IdentCache; conf: Confi
|
||||
if conf.cmd == cmdNimscript: return false
|
||||
# now process command line arguments again, because some options in the
|
||||
# command line can overwrite the config file's settings
|
||||
extccomp.initVars(conf)
|
||||
if conf.backend != backendJs: # bug #19059
|
||||
extccomp.initVars(conf)
|
||||
self.processCmdLine(passCmd2, "", conf)
|
||||
if conf.cmd == cmdNone:
|
||||
rawMessage(conf, errGenerated, "command missing")
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
# This module handles the parsing of command line arguments.
|
||||
|
||||
|
||||
# We do this here before the 'import' statement so 'defined' does not get
|
||||
# confused with 'TGCMode.gcMarkAndSweep' etc.
|
||||
template bootSwitch(name, expr, userString) =
|
||||
@@ -25,18 +24,20 @@ bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep")
|
||||
bootSwitch(usedGoGC, defined(gogc), "--gc:go")
|
||||
bootSwitch(usedNoGC, defined(nogc), "--gc:none")
|
||||
|
||||
import std/[setutils, os, strutils, parseutils, parseopt, sequtils, strtabs]
|
||||
import
|
||||
os, msgs, options, nversion, condsyms, strutils, extccomp, platform,
|
||||
wordrecg, parseutils, nimblecmd, parseopt, sequtils, lineinfos,
|
||||
pathutils, strtabs
|
||||
msgs, options, nversion, condsyms, extccomp, platform,
|
||||
wordrecg, nimblecmd, lineinfos, pathutils
|
||||
|
||||
from ast import eqTypeFlags, tfGcSafe, tfNoSideEffect
|
||||
import std/pathnorm
|
||||
|
||||
from ast import setUseIc, eqTypeFlags, tfGcSafe, tfNoSideEffect
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
# but some have deps to imported modules. Yay.
|
||||
bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
|
||||
bootSwitch(usedNativeStacktrace,
|
||||
defined(nativeStackTrace) and nativeStackTraceSupported,
|
||||
"-d:nativeStackTrace")
|
||||
bootSwitch(usedFFI, hasFFI, "-d:nimHasLibFFI")
|
||||
|
||||
type
|
||||
@@ -101,7 +102,7 @@ proc writeVersionInfo(conf: ConfigRef; pass: TCmdLinePass) =
|
||||
msgWriteln(conf, "git hash: " & gitHash, {msgStdout})
|
||||
|
||||
msgWriteln(conf, "active boot switches:" & usedRelease & usedDanger &
|
||||
usedTinyC & useLinenoise & usedNativeStacktrace &
|
||||
usedTinyC & useLinenoise &
|
||||
usedFFI & usedBoehm & usedMarkAndSweep & usedGoGC & usedNoGC,
|
||||
{msgStdout})
|
||||
msgQuit(0)
|
||||
@@ -117,7 +118,7 @@ const
|
||||
errInvalidCmdLineOption = "invalid command line option: '$1'"
|
||||
errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found"
|
||||
errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found"
|
||||
errOffHintsError = "'off', 'hint' or 'error' expected, but '$1' found"
|
||||
errOffHintsError = "'off', 'hint', 'error' or 'usages' expected, but '$1' found"
|
||||
|
||||
proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) =
|
||||
if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-")
|
||||
@@ -141,10 +142,19 @@ proc splitSwitch(conf: ConfigRef; switch: string, cmd, arg: var string, pass: TC
|
||||
elif switch[i] == '[': arg = substr(switch, i)
|
||||
else: invalidCmdLineOption(conf, pass, switch, info)
|
||||
|
||||
template switchOn(arg: string): bool =
|
||||
# xxx use `switchOn` wherever appropriate
|
||||
case arg.normalize
|
||||
of "", "on": true
|
||||
of "off": false
|
||||
else:
|
||||
localError(conf, info, errOnOrOffExpectedButXFound % arg)
|
||||
false
|
||||
|
||||
proc processOnOffSwitch(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass,
|
||||
info: TLineInfo) =
|
||||
case arg.normalize
|
||||
of "","on": conf.options.incl op
|
||||
of "", "on": conf.options.incl op
|
||||
of "off": conf.options.excl op
|
||||
else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
|
||||
|
||||
@@ -176,7 +186,7 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
|
||||
info: TLineInfo; orig: string; conf: ConfigRef) =
|
||||
var id = "" # arg = key or [key] or key:val or [key]:val; with val=on|off
|
||||
var i = 0
|
||||
var n = hintMin
|
||||
var notes: set[TMsgKind] = {}
|
||||
var isBracket = false
|
||||
if i < arg.len and arg[i] == '[':
|
||||
isBracket = true
|
||||
@@ -191,37 +201,42 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
|
||||
if i == arg.len: discard
|
||||
elif i < arg.len and (arg[i] in {':', '='}): inc(i)
|
||||
else: invalidCmdLineOption(conf, pass, orig, info)
|
||||
# unfortunately, hintUser and warningUser clash
|
||||
if state in {wHint, wHintAsError}:
|
||||
let x = findStr(hintMin, hintMax, id, errUnknown)
|
||||
if x != errUnknown: n = TNoteKind(x)
|
||||
else: localError(conf, info, "unknown hint: " & id)
|
||||
else:
|
||||
let x = findStr(warnMin, warnMax, id, errUnknown)
|
||||
if x != errUnknown: n = TNoteKind(x)
|
||||
else: localError(conf, info, "unknown warning: " & id)
|
||||
|
||||
let isSomeHint = state in {wHint, wHintAsError}
|
||||
template findNote(noteMin, noteMax, name) =
|
||||
# unfortunately, hintUser and warningUser clash, otherwise implementation would simplify a bit
|
||||
let x = findStr(noteMin, noteMax, id, errUnknown)
|
||||
if x != errUnknown: notes = {TNoteKind(x)}
|
||||
else:
|
||||
if isSomeHint:
|
||||
message(conf, info, hintUnknownHint, id)
|
||||
else:
|
||||
localError(conf, info, "unknown $#: $#" % [name, id])
|
||||
case id.normalize
|
||||
of "all": # other note groups would be easy to support via additional cases
|
||||
notes = if isSomeHint: {hintMin..hintMax} else: {warnMin..warnMax}
|
||||
elif isSomeHint: findNote(hintMin, hintMax, "hint")
|
||||
else: findNote(warnMin, warnMax, "warning")
|
||||
var val = substr(arg, i).normalize
|
||||
if val == "": val = "on"
|
||||
if val notin ["on", "off"]:
|
||||
# xxx in future work we should also allow users to have control over `foreignPackageNotes`
|
||||
# so that they can enable `hints|warnings|warningAsErrors` for all the code they depend on.
|
||||
localError(conf, info, errOnOrOffExpectedButXFound % arg)
|
||||
elif n notin conf.cmdlineNotes or pass == passCmd1:
|
||||
if pass == passCmd1: incl(conf.cmdlineNotes, n)
|
||||
incl(conf.modifiedyNotes, n)
|
||||
case val
|
||||
of "on":
|
||||
if state in {wWarningAsError, wHintAsError}:
|
||||
incl(conf.warningAsErrors, n) # xxx rename warningAsErrors to noteAsErrors
|
||||
else:
|
||||
incl(conf.notes, n)
|
||||
incl(conf.mainPackageNotes, n)
|
||||
of "off":
|
||||
if state in {wWarningAsError, wHintAsError}:
|
||||
excl(conf.warningAsErrors, n)
|
||||
else:
|
||||
excl(conf.notes, n)
|
||||
excl(conf.mainPackageNotes, n)
|
||||
excl(conf.foreignPackageNotes, n)
|
||||
else:
|
||||
let isOn = val == "on"
|
||||
if isOn and id.normalize == "all":
|
||||
localError(conf, info, "only 'all:off' is supported")
|
||||
for n in notes:
|
||||
if n notin conf.cmdlineNotes or pass == passCmd1:
|
||||
if pass == passCmd1: incl(conf.cmdlineNotes, n)
|
||||
incl(conf.modifiedyNotes, n)
|
||||
if state in {wWarningAsError, wHintAsError}:
|
||||
conf.warningAsErrors[n] = isOn # xxx rename warningAsErrors to noteAsErrors
|
||||
else:
|
||||
conf.notes[n] = isOn
|
||||
conf.mainPackageNotes[n] = isOn
|
||||
if not isOn: excl(conf.foreignPackageNotes, n)
|
||||
|
||||
proc processCompile(conf: ConfigRef; filename: string) =
|
||||
var found = findFile(conf, filename)
|
||||
@@ -229,10 +244,10 @@ proc processCompile(conf: ConfigRef; filename: string) =
|
||||
extccomp.addExternalFileToCompile(conf, found)
|
||||
|
||||
const
|
||||
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
|
||||
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
|
||||
errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found"
|
||||
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console' or 'lib' expected, but '$1' found"
|
||||
errInvalidExceptionSystem = "'goto', 'setjump', 'cpp' or 'quirky' expected, but '$1' found"
|
||||
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
|
||||
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
|
||||
|
||||
template warningOptionNoop(switch: string) =
|
||||
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
|
||||
@@ -242,7 +257,7 @@ template deprecatedAlias(oldName, newName: string) =
|
||||
|
||||
proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo): bool =
|
||||
case switch.normalize
|
||||
of "gc":
|
||||
of "gc", "mm":
|
||||
case arg.normalize
|
||||
of "boehm": result = conf.selectedGC == gcBoehm
|
||||
of "refc": result = conf.selectedGC == gcRefc
|
||||
@@ -253,14 +268,18 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
of "go": result = conf.selectedGC == gcGo
|
||||
of "none": result = conf.selectedGC == gcNone
|
||||
of "stack", "regions": result = conf.selectedGC == gcRegions
|
||||
of "v2", "generational": warningOptionNoop(arg)
|
||||
else: localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg)
|
||||
of "atomicarc": result = conf.selectedGC == gcAtomicArc
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg)
|
||||
of "opt":
|
||||
case arg.normalize
|
||||
of "speed": result = contains(conf.options, optOptimizeSpeed)
|
||||
of "size": result = contains(conf.options, optOptimizeSize)
|
||||
of "none": result = conf.options * {optOptimizeSpeed, optOptimizeSize} == {}
|
||||
else: localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg)
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg)
|
||||
of "verbosity": result = $conf.verbosity == arg
|
||||
of "app":
|
||||
case arg.normalize
|
||||
@@ -270,7 +289,9 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
not contains(conf.globalOptions, optGenGuiApp)
|
||||
of "staticlib": result = contains(conf.globalOptions, optGenStaticLib) and
|
||||
not contains(conf.globalOptions, optGenGuiApp)
|
||||
else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
|
||||
of "dynliboverride":
|
||||
result = isDynlibOverride(conf, arg)
|
||||
of "exceptions":
|
||||
@@ -279,8 +300,12 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
of "setjmp": result = conf.exc == excSetjmp
|
||||
of "quirky": result = conf.exc == excQuirky
|
||||
of "goto": result = conf.exc == excGoto
|
||||
else: localError(conf, info, errInvalidExceptionSystem % arg)
|
||||
else: invalidCmdLineOption(conf, passCmd1, switch, info)
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errInvalidExceptionSystem % arg)
|
||||
else:
|
||||
result = false
|
||||
invalidCmdLineOption(conf, passCmd1, switch, info)
|
||||
|
||||
proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool =
|
||||
case switch.normalize
|
||||
@@ -318,6 +343,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
|
||||
of "run", "r": result = contains(conf.globalOptions, optRun)
|
||||
of "symbolfiles": result = conf.symbolFiles != disabledSf
|
||||
of "genscript": result = contains(conf.globalOptions, optGenScript)
|
||||
of "gencdeps": result = contains(conf.globalOptions, optGenCDeps)
|
||||
of "threads": result = contains(conf.globalOptions, optThreads)
|
||||
of "tlsemulation": result = contains(conf.globalOptions, optTlsEmulation)
|
||||
of "implicitstatic": result = contains(conf.options, optImplicitStatic)
|
||||
@@ -325,8 +351,14 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
|
||||
if switch.normalize == "patterns": deprecatedAlias(switch, "trmacros")
|
||||
result = contains(conf.options, optTrMacros)
|
||||
of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace)
|
||||
of "nilseqs", "nilchecks", "taintmode": warningOptionNoop(switch)
|
||||
else: invalidCmdLineOption(conf, passCmd1, switch, info)
|
||||
of "nilseqs", "nilchecks", "taintmode":
|
||||
warningOptionNoop(switch)
|
||||
result = false
|
||||
of "panics": result = contains(conf.globalOptions, optPanics)
|
||||
of "jsbigint64": result = contains(conf.globalOptions, optJsBigInt64)
|
||||
else:
|
||||
result = false
|
||||
invalidCmdLineOption(conf, passCmd1, switch, info)
|
||||
|
||||
proc processPath(conf: ConfigRef; path: string, info: TLineInfo,
|
||||
notRelativeToProj = false): AbsoluteDir =
|
||||
@@ -359,31 +391,53 @@ proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): AbsoluteDir
|
||||
const
|
||||
errInvalidNumber = "$1 is not a valid number"
|
||||
|
||||
proc makeAbsolute(s: string): AbsoluteFile =
|
||||
if isAbsolute(s):
|
||||
AbsoluteFile pathnorm.normalizePath(s)
|
||||
else:
|
||||
AbsoluteFile pathnorm.normalizePath(os.getCurrentDir() / s)
|
||||
|
||||
proc setTrackingInfo(conf: ConfigRef; dirty, file, line, column: string,
|
||||
info: TLineInfo) =
|
||||
## set tracking info, common code for track, trackDirty, & ideTrack
|
||||
var ln: int = 0
|
||||
var col: int = 0
|
||||
if parseUtils.parseInt(line, ln) <= 0:
|
||||
localError(conf, info, errInvalidNumber % line)
|
||||
if parseUtils.parseInt(column, col) <= 0:
|
||||
localError(conf, info, errInvalidNumber % column)
|
||||
|
||||
let a = makeAbsolute(file)
|
||||
if dirty == "":
|
||||
conf.m.trackPos = newLineInfo(conf, a, ln, col)
|
||||
else:
|
||||
let dirtyOriginalIdx = fileInfoIdx(conf, a)
|
||||
if dirtyOriginalIdx.int32 >= 0:
|
||||
msgs.setDirtyFile(conf, dirtyOriginalIdx, makeAbsolute(dirty))
|
||||
conf.m.trackPos = newLineInfo(dirtyOriginalIdx, ln, col)
|
||||
|
||||
proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) =
|
||||
var a = arg.split(',')
|
||||
if a.len != 4: localError(conf, info,
|
||||
"DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN expected")
|
||||
var line, column: int
|
||||
if parseUtils.parseInt(a[2], line) <= 0:
|
||||
localError(conf, info, errInvalidNumber % a[1])
|
||||
if parseUtils.parseInt(a[3], column) <= 0:
|
||||
localError(conf, info, errInvalidNumber % a[2])
|
||||
|
||||
let dirtyOriginalIdx = fileInfoIdx(conf, AbsoluteFile a[1])
|
||||
if dirtyOriginalIdx.int32 >= 0:
|
||||
msgs.setDirtyFile(conf, dirtyOriginalIdx, AbsoluteFile a[0])
|
||||
|
||||
conf.m.trackPos = newLineInfo(dirtyOriginalIdx, line, column)
|
||||
setTrackingInfo(conf, a[0], a[1], a[2], a[3], info)
|
||||
|
||||
proc track(conf: ConfigRef; arg: string, info: TLineInfo) =
|
||||
var a = arg.split(',')
|
||||
if a.len != 3: localError(conf, info, "FILE,LINE,COLUMN expected")
|
||||
var line, column: int
|
||||
if parseUtils.parseInt(a[1], line) <= 0:
|
||||
localError(conf, info, errInvalidNumber % a[1])
|
||||
if parseUtils.parseInt(a[2], column) <= 0:
|
||||
localError(conf, info, errInvalidNumber % a[2])
|
||||
conf.m.trackPos = newLineInfo(conf, AbsoluteFile a[0], line, column)
|
||||
setTrackingInfo(conf, "", a[0], a[1], a[2], info)
|
||||
|
||||
proc trackIde(conf: ConfigRef; cmd: IdeCmd, arg: string, info: TLineInfo) =
|
||||
## set the tracking info related to an ide cmd, supports optional dirty file
|
||||
var a = arg.split(',')
|
||||
case a.len
|
||||
of 4:
|
||||
setTrackingInfo(conf, a[0], a[1], a[2], a[3], info)
|
||||
of 3:
|
||||
setTrackingInfo(conf, "", a[0], a[1], a[2], info)
|
||||
else:
|
||||
localError(conf, info, "[DIRTY_BUFFER,]ORIGINAL_FILE,LINE,COLUMN expected")
|
||||
conf.ideCmd = cmd
|
||||
|
||||
proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
|
||||
if pass in {passCmd2, passPP}:
|
||||
@@ -408,16 +462,21 @@ proc handleCmdInput*(conf: ConfigRef) =
|
||||
proc parseCommand*(command: string): Command =
|
||||
case command.normalize
|
||||
of "c", "cc", "compile", "compiletoc": cmdCompileToC
|
||||
of "nir": cmdCompileToNir
|
||||
of "cpp", "compiletocpp": cmdCompileToCpp
|
||||
of "objc", "compiletooc": cmdCompileToOC
|
||||
of "js", "compiletojs": cmdCompileToJS
|
||||
of "r": cmdCrun
|
||||
of "m": cmdM
|
||||
of "run": cmdTcc
|
||||
of "check": cmdCheck
|
||||
of "e": cmdNimscript
|
||||
of "doc0": cmdDoc0
|
||||
of "doc2", "doc": cmdDoc2
|
||||
of "doc2", "doc": cmdDoc
|
||||
of "doc2tex": cmdDoc2tex
|
||||
of "rst2html": cmdRst2html
|
||||
of "md2tex": cmdMd2tex
|
||||
of "md2html": cmdMd2html
|
||||
of "rst2tex": cmdRst2tex
|
||||
of "jsondoc0": cmdJsondoc0
|
||||
of "jsondoc2", "jsondoc": cmdJsondoc
|
||||
@@ -441,6 +500,7 @@ proc setCmd*(conf: ConfigRef, cmd: Command) =
|
||||
of cmdCompileToCpp: conf.backend = backendCpp
|
||||
of cmdCompileToOC: conf.backend = backendObjc
|
||||
of cmdCompileToJS: conf.backend = backendJs
|
||||
of cmdCompileToNir: conf.backend = backendNir
|
||||
else: discard
|
||||
|
||||
proc setCommandEarly*(conf: ConfigRef, command: string) =
|
||||
@@ -449,15 +509,121 @@ proc setCommandEarly*(conf: ConfigRef, command: string) =
|
||||
# command early customizations
|
||||
# must be handled here to honor subsequent `--hint:x:on|off`
|
||||
case conf.cmd
|
||||
of cmdRst2html, cmdRst2tex: # xxx see whether to add others: cmdGendepend, etc.
|
||||
of cmdRst2html, cmdRst2tex, cmdMd2html, cmdMd2tex:
|
||||
# xxx see whether to add others: cmdGendepend, etc.
|
||||
conf.foreignPackageNotes = {hintSuccessX}
|
||||
else:
|
||||
conf.foreignPackageNotes = foreignPackageNotesDefault
|
||||
|
||||
proc specialDefine(conf: ConfigRef, key: string; pass: TCmdLinePass) =
|
||||
# Keep this syncronized with the default config/nim.cfg!
|
||||
if cmpIgnoreStyle(key, "nimQuirky") == 0:
|
||||
conf.exc = excQuirky
|
||||
elif cmpIgnoreStyle(key, "release") == 0 or cmpIgnoreStyle(key, "danger") == 0:
|
||||
if pass in {passCmd1, passPP}:
|
||||
conf.options.excl {optStackTrace, optLineTrace, optLineDir, optOptimizeSize}
|
||||
conf.globalOptions.excl {optExcessiveStackTrace, optCDebug}
|
||||
conf.options.incl optOptimizeSpeed
|
||||
if cmpIgnoreStyle(key, "danger") == 0 or cmpIgnoreStyle(key, "quick") == 0:
|
||||
if pass in {passCmd1, passPP}:
|
||||
conf.options.excl {optObjCheck, optFieldCheck, optRangeCheck, optBoundsCheck,
|
||||
optOverflowCheck, optAssert, optStackTrace, optLineTrace, optLineDir}
|
||||
conf.globalOptions.excl {optCDebug}
|
||||
|
||||
proc initOrcDefines*(conf: ConfigRef) =
|
||||
conf.selectedGC = gcOrc
|
||||
defineSymbol(conf.symbols, "gcorc")
|
||||
defineSymbol(conf.symbols, "gcdestructors")
|
||||
incl conf.globalOptions, optSeqDestructors
|
||||
incl conf.globalOptions, optTinyRtti
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
defineSymbol(conf.symbols, "nimV2")
|
||||
if conf.exc == excNone and conf.backend != backendCpp:
|
||||
conf.exc = excGoto
|
||||
|
||||
proc registerArcOrc(pass: TCmdLinePass, conf: ConfigRef) =
|
||||
defineSymbol(conf.symbols, "gcdestructors")
|
||||
incl conf.globalOptions, optSeqDestructors
|
||||
incl conf.globalOptions, optTinyRtti
|
||||
if pass in {passCmd2, passPP}:
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
defineSymbol(conf.symbols, "nimV2")
|
||||
if conf.exc == excNone and conf.backend != backendCpp:
|
||||
conf.exc = excGoto
|
||||
|
||||
proc unregisterArcOrc*(conf: ConfigRef) =
|
||||
undefSymbol(conf.symbols, "gcdestructors")
|
||||
undefSymbol(conf.symbols, "gcarc")
|
||||
undefSymbol(conf.symbols, "gcorc")
|
||||
undefSymbol(conf.symbols, "gcatomicarc")
|
||||
undefSymbol(conf.symbols, "nimSeqsV2")
|
||||
undefSymbol(conf.symbols, "nimV2")
|
||||
excl conf.globalOptions, optSeqDestructors
|
||||
excl conf.globalOptions, optTinyRtti
|
||||
|
||||
proc processMemoryManagementOption(switch, arg: string, pass: TCmdLinePass,
|
||||
info: TLineInfo; conf: ConfigRef) =
|
||||
if conf.backend == backendJs: return # for: bug #16033
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "boehm":
|
||||
unregisterArcOrc(conf)
|
||||
conf.selectedGC = gcBoehm
|
||||
defineSymbol(conf.symbols, "boehmgc")
|
||||
incl conf.globalOptions, optTlsEmulation # Boehm GC doesn't scan the real TLS
|
||||
of "refc":
|
||||
unregisterArcOrc(conf)
|
||||
defineSymbol(conf.symbols, "gcrefc")
|
||||
conf.selectedGC = gcRefc
|
||||
of "markandsweep":
|
||||
unregisterArcOrc(conf)
|
||||
conf.selectedGC = gcMarkAndSweep
|
||||
defineSymbol(conf.symbols, "gcmarkandsweep")
|
||||
of "destructors", "arc":
|
||||
conf.selectedGC = gcArc
|
||||
defineSymbol(conf.symbols, "gcarc")
|
||||
registerArcOrc(pass, conf)
|
||||
of "orc":
|
||||
conf.selectedGC = gcOrc
|
||||
defineSymbol(conf.symbols, "gcorc")
|
||||
registerArcOrc(pass, conf)
|
||||
of "atomicarc":
|
||||
conf.selectedGC = gcAtomicArc
|
||||
defineSymbol(conf.symbols, "gcatomicarc")
|
||||
registerArcOrc(pass, conf)
|
||||
of "hooks":
|
||||
conf.selectedGC = gcHooks
|
||||
defineSymbol(conf.symbols, "gchooks")
|
||||
incl conf.globalOptions, optSeqDestructors
|
||||
processOnOffSwitchG(conf, {optSeqDestructors}, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
of "go":
|
||||
unregisterArcOrc(conf)
|
||||
conf.selectedGC = gcGo
|
||||
defineSymbol(conf.symbols, "gogc")
|
||||
of "none":
|
||||
unregisterArcOrc(conf)
|
||||
conf.selectedGC = gcNone
|
||||
defineSymbol(conf.symbols, "nogc")
|
||||
of "stack", "regions":
|
||||
unregisterArcOrc(conf)
|
||||
conf.selectedGC = gcRegions
|
||||
defineSymbol(conf.symbols, "gcregions")
|
||||
else: localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg)
|
||||
|
||||
proc pathRelativeToConfig(arg: string, pass: TCmdLinePass, conf: ConfigRef): string =
|
||||
if pass == passPP and not isAbsolute(arg):
|
||||
assert isAbsolute(conf.currentConfigDir), "something is wrong with currentConfigDir"
|
||||
result = conf.currentConfigDir / arg
|
||||
else:
|
||||
result = arg
|
||||
|
||||
proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf: ConfigRef) =
|
||||
var
|
||||
key, val: string
|
||||
var key = ""
|
||||
var val = ""
|
||||
case switch.normalize
|
||||
of "eval":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -472,17 +638,17 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
for path in nimbleSubs(conf, arg):
|
||||
addPath(conf, if pass == passPP: processCfgPath(conf, path, info)
|
||||
else: processPath(conf, path, info), info)
|
||||
of "nimblepath", "babelpath":
|
||||
if switch.normalize == "babelpath": deprecatedAlias(switch, "nimblepath")
|
||||
of "nimblepath":
|
||||
if pass in {passCmd2, passPP} and optNoNimblePath notin conf.globalOptions:
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
var path = processPath(conf, arg, info, notRelativeToProj=true)
|
||||
let nimbleDir = AbsoluteDir getEnv("NIMBLE_DIR")
|
||||
if not nimbleDir.isEmpty and pass == passPP:
|
||||
path = nimbleDir / RelativeDir"pkgs2"
|
||||
nimblePath(conf, path, info)
|
||||
path = nimbleDir / RelativeDir"pkgs"
|
||||
nimblePath(conf, path, info)
|
||||
of "nonimblepath", "nobabelpath":
|
||||
if switch.normalize == "nobabelpath": deprecatedAlias(switch, "nonimblepath")
|
||||
of "nonimblepath":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
disableNimblePath(conf)
|
||||
of "clearnimblepath":
|
||||
@@ -495,7 +661,11 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf.lazyPaths.keepItIf(it != path)
|
||||
of "nimcache":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.nimcacheDir = processPath(conf, arg, info, notRelativeToProj=true)
|
||||
var arg = arg
|
||||
# refs bug #18674, otherwise `--os:windows` messes up with `--nimcache` set
|
||||
# in config nims files, e.g. via: `import os; switch("nimcache", "/tmp/somedir")`
|
||||
if conf.target.targetOS == osWindows and DirSep == '/': arg = arg.replace('\\', '/')
|
||||
conf.nimcacheDir = processPath(conf, pathRelativeToConfig(arg, pass, conf), info, notRelativeToProj=true)
|
||||
of "out", "o":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
let f = splitFile(processPath(conf, arg, info, notRelativeToProj=true).string)
|
||||
@@ -514,18 +684,21 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "backend", "b":
|
||||
let backend = parseEnum(arg.normalize, TBackend.default)
|
||||
if backend == TBackend.default: localError(conf, info, "invalid backend: '$1'" % arg)
|
||||
if backend == backendJs: # bug #21209
|
||||
conf.globalOptions.excl {optThreadAnalysis, optThreads}
|
||||
if optRun in conf.globalOptions:
|
||||
# for now, -r uses nodejs, so define nodejs
|
||||
defineSymbol(conf.symbols, "nodejs")
|
||||
conf.backend = backend
|
||||
of "doccmd": conf.docCmd = arg
|
||||
of "define", "d":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if {':', '='} in arg:
|
||||
splitSwitch(conf, arg, key, val, pass, info)
|
||||
if cmpIgnoreStyle(key, "nimQuirky") == 0:
|
||||
conf.exc = excQuirky
|
||||
specialDefine(conf, key, pass)
|
||||
defineSymbol(conf.symbols, key, val)
|
||||
else:
|
||||
if cmpIgnoreStyle(arg, "nimQuirky") == 0:
|
||||
conf.exc = excQuirky
|
||||
specialDefine(conf, arg, pass)
|
||||
defineSymbol(conf.symbols, arg)
|
||||
of "undef", "u":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -552,59 +725,10 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "project":
|
||||
processOnOffSwitchG(conf, {optWholeProject, optGenIndex}, arg, pass, info)
|
||||
of "gc":
|
||||
if conf.backend == backendJs: return # for: bug #16033
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "boehm":
|
||||
conf.selectedGC = gcBoehm
|
||||
defineSymbol(conf.symbols, "boehmgc")
|
||||
incl conf.globalOptions, optTlsEmulation # Boehm GC doesn't scan the real TLS
|
||||
of "refc":
|
||||
conf.selectedGC = gcRefc
|
||||
of "markandsweep":
|
||||
conf.selectedGC = gcMarkAndSweep
|
||||
defineSymbol(conf.symbols, "gcmarkandsweep")
|
||||
of "destructors", "arc":
|
||||
conf.selectedGC = gcArc
|
||||
defineSymbol(conf.symbols, "gcdestructors")
|
||||
defineSymbol(conf.symbols, "gcarc")
|
||||
incl conf.globalOptions, optSeqDestructors
|
||||
incl conf.globalOptions, optTinyRtti
|
||||
if pass in {passCmd2, passPP}:
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
defineSymbol(conf.symbols, "nimV2")
|
||||
if conf.exc == excNone and conf.backend != backendCpp:
|
||||
conf.exc = excGoto
|
||||
of "orc":
|
||||
conf.selectedGC = gcOrc
|
||||
defineSymbol(conf.symbols, "gcdestructors")
|
||||
defineSymbol(conf.symbols, "gcorc")
|
||||
incl conf.globalOptions, optSeqDestructors
|
||||
incl conf.globalOptions, optTinyRtti
|
||||
if pass in {passCmd2, passPP}:
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
defineSymbol(conf.symbols, "nimV2")
|
||||
if conf.exc == excNone and conf.backend != backendCpp:
|
||||
conf.exc = excGoto
|
||||
of "hooks":
|
||||
conf.selectedGC = gcHooks
|
||||
defineSymbol(conf.symbols, "gchooks")
|
||||
incl conf.globalOptions, optSeqDestructors
|
||||
processOnOffSwitchG(conf, {optSeqDestructors}, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
of "go":
|
||||
conf.selectedGC = gcGo
|
||||
defineSymbol(conf.symbols, "gogc")
|
||||
of "none":
|
||||
conf.selectedGC = gcNone
|
||||
defineSymbol(conf.symbols, "nogc")
|
||||
of "stack", "regions":
|
||||
conf.selectedGC = gcRegions
|
||||
defineSymbol(conf.symbols, "gcregions")
|
||||
of "v2": warningOptionNoop(arg)
|
||||
else: localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg)
|
||||
warningDeprecated(conf, info, "`gc:option` is deprecated; use `mm:option` instead")
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "mm":
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "warnings", "w":
|
||||
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
|
||||
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
|
||||
@@ -672,10 +796,13 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "linedir": processOnOffSwitch(conf, {optLineDir}, arg, pass, info)
|
||||
of "assertions", "a": processOnOffSwitch(conf, {optAssert}, arg, pass, info)
|
||||
of "threads":
|
||||
if conf.backend == backendJs: discard
|
||||
if conf.backend == backendJs or conf.cmd == cmdNimscript: discard
|
||||
else: processOnOffSwitchG(conf, {optThreads}, arg, pass, info)
|
||||
#if optThreads in conf.globalOptions: conf.setNote(warnGcUnsafe)
|
||||
of "tlsemulation": processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info)
|
||||
of "tlsemulation":
|
||||
processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info)
|
||||
if optTlsEmulation in conf.globalOptions:
|
||||
conf.legacyFeatures.incl emitGenerics
|
||||
of "implicitstatic":
|
||||
processOnOffSwitch(conf, {optImplicitStatic}, arg, pass, info)
|
||||
of "patterns", "trmacros":
|
||||
@@ -712,6 +839,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
defineSymbol(conf.symbols, "dll")
|
||||
of "staticlib":
|
||||
incl(conf.globalOptions, optGenStaticLib)
|
||||
incl(conf.globalOptions, optNoMain)
|
||||
excl(conf.globalOptions, optGenGuiApp)
|
||||
defineSymbol(conf.symbols, "library")
|
||||
defineSymbol(conf.symbols, "staticlib")
|
||||
@@ -731,12 +859,20 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "clib":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.cLinkedLibs.add processPath(conf, arg, info).string
|
||||
conf.cLinkedLibs.add arg
|
||||
of "header":
|
||||
if conf != nil: conf.headerFile = arg
|
||||
incl(conf.globalOptions, optGenIndex)
|
||||
of "nimbasepattern":
|
||||
if conf != nil: conf.nimbasePattern = arg
|
||||
of "index":
|
||||
processOnOffSwitchG(conf, {optGenIndex}, arg, pass, info)
|
||||
case arg.normalize
|
||||
of "", "on": conf.globalOptions.incl {optGenIndex}
|
||||
of "only": conf.globalOptions.incl {optGenIndexOnly, optGenIndex}
|
||||
of "off": conf.globalOptions.excl {optGenIndex, optGenIndexOnly}
|
||||
else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
|
||||
of "noimportdoc":
|
||||
processOnOffSwitchG(conf, {optNoImportdoc}, arg, pass, info)
|
||||
of "import":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
@@ -769,21 +905,28 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
setTarget(conf.target, conf.target.targetOS, cpu)
|
||||
of "run", "r":
|
||||
processOnOffSwitchG(conf, {optRun}, arg, pass, info)
|
||||
if conf.backend == backendJs:
|
||||
# for now, -r uses nodejs, so define nodejs
|
||||
defineSymbol(conf.symbols, "nodejs")
|
||||
of "maxloopiterationsvm":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.maxLoopIterationsVM = parseInt(arg)
|
||||
var value: int = 10_000_000
|
||||
discard parseSaturatedNatural(arg, value)
|
||||
if not value > 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
|
||||
conf.maxLoopIterationsVM = value
|
||||
of "errormax":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
# Note: `nim check` (etc) can overwrite this.
|
||||
# `0` is meaningless, give it a useful meaning as in clang's -ferror-limit
|
||||
# If user doesn't set this flag and the code doesn't either, it'd
|
||||
# have the same effect as errorMax = 1
|
||||
let ret = parseInt(arg)
|
||||
conf.errorMax = if ret == 0: high(int) else: ret
|
||||
var value: int = 0
|
||||
discard parseSaturatedNatural(arg, value)
|
||||
conf.errorMax = if value == 0: high(int) else: value
|
||||
of "verbosity":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
let verbosity = parseInt(arg)
|
||||
if verbosity notin {0..3}:
|
||||
if verbosity notin 0..3:
|
||||
localError(conf, info, "invalid verbosity level: '$1'" % arg)
|
||||
conf.verbosity = verbosity
|
||||
var verb = NotesVerbosity[conf.verbosity]
|
||||
@@ -793,7 +936,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf.mainPackageNotes = conf.notes
|
||||
of "parallelbuild":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.numberOfProcessors = parseInt(arg)
|
||||
var value: int = 0
|
||||
discard parseSaturatedNatural(arg, value)
|
||||
conf.numberOfProcessors = value
|
||||
of "version", "v":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
writeVersionInfo(conf, pass)
|
||||
@@ -818,6 +963,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "v2": conf.symbolFiles = v2Sf
|
||||
of "stress": conf.symbolFiles = stressTest
|
||||
else: localError(conf, info, "invalid option for --incremental: " & arg)
|
||||
setUseIc(conf.symbolFiles != disabledSf)
|
||||
of "skipcfg":
|
||||
processOnOffSwitchG(conf, {optSkipSystemConfigFile}, arg, pass, info)
|
||||
of "skipprojcfg":
|
||||
@@ -830,6 +976,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
if switch.normalize == "gendeps": deprecatedAlias(switch, "genscript")
|
||||
processOnOffSwitchG(conf, {optGenScript}, arg, pass, info)
|
||||
processOnOffSwitchG(conf, {optCompileOnly}, arg, pass, info)
|
||||
of "gencdeps":
|
||||
processOnOffSwitchG(conf, {optGenCDeps}, arg, pass, info)
|
||||
of "colors": processOnOffSwitchG(conf, {optUseColors}, arg, pass, info)
|
||||
of "lib":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -839,8 +987,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
splitSwitch(conf, arg, key, val, pass, info)
|
||||
os.putEnv(key, val)
|
||||
of "cc":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
setCC(conf, arg, info)
|
||||
if conf.backend != backendJs: # bug #19330
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
setCC(conf, arg, info)
|
||||
of "track":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
track(conf, arg, info)
|
||||
@@ -851,18 +1000,40 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
conf.ideCmd = ideSug
|
||||
of "def":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
conf.ideCmd = ideDef
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
trackIde(conf, ideDef, arg, info)
|
||||
of "context":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
conf.ideCmd = ideCon
|
||||
of "usages":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
conf.ideCmd = ideUse
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
trackIde(conf, ideUse, arg, info)
|
||||
of "defusages":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
trackIde(conf, ideDus, arg, info)
|
||||
of "stdout":
|
||||
processOnOffSwitchG(conf, {optStdout}, arg, pass, info)
|
||||
of "filenames":
|
||||
case arg.normalize
|
||||
of "abs": conf.filenameOption = foAbs
|
||||
of "canonical": conf.filenameOption = foCanonical
|
||||
of "legacyrelproj": conf.filenameOption = foLegacyRelProj
|
||||
else: localError(conf, info, "expected: abs|canonical|legacyRelProj, got: $1" % arg)
|
||||
of "processing":
|
||||
incl(conf.notes, hintProcessing)
|
||||
incl(conf.mainPackageNotes, hintProcessing)
|
||||
case arg.normalize
|
||||
of "dots": conf.hintProcessingDots = true
|
||||
of "filenames": conf.hintProcessingDots = false
|
||||
of "off":
|
||||
excl(conf.notes, hintProcessing)
|
||||
excl(conf.mainPackageNotes, hintProcessing)
|
||||
else: localError(conf, info, "expected: dots|filenames|off, got: $1" % arg)
|
||||
of "unitsep":
|
||||
conf.unitSep = if switchOn(arg): "\31" else: ""
|
||||
of "listfullpaths":
|
||||
processOnOffSwitchG(conf, {optListFullPaths}, arg, pass, info)
|
||||
# xxx in future work, use `warningDeprecated`
|
||||
conf.filenameOption = if switchOn(arg): foAbs else: foCanonical
|
||||
of "spellsuggest":
|
||||
if arg.len == 0: conf.spellSuggestMax = spellSuggestSecretSauce
|
||||
elif arg == "auto": conf.spellSuggestMax = spellSuggestSecretSauce
|
||||
@@ -890,6 +1061,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
conf.exc = low(ExceptionSystem)
|
||||
defineSymbol(conf.symbols, "noCppExceptions")
|
||||
of "shownonexports":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
showNonExportedFields(conf)
|
||||
of "exceptions":
|
||||
case arg.normalize
|
||||
of "cpp": conf.exc = excCpp
|
||||
@@ -924,6 +1098,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError}
|
||||
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError}
|
||||
of "error": conf.globalOptions = conf.globalOptions + {optStyleError}
|
||||
of "usages": conf.globalOptions.incl optStyleUsages
|
||||
else: localError(conf, info, errOffHintsError % arg)
|
||||
of "showallmismatches":
|
||||
processOnOffSwitchG(conf, {optShowAllMismatches}, arg, pass, info)
|
||||
@@ -943,25 +1118,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "expandarc":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.arcToExpand[arg] = "T"
|
||||
of "useversion":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
case arg
|
||||
of "1.0":
|
||||
defineSymbol(conf.symbols, "NimMajor", "1")
|
||||
defineSymbol(conf.symbols, "NimMinor", "0")
|
||||
# old behaviors go here:
|
||||
defineSymbol(conf.symbols, "nimOldRelativePathBehavior")
|
||||
undefSymbol(conf.symbols, "nimDoesntTrackDefects")
|
||||
ast.eqTypeFlags.excl {tfGcSafe, tfNoSideEffect}
|
||||
conf.globalOptions.incl optNimV1Emulation
|
||||
of "1.2":
|
||||
defineSymbol(conf.symbols, "NimMajor", "1")
|
||||
defineSymbol(conf.symbols, "NimMinor", "2")
|
||||
conf.globalOptions.incl optNimV12Emulation
|
||||
else:
|
||||
localError(conf, info, "unknown Nim version; currently supported values are: `1.0`, `1.2`")
|
||||
# always be compatible with 1.x.100:
|
||||
defineSymbol(conf.symbols, "NimPatch", "100")
|
||||
of "benchmarkvm":
|
||||
processOnOffSwitchG(conf, {optBenchmarkVM}, arg, pass, info)
|
||||
of "profilevm":
|
||||
@@ -975,6 +1131,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
processOnOffSwitchG(conf, {optPanics}, arg, pass, info)
|
||||
if optPanics in conf.globalOptions:
|
||||
defineSymbol(conf.symbols, "nimPanics")
|
||||
of "jsbigint64":
|
||||
processOnOffSwitchG(conf, {optJsBigInt64}, arg, pass, info)
|
||||
of "sourcemap": # xxx document in --fullhelp
|
||||
conf.globalOptions.incl optSourcemap
|
||||
conf.options.incl optLineDir
|
||||
@@ -982,13 +1140,15 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
processOnOffSwitchG(conf, {optEnableDeepCopy}, arg, pass, info)
|
||||
of "": # comes from "-" in for example: `nim c -r -` (gets stripped from -)
|
||||
handleStdinInput(conf)
|
||||
of "nilseqs", "nilchecks", "mainmodule", "m", "symbol", "taintmode", "cs", "deadcodeelim": warningOptionNoop(switch)
|
||||
of "nilseqs", "nilchecks", "symbol", "taintmode", "cs", "deadcodeelim": warningOptionNoop(switch)
|
||||
of "nimmainprefix": conf.nimMainPrefix = arg
|
||||
else:
|
||||
if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg)
|
||||
else: invalidCmdLineOption(conf, pass, switch, info)
|
||||
|
||||
proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) =
|
||||
var cmd, arg: string
|
||||
var cmd = ""
|
||||
var arg = ""
|
||||
splitSwitch(config, switch, cmd, arg, pass, gCmdLineInfo)
|
||||
processSwitch(cmd, arg, pass, gCmdLineInfo, config)
|
||||
|
||||
@@ -1015,13 +1175,20 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser;
|
||||
config.projectName = unixToNativePath(p.key)
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
elif pass != passCmd2: setCommandEarly(config, p.key)
|
||||
elif pass != passCmd2:
|
||||
setCommandEarly(config, p.key)
|
||||
result = false
|
||||
else: result = false
|
||||
else:
|
||||
if pass == passCmd1: config.commandArgs.add p.key
|
||||
if argsCount == 1:
|
||||
if p.key.endsWith(".nims"):
|
||||
incl(config.globalOptions, optWasNimscript)
|
||||
# support UNIX style filenames everywhere for portable build scripts:
|
||||
if config.projectName.len == 0:
|
||||
config.projectName = unixToNativePath(p.key)
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
inc argsCount
|
||||
|
||||
28
compiler/compiler.nimble
Normal file
28
compiler/compiler.nimble
Normal file
@@ -0,0 +1,28 @@
|
||||
include "../lib/system/compilation.nim"
|
||||
version = $NimMajor & "." & $NimMinor & "." & $NimPatch
|
||||
author = "Andreas Rumpf"
|
||||
description = "Compiler package providing the compiler sources as a library."
|
||||
license = "MIT"
|
||||
skipDirs = @["."]
|
||||
installDirs = @["compiler"]
|
||||
|
||||
import os
|
||||
|
||||
var compilerDir = ""
|
||||
|
||||
before install:
|
||||
rmDir("compiler")
|
||||
|
||||
let
|
||||
files = listFiles(".")
|
||||
dirs = listDirs(".")
|
||||
|
||||
mkDir("compiler")
|
||||
|
||||
for f in files:
|
||||
cpFile(f, "compiler" / f)
|
||||
|
||||
for d in dirs:
|
||||
cpDir(d, "compiler" / d)
|
||||
|
||||
requires "nim"
|
||||
@@ -11,10 +11,12 @@
|
||||
## for details. Note this is a first implementation and only the "Concept matching"
|
||||
## section has been implemented.
|
||||
|
||||
import ast, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer,
|
||||
types, intsets
|
||||
import ast, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer, types
|
||||
|
||||
from magicsys import addSonSkipIntLit
|
||||
import std/intsets
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
const
|
||||
logBindings = false
|
||||
@@ -23,30 +25,18 @@ const
|
||||
## --------------------------------------
|
||||
|
||||
proc declareSelf(c: PContext; info: TLineInfo) =
|
||||
## adds the magical 'Self' symbols to the current scope.
|
||||
## Adds the magical 'Self' symbols to the current scope.
|
||||
let ow = getCurrOwner(c)
|
||||
let s = newSym(skType, getIdent(c.cache, "Self"), nextSymId(c.idgen), ow, info)
|
||||
s.typ = newType(tyTypeDesc, nextTypeId(c.idgen), ow)
|
||||
let s = newSym(skType, getIdent(c.cache, "Self"), c.idgen, ow, info)
|
||||
s.typ = newType(tyTypeDesc, c.idgen, ow)
|
||||
s.typ.flags.incl {tfUnresolved, tfPacked}
|
||||
s.typ.add newType(tyEmpty, nextTypeId(c.idgen), ow)
|
||||
s.typ.add newType(tyEmpty, c.idgen, ow)
|
||||
addDecl(c, s, info)
|
||||
|
||||
proc isSelf*(t: PType): bool {.inline.} =
|
||||
## is this the magical 'Self' type?
|
||||
t.kind == tyTypeDesc and tfPacked in t.flags
|
||||
|
||||
proc makeTypeDesc*(c: PContext, typ: PType): PType =
|
||||
if typ.kind == tyTypeDesc and not isSelf(typ):
|
||||
result = typ
|
||||
else:
|
||||
result = newTypeS(tyTypeDesc, c)
|
||||
incl result.flags, tfCheckedForDestructor
|
||||
result.addSonSkipIntLit(typ, c.idgen)
|
||||
|
||||
proc semConceptDecl(c: PContext; n: PNode): PNode =
|
||||
## Recursive helper for semantic checking for the concept declaration.
|
||||
## Currently we only support lists of statements containing 'proc'
|
||||
## declarations and the like.
|
||||
## Currently we only support (possibly empty) lists of statements
|
||||
## containing 'proc' declarations and the like.
|
||||
case n.kind
|
||||
of nkStmtList, nkStmtListExpr:
|
||||
result = shallowCopy(n)
|
||||
@@ -59,8 +49,10 @@ proc semConceptDecl(c: PContext; n: PNode): PNode =
|
||||
for i in 0..<n.len-1:
|
||||
result[i] = n[i]
|
||||
result[^1] = semConceptDecl(c, n[^1])
|
||||
of nkCommentStmt:
|
||||
result = n
|
||||
else:
|
||||
localError(c.config, n.info, "unexpected construct in the new-styled concept " & renderTree(n))
|
||||
localError(c.config, n.info, "unexpected construct in the new-styled concept: " & renderTree(n))
|
||||
result = n
|
||||
|
||||
proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
|
||||
@@ -97,14 +89,14 @@ proc existingBinding(m: MatchCon; key: PType): PType =
|
||||
proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool
|
||||
|
||||
proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool =
|
||||
## the heart of the concept matching process. 'f' is the formal parameter of some
|
||||
## The heart of the concept matching process. 'f' is the formal parameter of some
|
||||
## routine inside the concept that we're looking for. 'a' is the formal parameter
|
||||
## of a routine that might match.
|
||||
const
|
||||
ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred}
|
||||
case f.kind
|
||||
of tyAlias:
|
||||
result = matchType(c, f.lastSon, a, m)
|
||||
result = matchType(c, f.skipModifier, a, m)
|
||||
of tyTypeDesc:
|
||||
if isSelf(f):
|
||||
#let oldLen = m.inferred.len
|
||||
@@ -113,15 +105,19 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool =
|
||||
#m.inferred.setLen oldLen
|
||||
#echo "A for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)
|
||||
else:
|
||||
if a.kind == tyTypeDesc and f.len == a.len:
|
||||
for i in 0..<a.len:
|
||||
if not matchType(c, f[i], a[i], m): return false
|
||||
return true
|
||||
if a.kind == tyTypeDesc and f.hasElementType == a.hasElementType:
|
||||
if f.hasElementType:
|
||||
result = matchType(c, f.elementType, a.elementType, m)
|
||||
else:
|
||||
result = true # both lack it
|
||||
else:
|
||||
result = false
|
||||
|
||||
of tyGenericInvocation:
|
||||
if a.kind == tyGenericInst and a[0].kind == tyGenericBody:
|
||||
if sameType(f[0], a[0]) and f.len == a.len-1:
|
||||
for i in 1 ..< f.len:
|
||||
result = false
|
||||
if a.kind == tyGenericInst and a.genericHead.kind == tyGenericBody:
|
||||
if sameType(f.genericHead, a.genericHead) and f.kidsLen == a.kidsLen-1:
|
||||
for i in FirstGenericParamAt ..< f.kidsLen:
|
||||
if not matchType(c, f[i], a[i], m): return false
|
||||
return true
|
||||
of tyGenericParam:
|
||||
@@ -131,17 +127,17 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool =
|
||||
else:
|
||||
let old = existingBinding(m, f)
|
||||
if old == nil:
|
||||
if f.len > 0 and f[0].kind != tyNone:
|
||||
if f.hasElementType and f.elementType.kind != tyNone:
|
||||
# also check the generic's constraints:
|
||||
let oldLen = m.inferred.len
|
||||
result = matchType(c, f[0], a, m)
|
||||
result = matchType(c, f.elementType, a, m)
|
||||
m.inferred.setLen oldLen
|
||||
if result:
|
||||
when logBindings: echo "A adding ", f, " ", ak
|
||||
m.inferred.add((f, ak))
|
||||
elif m.magic == mArrGet and ak.kind in {tyArray, tyOpenArray, tySequence, tyVarargs, tyCString, tyString}:
|
||||
elif m.magic == mArrGet and ak.kind in {tyArray, tyOpenArray, tySequence, tyVarargs, tyCstring, tyString}:
|
||||
when logBindings: echo "B adding ", f, " ", lastSon ak
|
||||
m.inferred.add((f, lastSon ak))
|
||||
m.inferred.add((f, last ak))
|
||||
result = true
|
||||
else:
|
||||
when logBindings: echo "C adding ", f, " ", ak
|
||||
@@ -152,25 +148,27 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool =
|
||||
result = matchType(c, old, ak, m)
|
||||
if m.magic == mArrPut and ak.kind == tyGenericParam:
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
#echo "B for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)
|
||||
|
||||
of tyVar, tySink, tyLent, tyOwned:
|
||||
# modifiers in the concept must be there in the actual implementation
|
||||
# too but not vice versa.
|
||||
if a.kind == f.kind:
|
||||
result = matchType(c, f.sons[0], a.sons[0], m)
|
||||
result = matchType(c, f.elementType, a.elementType, m)
|
||||
elif m.magic == mArrPut:
|
||||
result = matchType(c, f.sons[0], a, m)
|
||||
result = matchType(c, f.elementType, a, m)
|
||||
else:
|
||||
result = false
|
||||
of tyEnum, tyObject, tyDistinct:
|
||||
result = sameType(f, a)
|
||||
of tyEmpty, tyString, tyCString, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid:
|
||||
of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid:
|
||||
result = a.skipTypes(ignorableForArgType).kind == f.kind
|
||||
of tyBool, tyChar, tyInt..tyUInt64:
|
||||
let ak = a.skipTypes(ignorableForArgType)
|
||||
result = ak.kind == f.kind or ak.kind == tyOrdinal or
|
||||
(ak.kind == tyGenericParam and ak.len > 0 and ak[0].kind == tyOrdinal)
|
||||
(ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal)
|
||||
of tyConcept:
|
||||
let oldLen = m.inferred.len
|
||||
let oldPotentialImplementation = m.potentialImplementation
|
||||
@@ -181,9 +179,11 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool =
|
||||
m.inferred.setLen oldLen
|
||||
of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr,
|
||||
tyGenericInst:
|
||||
# ^ XXX Rewrite this logic, it's more complex than it needs to be.
|
||||
result = false
|
||||
let ak = a.skipTypes(ignorableForArgType - {f.kind})
|
||||
if ak.kind == f.kind and f.len == ak.len:
|
||||
for i in 0..<ak.len:
|
||||
if ak.kind == f.kind and f.kidsLen == ak.kidsLen:
|
||||
for i in 0..<ak.kidsLen:
|
||||
if not matchType(c, f[i], ak[i], m): return false
|
||||
return true
|
||||
of tyOr:
|
||||
@@ -192,29 +192,30 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool =
|
||||
# say the concept requires 'int|float|string' if the potentialImplementation
|
||||
# says 'int|string' that is good enough.
|
||||
var covered = 0
|
||||
for i in 0..<f.len:
|
||||
for j in 0..<a.len:
|
||||
for ff in f.kids:
|
||||
for aa in a.kids:
|
||||
let oldLenB = m.inferred.len
|
||||
let r = matchType(c, f[i], a[j], m)
|
||||
let r = matchType(c, ff, aa, m)
|
||||
if r:
|
||||
inc covered
|
||||
break
|
||||
m.inferred.setLen oldLenB
|
||||
|
||||
result = covered >= a.len
|
||||
result = covered >= a.kidsLen
|
||||
if not result:
|
||||
m.inferred.setLen oldLen
|
||||
else:
|
||||
for i in 0..<f.len:
|
||||
result = matchType(c, f[i], a, m)
|
||||
result = false
|
||||
for ff in f.kids:
|
||||
result = matchType(c, ff, a, m)
|
||||
if result: break # and remember the binding!
|
||||
m.inferred.setLen oldLen
|
||||
of tyNot:
|
||||
if a.kind == tyNot:
|
||||
result = matchType(c, f[0], a[0], m)
|
||||
result = matchType(c, f.elementType, a.elementType, m)
|
||||
else:
|
||||
let oldLen = m.inferred.len
|
||||
result = not matchType(c, f[0], a, m)
|
||||
result = not matchType(c, f.elementType, a, m)
|
||||
m.inferred.setLen oldLen
|
||||
of tyAnything:
|
||||
result = true
|
||||
@@ -253,7 +254,7 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool =
|
||||
m.inferred.setLen oldLen
|
||||
return false
|
||||
|
||||
if not matchReturnType(c, n[0].sym.typ.sons[0], candidate.typ.sons[0], m):
|
||||
if not matchReturnType(c, n[0].sym.typ.returnType, candidate.typ.returnType, m):
|
||||
m.inferred.setLen oldLen
|
||||
return false
|
||||
|
||||
@@ -270,7 +271,7 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool =
|
||||
proc matchSyms(c: PContext, n: PNode; kinds: set[TSymKind]; m: var MatchCon): bool =
|
||||
## Walk the current scope, extract candidates which the same name as 'n[namePos]',
|
||||
## 'n' is the nkProcDef or similar from the concept that we try to match.
|
||||
let candidates = searchInScopesFilterBy(c, n[namePos].sym.name, kinds)
|
||||
let candidates = searchInScopesAllCandidatesFilterBy(c, n[namePos].sym.name, kinds)
|
||||
for candidate in candidates:
|
||||
#echo "considering ", typeToString(candidate.typ), " ", candidate.magic
|
||||
m.magic = candidate.magic
|
||||
@@ -302,17 +303,19 @@ proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
|
||||
result = matchSyms(c, n, {skMethod}, m)
|
||||
of nkIteratorDef:
|
||||
result = matchSyms(c, n, {skIterator}, m)
|
||||
of nkCommentStmt:
|
||||
result = true
|
||||
else:
|
||||
# error was reported earlier.
|
||||
result = false
|
||||
|
||||
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var TIdTable; invocation: PType): bool =
|
||||
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var TypeMapping; invocation: PType): bool =
|
||||
## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but
|
||||
## we extract its AST via 'concpt.n.lastSon'). 'arg' is the type that might fullfill the
|
||||
## we extract its AST via 'concpt.n.lastSon'). 'arg' is the type that might fulfill the
|
||||
## concept's requirements. If so, we return true and fill the 'bindings' with pairs of
|
||||
## (typeVar, instance) pairs. ('typeVar' is usually simply written as a generic 'T'.)
|
||||
## 'invocation' can be nil for atomic concepts. For non-atomic concepts, it contains the
|
||||
## 'C[S, T]' parent type that we look for. We need this because we need to store bindings
|
||||
## `C[S, T]` parent type that we look for. We need this because we need to store bindings
|
||||
## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that
|
||||
## we do not add any bindings at all on an unsuccessful match!
|
||||
var m = MatchCon(inferred: @[], potentialImplementation: arg)
|
||||
@@ -333,8 +336,8 @@ proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var TIdTable; invo
|
||||
# we have a match, so bind 'arg' itself to 'concpt':
|
||||
bindings.idTablePut(concpt, arg)
|
||||
# invocation != nil means we have a non-atomic concept:
|
||||
if invocation != nil and arg.kind == tyGenericInst and invocation.len == arg.len-1:
|
||||
if invocation != nil and arg.kind == tyGenericInst and invocation.kidsLen == arg.kidsLen-1:
|
||||
# bind even more generic parameters
|
||||
assert invocation.kind == tyGenericInvocation
|
||||
for i in 1 ..< invocation.len:
|
||||
for i in FirstGenericParamAt ..< invocation.kidsLen:
|
||||
bindings.idTablePut(invocation[i], arg[i])
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# This module handles the conditional symbols.
|
||||
|
||||
import
|
||||
strtabs
|
||||
std/strtabs
|
||||
|
||||
from options import Feature
|
||||
from lineinfos import hintMin, hintMax, warnMin, warnMax
|
||||
@@ -25,7 +25,7 @@ proc undefSymbol*(symbols: StringTableRef; symbol: string) =
|
||||
# result = if isDefined(symbol): gSymbols[symbol] else: nil
|
||||
|
||||
iterator definedSymbolNames*(symbols: StringTableRef): string =
|
||||
for key, val in pairs(symbols):
|
||||
for key in keys(symbols):
|
||||
yield key
|
||||
|
||||
proc countDefinedSymbols*(symbols: StringTableRef): int =
|
||||
@@ -45,10 +45,8 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimNewTypedesc") # deadcode
|
||||
defineSymbol("nimrequiresnimframe") # deadcode
|
||||
defineSymbol("nimparsebiggestfloatmagic") # deadcode
|
||||
defineSymbol("nimalias") # deadcode
|
||||
defineSymbol("nimlocks") # deadcode
|
||||
defineSymbol("nimnode") # deadcode pending `nimnode` reference in opengl package
|
||||
# refs https://github.com/nim-lang/opengl/pull/79
|
||||
defineSymbol("nimnode") # deadcode
|
||||
defineSymbol("nimvarargstyped") # deadcode
|
||||
defineSymbol("nimtypedescfixed") # deadcode
|
||||
defineSymbol("nimKnowsNimvm") # deadcode
|
||||
@@ -70,7 +68,7 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimVmExportFixed") # deadcode
|
||||
defineSymbol("nimHasSymOwnerInMacro") # deadcode
|
||||
defineSymbol("nimNewRuntime") # deadcode
|
||||
defineSymbol("nimIncrSeqV3") # xxx: turn this into deadcode
|
||||
defineSymbol("nimIncrSeqV3") # deadcode
|
||||
defineSymbol("nimAshr") # deadcode
|
||||
defineSymbol("nimNoNilSeqs") # deadcode
|
||||
defineSymbol("nimNoNilSeqs2") # deadcode
|
||||
@@ -84,10 +82,24 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimHasSignatureHashInMacro") # deadcode
|
||||
defineSymbol("nimHasDefault") # deadcode
|
||||
defineSymbol("nimMacrosSizealignof") # deadcode
|
||||
defineSymbol("nimNoZeroExtendMagic") # deadcode
|
||||
defineSymbol("nimMacrosGetNodeId") # deadcode
|
||||
defineSymbol("nimFixedForwardGeneric") # deadcode
|
||||
defineSymbol("nimToOpenArrayCString") # deadcode
|
||||
defineSymbol("nimHasUsed") # deadcode
|
||||
defineSymbol("nimnomagic64") # deadcode
|
||||
defineSymbol("nimNewShiftOps") # deadcode
|
||||
defineSymbol("nimHasCursor") # deadcode
|
||||
defineSymbol("nimAlignPragma") # deadcode
|
||||
defineSymbol("nimHasExceptionsQuery") # deadcode
|
||||
defineSymbol("nimHasIsNamedTuple") # deadcode
|
||||
defineSymbol("nimHashOrdinalFixed") # deadcode
|
||||
defineSymbol("nimHasSinkInference") # deadcode
|
||||
defineSymbol("nimNewIntegerOps") # deadcode
|
||||
defineSymbol("nimHasInvariant") # deadcode
|
||||
|
||||
|
||||
|
||||
# > 0.20.0
|
||||
defineSymbol("nimNoZeroExtendMagic")
|
||||
defineSymbol("nimMacrosGetNodeId")
|
||||
for f in Feature:
|
||||
defineSymbol("nimHas" & $f)
|
||||
|
||||
@@ -98,31 +110,18 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
|
||||
defineSymbol("nimFixedOwned")
|
||||
defineSymbol("nimHasStyleChecks")
|
||||
defineSymbol("nimToOpenArrayCString")
|
||||
defineSymbol("nimHasUsed")
|
||||
defineSymbol("nimFixedForwardGeneric")
|
||||
defineSymbol("nimnomagic64")
|
||||
defineSymbol("nimNewShiftOps")
|
||||
defineSymbol("nimHasCursor")
|
||||
defineSymbol("nimAlignPragma")
|
||||
defineSymbol("nimHasExceptionsQuery")
|
||||
defineSymbol("nimHasIsNamedTuple")
|
||||
defineSymbol("nimHashOrdinalFixed")
|
||||
|
||||
when defined(nimHasLibFFI):
|
||||
# Renaming as we can't conflate input vs output define flags; e.g. this
|
||||
# will report the right thing regardless of whether user adds
|
||||
# `-d:nimHasLibFFI` in his user config.
|
||||
defineSymbol("nimHasLibFFIEnabled")
|
||||
defineSymbol("nimHasLibFFIEnabled") # deadcode
|
||||
|
||||
defineSymbol("nimHasSinkInference")
|
||||
defineSymbol("nimNewIntegerOps")
|
||||
defineSymbol("nimHasInvariant")
|
||||
defineSymbol("nimHasStacktraceMsgs")
|
||||
defineSymbol("nimHasStacktraceMsgs") # deadcode
|
||||
defineSymbol("nimDoesntTrackDefects")
|
||||
defineSymbol("nimHasLentIterators")
|
||||
defineSymbol("nimHasDeclaredMagic")
|
||||
defineSymbol("nimHasStacktracesModule")
|
||||
defineSymbol("nimHasLentIterators") # deadcode
|
||||
defineSymbol("nimHasDeclaredMagic") # deadcode
|
||||
defineSymbol("nimHasStacktracesModule") # deadcode
|
||||
defineSymbol("nimHasEffectTraitsModule")
|
||||
defineSymbol("nimHasCastPragmaBlocks")
|
||||
defineSymbol("nimHasDeclaredLocs")
|
||||
@@ -130,3 +129,40 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimHasWarningAsError")
|
||||
defineSymbol("nimHasHintAsError")
|
||||
defineSymbol("nimHasSpellSuggest")
|
||||
defineSymbol("nimHasCustomLiterals")
|
||||
defineSymbol("nimHasUnifiedTuple")
|
||||
defineSymbol("nimHasIterable")
|
||||
defineSymbol("nimHasTypeofVoid") # deadcode
|
||||
defineSymbol("nimHasDragonBox") # deadcode
|
||||
defineSymbol("nimHasHintAll")
|
||||
defineSymbol("nimHasTrace")
|
||||
defineSymbol("nimHasEffectsOf")
|
||||
|
||||
defineSymbol("nimHasEnforceNoRaises")
|
||||
defineSymbol("nimHasTopDownInference")
|
||||
defineSymbol("nimHasTemplateRedefinitionPragma")
|
||||
defineSymbol("nimHasCstringCase")
|
||||
defineSymbol("nimHasCallsitePragma")
|
||||
|
||||
defineSymbol("nimHasWarnCastSizes") # deadcode
|
||||
defineSymbol("nimHasOutParams")
|
||||
defineSymbol("nimHasSystemRaisesDefect")
|
||||
defineSymbol("nimHasWarnUnnamedBreak")
|
||||
defineSymbol("nimHasGenericDefine")
|
||||
defineSymbol("nimHasDefineAliases")
|
||||
defineSymbol("nimHasWarnBareExcept")
|
||||
defineSymbol("nimHasDup")
|
||||
defineSymbol("nimHasChecksums")
|
||||
defineSymbol("nimHasSendable")
|
||||
defineSymbol("nimAllowNonVarDestructor")
|
||||
defineSymbol("nimHasQuirky")
|
||||
defineSymbol("nimHasEnsureMove")
|
||||
defineSymbol("nimHasNoReturnError")
|
||||
|
||||
defineSymbol("nimUseStrictDefs")
|
||||
defineSymbol("nimHasNolineTooLong")
|
||||
|
||||
defineSymbol("nimHasCastExtendedVm")
|
||||
defineSymbol("nimHasWarnStdPrefix")
|
||||
|
||||
defineSymbol("nimHasVtables")
|
||||
|
||||
72
compiler/debugutils.nim
Normal file
72
compiler/debugutils.nim
Normal file
@@ -0,0 +1,72 @@
|
||||
##[
|
||||
Utilities to help with debugging nim compiler.
|
||||
|
||||
Experimental API, subject to change.
|
||||
]##
|
||||
|
||||
#[
|
||||
## example
|
||||
useful debugging flags:
|
||||
--stacktrace -d:debug -d:nimDebugUtils
|
||||
nim c -o:bin/nim_temp --stacktrace -d:debug -d:nimDebugUtils compiler/nim
|
||||
|
||||
## future work
|
||||
* expose and improve astalgo.debug, replacing it by std/prettyprints,
|
||||
refs https://github.com/nim-lang/RFCs/issues/385
|
||||
]#
|
||||
|
||||
import options
|
||||
import std/wrapnils
|
||||
export wrapnils
|
||||
# allows using things like: `?.n.sym.typ.len`
|
||||
|
||||
import std/stackframes
|
||||
export stackframes
|
||||
# allows using things like: `setFrameMsg c.config$n.info & " " & $n.kind`
|
||||
# which doesn't log, but augments stacktrace with side channel information
|
||||
|
||||
var conf0: ConfigRef
|
||||
|
||||
proc onNewConfigRef*(conf: ConfigRef) {.inline.} =
|
||||
## Caches `conf`, which can be retrieved with `getConfigRef`.
|
||||
## This avoids having to forward `conf` all the way down the call chain to
|
||||
## procs that need it during a debugging session.
|
||||
conf0 = conf
|
||||
|
||||
proc getConfigRef*(): ConfigRef =
|
||||
## nil, if -d:nimDebugUtils wasn't specified
|
||||
result = conf0
|
||||
|
||||
proc isCompilerDebug*(): bool =
|
||||
##[
|
||||
Provides a simple way for user code to enable/disable logging in the compiler
|
||||
in a granular way. This can then be used in the compiler as follows:
|
||||
```nim
|
||||
if isCompilerDebug():
|
||||
echo ?.n.sym.typ.len
|
||||
```
|
||||
]##
|
||||
runnableExamples:
|
||||
proc main =
|
||||
echo 2
|
||||
{.define(nimCompilerDebug).}
|
||||
echo 3.5 # code section in which `isCompilerDebug` will be true
|
||||
{.undef(nimCompilerDebug).}
|
||||
echo 'x'
|
||||
conf0.isDefined("nimCompilerDebug")
|
||||
|
||||
proc enteringDebugSection*() {.exportc, dynlib.} =
|
||||
## Provides a way for native debuggers to enable breakpoints, watchpoints, etc
|
||||
## when code of interest is being compiled.
|
||||
##
|
||||
## Set your debugger to break on entering `nimCompilerIsEnteringDebugSection`
|
||||
## and then execute a desired command.
|
||||
discard
|
||||
|
||||
proc exitingDebugSection*() {.exportc, dynlib.} =
|
||||
## Provides a way for native debuggers to disable breakpoints, watchpoints, etc
|
||||
## when code of interest is no longer being compiled.
|
||||
##
|
||||
## Set your debugger to break on entering `exitingDebugSection`
|
||||
## and then execute a desired command.
|
||||
discard
|
||||
@@ -9,10 +9,17 @@
|
||||
|
||||
# This module implements a dependency file generator.
|
||||
|
||||
import
|
||||
options, ast, ropes, idents, passes, modulepaths, pathutils
|
||||
import options, ast, ropes, pathutils, msgs, lineinfos
|
||||
|
||||
import modulegraphs
|
||||
|
||||
import std/[os, parseutils]
|
||||
import std/strutils except addf
|
||||
import std/private/globs
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
from modulegraphs import ModuleGraph, PPassContext
|
||||
|
||||
type
|
||||
TGen = object of PPassContext
|
||||
@@ -25,21 +32,67 @@ type
|
||||
dotGraph: Rope
|
||||
|
||||
proc addDependencyAux(b: Backend; importing, imported: string) =
|
||||
b.dotGraph.addf("$1 -> \"$2\";$n", [rope(importing), rope(imported)])
|
||||
b.dotGraph.addf("\"$1\" -> \"$2\";$n", [rope(importing), rope(imported)])
|
||||
# s1 -> s2_4[label="[0-9]"];
|
||||
|
||||
proc addDotDependency(c: PPassContext, n: PNode): PNode =
|
||||
proc toNimblePath(s: string, isStdlib: bool): string =
|
||||
const stdPrefix = "std/"
|
||||
const pkgPrefix = "pkg/"
|
||||
if isStdlib:
|
||||
let sub = "lib/"
|
||||
var start = s.find(sub)
|
||||
if start < 0:
|
||||
raiseAssert "unreachable"
|
||||
else:
|
||||
start += sub.len
|
||||
let base = s[start..^1]
|
||||
|
||||
if base.startsWith("system") or base.startsWith("std"):
|
||||
result = base
|
||||
else:
|
||||
for dir in stdlibDirs:
|
||||
if base.startsWith(dir):
|
||||
return stdPrefix & base.splitFile.name
|
||||
|
||||
result = stdPrefix & base
|
||||
else:
|
||||
var sub = getEnv("NIMBLE_DIR")
|
||||
if sub.len == 0:
|
||||
sub = ".nimble/pkgs/"
|
||||
else:
|
||||
sub.add "/pkgs/"
|
||||
var start = s.find(sub)
|
||||
if start < 0:
|
||||
sub[^1] = '2'
|
||||
sub.add '/'
|
||||
start = s.find(sub) # /pkgs2
|
||||
if start < 0:
|
||||
return s
|
||||
|
||||
start += sub.len
|
||||
start += skipUntil(s, '/', start)
|
||||
start += 1
|
||||
result = pkgPrefix & s[start..^1]
|
||||
|
||||
proc addDependency(c: PPassContext, g: PGen, b: Backend, n: PNode) =
|
||||
doAssert n.kind == nkSym, $n.kind
|
||||
|
||||
let path = splitFile(toProjPath(g.config, n.sym.position.FileIndex))
|
||||
let modulePath = splitFile(toProjPath(g.config, g.module.position.FileIndex))
|
||||
let parent = nativeToUnixPath(modulePath.dir / modulePath.name).toNimblePath(belongsToStdlib(g.graph, g.module))
|
||||
let child = nativeToUnixPath(path.dir / path.name).toNimblePath(belongsToStdlib(g.graph, n.sym))
|
||||
addDependencyAux(b, parent, child)
|
||||
|
||||
proc addDotDependency*(c: PPassContext, n: PNode): PNode =
|
||||
result = n
|
||||
let g = PGen(c)
|
||||
let b = Backend(g.graph.backend)
|
||||
case n.kind
|
||||
of nkImportStmt:
|
||||
for i in 0..<n.len:
|
||||
var imported = getModuleName(g.config, n[i])
|
||||
addDependencyAux(b, g.module.name.s, imported)
|
||||
addDependency(c, g, b, n[i])
|
||||
of nkFromStmt, nkImportExceptStmt:
|
||||
var imported = getModuleName(g.config, n[0])
|
||||
addDependencyAux(b, g.module.name.s, imported)
|
||||
addDependency(c, g, b, n[0])
|
||||
of nkStmtList, nkBlockStmt, nkStmtListExpr, nkBlockExpr:
|
||||
for i in 0..<n.len: discard addDotDependency(c, n[i])
|
||||
else:
|
||||
@@ -51,18 +104,7 @@ proc generateDot*(graph: ModuleGraph; project: AbsoluteFile) =
|
||||
rope(project.splitFile.name), b.dotGraph],
|
||||
changeFileExt(project, "dot"))
|
||||
|
||||
when not defined(nimHasSinkInference):
|
||||
{.pragma: nosinks.}
|
||||
|
||||
proc myOpen(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext {.nosinks.} =
|
||||
var g: PGen
|
||||
new(g)
|
||||
g.module = module
|
||||
g.config = graph.config
|
||||
g.graph = graph
|
||||
proc setupDependPass*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
|
||||
result = PGen(module: module, config: graph.config, graph: graph)
|
||||
if graph.backend == nil:
|
||||
graph.backend = Backend(dotGraph: nil)
|
||||
result = g
|
||||
|
||||
const gendependPass* = makePass(open = myOpen, process = addDotDependency)
|
||||
|
||||
graph.backend = Backend(dotGraph: "")
|
||||
|
||||
586
compiler/dfa.nim
586
compiler/dfa.nim
@@ -9,37 +9,34 @@
|
||||
|
||||
## Data flow analysis for Nim.
|
||||
## We transform the AST into a linear list of instructions first to
|
||||
## make this easier to handle: There are only 2 different branching
|
||||
## make this easier to handle: There are only 3 different branching
|
||||
## instructions: 'goto X' is an unconditional goto, 'fork X'
|
||||
## is a conditional goto (either the next instruction or 'X' can be
|
||||
## taken). Exhaustive case statements are translated
|
||||
## taken), 'loop X' is the only jump that jumps back.
|
||||
##
|
||||
## Exhaustive case statements are translated
|
||||
## so that the last branch is transformed into an 'else' branch.
|
||||
## ``return`` and ``break`` are all covered by 'goto'.
|
||||
##
|
||||
## Control flow through exception handling:
|
||||
## Contrary to popular belief, exception handling doesn't cause
|
||||
## many problems for this DFA representation, ``raise`` is a statement
|
||||
## that ``goes to`` the outer ``finally`` or ``except`` if there is one,
|
||||
## otherwise it is the same as ``return``. Every call is treated as
|
||||
## a call that can potentially ``raise``. However, without a surrounding
|
||||
## ``try`` we don't emit these ``fork ReturnLabel`` instructions in order
|
||||
## to speed up the dataflow analysis passes.
|
||||
##
|
||||
## The data structures and algorithms used here are inspired by
|
||||
## "A Graph–Free Approach to Data–Flow Analysis" by Markus Mohnen.
|
||||
## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf
|
||||
|
||||
import ast, types, intsets, lineinfos, renderer
|
||||
import ast, lineinfos, renderer, aliasanalysis
|
||||
import std/private/asciitables
|
||||
import std/intsets
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
InstrKind* = enum
|
||||
goto, fork, def, use
|
||||
goto, loop, fork, def, use
|
||||
Instr* = object
|
||||
n*: PNode # contains the def/use location.
|
||||
case kind*: InstrKind
|
||||
of goto, fork: dest*: int
|
||||
else: discard
|
||||
of goto, fork, loop: dest*: int
|
||||
of def, use:
|
||||
n*: PNode # contains the def/use location.
|
||||
|
||||
ControlFlowGraph* = seq[Instr]
|
||||
|
||||
@@ -49,24 +46,26 @@ type
|
||||
case isTryBlock: bool
|
||||
of false:
|
||||
label: PSym
|
||||
breakFixups: seq[(TPosition, seq[PNode])] #Contains the gotos for the breaks along with their pending finales
|
||||
breakFixups: seq[(TPosition, seq[PNode])] # Contains the gotos for the breaks along with their pending finales
|
||||
of true:
|
||||
finale: PNode
|
||||
raiseFixups: seq[TPosition] #Contains the gotos for the raises
|
||||
raiseFixups: seq[TPosition] # Contains the gotos for the raises
|
||||
|
||||
Con = object
|
||||
code: ControlFlowGraph
|
||||
inTryStmt: int
|
||||
inTryStmt, interestingInstructions: int
|
||||
blocks: seq[TBlock]
|
||||
owner: PSym
|
||||
root: PSym
|
||||
|
||||
proc codeListing(c: ControlFlowGraph, start = 0; last = -1): string =
|
||||
# for debugging purposes
|
||||
# first iteration: compute all necessary labels:
|
||||
result = ""
|
||||
var jumpTargets = initIntSet()
|
||||
let last = if last < 0: c.len-1 else: min(last, c.len-1)
|
||||
for i in start..last:
|
||||
if c[i].kind in {goto, fork}:
|
||||
if c[i].kind in {goto, fork, loop}:
|
||||
jumpTargets.incl(i+c[i].dest)
|
||||
var i = start
|
||||
while i <= last:
|
||||
@@ -77,12 +76,12 @@ proc codeListing(c: ControlFlowGraph, start = 0; last = -1): string =
|
||||
case c[i].kind
|
||||
of def, use:
|
||||
result.add renderTree(c[i].n)
|
||||
of goto, fork:
|
||||
result.add("\t#")
|
||||
result.add($c[i].n.info.line)
|
||||
result.add("\n")
|
||||
of goto, fork, loop:
|
||||
result.add "L"
|
||||
result.addInt c[i].dest+i
|
||||
result.add("\t#")
|
||||
result.add($c[i].n.info.line)
|
||||
result.add("\n")
|
||||
inc i
|
||||
if i in jumpTargets: result.add("L" & $i & ": End\n")
|
||||
|
||||
@@ -90,181 +89,13 @@ proc echoCfg*(c: ControlFlowGraph; start = 0; last = -1) {.deprecated.} =
|
||||
## echos the ControlFlowGraph for debugging purposes.
|
||||
echo codeListing(c, start, last).alignTable
|
||||
|
||||
proc forkI(c: var Con; n: PNode): TPosition =
|
||||
proc forkI(c: var Con): TPosition =
|
||||
result = TPosition(c.code.len)
|
||||
c.code.add Instr(n: n, kind: fork, dest: 0)
|
||||
c.code.add Instr(kind: fork, dest: 0)
|
||||
|
||||
proc gotoI(c: var Con; n: PNode): TPosition =
|
||||
proc gotoI(c: var Con): TPosition =
|
||||
result = TPosition(c.code.len)
|
||||
c.code.add Instr(n: n, kind: goto, dest: 0)
|
||||
|
||||
#[
|
||||
|
||||
Join is no more
|
||||
===============
|
||||
Instead of generating join instructions we adapt our traversal of the CFG.
|
||||
|
||||
When encountering a fork we split into two paths, we follow the path
|
||||
starting at "pc + 1" until it encounters the joinpoint: "pc + forkInstr.dest".
|
||||
If we encounter gotos that would jump further than the current joinpoint,
|
||||
as can happen with gotos generated by unstructured controlflow such as break, raise or return,
|
||||
we simply suspend following the current path, and follow the other path until the new joinpoint
|
||||
which is simply the instruction pointer returned to us by the now suspended path.
|
||||
If the path we are following now, also encounters a goto that exceeds the joinpoint
|
||||
we repeat the process; suspending the current path and evaluating the other one with a new joinpoint.
|
||||
If we eventually reach a common joinpoint we join the two paths.
|
||||
This new "ping-pong" approach has the obvious advantage of not requiring join instructions, as such
|
||||
cutting down on the CFG size but is also mandatory for correctly handling complicated cases
|
||||
of unstructured controlflow.
|
||||
|
||||
|
||||
Design of join
|
||||
==============
|
||||
|
||||
block:
|
||||
if cond: break
|
||||
def(x)
|
||||
|
||||
use(x)
|
||||
|
||||
Generates:
|
||||
|
||||
L0: fork lab1
|
||||
join L0 # patched.
|
||||
goto Louter
|
||||
lab1:
|
||||
def x
|
||||
join L0
|
||||
Louter:
|
||||
use x
|
||||
|
||||
|
||||
block outer:
|
||||
while a:
|
||||
while b:
|
||||
if foo:
|
||||
if bar:
|
||||
break outer # --> we need to 'join' every pushed 'fork' here
|
||||
|
||||
|
||||
This works and then our abstract interpretation needs to deal with 'fork'
|
||||
differently. It really causes a split in execution. Two threads are
|
||||
"spawned" and both need to reach the 'join L' instruction. Afterwards
|
||||
the abstract interpretations are joined and execution resumes single
|
||||
threaded.
|
||||
|
||||
|
||||
Abstract Interpretation
|
||||
-----------------------
|
||||
|
||||
proc interpret(pc, state, comesFrom): state =
|
||||
result = state
|
||||
# we need an explicit 'create' instruction (an explicit heap), in order
|
||||
# to deal with 'var x = create(); var y = x; var z = y; destroy(z)'
|
||||
while true:
|
||||
case pc
|
||||
of fork:
|
||||
let a = interpret(pc+1, result, pc)
|
||||
let b = interpret(forkTarget, result, pc)
|
||||
result = a ++ b # ++ is a union operation
|
||||
inc pc
|
||||
of join:
|
||||
if joinTarget == comesFrom: return result
|
||||
else: inc pc
|
||||
of use X:
|
||||
if not result.contains(x):
|
||||
error "variable not initialized " & x
|
||||
inc pc
|
||||
of def X:
|
||||
if not result.contains(x):
|
||||
result.incl X
|
||||
else:
|
||||
error "overwrite of variable causes memory leak " & x
|
||||
inc pc
|
||||
of destroy X:
|
||||
result.excl X
|
||||
|
||||
This is correct but still can lead to false positives:
|
||||
|
||||
proc p(cond: bool) =
|
||||
if cond:
|
||||
new(x)
|
||||
otherThings()
|
||||
if cond:
|
||||
destroy x
|
||||
|
||||
Is not a leak. We should find a way to model *data* flow, not just
|
||||
control flow. One solution is to rewrite the 'if' without a fork
|
||||
instruction. The unstructured aspect can now be easily dealt with
|
||||
the 'goto' and 'join' instructions.
|
||||
|
||||
proc p(cond: bool) =
|
||||
L0: fork Lend
|
||||
new(x)
|
||||
# do not 'join' here!
|
||||
|
||||
Lend:
|
||||
otherThings()
|
||||
join L0 # SKIP THIS FOR new(x) SOMEHOW
|
||||
destroy x
|
||||
join L0 # but here.
|
||||
|
||||
|
||||
|
||||
But if we follow 'goto Louter' we will never come to the join point.
|
||||
We restore the bindings after popping pc from the stack then there
|
||||
"no" problem?!
|
||||
|
||||
|
||||
while cond:
|
||||
prelude()
|
||||
if not condB: break
|
||||
postlude()
|
||||
|
||||
--->
|
||||
var setFlag = true
|
||||
while cond and not setFlag:
|
||||
prelude()
|
||||
if not condB:
|
||||
setFlag = true # BUT: Dependency
|
||||
if not setFlag: # HERE
|
||||
postlude()
|
||||
|
||||
--->
|
||||
var setFlag = true
|
||||
while cond and not setFlag:
|
||||
prelude()
|
||||
if not condB:
|
||||
postlude()
|
||||
setFlag = true
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
while cond:
|
||||
prelude()
|
||||
if more:
|
||||
if not condB: break
|
||||
stuffHere()
|
||||
postlude()
|
||||
|
||||
-->
|
||||
var setFlag = true
|
||||
while cond and not setFlag:
|
||||
prelude()
|
||||
if more:
|
||||
if not condB:
|
||||
setFlag = false
|
||||
else:
|
||||
stuffHere()
|
||||
postlude()
|
||||
else:
|
||||
postlude()
|
||||
|
||||
This is getting complicated. Instead we keep the whole 'join' idea but
|
||||
duplicate the 'join' instructions on breaks and return exits!
|
||||
|
||||
]#
|
||||
c.code.add Instr(kind: goto, dest: 0)
|
||||
|
||||
proc genLabel(c: Con): TPosition = TPosition(c.code.len)
|
||||
|
||||
@@ -272,8 +103,8 @@ template checkedDistance(dist): int =
|
||||
doAssert low(int) div 2 + 1 < dist and dist < high(int) div 2
|
||||
dist
|
||||
|
||||
proc jmpBack(c: var Con, n: PNode, p = TPosition(0)) =
|
||||
c.code.add Instr(n: n, kind: goto, dest: checkedDistance(p.int - c.code.len))
|
||||
proc jmpBack(c: var Con, p = TPosition(0)) =
|
||||
c.code.add Instr(kind: loop, dest: checkedDistance(p.int - c.code.len))
|
||||
|
||||
proc patch(c: var Con, p: TPosition) =
|
||||
# patch with current index
|
||||
@@ -282,13 +113,13 @@ proc patch(c: var Con, p: TPosition) =
|
||||
proc gen(c: var Con; n: PNode)
|
||||
|
||||
proc popBlock(c: var Con; oldLen: int) =
|
||||
var exits: seq[TPosition]
|
||||
exits.add c.gotoI(newNode(nkEmpty))
|
||||
var exits: seq[TPosition] = @[]
|
||||
exits.add c.gotoI()
|
||||
for f in c.blocks[oldLen].breakFixups:
|
||||
c.patch(f[0])
|
||||
for finale in f[1]:
|
||||
c.gen(finale)
|
||||
exits.add c.gotoI(newNode(nkEmpty))
|
||||
exits.add c.gotoI()
|
||||
for e in exits:
|
||||
c.patch e
|
||||
c.blocks.setLen(oldLen)
|
||||
@@ -299,90 +130,29 @@ template withBlock(labl: PSym; body: untyped) =
|
||||
body
|
||||
popBlock(c, oldLen)
|
||||
|
||||
proc isTrue(n: PNode): bool =
|
||||
n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
|
||||
n.kind == nkIntLit and n.intVal != 0
|
||||
|
||||
when true:
|
||||
proc genWhile(c: var Con; n: PNode) =
|
||||
# We unroll every loop 3 times. We emulate 0, 1, 2 iterations
|
||||
# through the loop. We need to prove this is correct for our
|
||||
# purposes. But Herb Sutter claims it is. (Proof by authority.)
|
||||
#[
|
||||
while cond:
|
||||
body
|
||||
|
||||
Becomes:
|
||||
|
||||
block:
|
||||
if cond:
|
||||
body
|
||||
if cond:
|
||||
body
|
||||
if cond:
|
||||
body
|
||||
|
||||
We still need to ensure 'break' resolves properly, so an AST to AST
|
||||
translation is impossible.
|
||||
|
||||
So the code to generate is:
|
||||
|
||||
cond
|
||||
fork L4 # F1
|
||||
body
|
||||
cond
|
||||
fork L5 # F2
|
||||
body
|
||||
cond
|
||||
fork L6 # F3
|
||||
body
|
||||
L6:
|
||||
join F3
|
||||
L5:
|
||||
join F2
|
||||
L4:
|
||||
join F1
|
||||
]#
|
||||
if isTrue(n[0]):
|
||||
# 'while true' is an idiom in Nim and so we produce
|
||||
# better code for it:
|
||||
withBlock(nil):
|
||||
for i in 0..2:
|
||||
c.gen(n[1])
|
||||
else:
|
||||
withBlock(nil):
|
||||
var endings: array[3, TPosition]
|
||||
for i in 0..2:
|
||||
c.gen(n[0])
|
||||
endings[i] = c.forkI(n)
|
||||
c.gen(n[1])
|
||||
for i in countdown(endings.high, 0):
|
||||
c.patch(endings[i])
|
||||
|
||||
else:
|
||||
proc genWhile(c: var Con; n: PNode) =
|
||||
# lab1:
|
||||
# cond, tmp
|
||||
# fork tmp, lab2
|
||||
# body
|
||||
# jmp lab1
|
||||
# lab2:
|
||||
let lab1 = c.genLabel
|
||||
withBlock(nil):
|
||||
if isTrue(n[0]):
|
||||
c.gen(n[1])
|
||||
c.jmpBack(n, lab1)
|
||||
else:
|
||||
c.gen(n[0])
|
||||
forkT(n):
|
||||
c.gen(n[1])
|
||||
c.jmpBack(n, lab1)
|
||||
|
||||
template forkT(n, body) =
|
||||
let lab1 = c.forkI(n)
|
||||
template forkT(body) =
|
||||
let lab1 = c.forkI()
|
||||
body
|
||||
c.patch(lab1)
|
||||
|
||||
proc genWhile(c: var Con; n: PNode) =
|
||||
# lab1:
|
||||
# cond, tmp
|
||||
# fork tmp, lab2
|
||||
# body
|
||||
# jmp lab1
|
||||
# lab2:
|
||||
let lab1 = c.genLabel
|
||||
withBlock(nil):
|
||||
if isTrue(n[0]):
|
||||
c.gen(n[1])
|
||||
c.jmpBack(lab1)
|
||||
else:
|
||||
c.gen(n[0])
|
||||
forkT:
|
||||
c.gen(n[1])
|
||||
c.jmpBack(lab1)
|
||||
|
||||
proc genIf(c: var Con, n: PNode) =
|
||||
#[
|
||||
|
||||
@@ -411,34 +181,32 @@ proc genIf(c: var Con, n: PNode) =
|
||||
goto Lend3
|
||||
L3:
|
||||
D
|
||||
goto Lend3 # not eliminated to simplify the join generation
|
||||
Lend3:
|
||||
join F3
|
||||
Lend2:
|
||||
join F2
|
||||
Lend:
|
||||
join F1
|
||||
|
||||
]#
|
||||
var endings: seq[TPosition] = @[]
|
||||
let oldInteresting = c.interestingInstructions
|
||||
let oldLen = c.code.len
|
||||
|
||||
for i in 0..<n.len:
|
||||
let it = n[i]
|
||||
c.gen(it[0])
|
||||
if it.len == 2:
|
||||
forkT(it[1]):
|
||||
c.gen(it[1])
|
||||
endings.add c.gotoI(it[1])
|
||||
for i in countdown(endings.high, 0):
|
||||
c.patch(endings[i])
|
||||
forkT:
|
||||
c.gen(it.lastSon)
|
||||
endings.add c.gotoI()
|
||||
|
||||
if oldInteresting == c.interestingInstructions:
|
||||
setLen c.code, oldLen
|
||||
else:
|
||||
for i in countdown(endings.high, 0):
|
||||
c.patch(endings[i])
|
||||
|
||||
proc genAndOr(c: var Con; n: PNode) =
|
||||
# asgn dest, a
|
||||
# fork lab1
|
||||
# asgn dest, b
|
||||
# lab1:
|
||||
# join F1
|
||||
c.gen(n[1])
|
||||
forkT(n):
|
||||
forkT:
|
||||
c.gen(n[2])
|
||||
|
||||
proc genCase(c: var Con; n: PNode) =
|
||||
@@ -453,39 +221,40 @@ proc genCase(c: var Con; n: PNode) =
|
||||
# elsePart
|
||||
# Lend:
|
||||
let isExhaustive = skipTypes(n[0].typ,
|
||||
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
|
||||
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
|
||||
|
||||
# we generate endings as a set of chained gotos, this is a bit awkward but it
|
||||
# ensures when recursively traversing the CFG for various analysis, we don't
|
||||
# artificially extended the life of each branch (for the purposes of DFA)
|
||||
# beyond the minimum amount.
|
||||
var endings: seq[TPosition] = @[]
|
||||
c.gen(n[0])
|
||||
let oldInteresting = c.interestingInstructions
|
||||
let oldLen = c.code.len
|
||||
for i in 1..<n.len:
|
||||
let it = n[i]
|
||||
if it.len == 1 or (i == n.len-1 and isExhaustive):
|
||||
# treat the last branch as 'else' if this is an exhaustive case statement.
|
||||
c.gen(it.lastSon)
|
||||
if endings.len != 0:
|
||||
c.patch(endings[^1])
|
||||
else:
|
||||
forkT(it.lastSon):
|
||||
forkT:
|
||||
c.gen(it.lastSon)
|
||||
if endings.len != 0:
|
||||
c.patch(endings[^1])
|
||||
endings.add c.gotoI(it.lastSon)
|
||||
endings.add c.gotoI()
|
||||
|
||||
if oldInteresting == c.interestingInstructions:
|
||||
setLen c.code, oldLen
|
||||
else:
|
||||
for i in countdown(endings.high, 0):
|
||||
c.patch(endings[i])
|
||||
|
||||
proc genBlock(c: var Con; n: PNode) =
|
||||
withBlock(n[0].sym):
|
||||
c.gen(n[1])
|
||||
|
||||
proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
|
||||
let lab1 = c.gotoI(n)
|
||||
let lab1 = c.gotoI()
|
||||
if c.blocks[i].isTryBlock:
|
||||
c.blocks[i].raiseFixups.add lab1
|
||||
else:
|
||||
var trailingFinales: seq[PNode]
|
||||
if c.inTryStmt > 0: #Ok, we are in a try, lets see which (if any) try's we break out from:
|
||||
var trailingFinales: seq[PNode] = @[]
|
||||
if c.inTryStmt > 0:
|
||||
# Ok, we are in a try, lets see which (if any) try's we break out from:
|
||||
for b in countdown(c.blocks.high, i):
|
||||
if c.blocks[b].isTryBlock:
|
||||
trailingFinales.add c.blocks[b].finale
|
||||
@@ -493,6 +262,7 @@ proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
|
||||
c.blocks[i].breakFixups.add (lab1, trailingFinales)
|
||||
|
||||
proc genBreak(c: var Con; n: PNode) =
|
||||
inc c.interestingInstructions
|
||||
if n[0].kind == nkSym:
|
||||
for i in countdown(c.blocks.high, 0):
|
||||
if not c.blocks[i].isTryBlock and c.blocks[i].label == n[0].sym:
|
||||
@@ -523,9 +293,9 @@ proc genTry(c: var Con; n: PNode) =
|
||||
for i in 1..<n.len:
|
||||
let it = n[i]
|
||||
if it.kind != nkFinally:
|
||||
forkT(it):
|
||||
forkT:
|
||||
c.gen(it.lastSon)
|
||||
endings.add c.gotoI(it)
|
||||
endings.add c.gotoI()
|
||||
for i in countdown(endings.high, 0):
|
||||
c.patch(endings[i])
|
||||
|
||||
@@ -533,26 +303,28 @@ proc genTry(c: var Con; n: PNode) =
|
||||
if fin.kind == nkFinally:
|
||||
c.gen(fin[0])
|
||||
|
||||
template genNoReturn(c: var Con; n: PNode) =
|
||||
template genNoReturn(c: var Con) =
|
||||
# leave the graph
|
||||
c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
|
||||
c.code.add Instr(kind: goto, dest: high(int) - c.code.len)
|
||||
|
||||
proc genRaise(c: var Con; n: PNode) =
|
||||
inc c.interestingInstructions
|
||||
gen(c, n[0])
|
||||
if c.inTryStmt > 0:
|
||||
for i in countdown(c.blocks.high, 0):
|
||||
if c.blocks[i].isTryBlock:
|
||||
genBreakOrRaiseAux(c, i, n)
|
||||
return
|
||||
assert false #Unreachable
|
||||
assert false # Unreachable
|
||||
else:
|
||||
genNoReturn(c, n)
|
||||
genNoReturn(c)
|
||||
|
||||
proc genImplicitReturn(c: var Con) =
|
||||
if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len:
|
||||
gen(c, c.owner.ast[resultPos])
|
||||
|
||||
proc genReturn(c: var Con; n: PNode) =
|
||||
inc c.interestingInstructions
|
||||
if n[0].kind != nkEmpty:
|
||||
gen(c, n[0])
|
||||
else:
|
||||
@@ -561,124 +333,6 @@ proc genReturn(c: var Con; n: PNode) =
|
||||
|
||||
const
|
||||
InterestingSyms = {skVar, skResult, skLet, skParam, skForVar, skTemp}
|
||||
PathKinds0 = {nkDotExpr, nkCheckedFieldExpr,
|
||||
nkBracketExpr, nkDerefExpr, nkHiddenDeref,
|
||||
nkAddr, nkHiddenAddr,
|
||||
nkObjDownConv, nkObjUpConv}
|
||||
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv}
|
||||
|
||||
proc skipConvDfa*(n: PNode): PNode =
|
||||
result = n
|
||||
while true:
|
||||
case result.kind
|
||||
of nkObjDownConv, nkObjUpConv:
|
||||
result = result[0]
|
||||
of PathKinds1:
|
||||
result = result[1]
|
||||
else: break
|
||||
|
||||
type AliasKind* = enum
|
||||
yes, no, maybe
|
||||
|
||||
proc aliases*(obj, field: PNode): AliasKind =
|
||||
# obj -> field:
|
||||
# x -> x: true
|
||||
# x -> x.f: true
|
||||
# x.f -> x: false
|
||||
# x.f -> x.f: true
|
||||
# x.f -> x.v: false
|
||||
# x -> x[0]: true
|
||||
# x[0] -> x: false
|
||||
# x[0] -> x[0]: true
|
||||
# x[0] -> x[1]: false
|
||||
# x -> x[i]: true
|
||||
# x[i] -> x: false
|
||||
# x[i] -> x[i]: maybe; Further analysis could make this return true when i is a runtime-constant
|
||||
# x[i] -> x[j]: maybe; also returns maybe if only one of i or j is a compiletime-constant
|
||||
template collectImportantNodes(result, n) =
|
||||
var result: seq[PNode]
|
||||
var n = n
|
||||
while true:
|
||||
case n.kind
|
||||
of PathKinds0 - {nkDotExpr, nkCheckedFieldExpr, nkBracketExpr}:
|
||||
n = n[0]
|
||||
of PathKinds1:
|
||||
n = n[1]
|
||||
of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr:
|
||||
result.add n
|
||||
n = n[0]
|
||||
of nkSym:
|
||||
result.add n; break
|
||||
else: return no
|
||||
|
||||
collectImportantNodes(objImportantNodes, obj)
|
||||
collectImportantNodes(fieldImportantNodes, field)
|
||||
|
||||
# If field is less nested than obj, then it cannot be part of/aliased by obj
|
||||
if fieldImportantNodes.len < objImportantNodes.len: return no
|
||||
|
||||
result = yes
|
||||
for i in 1..objImportantNodes.len:
|
||||
# We compare the nodes leading to the location of obj and field
|
||||
# with each other.
|
||||
# We continue until they diverge, in which case we return no, or
|
||||
# until we reach the location of obj, in which case we do not need
|
||||
# to look further, since field must be part of/aliased by obj now.
|
||||
# If we encounter an element access using an index which is a runtime value,
|
||||
# we simply return maybe instead of yes; should further nodes not diverge.
|
||||
let currFieldPath = fieldImportantNodes[^i]
|
||||
let currObjPath = objImportantNodes[^i]
|
||||
|
||||
if currFieldPath.kind != currObjPath.kind:
|
||||
return no
|
||||
|
||||
case currFieldPath.kind
|
||||
of nkSym:
|
||||
if currFieldPath.sym != currObjPath.sym: return no
|
||||
of nkDotExpr:
|
||||
if currFieldPath[1].sym != currObjPath[1].sym: return no
|
||||
of nkCheckedFieldExpr:
|
||||
if currFieldPath[0][1].sym != currObjPath[0][1].sym: return no
|
||||
of nkBracketExpr:
|
||||
if currFieldPath[1].kind in nkLiterals and currObjPath[1].kind in nkLiterals:
|
||||
if currFieldPath[1].intVal != currObjPath[1].intVal:
|
||||
return no
|
||||
else:
|
||||
result = maybe
|
||||
else: assert false # unreachable
|
||||
|
||||
proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool =
|
||||
var n = orig
|
||||
while true:
|
||||
case n.kind
|
||||
of PathKinds0 - {nkHiddenDeref, nkDerefExpr}:
|
||||
n = n[0]
|
||||
of PathKinds1:
|
||||
n = n[1]
|
||||
of nkHiddenDeref, nkDerefExpr:
|
||||
# We "own" sinkparam[].loc but not ourVar[].location as it is a nasty
|
||||
# pointer indirection.
|
||||
# bug #14159, we cannot reason about sinkParam[].location as it can
|
||||
# still be shared for tyRef.
|
||||
n = n[0]
|
||||
return n.kind == nkSym and n.sym.owner == owner and
|
||||
(n.sym.typ.skipTypes(abstractInst-{tyOwned}).kind in {tyOwned})
|
||||
else: break
|
||||
# XXX Allow closure deref operations here if we know
|
||||
# the owner controlled the closure allocation?
|
||||
result = n.kind == nkSym and n.sym.owner == owner and
|
||||
{sfGlobal, sfThread, sfCursor} * n.sym.flags == {} and
|
||||
(n.sym.kind != skParam or isSinkParam(n.sym)) # or n.sym.typ.kind == tyVar)
|
||||
# Note: There is a different move analyzer possible that checks for
|
||||
# consume(param.key); param.key = newValue for all paths. Then code like
|
||||
#
|
||||
# let splited = split(move self.root, x)
|
||||
# self.root = merge(splited.lower, splited.greater)
|
||||
#
|
||||
# could be written without the ``move self.root``. However, this would be
|
||||
# wrong! Then the write barrier for the ``self.root`` assignment would
|
||||
# free the old data and all is lost! Lesson: Don't be too smart, trust the
|
||||
# lower level C++ optimizer to specialize this code.
|
||||
|
||||
proc skipTrivials(c: var Con, n: PNode): PNode =
|
||||
result = n
|
||||
@@ -696,16 +350,20 @@ proc skipTrivials(c: var Con, n: PNode): PNode =
|
||||
proc genUse(c: var Con; orig: PNode) =
|
||||
let n = c.skipTrivials(orig)
|
||||
|
||||
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
||||
c.code.add Instr(n: orig, kind: use)
|
||||
elif n.kind in nkCallKinds:
|
||||
if n.kind == nkSym:
|
||||
if n.sym.kind in InterestingSyms and n.sym == c.root:
|
||||
c.code.add Instr(kind: use, n: orig)
|
||||
inc c.interestingInstructions
|
||||
else:
|
||||
gen(c, n)
|
||||
|
||||
proc genDef(c: var Con; orig: PNode) =
|
||||
let n = c.skipTrivials(orig)
|
||||
|
||||
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
||||
c.code.add Instr(n: orig, kind: def)
|
||||
if n.sym == c.root:
|
||||
c.code.add Instr(kind: def, n: orig)
|
||||
inc c.interestingInstructions
|
||||
|
||||
proc genCall(c: var Con; n: PNode) =
|
||||
gen(c, n[0])
|
||||
@@ -713,18 +371,17 @@ proc genCall(c: var Con; n: PNode) =
|
||||
if t != nil: t = t.skipTypes(abstractInst)
|
||||
for i in 1..<n.len:
|
||||
gen(c, n[i])
|
||||
when false:
|
||||
if t != nil and i < t.len and t[i].kind == tyOut:
|
||||
# Pass by 'out' is a 'must def'. Good enough for a move optimizer.
|
||||
genDef(c, n[i])
|
||||
if t != nil and i < t.signatureLen and isOutParam(t[i]):
|
||||
# Pass by 'out' is a 'must def'. Good enough for a move optimizer.
|
||||
genDef(c, n[i])
|
||||
# every call can potentially raise:
|
||||
if c.inTryStmt > 0 and canRaiseConservative(n[0]):
|
||||
inc c.interestingInstructions
|
||||
# we generate the instruction sequence:
|
||||
# fork lab1
|
||||
# goto exceptionHandler (except or finally)
|
||||
# lab1:
|
||||
# join F1
|
||||
forkT(n):
|
||||
forkT:
|
||||
for i in countdown(c.blocks.high, 0):
|
||||
if c.blocks[i].isTryBlock:
|
||||
genBreakOrRaiseAux(c, i, n)
|
||||
@@ -762,12 +419,18 @@ proc gen(c: var Con; n: PNode) =
|
||||
else:
|
||||
genCall(c, n)
|
||||
if sfNoReturn in n[0].sym.flags:
|
||||
genNoReturn(c, n)
|
||||
genNoReturn(c)
|
||||
else:
|
||||
genCall(c, n)
|
||||
of nkCharLit..nkNilLit: discard
|
||||
of nkAsgn, nkFastAsgn:
|
||||
of nkAsgn, nkFastAsgn, nkSinkAsgn:
|
||||
gen(c, n[1])
|
||||
|
||||
if n[0].kind in PathKinds0:
|
||||
let a = c.skipTrivials(n[0])
|
||||
if a.kind in nkCallKinds:
|
||||
gen(c, a)
|
||||
|
||||
# watch out: 'obj[i].f2 = value' sets 'f2' but
|
||||
# "uses" 'i'. But we are only talking about builtin array indexing so
|
||||
# it doesn't matter and 'x = 34' is NOT a usage of 'x'.
|
||||
@@ -794,16 +457,35 @@ proc gen(c: var Con; n: PNode) =
|
||||
of nkConv, nkExprColonExpr, nkExprEqExpr, nkCast, PathKinds1:
|
||||
gen(c, n[1])
|
||||
of nkVarSection, nkLetSection: genVarSection(c, n)
|
||||
of nkDefer: doAssert false, "dfa construction pass requires the elimination of 'defer'"
|
||||
of nkDefer: raiseAssert "dfa construction pass requires the elimination of 'defer'"
|
||||
else: discard
|
||||
|
||||
proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
|
||||
when false:
|
||||
proc optimizeJumps(c: var ControlFlowGraph) =
|
||||
for i in 0..<c.len:
|
||||
case c[i].kind
|
||||
of goto, fork:
|
||||
var pc = i + c[i].dest
|
||||
if pc < c.len and c[pc].kind == goto:
|
||||
while pc < c.len and c[pc].kind == goto:
|
||||
let newPc = pc + c[pc].dest
|
||||
if newPc > pc:
|
||||
pc = newPc
|
||||
else:
|
||||
break
|
||||
c[i].dest = pc - i
|
||||
of loop, def, use: discard
|
||||
|
||||
proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph =
|
||||
## constructs a control flow graph for ``body``.
|
||||
var c = Con(code: @[], blocks: @[], owner: s)
|
||||
var c = Con(code: @[], blocks: @[], owner: s, root: root)
|
||||
withBlock(s):
|
||||
gen(c, body)
|
||||
genImplicitReturn(c)
|
||||
when defined(gcArc) or defined(gcOrc):
|
||||
if root.kind == skResult:
|
||||
genImplicitReturn(c)
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = c.code # will move
|
||||
else:
|
||||
shallowCopy(result, c.code)
|
||||
when false:
|
||||
optimizeJumps result
|
||||
|
||||
1373
compiler/docgen.nim
1373
compiler/docgen.nim
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
# semantic checking.
|
||||
|
||||
import
|
||||
options, ast, msgs, passes, docgen, lineinfos, pathutils
|
||||
options, ast, msgs, docgen, lineinfos, pathutils, packages
|
||||
|
||||
from modulegraphs import ModuleGraph, PPassContext
|
||||
|
||||
@@ -23,7 +23,7 @@ type
|
||||
PGen = ref TGen
|
||||
|
||||
proc shouldProcess(g: PGen): bool =
|
||||
(optWholeProject in g.doc.conf.globalOptions and g.module.getnimblePkgId == g.doc.conf.mainPackageId) or
|
||||
(optWholeProject in g.doc.conf.globalOptions and g.doc.conf.belongsToProjectPackage(g.module)) or
|
||||
sfMainModule in g.module.flags or g.config.projectMainIdx == g.module.info.fileIndex
|
||||
|
||||
template closeImpl(body: untyped) {.dirty.} =
|
||||
@@ -31,31 +31,34 @@ template closeImpl(body: untyped) {.dirty.} =
|
||||
let useWarning = sfMainModule notin g.module.flags
|
||||
let groupedToc = true
|
||||
if shouldProcess(g):
|
||||
finishGenerateDoc(g.doc)
|
||||
body
|
||||
try:
|
||||
generateIndex(g.doc)
|
||||
except IOError:
|
||||
discard
|
||||
|
||||
proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
|
||||
proc closeDoc*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
|
||||
result = nil
|
||||
closeImpl:
|
||||
writeOutput(g.doc, useWarning, groupedToc)
|
||||
|
||||
proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
|
||||
proc closeJson*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
|
||||
result = nil
|
||||
closeImpl:
|
||||
writeOutputJson(g.doc, useWarning)
|
||||
|
||||
proc processNode(c: PPassContext, n: PNode): PNode =
|
||||
proc processNode*(c: PPassContext, n: PNode): PNode =
|
||||
result = n
|
||||
var g = PGen(c)
|
||||
if shouldProcess(g):
|
||||
generateDoc(g.doc, n, n)
|
||||
generateDoc(g.doc, n, n, g.config)
|
||||
|
||||
proc processNodeJson(c: PPassContext, n: PNode): PNode =
|
||||
proc processNodeJson*(c: PPassContext, n: PNode): PNode =
|
||||
result = n
|
||||
var g = PGen(c)
|
||||
if shouldProcess(g):
|
||||
generateJson(g.doc, n, false)
|
||||
generateJson(g.doc, n, g.config, false)
|
||||
|
||||
template myOpenImpl(ext: untyped) {.dirty.} =
|
||||
var g: PGen
|
||||
@@ -63,20 +66,15 @@ template myOpenImpl(ext: untyped) {.dirty.} =
|
||||
g.module = module
|
||||
g.config = graph.config
|
||||
var d = newDocumentor(AbsoluteFile toFullPath(graph.config, FileIndex module.position),
|
||||
graph.cache, graph.config, ext, module)
|
||||
d.hasToc = true
|
||||
graph.cache, graph.config, ext, module, hasToc = true)
|
||||
g.doc = d
|
||||
result = g
|
||||
|
||||
proc myOpen(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
|
||||
proc openHtml*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
|
||||
myOpenImpl(HtmlExt)
|
||||
|
||||
proc myOpenJson(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
|
||||
proc openTex*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
|
||||
myOpenImpl(TexExt)
|
||||
|
||||
proc openJson*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
|
||||
myOpenImpl(JsonExt)
|
||||
|
||||
const docgen2Pass* = makePass(open = myOpen, process = processNode, close = close)
|
||||
const docgen2JsonPass* = makePass(open = myOpenJson, process = processNodeJson,
|
||||
close = closeJson)
|
||||
|
||||
proc finishDoc2Pass*(project: string) =
|
||||
discard
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
|
||||
import ast, idents, lineinfos, modulegraphs, magicsys
|
||||
|
||||
proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym =
|
||||
result = newSym(skProc, getIdent(g.cache, "$"), nextSymId idgen, t.owner, info)
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
let dest = newSym(skParam, getIdent(g.cache, "e"), nextSymId idgen, result, info)
|
||||
|
||||
proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym =
|
||||
result = newSym(skProc, getIdent(g.cache, "$"), idgen, t.owner, info)
|
||||
|
||||
let dest = newSym(skParam, getIdent(g.cache, "e"), idgen, result, info)
|
||||
dest.typ = t
|
||||
|
||||
let res = newSym(skResult, getIdent(g.cache, "result"), nextSymId idgen, result, info)
|
||||
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
|
||||
res.typ = getSysType(g, info, tyString)
|
||||
|
||||
result.typ = newType(tyProc, nextTypeId idgen, t.owner)
|
||||
result.typ = newType(tyProc, idgen, t.owner)
|
||||
result.typ.n = newNodeI(nkFormalParams, info)
|
||||
rawAddSon(result.typ, res.typ)
|
||||
result.typ.n.add newNodeI(nkEffectList, info)
|
||||
@@ -26,7 +30,7 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
|
||||
assert(t.n[i].kind == nkSym)
|
||||
var field = t.n[i].sym
|
||||
let val = if field.ast == nil: field.name.s else: field.ast.strVal
|
||||
caseStmt.add newTree(nkOfBranch, newSymNode(field),
|
||||
caseStmt.add newTree(nkOfBranch, newIntTypeNode(field.position, t),
|
||||
newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), newStrNode(val, info))))
|
||||
#newIntTypeNode(nkIntLit, field.position, t)
|
||||
|
||||
@@ -52,26 +56,27 @@ proc searchObjCaseImpl(obj: PNode; field: PSym): PNode =
|
||||
if obj.kind == nkRecCase and obj[0].kind == nkSym and obj[0].sym == field:
|
||||
result = obj
|
||||
else:
|
||||
result = nil
|
||||
for x in obj:
|
||||
result = searchObjCaseImpl(x, field)
|
||||
if result != nil: break
|
||||
|
||||
proc searchObjCase(t: PType; field: PSym): PNode =
|
||||
result = searchObjCaseImpl(t.n, field)
|
||||
if result == nil and t.len > 0:
|
||||
result = searchObjCase(t[0].skipTypes({tyAlias, tyGenericInst, tyRef, tyPtr}), field)
|
||||
if result == nil and t.baseClass != nil:
|
||||
result = searchObjCase(t.baseClass.skipTypes({tyAlias, tyGenericInst, tyRef, tyPtr}), field)
|
||||
doAssert result != nil
|
||||
|
||||
proc genCaseObjDiscMapping*(t: PType; field: PSym; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym =
|
||||
result = newSym(skProc, getIdent(g.cache, "objDiscMapping"), nextSymId idgen, t.owner, info)
|
||||
result = newSym(skProc, getIdent(g.cache, "objDiscMapping"), idgen, t.owner, info)
|
||||
|
||||
let dest = newSym(skParam, getIdent(g.cache, "e"), nextSymId idgen, result, info)
|
||||
let dest = newSym(skParam, getIdent(g.cache, "e"), idgen, result, info)
|
||||
dest.typ = field.typ
|
||||
|
||||
let res = newSym(skResult, getIdent(g.cache, "result"), nextSymId idgen, result, info)
|
||||
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
|
||||
res.typ = getSysType(g, info, tyUInt8)
|
||||
|
||||
result.typ = newType(tyProc, nextTypeId idgen, t.owner)
|
||||
result.typ = newType(tyProc, idgen, t.owner)
|
||||
result.typ.n = newNodeI(nkFormalParams, info)
|
||||
rawAddSon(result.typ, res.typ)
|
||||
result.typ.n.add newNodeI(nkEffectList, info)
|
||||
|
||||
85
compiler/errorhandling.nim
Normal file
85
compiler/errorhandling.nim
Normal file
@@ -0,0 +1,85 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## This module contains support code for new-styled error
|
||||
## handling via an `nkError` node kind.
|
||||
|
||||
import ast, renderer, options, types
|
||||
import std/strutils
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
ErrorKind* = enum ## expand as you need.
|
||||
RawTypeMismatchError
|
||||
ExpressionCannotBeCalled
|
||||
CustomError
|
||||
WrongNumberOfArguments
|
||||
AmbiguousCall
|
||||
|
||||
proc errorSubNode*(n: PNode): PNode =
|
||||
case n.kind
|
||||
of nkEmpty..nkNilLit:
|
||||
result = nil
|
||||
of nkError:
|
||||
result = n
|
||||
else:
|
||||
result = nil
|
||||
for i in 0..<n.len:
|
||||
result = errorSubNode(n[i])
|
||||
if result != nil: break
|
||||
|
||||
proc newError*(wrongNode: PNode; k: ErrorKind; args: varargs[PNode]): PNode =
|
||||
assert wrongNode.kind != nkError
|
||||
let innerError = errorSubNode(wrongNode)
|
||||
if innerError != nil:
|
||||
return innerError
|
||||
var idgen = idGeneratorForPackage(-1'i32)
|
||||
result = newNodeIT(nkError, wrongNode.info, newType(tyError, idgen, nil))
|
||||
result.add wrongNode
|
||||
result.add newIntNode(nkIntLit, ord(k))
|
||||
for a in args: result.add a
|
||||
|
||||
proc newError*(wrongNode: PNode; msg: string): PNode =
|
||||
assert wrongNode.kind != nkError
|
||||
let innerError = errorSubNode(wrongNode)
|
||||
if innerError != nil:
|
||||
return innerError
|
||||
var idgen = idGeneratorForPackage(-1'i32)
|
||||
result = newNodeIT(nkError, wrongNode.info, newType(tyError, idgen, nil))
|
||||
result.add wrongNode
|
||||
result.add newIntNode(nkIntLit, ord(CustomError))
|
||||
result.add newStrNode(msg, wrongNode.info)
|
||||
|
||||
proc errorToString*(config: ConfigRef; n: PNode): string =
|
||||
assert n.kind == nkError
|
||||
assert n.len > 1
|
||||
let wrongNode = n[0]
|
||||
case ErrorKind(n[1].intVal)
|
||||
of RawTypeMismatchError:
|
||||
result = "type mismatch"
|
||||
of ExpressionCannotBeCalled:
|
||||
result = "expression '$1' cannot be called" % wrongNode[0].renderTree
|
||||
of CustomError:
|
||||
result = n[2].strVal
|
||||
of WrongNumberOfArguments:
|
||||
result = "wrong number of arguments"
|
||||
of AmbiguousCall:
|
||||
let a = n[2].sym
|
||||
let b = n[3].sym
|
||||
var args = "("
|
||||
for i in 1..<wrongNode.len:
|
||||
if i > 1: args.add(", ")
|
||||
args.add(typeToString(wrongNode[i].typ))
|
||||
args.add(")")
|
||||
result = "ambiguous call; both $1 and $2 match for: $3" % [
|
||||
getProcHeader(config, a),
|
||||
getProcHeader(config, b),
|
||||
args]
|
||||
@@ -9,9 +9,11 @@
|
||||
|
||||
## This file implements the FFI part of the evaluator for Nim code.
|
||||
|
||||
import ast, types, options, tables, dynlib, msgs, lineinfos
|
||||
from os import getAppFilename
|
||||
import pkg/libffi
|
||||
import ast, types, options, msgs, lineinfos
|
||||
from std/os import getAppFilename
|
||||
import libffi/libffi
|
||||
|
||||
import std/[tables, dynlib]
|
||||
|
||||
when defined(windows):
|
||||
const libcDll = "msvcrt.dll"
|
||||
@@ -37,9 +39,10 @@ else:
|
||||
var gExeHandle = loadLib()
|
||||
|
||||
proc getDll(conf: ConfigRef, cache: var TDllCache; dll: string; info: TLineInfo): pointer =
|
||||
result = nil
|
||||
if dll in cache:
|
||||
return cache[dll]
|
||||
var libs: seq[string]
|
||||
var libs: seq[string] = @[]
|
||||
libCandidates(dll, libs)
|
||||
for c in libs:
|
||||
result = loadLib(c)
|
||||
@@ -61,23 +64,23 @@ proc importcSymbol*(conf: ConfigRef, sym: PSym): PNode =
|
||||
let lib = sym.annex
|
||||
if lib != nil and lib.path.kind notin {nkStrLit..nkTripleStrLit}:
|
||||
globalError(conf, sym.info, "dynlib needs to be a string lit")
|
||||
var theAddr: pointer
|
||||
var theAddr: pointer = nil
|
||||
if (lib.isNil or lib.kind == libHeader) and not gExeHandle.isNil:
|
||||
libPathMsg = "current exe: " & getAppFilename() & " nor libc: " & libcDll
|
||||
# first try this exe itself:
|
||||
theAddr = gExeHandle.symAddr(name)
|
||||
theAddr = gExeHandle.symAddr(name.cstring)
|
||||
# then try libc:
|
||||
if theAddr.isNil:
|
||||
let dllhandle = getDll(conf, gDllCache, libcDll, sym.info)
|
||||
theAddr = dllhandle.symAddr(name)
|
||||
theAddr = dllhandle.symAddr(name.cstring)
|
||||
elif not lib.isNil:
|
||||
let dll = if lib.kind == libHeader: libcDll else: lib.path.strVal
|
||||
libPathMsg = dll
|
||||
let dllhandle = getDll(conf, gDllCache, dll, sym.info)
|
||||
theAddr = dllhandle.symAddr(name)
|
||||
theAddr = dllhandle.symAddr(name.cstring)
|
||||
if theAddr.isNil: globalError(conf, sym.info,
|
||||
"cannot import symbol: " & name & " from " & libPathMsg)
|
||||
result.intVal = cast[ByteAddress](theAddr)
|
||||
result.intVal = cast[int](theAddr)
|
||||
|
||||
proc mapType(conf: ConfigRef, t: ast.PType): ptr libffi.Type =
|
||||
if t == nil: return addr libffi.type_void
|
||||
@@ -92,11 +95,11 @@ proc mapType(conf: ConfigRef, t: ast.PType): ptr libffi.Type =
|
||||
else: result = nil
|
||||
of tyFloat, tyFloat64: result = addr libffi.type_double
|
||||
of tyFloat32: result = addr libffi.type_float
|
||||
of tyVar, tyLent, tyPointer, tyPtr, tyRef, tyCString, tySequence, tyString, tyUntyped,
|
||||
of tyVar, tyLent, tyPointer, tyPtr, tyRef, tyCstring, tySequence, tyString, tyUntyped,
|
||||
tyTyped, tyTypeDesc, tyProc, tyArray, tyStatic, tyNil:
|
||||
result = addr libffi.type_pointer
|
||||
of tyDistinct, tyAlias, tySink:
|
||||
result = mapType(conf, t[0])
|
||||
result = mapType(conf, t.skipModifier)
|
||||
else:
|
||||
result = nil
|
||||
# too risky:
|
||||
@@ -108,12 +111,13 @@ proc mapCallConv(conf: ConfigRef, cc: TCallingConvention, info: TLineInfo): TABI
|
||||
of ccStdCall: result = when defined(windows) and defined(x86): STDCALL else: DEFAULT_ABI
|
||||
of ccCDecl: result = DEFAULT_ABI
|
||||
else:
|
||||
result = default(TABI)
|
||||
globalError(conf, info, "cannot map calling convention to FFI")
|
||||
|
||||
template rd(T, p: untyped): untyped = (cast[ptr T](p))[]
|
||||
template wr(T, p, v: untyped): untyped = (cast[ptr T](p))[] = v
|
||||
template rd(typ, p: untyped): untyped = (cast[ptr typ](p))[]
|
||||
template wr(typ, p, v: untyped): untyped = (cast[ptr typ](p))[] = v
|
||||
template `+!`(x, y: untyped): untyped =
|
||||
cast[pointer](cast[ByteAddress](x) + y)
|
||||
cast[pointer](cast[int](x) + y)
|
||||
|
||||
proc packSize(conf: ConfigRef, v: PNode, typ: PType): int =
|
||||
## computes the size of the blob
|
||||
@@ -122,16 +126,18 @@ proc packSize(conf: ConfigRef, v: PNode, typ: PType): int =
|
||||
if v.kind in {nkNilLit, nkPtrLit}:
|
||||
result = sizeof(pointer)
|
||||
else:
|
||||
result = sizeof(pointer) + packSize(conf, v[0], typ.lastSon)
|
||||
result = sizeof(pointer) + packSize(conf, v[0], typ.elementType)
|
||||
of tyDistinct, tyGenericInst, tyAlias, tySink:
|
||||
result = packSize(conf, v, typ[0])
|
||||
result = packSize(conf, v, typ.skipModifier)
|
||||
of tyArray:
|
||||
# consider: ptr array[0..1000_000, int] which is common for interfacing;
|
||||
# we use the real length here instead
|
||||
if v.kind in {nkNilLit, nkPtrLit}:
|
||||
result = sizeof(pointer)
|
||||
elif v.len != 0:
|
||||
result = v.len * packSize(conf, v[0], typ[1])
|
||||
result = v.len * packSize(conf, v[0], typ.elementType)
|
||||
else:
|
||||
result = 0
|
||||
else:
|
||||
result = getSize(conf, typ).int
|
||||
|
||||
@@ -140,6 +146,7 @@ proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer)
|
||||
proc getField(conf: ConfigRef, n: PNode; position: int): PSym =
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
result = nil
|
||||
for i in 0..<n.len:
|
||||
result = getField(conf, n[i], position)
|
||||
if result != nil: return
|
||||
@@ -154,7 +161,8 @@ proc getField(conf: ConfigRef, n: PNode; position: int): PSym =
|
||||
else: internalError(conf, n.info, "getField(record case branch)")
|
||||
of nkSym:
|
||||
if n.sym.position == position: result = n.sym
|
||||
else: discard
|
||||
else: result = nil
|
||||
else: result = nil
|
||||
|
||||
proc packObject(conf: ConfigRef, x: PNode, typ: PType, res: pointer) =
|
||||
internalAssert conf, x.kind in {nkObjConstr, nkPar, nkTupleConstr}
|
||||
@@ -177,8 +185,8 @@ const maxPackDepth = 20
|
||||
var packRecCheck = 0
|
||||
|
||||
proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer) =
|
||||
template awr(T, v: untyped): untyped =
|
||||
wr(T, res, v)
|
||||
template awr(typ, v: untyped): untyped =
|
||||
wr(typ, res, v)
|
||||
|
||||
case typ.kind
|
||||
of tyBool: awr(bool, v.intVal != 0)
|
||||
@@ -205,7 +213,7 @@ proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer) =
|
||||
of tyFloat32: awr(float32, v.floatVal)
|
||||
of tyFloat64: awr(float64, v.floatVal)
|
||||
|
||||
of tyPointer, tyProc, tyCString, tyString:
|
||||
of tyPointer, tyProc, tyCstring, tyString:
|
||||
if v.kind == nkNilLit:
|
||||
# nothing to do since the memory is 0 initialized anyway
|
||||
discard
|
||||
@@ -226,19 +234,19 @@ proc pack(conf: ConfigRef, v: PNode, typ: PType, res: pointer) =
|
||||
packRecCheck = 0
|
||||
globalError(conf, v.info, "cannot map value to FFI " & typeToString(v.typ))
|
||||
inc packRecCheck
|
||||
pack(conf, v[0], typ.lastSon, res +! sizeof(pointer))
|
||||
pack(conf, v[0], typ.elementType, res +! sizeof(pointer))
|
||||
dec packRecCheck
|
||||
awr(pointer, res +! sizeof(pointer))
|
||||
of tyArray:
|
||||
let baseSize = getSize(conf, typ[1])
|
||||
let baseSize = getSize(conf, typ.elementType)
|
||||
for i in 0..<v.len:
|
||||
pack(conf, v[i], typ[1], res +! i * baseSize)
|
||||
pack(conf, v[i], typ.elementType, res +! i * baseSize)
|
||||
of tyObject, tyTuple:
|
||||
packObject(conf, v, typ, res)
|
||||
of tyNil:
|
||||
discard
|
||||
of tyDistinct, tyGenericInst, tyAlias, tySink:
|
||||
pack(conf, v, typ[0], res)
|
||||
pack(conf, v, typ.skipModifier, res)
|
||||
else:
|
||||
globalError(conf, v.info, "cannot map value to FFI " & typeToString(v.typ))
|
||||
|
||||
@@ -296,9 +304,9 @@ proc unpackArray(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
result = n
|
||||
if result.kind != nkBracket:
|
||||
globalError(conf, n.info, "cannot map value from FFI")
|
||||
let baseSize = getSize(conf, typ[1])
|
||||
let baseSize = getSize(conf, typ.elementType)
|
||||
for i in 0..<result.len:
|
||||
result[i] = unpack(conf, x +! i * baseSize, typ[1], result[i])
|
||||
result[i] = unpack(conf, x +! i * baseSize, typ.elementType, result[i])
|
||||
|
||||
proc canonNodeKind(k: TNodeKind): TNodeKind =
|
||||
case k
|
||||
@@ -356,6 +364,7 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
of 4: awi(nkIntLit, rd(int32, x).BiggestInt)
|
||||
of 8: awi(nkIntLit, rd(int64, x).BiggestInt)
|
||||
else:
|
||||
result = nil
|
||||
globalError(conf, n.info, "cannot map value from FFI (tyEnum, tySet)")
|
||||
of tyFloat: awf(nkFloatLit, rd(float, x))
|
||||
of tyFloat32: awf(nkFloat32Lit, rd(float32, x))
|
||||
@@ -369,24 +378,25 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
# in their unboxed representation so nothing it to be unpacked:
|
||||
result = n
|
||||
else:
|
||||
awi(nkPtrLit, cast[ByteAddress](p))
|
||||
awi(nkPtrLit, cast[int](p))
|
||||
of tyPtr, tyRef, tyVar, tyLent:
|
||||
let p = rd(pointer, x)
|
||||
if p.isNil:
|
||||
setNil()
|
||||
elif n == nil or n.kind == nkPtrLit:
|
||||
awi(nkPtrLit, cast[ByteAddress](p))
|
||||
awi(nkPtrLit, cast[int](p))
|
||||
elif n != nil and n.len == 1:
|
||||
internalAssert(conf, n.kind == nkRefTy)
|
||||
n[0] = unpack(conf, p, typ.lastSon, n[0])
|
||||
n[0] = unpack(conf, p, typ.elementType, n[0])
|
||||
result = n
|
||||
else:
|
||||
result = nil
|
||||
globalError(conf, n.info, "cannot map value from FFI " & typeToString(typ))
|
||||
of tyObject, tyTuple:
|
||||
result = unpackObject(conf, x, typ, n)
|
||||
of tyArray:
|
||||
result = unpackArray(conf, x, typ, n)
|
||||
of tyCString, tyString:
|
||||
of tyCstring, tyString:
|
||||
let p = rd(cstring, x)
|
||||
if p.isNil:
|
||||
setNil()
|
||||
@@ -395,14 +405,15 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
of tyNil:
|
||||
setNil()
|
||||
of tyDistinct, tyGenericInst, tyAlias, tySink:
|
||||
result = unpack(conf, x, typ.lastSon, n)
|
||||
result = unpack(conf, x, typ.skipModifier, n)
|
||||
else:
|
||||
# XXX what to do with 'array' here?
|
||||
result = nil
|
||||
globalError(conf, n.info, "cannot map value from FFI " & typeToString(typ))
|
||||
|
||||
proc fficast*(conf: ConfigRef, x: PNode, destTyp: PType): PNode =
|
||||
if x.kind == nkPtrLit and x.typ.kind in {tyPtr, tyRef, tyVar, tyLent, tyPointer,
|
||||
tyProc, tyCString, tyString,
|
||||
tyProc, tyCstring, tyString,
|
||||
tySequence}:
|
||||
result = newNodeIT(x.kind, x.info, destTyp)
|
||||
result.intVal = x.intVal
|
||||
@@ -423,8 +434,8 @@ proc fficast*(conf: ConfigRef, x: PNode, destTyp: PType): PNode =
|
||||
proc callForeignFunction*(conf: ConfigRef, call: PNode): PNode =
|
||||
internalAssert conf, call[0].kind == nkPtrLit
|
||||
|
||||
var cif: TCif
|
||||
var sig: ParamList
|
||||
var cif: TCif = default(TCif)
|
||||
var sig: ParamList = default(ParamList)
|
||||
# use the arguments' types for varargs support:
|
||||
for i in 1..<call.len:
|
||||
sig[i-1] = mapType(conf, call[i].typ)
|
||||
@@ -433,24 +444,24 @@ proc callForeignFunction*(conf: ConfigRef, call: PNode): PNode =
|
||||
|
||||
let typ = call[0].typ
|
||||
if prep_cif(cif, mapCallConv(conf, typ.callConv, call.info), cuint(call.len-1),
|
||||
mapType(conf, typ[0]), sig) != OK:
|
||||
mapType(conf, typ.returnType), sig) != OK:
|
||||
globalError(conf, call.info, "error in FFI call")
|
||||
|
||||
var args: ArgList
|
||||
var args: ArgList = default(ArgList)
|
||||
let fn = cast[pointer](call[0].intVal)
|
||||
for i in 1..<call.len:
|
||||
var t = call[i].typ
|
||||
args[i-1] = alloc0(packSize(conf, call[i], t))
|
||||
pack(conf, call[i], t, args[i-1])
|
||||
let retVal = if isEmptyType(typ[0]): pointer(nil)
|
||||
else: alloc(getSize(conf, typ[0]).int)
|
||||
let retVal = if isEmptyType(typ.returnType): pointer(nil)
|
||||
else: alloc(getSize(conf, typ.returnType).int)
|
||||
|
||||
libffi.call(cif, fn, retVal, args)
|
||||
|
||||
if retVal.isNil:
|
||||
result = newNode(nkEmpty)
|
||||
else:
|
||||
result = unpack(conf, retVal, typ[0], nil)
|
||||
result = unpack(conf, retVal, typ.returnType, nil)
|
||||
result.info = call.info
|
||||
|
||||
if retVal != nil: dealloc retVal
|
||||
@@ -463,8 +474,8 @@ proc callForeignFunction*(conf: ConfigRef, fn: PNode, fntyp: PType,
|
||||
info: TLineInfo): PNode =
|
||||
internalAssert conf, fn.kind == nkPtrLit
|
||||
|
||||
var cif: TCif
|
||||
var sig: ParamList
|
||||
var cif: TCif = default(TCif)
|
||||
var sig: ParamList = default(ParamList)
|
||||
for i in 0..len-1:
|
||||
var aTyp = args[i+start].typ
|
||||
if aTyp.isNil:
|
||||
@@ -478,7 +489,7 @@ proc callForeignFunction*(conf: ConfigRef, fn: PNode, fntyp: PType,
|
||||
mapType(conf, fntyp[0]), sig) != OK:
|
||||
globalError(conf, info, "error in FFI call")
|
||||
|
||||
var cargs: ArgList
|
||||
var cargs: ArgList = default(ArgList)
|
||||
let fn = cast[pointer](fn.intVal)
|
||||
for i in 0..len-1:
|
||||
let t = args[i+start].typ
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
|
||||
## Template evaluation engine. Now hygienic.
|
||||
|
||||
import
|
||||
strutils, options, ast, astalgo, msgs, renderer, lineinfos, idents
|
||||
import options, ast, astalgo, msgs, renderer, lineinfos, idents, trees
|
||||
import std/strutils
|
||||
|
||||
type
|
||||
TemplCtx = object
|
||||
owner, genSymOwner: PSym
|
||||
instLines: bool # use the instantiation lines numbers
|
||||
isDeclarative: bool
|
||||
mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
|
||||
# new symbol
|
||||
mapping: SymMapping # every gensym'ed symbol needs to be mapped to some
|
||||
# new symbol
|
||||
config: ConfigRef
|
||||
ic: IdentCache
|
||||
instID: int
|
||||
@@ -26,7 +26,7 @@ type
|
||||
|
||||
proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
|
||||
result = copyNode(a)
|
||||
if ctx.instLines: result.info = b.info
|
||||
if ctx.instLines: setInfoRecursive(result, b.info)
|
||||
|
||||
proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
|
||||
template handleParam(param) =
|
||||
@@ -44,12 +44,12 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
|
||||
handleParam actual[s.position]
|
||||
elif (s.owner != nil) and (s.kind == skGenericParam or
|
||||
s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam):
|
||||
handleParam actual[s.owner.typ.len + s.position - 1]
|
||||
handleParam actual[s.owner.typ.signatureLen + s.position - 1]
|
||||
else:
|
||||
internalAssert c.config, sfGenSym in s.flags or s.kind == skType
|
||||
var x = PSym(idTableGet(c.mapping, s))
|
||||
var x = idTableGet(c.mapping, s)
|
||||
if x == nil:
|
||||
x = copySym(s, nextSymId(c.idgen))
|
||||
x = copySym(s, c.idgen)
|
||||
# sem'check needs to set the owner properly later, see bug #9476
|
||||
x.owner = nil # c.genSymOwner
|
||||
#if x.kind == skParam and x.owner.kind == skModule:
|
||||
@@ -83,10 +83,14 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
|
||||
not c.isDeclarative:
|
||||
c.isDeclarative = true
|
||||
isDeclarative = true
|
||||
var res = copyNode(c, templ, actual)
|
||||
for i in 0..<templ.len:
|
||||
evalTemplateAux(templ[i], actual, c, res)
|
||||
result.add res
|
||||
if (not c.isDeclarative) and templ.kind in nkCallKinds and isRunnableExamples(templ[0]):
|
||||
# fixes bug #16993, bug #18054
|
||||
discard
|
||||
else:
|
||||
var res = copyNode(c, templ, actual)
|
||||
for i in 0..<templ.len:
|
||||
evalTemplateAux(templ[i], actual, c, res)
|
||||
result.add res
|
||||
if isDeclarative: c.isDeclarative = false
|
||||
|
||||
const
|
||||
@@ -112,7 +116,7 @@ proc evalTemplateArgs(n: PNode, s: PSym; conf: ConfigRef; fromHlo: bool): PNode
|
||||
# now that we have working untyped parameters.
|
||||
genericParams = if fromHlo: 0
|
||||
else: s.ast[genericParamsPos].len
|
||||
expectedRegularParams = s.typ.len-1
|
||||
expectedRegularParams = s.typ.paramsLen
|
||||
givenRegularParams = totalParams - genericParams
|
||||
if givenRegularParams < 0: givenRegularParams = 0
|
||||
|
||||
@@ -178,14 +182,14 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
|
||||
|
||||
# replace each param by the corresponding node:
|
||||
var args = evalTemplateArgs(n, tmpl, conf, fromHlo)
|
||||
var ctx: TemplCtx
|
||||
ctx.owner = tmpl
|
||||
ctx.genSymOwner = genSymOwner
|
||||
ctx.config = conf
|
||||
ctx.ic = ic
|
||||
initIdTable(ctx.mapping)
|
||||
ctx.instID = instID[]
|
||||
ctx.idgen = idgen
|
||||
var ctx = TemplCtx(owner: tmpl,
|
||||
genSymOwner: genSymOwner,
|
||||
config: conf,
|
||||
ic: ic,
|
||||
mapping: initSymMapping(),
|
||||
instID: instID[],
|
||||
idgen: idgen
|
||||
)
|
||||
|
||||
let body = tmpl.ast[bodyPos]
|
||||
#echo "instantion of ", renderTree(body, {renderIds})
|
||||
@@ -200,7 +204,7 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
|
||||
result = copyNode(body)
|
||||
ctx.instLines = sfCallsite in tmpl.flags
|
||||
if ctx.instLines:
|
||||
result.info = n.info
|
||||
setInfoRecursive(result, n.info)
|
||||
for i in 0..<body.safeLen:
|
||||
evalTemplateAux(body[i], args, ctx, result)
|
||||
result.flags.incl nfFromTemplate
|
||||
|
||||
131
compiler/expanddefaults.nim
Normal file
131
compiler/expanddefaults.nim
Normal file
@@ -0,0 +1,131 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2023 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
import lineinfos, ast, types
|
||||
|
||||
proc caseObjDefaultBranch*(obj: PNode; branch: Int128): int =
|
||||
result = 0
|
||||
for i in 1 ..< obj.len:
|
||||
for j in 0 .. obj[i].len - 2:
|
||||
if obj[i][j].kind == nkRange:
|
||||
let x = getOrdValue(obj[i][j][0])
|
||||
let y = getOrdValue(obj[i][j][1])
|
||||
if branch >= x and branch <= y:
|
||||
return i
|
||||
elif getOrdValue(obj[i][j]) == branch:
|
||||
return i
|
||||
if obj[i].len == 1:
|
||||
# else branch
|
||||
return i
|
||||
return 1
|
||||
|
||||
template newZero(t: PType; info: TLineInfo; k = nkIntLit): PNode = newNodeIT(k, info, t)
|
||||
|
||||
proc expandDefault*(t: PType; info: TLineInfo): PNode
|
||||
|
||||
proc expandField(s: PSym; info: TLineInfo): PNode =
|
||||
result = newNodeIT(nkExprColonExpr, info, s.typ)
|
||||
result.add newSymNode(s)
|
||||
result.add expandDefault(s.typ, info)
|
||||
|
||||
proc expandDefaultN(n: PNode; info: TLineInfo; res: PNode) =
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
for i in 0..<n.len:
|
||||
expandDefaultN(n[i], info, res)
|
||||
of nkRecCase:
|
||||
res.add expandField(n[0].sym, info)
|
||||
var branch = Zero
|
||||
let constOrNil = n[0].sym.astdef
|
||||
if constOrNil != nil:
|
||||
branch = getOrdValue(constOrNil)
|
||||
|
||||
let selectedBranch = caseObjDefaultBranch(n, branch)
|
||||
let b = lastSon(n[selectedBranch])
|
||||
expandDefaultN b, info, res
|
||||
of nkSym:
|
||||
res.add expandField(n.sym, info)
|
||||
else:
|
||||
discard
|
||||
|
||||
proc expandDefaultObj(t: PType; info: TLineInfo; res: PNode) =
|
||||
if t.baseClass != nil:
|
||||
expandDefaultObj(t.baseClass, info, res)
|
||||
expandDefaultN(t.n, info, res)
|
||||
|
||||
proc expandDefault(t: PType; info: TLineInfo): PNode =
|
||||
case t.kind
|
||||
of tyInt: result = newZero(t, info, nkIntLit)
|
||||
of tyInt8: result = newZero(t, info, nkInt8Lit)
|
||||
of tyInt16: result = newZero(t, info, nkInt16Lit)
|
||||
of tyInt32: result = newZero(t, info, nkInt32Lit)
|
||||
of tyInt64: result = newZero(t, info, nkInt64Lit)
|
||||
of tyUInt: result = newZero(t, info, nkUIntLit)
|
||||
of tyUInt8: result = newZero(t, info, nkUInt8Lit)
|
||||
of tyUInt16: result = newZero(t, info, nkUInt16Lit)
|
||||
of tyUInt32: result = newZero(t, info, nkUInt32Lit)
|
||||
of tyUInt64: result = newZero(t, info, nkUInt64Lit)
|
||||
of tyFloat: result = newZero(t, info, nkFloatLit)
|
||||
of tyFloat32: result = newZero(t, info, nkFloat32Lit)
|
||||
of tyFloat64: result = newZero(t, info, nkFloat64Lit)
|
||||
of tyFloat128: result = newZero(t, info, nkFloat64Lit)
|
||||
of tyChar: result = newZero(t, info, nkCharLit)
|
||||
of tyBool: result = newZero(t, info, nkIntLit)
|
||||
of tyEnum:
|
||||
# Could use low(T) here to finally fix old language quirks
|
||||
result = newZero(t, info, nkIntLit)
|
||||
of tyRange:
|
||||
# Could use low(T) here to finally fix old language quirks
|
||||
result = expandDefault(skipModifier t, info)
|
||||
of tyVoid: result = newZero(t, info, nkEmpty)
|
||||
of tySink, tyGenericInst, tyDistinct, tyAlias, tyOwned:
|
||||
result = expandDefault(t.skipModifier, info)
|
||||
of tyOrdinal, tyGenericBody, tyGenericParam, tyInferred, tyStatic:
|
||||
if t.hasElementType:
|
||||
result = expandDefault(t.skipModifier, info)
|
||||
else:
|
||||
result = newZero(t, info, nkEmpty)
|
||||
of tyFromExpr:
|
||||
if t.n != nil and t.n.typ != nil:
|
||||
result = expandDefault(t.n.typ, info)
|
||||
else:
|
||||
result = newZero(t, info, nkEmpty)
|
||||
of tyArray:
|
||||
result = newZero(t, info, nkBracket)
|
||||
let n = toInt64(lengthOrd(nil, t))
|
||||
for i in 0..<n:
|
||||
result.add expandDefault(t.elementType, info)
|
||||
of tyPtr, tyRef, tyProc, tyPointer, tyCstring:
|
||||
result = newZero(t, info, nkNilLit)
|
||||
of tyVar, tyLent:
|
||||
let e = t.elementType
|
||||
if e.skipTypes(abstractInst).kind in {tyOpenArray, tyVarargs}:
|
||||
# skip the modifier, `var openArray` is a (ptr, len) pair too:
|
||||
result = expandDefault(e, info)
|
||||
else:
|
||||
result = newZero(e, info, nkNilLit)
|
||||
of tySet:
|
||||
result = newZero(t, info, nkCurly)
|
||||
of tyObject:
|
||||
result = newNodeIT(nkObjConstr, info, t)
|
||||
result.add newNodeIT(nkType, info, t)
|
||||
expandDefaultObj(t, info, result)
|
||||
of tyTuple:
|
||||
result = newZero(t, info, nkTupleConstr)
|
||||
for it in t.kids:
|
||||
result.add expandDefault(it, info)
|
||||
of tyVarargs, tyOpenArray, tySequence, tyUncheckedArray:
|
||||
result = newZero(t, info, nkBracket)
|
||||
of tyString:
|
||||
result = newZero(t, info, nkStrLit)
|
||||
of tyNone, tyEmpty, tyUntyped, tyTyped, tyTypeDesc,
|
||||
tyNil, tyGenericInvocation, tyProxy, tyBuiltInTypeClass,
|
||||
tyUserTypeClass, tyUserTypeClassInst, tyCompositeTypeClass,
|
||||
tyAnd, tyOr, tyNot, tyAnything, tyConcept, tyIterable, tyForward:
|
||||
result = newZero(t, info, nkEmpty) # bug indicator
|
||||
@@ -12,9 +12,16 @@
|
||||
# from a lineinfos file, to provide generalized procedures to compile
|
||||
# nim files.
|
||||
|
||||
import ropes, platform, condsyms, options, msgs, lineinfos, pathutils
|
||||
import ropes, platform, condsyms, options, msgs, lineinfos, pathutils, modulepaths
|
||||
|
||||
import std/[os, strutils, osproc, sha1, streams, sequtils, times, strtabs, json]
|
||||
import std/[os, osproc, streams, sequtils, times, strtabs, json, jsonutils, sugar, parseutils]
|
||||
|
||||
import std / strutils except addf
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions]
|
||||
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
|
||||
type
|
||||
TInfoCCProp* = enum # properties of the C compiler:
|
||||
@@ -26,6 +33,7 @@ type
|
||||
hasGnuAsm, # CC's asm uses the absurd GNU assembler syntax
|
||||
hasDeclspec, # CC has __declspec(X)
|
||||
hasAttribute, # CC has __attribute__((X))
|
||||
hasBuiltinUnreachable # CC has __builtin_unreachable
|
||||
TInfoCCProps* = set[TInfoCCProp]
|
||||
TInfoCC* = tuple[
|
||||
name: string, # the short name of the compiler
|
||||
@@ -83,12 +91,12 @@ compiler gcc:
|
||||
linkLibCmd: " -l$1",
|
||||
debug: "",
|
||||
pic: "-fPIC",
|
||||
asmStmtFrmt: "asm($1);$n",
|
||||
asmStmtFrmt: "__asm__($1);$n",
|
||||
structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
|
||||
produceAsm: gnuAsmListing,
|
||||
cppXsupport: "-std=gnu++14 -funsigned-char",
|
||||
cppXsupport: "-std=gnu++17 -funsigned-char",
|
||||
props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
|
||||
hasAttribute})
|
||||
hasAttribute, hasBuiltinUnreachable})
|
||||
|
||||
# GNU C and C++ Compiler
|
||||
compiler nintendoSwitchGCC:
|
||||
@@ -113,9 +121,9 @@ compiler nintendoSwitchGCC:
|
||||
asmStmtFrmt: "asm($1);$n",
|
||||
structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
|
||||
produceAsm: gnuAsmListing,
|
||||
cppXsupport: "-std=gnu++14 -funsigned-char",
|
||||
cppXsupport: "-std=gnu++17 -funsigned-char",
|
||||
props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
|
||||
hasAttribute})
|
||||
hasAttribute, hasBuiltinUnreachable})
|
||||
|
||||
# LLVM Frontend for GCC/G++
|
||||
compiler llvmGcc:
|
||||
@@ -150,7 +158,7 @@ compiler vcc:
|
||||
compileTmpl: "/c$vccplatform $options $include /nologo /Fo$objfile $file",
|
||||
buildGui: " /SUBSYSTEM:WINDOWS user32.lib ",
|
||||
buildDll: " /LD",
|
||||
buildLib: "lib /OUT:$libfile $objfiles",
|
||||
buildLib: "vccexe --command:lib$vccplatform /nologo /OUT:$libfile $objfiles",
|
||||
linkerExe: "cl",
|
||||
linkTmpl: "$builddll$vccplatform /Fe$exefile $objfiles $buildgui /nologo $options",
|
||||
includeCmd: " /I",
|
||||
@@ -252,7 +260,7 @@ compiler envcc:
|
||||
buildGui: "",
|
||||
buildDll: " -shared ",
|
||||
buildLib: "", # XXX: not supported yet
|
||||
linkerExe: "cc",
|
||||
linkerExe: "",
|
||||
linkTmpl: "-o $exefile $buildgui $builddll $objfiles $options",
|
||||
includeCmd: " -I",
|
||||
linkDirCmd: "", # XXX: not supported yet
|
||||
@@ -281,6 +289,11 @@ const
|
||||
|
||||
hExt* = ".h"
|
||||
|
||||
template writePrettyCmdsStderr(cmd) =
|
||||
if cmd.len > 0:
|
||||
flushDot(conf)
|
||||
stderr.writeLine(cmd)
|
||||
|
||||
proc nameToCC*(name: string): TSystemCC =
|
||||
## Returns the kind of compiler referred to by `name`, or ccNone
|
||||
## if the name doesn't refer to any known compiler.
|
||||
@@ -306,7 +319,7 @@ proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
|
||||
var fullSuffix = suffix
|
||||
case conf.backend
|
||||
of backendCpp, backendJs, backendObjc: fullSuffix = "." & $conf.backend & suffix
|
||||
of backendC: discard
|
||||
of backendC, backendNir: discard
|
||||
of backendInvalid:
|
||||
# during parsing of cfg files; we don't know the backend yet, no point in
|
||||
# guessing wrong thing
|
||||
@@ -318,7 +331,9 @@ proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
|
||||
platform.OS[conf.target.targetOS].name & '.' &
|
||||
CC[c].name & fullSuffix
|
||||
result = getConfigVar(conf, fullCCname)
|
||||
if result.len == 0:
|
||||
if existsConfigVar(conf, fullCCname):
|
||||
result = getConfigVar(conf, fullCCname)
|
||||
else:
|
||||
# not overridden for this cross compilation setting?
|
||||
result = getConfigVar(conf, CC[c].name & fullSuffix)
|
||||
else:
|
||||
@@ -362,6 +377,7 @@ proc initVars*(conf: ConfigRef) =
|
||||
|
||||
proc completeCfilePath*(conf: ConfigRef; cfile: AbsoluteFile,
|
||||
createSubDir: bool = true): AbsoluteFile =
|
||||
## Generate the absolute file path to the generated modules.
|
||||
result = completeGeneratedFilePath(conf, cfile, createSubDir)
|
||||
|
||||
proc toObjFile*(conf: ConfigRef; filename: AbsoluteFile): AbsoluteFile =
|
||||
@@ -372,7 +388,7 @@ proc addFileToCompile*(conf: ConfigRef; cf: Cfile) =
|
||||
conf.toCompile.add(cf)
|
||||
|
||||
proc addLocalCompileOption*(conf: ConfigRef; option: string; nimfile: AbsoluteFile) =
|
||||
let key = completeCfilePath(conf, withPackageName(conf, nimfile)).string
|
||||
let key = completeCfilePath(conf, mangleModuleName(conf, nimfile).AbsoluteFile).string
|
||||
var value = conf.cfileSpecificOptions.getOrDefault(key)
|
||||
if strutils.find(value, option, 0) < 0:
|
||||
addOpt(value, option)
|
||||
@@ -425,7 +441,13 @@ proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} =
|
||||
# really: Cross compilation from Linux to Linux for example is entirely
|
||||
# reasonable.
|
||||
# `optGenMapping` is included here for niminst.
|
||||
result = conf.globalOptions * {optGenScript, optGenMapping} != {}
|
||||
# We use absolute paths for vcc / cl, see issue #19883.
|
||||
let options =
|
||||
if conf.cCompiler == ccVcc:
|
||||
{optGenMapping}
|
||||
else:
|
||||
{optGenScript, optGenMapping}
|
||||
result = conf.globalOptions * options != {}
|
||||
|
||||
proc cFileSpecificOptions(conf: ConfigRef; nimname, fullNimFile: string): string =
|
||||
result = conf.compileOptions
|
||||
@@ -465,6 +487,10 @@ proc vccplatform(conf: ConfigRef): string =
|
||||
of cpuArm: " --platform:arm"
|
||||
of cpuAmd64: " --platform:amd64"
|
||||
else: ""
|
||||
else:
|
||||
result = ""
|
||||
else:
|
||||
result = ""
|
||||
|
||||
proc getLinkOptions(conf: ConfigRef): string =
|
||||
result = conf.linkOptions & " " & conf.linkOptionsCmd & " "
|
||||
@@ -478,7 +504,10 @@ proc needsExeExt(conf: ConfigRef): bool {.inline.} =
|
||||
(conf.target.hostOS == osWindows)
|
||||
|
||||
proc useCpp(conf: ConfigRef; cfile: AbsoluteFile): bool =
|
||||
conf.backend == backendCpp and not cfile.string.endsWith(".c")
|
||||
# List of possible file extensions taken from gcc
|
||||
for ext in [".C", ".cc", ".cpp", ".CPP", ".c++", ".cp", ".cxx"]:
|
||||
if cfile.string.endsWith(ext): return true
|
||||
false
|
||||
|
||||
proc envFlags(conf: ConfigRef): string =
|
||||
result = if conf.backend == backendCpp:
|
||||
@@ -486,14 +515,14 @@ proc envFlags(conf: ConfigRef): string =
|
||||
else:
|
||||
getEnv("CFLAGS")
|
||||
|
||||
proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: AbsoluteFile): string =
|
||||
proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; isCpp: bool): string =
|
||||
if compiler == ccEnv:
|
||||
result = if useCpp(conf, cfile):
|
||||
result = if isCpp:
|
||||
getEnv("CXX")
|
||||
else:
|
||||
getEnv("CC")
|
||||
else:
|
||||
result = if useCpp(conf, cfile):
|
||||
result = if isCpp:
|
||||
CC[compiler].cppCompiler
|
||||
else:
|
||||
CC[compiler].compilerExe
|
||||
@@ -507,47 +536,36 @@ proc ccHasSaneOverflow*(conf: ConfigRef): bool =
|
||||
result = false # assume an old or crappy GCC
|
||||
var exe = getConfigVar(conf, conf.cCompiler, ".exe")
|
||||
if exe.len == 0: exe = CC[conf.cCompiler].compilerExe
|
||||
let (s, exitCode) = try: execCmdEx(exe & " --version") except: ("", 1)
|
||||
# NOTE: should we need the full version, use -dumpfullversion
|
||||
let (s, exitCode) = try: execCmdEx(exe & " -dumpversion") except IOError, OSError, ValueError: ("", 1)
|
||||
if exitCode == 0:
|
||||
var i = 0
|
||||
var j = 0
|
||||
# the version is the last part of the first line:
|
||||
while i < s.len and s[i] != '\n':
|
||||
if s[i] in {' ', '\t'}: j = i+1
|
||||
inc i
|
||||
if j > 0:
|
||||
var major = 0
|
||||
while j < s.len and s[j] in {'0'..'9'}:
|
||||
major = major * 10 + (ord(s[j]) - ord('0'))
|
||||
inc j
|
||||
if i < s.len and s[j] == '.': inc j
|
||||
while j < s.len and s[j] in {'0'..'9'}:
|
||||
inc j
|
||||
if j+1 < s.len and s[j] == '.' and s[j+1] in {'0'..'9'}:
|
||||
# we found a third version number, chances are high
|
||||
# we really parsed the version:
|
||||
result = major >= 5
|
||||
var major: int = 0
|
||||
discard parseInt(s, major)
|
||||
result = major >= 5
|
||||
else:
|
||||
result = conf.cCompiler == ccCLang
|
||||
|
||||
proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string =
|
||||
result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe
|
||||
elif optMixedMode in conf.globalOptions and conf.backend != backendCpp: CC[compiler].cppCompiler
|
||||
else: getCompilerExe(conf, compiler, AbsoluteFile"")
|
||||
else: getCompilerExe(conf, compiler, optMixedMode in conf.globalOptions or conf.backend == backendCpp)
|
||||
|
||||
proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
|
||||
isMainFile = false; produceOutput = false): string =
|
||||
let c = conf.cCompiler
|
||||
let
|
||||
c = conf.cCompiler
|
||||
isCpp = useCpp(conf, cfile.cname)
|
||||
# We produce files like module.nim.cpp, so the absolute Nim filename is not
|
||||
# cfile.name but `cfile.cname.changeFileExt("")`:
|
||||
var options = cFileSpecificOptions(conf, cfile.nimname, cfile.cname.changeFileExt("").string)
|
||||
if useCpp(conf, cfile.cname):
|
||||
if isCpp:
|
||||
# needs to be prepended so that --passc:-std=c++17 can override default.
|
||||
# we could avoid allocation by making cFileSpecificOptions inplace
|
||||
options = CC[c].cppXsupport & ' ' & options
|
||||
# If any C++ file was compiled, we need to use C++ driver for linking as well
|
||||
incl conf.globalOptions, optMixedMode
|
||||
|
||||
var exe = getConfigVar(conf, c, ".exe")
|
||||
if exe.len == 0: exe = getCompilerExe(conf, c, cfile.cname)
|
||||
if exe.len == 0: exe = getCompilerExe(conf, c, isCpp)
|
||||
|
||||
if needsExeExt(conf): exe = addFileExt(exe, "exe")
|
||||
if (optGenDynLib in conf.globalOptions or (conf.hcrOn and not isMainFile)) and
|
||||
@@ -567,7 +585,7 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
|
||||
|
||||
compilePattern = joinPath(conf.cCompilerPath, exe)
|
||||
else:
|
||||
compilePattern = getCompilerExe(conf, c, cfile.cname)
|
||||
compilePattern = exe
|
||||
|
||||
includeCmd.add(join([CC[c].includeCmd, quoteShell(conf.projectPath.string)]))
|
||||
|
||||
@@ -609,7 +627,7 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
|
||||
"for the selected C compiler: " & CC[conf.cCompiler].name)
|
||||
|
||||
result.add(' ')
|
||||
result.addf(CC[c].compileTmpl, [
|
||||
strutils.addf(result, CC[c].compileTmpl, [
|
||||
"dfile", dfile,
|
||||
"file", cfsh, "objfile", quoteShell(objfile),
|
||||
"options", options, "include", includeCmd,
|
||||
@@ -629,9 +647,9 @@ proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash =
|
||||
proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
|
||||
if conf.backend == backendJs: return false # pre-existing behavior, but not sure it's good
|
||||
|
||||
let hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1")
|
||||
let hashFile = toGeneratedFile(conf, conf.mangleModuleName(cfile.cname).AbsoluteFile, "sha1")
|
||||
let currentHash = footprint(conf, cfile)
|
||||
var f: File
|
||||
var f: File = default(File)
|
||||
if open(f, hashFile.string, fmRead):
|
||||
let oldHash = parseSecureHash(f.readLine())
|
||||
close(f)
|
||||
@@ -644,8 +662,10 @@ proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
|
||||
close(f)
|
||||
|
||||
proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) =
|
||||
# we want to generate the hash file unconditionally
|
||||
let extFileChanged = externalFileChanged(conf, c)
|
||||
if optForceFullMake notin conf.globalOptions and fileExists(c.obj) and
|
||||
not externalFileChanged(conf, c):
|
||||
not extFileChanged:
|
||||
c.flags.incl CfileFlag.Cached
|
||||
else:
|
||||
# make sure Nim keeps recompiling the external file on reruns
|
||||
@@ -660,11 +680,13 @@ proc addExternalFileToCompile*(conf: ConfigRef; filename: AbsoluteFile) =
|
||||
addExternalFileToCompile(conf, c)
|
||||
|
||||
proc getLinkCmd(conf: ConfigRef; output: AbsoluteFile,
|
||||
objfiles: string, isDllBuild: bool): string =
|
||||
objfiles: string, isDllBuild: bool, removeStaticFile: bool): string =
|
||||
if optGenStaticLib in conf.globalOptions:
|
||||
removeFile output # fixes: bug #16947
|
||||
if removeStaticFile:
|
||||
removeFile output # fixes: bug #16947
|
||||
result = CC[conf.cCompiler].buildLib % ["libfile", quoteShell(output),
|
||||
"objfiles", objfiles]
|
||||
"objfiles", objfiles,
|
||||
"vccplatform", vccplatform(conf)]
|
||||
else:
|
||||
var linkerExe = getConfigVar(conf, conf.cCompiler, ".linkerexe")
|
||||
if linkerExe.len == 0: linkerExe = getLinkerExe(conf, conf.cCompiler)
|
||||
@@ -697,7 +719,7 @@ proc getLinkCmd(conf: ConfigRef; output: AbsoluteFile,
|
||||
"buildgui", buildgui, "options", linkOptions, "objfiles", objfiles,
|
||||
"exefile", exefile, "nim", getPrefixDir(conf).string, "lib", conf.libpath.string])
|
||||
result.add ' '
|
||||
result.addf(linkTmpl, ["builddll", builddll,
|
||||
strutils.addf(result, linkTmpl, ["builddll", builddll,
|
||||
"mapfile", mapfile,
|
||||
"buildgui", buildgui, "options", linkOptions,
|
||||
"objfiles", objfiles, "exefile", exefile,
|
||||
@@ -745,8 +767,9 @@ proc getLinkCmd(conf: ConfigRef; output: AbsoluteFile,
|
||||
if optCDebug in conf.globalOptions and conf.cCompiler == ccVcc:
|
||||
result.add " /Zi /FS /Od"
|
||||
|
||||
template getLinkCmd(conf: ConfigRef; output: AbsoluteFile, objfiles: string): string =
|
||||
getLinkCmd(conf, output, objfiles, optGenDynLib in conf.globalOptions)
|
||||
template getLinkCmd(conf: ConfigRef; output: AbsoluteFile, objfiles: string,
|
||||
removeStaticFile = false): string =
|
||||
getLinkCmd(conf, output, objfiles, optGenDynLib in conf.globalOptions, removeStaticFile)
|
||||
|
||||
template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body: untyped) =
|
||||
try:
|
||||
@@ -761,6 +784,7 @@ template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body
|
||||
raise
|
||||
|
||||
proc getExtraCmds(conf: ConfigRef; output: AbsoluteFile): seq[string] =
|
||||
result = @[]
|
||||
when defined(macosx):
|
||||
if optCDebug in conf.globalOptions and optGenStaticLib notin conf.globalOptions:
|
||||
# if needed, add an option to skip or override location
|
||||
@@ -816,10 +840,22 @@ proc linkViaResponseFile(conf: ConfigRef; cmd: string) =
|
||||
else:
|
||||
writeFile(linkerArgs, args)
|
||||
try:
|
||||
execLinkCmd(conf, cmd.substr(0, last) & " @" & linkerArgs)
|
||||
when defined(macosx):
|
||||
execLinkCmd(conf, "xargs " & cmd.substr(0, last) & " < " & linkerArgs)
|
||||
else:
|
||||
execLinkCmd(conf, cmd.substr(0, last) & " @" & linkerArgs)
|
||||
finally:
|
||||
removeFile(linkerArgs)
|
||||
|
||||
proc linkViaShellScript(conf: ConfigRef; cmd: string) =
|
||||
let linkerScript = conf.projectName & "_" & "linkerScript.sh"
|
||||
writeFile(linkerScript, cmd)
|
||||
let shell = getEnv("SHELL")
|
||||
try:
|
||||
execLinkCmd(conf, shell & " " & linkerScript)
|
||||
finally:
|
||||
removeFile(linkerScript)
|
||||
|
||||
proc getObjFilePath(conf: ConfigRef, f: Cfile): string =
|
||||
if noAbsolutePaths(conf): f.obj.extractFilename
|
||||
else: f.obj.string
|
||||
@@ -831,28 +867,39 @@ proc hcrLinkTargetName(conf: ConfigRef, objFile: string, isMain = false): Absolu
|
||||
result = conf.getNimcacheDir / RelativeFile(targetName)
|
||||
|
||||
proc displayProgressCC(conf: ConfigRef, path, compileCmd: string): string =
|
||||
result = ""
|
||||
if conf.hasHint(hintCC):
|
||||
if optListCmd in conf.globalOptions or conf.verbosity > 1:
|
||||
result = MsgKindToStr[hintCC] % (demanglePackageName(path.splitFile.name) & ": " & compileCmd)
|
||||
result = MsgKindToStr[hintCC] % (demangleModuleName(path.splitFile.name) & ": " & compileCmd)
|
||||
else:
|
||||
result = MsgKindToStr[hintCC] % demanglePackageName(path.splitFile.name)
|
||||
result = MsgKindToStr[hintCC] % demangleModuleName(path.splitFile.name)
|
||||
|
||||
proc preventLinkCmdMaxCmdLen(conf: ConfigRef, linkCmd: string) =
|
||||
# Prevent linkcmd from exceeding the maximum command line length.
|
||||
# Windows's command line limit is about 8K (8191 characters) so C compilers on
|
||||
# Windows support a feature where the command line can be passed via ``@linkcmd``
|
||||
# to them.
|
||||
const MaxCmdLen = when defined(windows): 8_000 elif defined(macosx): 260_000 else: 32_000
|
||||
if linkCmd.len > MaxCmdLen:
|
||||
when defined(macosx):
|
||||
linkViaShellScript(conf, linkCmd)
|
||||
else:
|
||||
linkViaResponseFile(conf, linkCmd)
|
||||
else:
|
||||
execLinkCmd(conf, linkCmd)
|
||||
|
||||
proc callCCompiler*(conf: ConfigRef) =
|
||||
var
|
||||
linkCmd: string
|
||||
linkCmd: string = ""
|
||||
extraCmds: seq[string]
|
||||
if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}:
|
||||
return # speed up that call if only compiling and no script shall be
|
||||
# generated
|
||||
#var c = cCompiler
|
||||
var script: Rope = nil
|
||||
var cmds: TStringSeq
|
||||
var prettyCmds: TStringSeq
|
||||
let prettyCb = proc (idx: int) =
|
||||
if prettyCmds[idx].len > 0:
|
||||
flushDot(conf)
|
||||
# xxx should probably use stderr like other compiler messages, not stdout
|
||||
echo prettyCmds[idx]
|
||||
var script: Rope = ""
|
||||
var cmds: TStringSeq = default(TStringSeq)
|
||||
var prettyCmds: TStringSeq = default(TStringSeq)
|
||||
let prettyCb = proc (idx: int) = writePrettyCmdsStderr(prettyCmds[idx])
|
||||
|
||||
for idx, it in conf.toCompile:
|
||||
# call the C compiler for the .c file:
|
||||
@@ -890,7 +937,7 @@ proc callCCompiler*(conf: ConfigRef) =
|
||||
let objFile = conf.getObjFilePath(x)
|
||||
let buildDll = idx != mainFileIdx
|
||||
let linkTarget = conf.hcrLinkTargetName(objFile, not buildDll)
|
||||
cmds.add(getLinkCmd(conf, linkTarget, objfiles & " " & quoteShell(objFile), buildDll))
|
||||
cmds.add(getLinkCmd(conf, linkTarget, objfiles & " " & quoteShell(objFile), buildDll, removeStaticFile = true))
|
||||
# try to remove all .pdb files for the current binary so they don't accumulate endlessly in the nimcache
|
||||
# for more info check the comment inside of getLinkCmd() where the /PDB:<filename> MSVC flag is used
|
||||
if isVSCompatible(conf):
|
||||
@@ -912,18 +959,12 @@ proc callCCompiler*(conf: ConfigRef) =
|
||||
objfiles.add(' ')
|
||||
objfiles.add(quoteShell(objFile))
|
||||
let mainOutput = if optGenScript notin conf.globalOptions: conf.prepareToWriteOutput
|
||||
else: AbsoluteFile(conf.projectName)
|
||||
linkCmd = getLinkCmd(conf, mainOutput, objfiles)
|
||||
else: AbsoluteFile(conf.outFile)
|
||||
|
||||
linkCmd = getLinkCmd(conf, mainOutput, objfiles, removeStaticFile = true)
|
||||
extraCmds = getExtraCmds(conf, mainOutput)
|
||||
if optCompileOnly notin conf.globalOptions:
|
||||
const MaxCmdLen = when defined(windows): 8_000 else: 32_000
|
||||
if linkCmd.len > MaxCmdLen:
|
||||
# Windows's command line limit is about 8K (don't laugh...) so C compilers on
|
||||
# Windows support a feature where the command line can be passed via ``@linkcmd``
|
||||
# to them.
|
||||
linkViaResponseFile(conf, linkCmd)
|
||||
else:
|
||||
execLinkCmd(conf, linkCmd)
|
||||
preventLinkCmdMaxCmdLen(conf, linkCmd)
|
||||
for cmd in extraCmds:
|
||||
execExternalProgram(conf, cmd, hintExecuting)
|
||||
else:
|
||||
@@ -933,210 +974,108 @@ proc callCCompiler*(conf: ConfigRef) =
|
||||
script.add("\n")
|
||||
generateScript(conf, script)
|
||||
|
||||
|
||||
template hashNimExe(): string = $secureHashFile(os.getAppFilename())
|
||||
|
||||
proc writeJsonBuildInstructions*(conf: ConfigRef) =
|
||||
template lit(x: string) = f.write x
|
||||
template str(x: string) =
|
||||
buf.setLen 0
|
||||
escapeJson(x, buf)
|
||||
f.write buf
|
||||
proc jsonBuildInstructionsFile*(conf: ConfigRef): AbsoluteFile =
|
||||
# `outFile` is better than `projectName`, as it allows having different json
|
||||
# files for a given source file compiled with different options; it also
|
||||
# works out of the box with `hashMainCompilationParams`.
|
||||
result = getNimcacheDir(conf) / conf.outFile.changeFileExt("json")
|
||||
|
||||
proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) =
|
||||
var comma = false
|
||||
for i, it in clist:
|
||||
if CfileFlag.Cached in it.flags: continue
|
||||
let compileCmd = getCompileCFileCmd(conf, it)
|
||||
if comma: lit ",\L" else: comma = true
|
||||
lit "["
|
||||
str it.cname.string
|
||||
lit ", "
|
||||
str compileCmd
|
||||
lit "]"
|
||||
const cacheVersion = "D20210525T193831" # update when `BuildCache` spec changes
|
||||
type BuildCache = object
|
||||
cacheVersion: string
|
||||
outputFile: string
|
||||
compile: seq[(string, string)]
|
||||
link: seq[string]
|
||||
linkcmd: string
|
||||
extraCmds: seq[string]
|
||||
configFiles: seq[string] # the hash shouldn't be needed
|
||||
stdinInput: bool
|
||||
projectIsCmd: bool
|
||||
cmdInput: string
|
||||
currentDir: string
|
||||
cmdline: string
|
||||
depfiles: seq[(string, string)]
|
||||
nimexe: string
|
||||
|
||||
proc linkfiles(conf: ConfigRef; f: File; buf, objfiles: var string; clist: CfileList;
|
||||
llist: seq[string]) =
|
||||
var pastStart = false
|
||||
for it in llist:
|
||||
let objfile = if noAbsolutePaths(conf): it.extractFilename
|
||||
else: it
|
||||
let objstr = addFileExt(objfile, CC[conf.cCompiler].objExt)
|
||||
objfiles.add(' ')
|
||||
objfiles.add(objstr)
|
||||
if pastStart: lit ",\L"
|
||||
str objstr
|
||||
pastStart = true
|
||||
|
||||
for it in clist:
|
||||
let objstr = quoteShell(it.obj)
|
||||
objfiles.add(' ')
|
||||
objfiles.add(objstr)
|
||||
if pastStart: lit ",\L"
|
||||
str objstr
|
||||
pastStart = true
|
||||
lit "\L"
|
||||
|
||||
proc depfiles(conf: ConfigRef; f: File; buf: var string) =
|
||||
var i = 0
|
||||
proc writeJsonBuildInstructions*(conf: ConfigRef; deps: StringTableRef) =
|
||||
var linkFiles = collect(for it in conf.externalToLink:
|
||||
var it = it
|
||||
if conf.noAbsolutePaths: it = it.extractFilename
|
||||
it.addFileExt(CC[conf.cCompiler].objExt))
|
||||
for it in conf.toCompile: linkFiles.add it.obj.string
|
||||
var bcache = BuildCache(
|
||||
cacheVersion: cacheVersion,
|
||||
outputFile: conf.absOutFile.string,
|
||||
compile: collect(for i, it in conf.toCompile:
|
||||
if CfileFlag.Cached notin it.flags: (it.cname.string, getCompileCFileCmd(conf, it))),
|
||||
link: linkFiles,
|
||||
linkcmd: getLinkCmd(conf, conf.absOutFile, linkFiles.quoteShellCommand),
|
||||
extraCmds: getExtraCmds(conf, conf.absOutFile),
|
||||
stdinInput: conf.projectIsStdin,
|
||||
projectIsCmd: conf.projectIsCmd,
|
||||
cmdInput: conf.cmdInput,
|
||||
configFiles: conf.configFiles.mapIt(it.string),
|
||||
currentDir: getCurrentDir())
|
||||
if optRun in conf.globalOptions or isDefined(conf, "nimBetterRun"):
|
||||
bcache.cmdline = conf.commandLine
|
||||
for it in conf.m.fileInfos:
|
||||
let path = it.fullPath.string
|
||||
if isAbsolute(path): # TODO: else?
|
||||
if i > 0: lit "],\L"
|
||||
lit "["
|
||||
str path
|
||||
lit ", "
|
||||
str $secureHashFile(path)
|
||||
inc i
|
||||
lit "]\L"
|
||||
if path in deps:
|
||||
bcache.depfiles.add (path, deps[path])
|
||||
else: # backup for configs etc.
|
||||
bcache.depfiles.add (path, $secureHashFile(path))
|
||||
|
||||
bcache.nimexe = hashNimExe()
|
||||
conf.jsonBuildFile = conf.jsonBuildInstructionsFile
|
||||
conf.jsonBuildFile.string.writeFile(bcache.toJson.pretty)
|
||||
|
||||
var buf = newStringOfCap(50)
|
||||
|
||||
let jsonFile = conf.getNimcacheDir / RelativeFile(conf.projectName & ".json")
|
||||
conf.jsonBuildFile = jsonFile
|
||||
let output = conf.absOutFile
|
||||
|
||||
var f: File
|
||||
if open(f, jsonFile.string, fmWrite):
|
||||
lit "{\L"
|
||||
lit "\"outputFile\": "
|
||||
str $output
|
||||
|
||||
lit ",\L\"compile\":[\L"
|
||||
cfiles(conf, f, buf, conf.toCompile, false)
|
||||
lit "],\L\"link\":[\L"
|
||||
var objfiles = ""
|
||||
# XXX add every file here that is to link
|
||||
linkfiles(conf, f, buf, objfiles, conf.toCompile, conf.externalToLink)
|
||||
|
||||
lit "],\L\"linkcmd\": "
|
||||
str getLinkCmd(conf, output, objfiles)
|
||||
|
||||
lit ",\L\"extraCmds\": "
|
||||
lit $(%* getExtraCmds(conf, conf.absOutFile))
|
||||
|
||||
lit ",\L\"stdinInput\": "
|
||||
lit $(%* conf.projectIsStdin)
|
||||
lit ",\L\"projectIsCmd\": "
|
||||
lit $(%* conf.projectIsCmd)
|
||||
lit ",\L\"cmdInput\": "
|
||||
lit $(%* conf.cmdInput)
|
||||
lit ",\L\"currentDir\": "
|
||||
lit $(%* getCurrentDir())
|
||||
|
||||
if optRun in conf.globalOptions or isDefined(conf, "nimBetterRun"):
|
||||
lit ",\L\"cmdline\": "
|
||||
str conf.commandLine
|
||||
lit ",\L\"depfiles\":[\L"
|
||||
depfiles(conf, f, buf)
|
||||
lit "],\L\"nimexe\": \L"
|
||||
str hashNimExe()
|
||||
lit "\L"
|
||||
|
||||
lit "\L}\L"
|
||||
close(f)
|
||||
|
||||
proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile): bool =
|
||||
let jsonFile = toGeneratedFile(conf, projectfile, "json")
|
||||
if not fileExists(jsonFile): return true
|
||||
if not fileExists(conf.absOutFile): return true
|
||||
proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile): bool =
|
||||
result = false
|
||||
try:
|
||||
let data = json.parseFile(jsonFile.string)
|
||||
for key in "depfiles cmdline stdinInput currentDir".split:
|
||||
if not data.hasKey(key): return true
|
||||
if getCurrentDir() != data["currentDir"].getStr:
|
||||
# fixes bug #16271
|
||||
# Note that simply comparing `expandFilename(projectFile)` would
|
||||
# not be sufficient in case other flags depend implicitly on `getCurrentDir`,
|
||||
# and would require much more care. Simply re-compiling is safer for now.
|
||||
# A better strategy for future work would be to cache (with an LRU cache)
|
||||
# the N most recent unique build instructions, as done with `rdmd`,
|
||||
# which is both robust and avoids recompilation when switching back and forth
|
||||
# between projects, see https://github.com/timotheecour/Nim/issues/199
|
||||
return true
|
||||
let oldCmdLine = data["cmdline"].getStr
|
||||
if conf.commandLine != oldCmdLine:
|
||||
return true
|
||||
if hashNimExe() != data["nimexe"].getStr:
|
||||
return true
|
||||
let stdinInput = data["stdinInput"].getBool
|
||||
let projectIsCmd = data["projectIsCmd"].getBool
|
||||
if conf.projectIsStdin or stdinInput:
|
||||
# could optimize by returning false if stdin input was the same,
|
||||
# but I'm not sure how to get full stding input
|
||||
return true
|
||||
|
||||
if conf.projectIsCmd or projectIsCmd:
|
||||
if not (conf.projectIsCmd and projectIsCmd): return true
|
||||
if not data.hasKey("cmdInput"): return true
|
||||
let cmdInput = data["cmdInput"].getStr
|
||||
if cmdInput != conf.cmdInput: return true
|
||||
|
||||
let depfilesPairs = data["depfiles"]
|
||||
doAssert depfilesPairs.kind == JArray
|
||||
for p in depfilesPairs:
|
||||
doAssert p.kind == JArray
|
||||
# >= 2 for forwards compatibility with potential later .json files:
|
||||
doAssert p.len >= 2
|
||||
let depFilename = p[0].getStr
|
||||
let oldHashValue = p[1].getStr
|
||||
let newHashValue = $secureHashFile(depFilename)
|
||||
if oldHashValue != newHashValue:
|
||||
return true
|
||||
if not fileExists(jsonFile) or not fileExists(conf.absOutFile): return true
|
||||
var bcache: BuildCache = default(BuildCache)
|
||||
try: bcache.fromJson(jsonFile.string.parseFile)
|
||||
except IOError, OSError, ValueError:
|
||||
echo "Warning: JSON processing failed: ", getCurrentExceptionMsg()
|
||||
result = true
|
||||
stderr.write "Warning: JSON processing failed for: $#\n" % jsonFile.string
|
||||
return true
|
||||
if bcache.currentDir != getCurrentDir() or # fixes bug #16271
|
||||
bcache.configFiles != conf.configFiles.mapIt(it.string) or
|
||||
bcache.cacheVersion != cacheVersion or bcache.outputFile != conf.absOutFile.string or
|
||||
bcache.cmdline != conf.commandLine or bcache.nimexe != hashNimExe() or
|
||||
bcache.projectIsCmd != conf.projectIsCmd or conf.cmdInput != bcache.cmdInput: return true
|
||||
if bcache.stdinInput or conf.projectIsStdin: return true
|
||||
# xxx optimize by returning false if stdin input was the same
|
||||
for (file, hash) in bcache.depfiles:
|
||||
if $secureHashFile(file) != hash: return true
|
||||
|
||||
proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
|
||||
let jsonFile = toGeneratedFile(conf, projectfile, "json")
|
||||
try:
|
||||
let data = json.parseFile(jsonFile.string)
|
||||
|
||||
let output = data["outputFile"].getStr
|
||||
createDir output.parentDir
|
||||
let outputCurrent = $conf.absOutFile
|
||||
if output != outputCurrent:
|
||||
# previously, any specified output file would be silently ignored;
|
||||
# simply copying won't work in some cases, for example with `extraCmds`,
|
||||
# so we just make it an error, user should use same command for jsonscript
|
||||
# as was used with --compileOnly.
|
||||
globalError(conf, gCmdLineInfo, "jsonscript command outputFile '$1' must match '$2' which was specified during --compileOnly, see \"outputFile\" entry in '$3' " % [outputCurrent, output, jsonFile.string])
|
||||
|
||||
let toCompile = data["compile"]
|
||||
doAssert toCompile.kind == JArray
|
||||
var cmds: TStringSeq
|
||||
var prettyCmds: TStringSeq
|
||||
let prettyCb = proc (idx: int) =
|
||||
if prettyCmds[idx].len > 0: echo prettyCmds[idx]
|
||||
|
||||
for c in toCompile:
|
||||
doAssert c.kind == JArray
|
||||
doAssert c.len >= 2
|
||||
|
||||
cmds.add(c[1].getStr)
|
||||
prettyCmds.add displayProgressCC(conf, c[0].getStr, c[1].getStr)
|
||||
|
||||
execCmdsInParallel(conf, cmds, prettyCb)
|
||||
|
||||
let linkCmd = data["linkcmd"]
|
||||
doAssert linkCmd.kind == JString
|
||||
execLinkCmd(conf, linkCmd.getStr)
|
||||
if data.hasKey("extraCmds"):
|
||||
let extraCmds = data["extraCmds"]
|
||||
doAssert extraCmds.kind == JArray
|
||||
for cmd in extraCmds:
|
||||
doAssert cmd.kind == JString, $cmd.kind
|
||||
let cmd2 = cmd.getStr
|
||||
execExternalProgram(conf, cmd2, hintExecuting)
|
||||
|
||||
except:
|
||||
proc runJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile) =
|
||||
var bcache: BuildCache = default(BuildCache)
|
||||
try: bcache.fromJson(jsonFile.string.parseFile)
|
||||
except ValueError, KeyError, JsonKindError:
|
||||
let e = getCurrentException()
|
||||
quit "\ncaught exception:\n" & e.msg & "\nstacktrace:\n" & e.getStackTrace() &
|
||||
"error evaluating JSON file: " & jsonFile.string
|
||||
conf.quitOrRaise "\ncaught exception:\n$#\nstacktrace:\n$#error evaluating JSON file: $#" %
|
||||
[e.msg, e.getStackTrace(), jsonFile.string]
|
||||
let output = bcache.outputFile
|
||||
createDir output.parentDir
|
||||
let outputCurrent = $conf.absOutFile
|
||||
if output != outputCurrent or bcache.cacheVersion != cacheVersion:
|
||||
globalError(conf, gCmdLineInfo,
|
||||
"jsonscript command outputFile '$1' must match '$2' which was specified during --compileOnly, see \"outputFile\" entry in '$3' " %
|
||||
[outputCurrent, output, jsonFile.string])
|
||||
var cmds: TStringSeq = default(TStringSeq)
|
||||
var prettyCmds: TStringSeq= default(TStringSeq)
|
||||
let prettyCb = proc (idx: int) = writePrettyCmdsStderr(prettyCmds[idx])
|
||||
for (name, cmd) in bcache.compile:
|
||||
cmds.add cmd
|
||||
prettyCmds.add displayProgressCC(conf, name, cmd)
|
||||
execCmdsInParallel(conf, cmds, prettyCb)
|
||||
preventLinkCmdMaxCmdLen(conf, bcache.linkcmd)
|
||||
for cmd in bcache.extraCmds: execExternalProgram(conf, cmd, hintExecuting)
|
||||
|
||||
proc genMappingFiles(conf: ConfigRef; list: CfileList): Rope =
|
||||
result = ""
|
||||
for it in list:
|
||||
result.addf("--file:r\"$1\"$N", [rope(it.cname.string)])
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
# This module implements Nim's standard template filter.
|
||||
|
||||
import
|
||||
llstream, strutils, ast, msgs, options,
|
||||
llstream, ast, msgs, options,
|
||||
filters, lineinfos, pathutils
|
||||
|
||||
import std/strutils
|
||||
|
||||
type
|
||||
TParseState = enum
|
||||
psDirective, psTempl
|
||||
@@ -22,7 +24,7 @@ type
|
||||
info: TLineInfo
|
||||
indent, emitPar: int
|
||||
x: string # the current input line
|
||||
outp: PLLStream # the output will be parsed by pnimsyn
|
||||
outp: PLLStream # the output will be parsed by parser
|
||||
subsChar, nimDirective: char
|
||||
emit, conc, toStr: string
|
||||
curly, bracket, par: int
|
||||
@@ -201,17 +203,15 @@ proc parseLine(p: var TTmplParser) =
|
||||
|
||||
proc filterTmpl*(conf: ConfigRef, stdin: PLLStream, filename: AbsoluteFile,
|
||||
call: PNode): PLLStream =
|
||||
var p: TTmplParser
|
||||
p.config = conf
|
||||
p.info = newLineInfo(conf, filename, 0, 0)
|
||||
p.outp = llStreamOpen("")
|
||||
p.inp = stdin
|
||||
p.subsChar = charArg(conf, call, "subschar", 1, '$')
|
||||
p.nimDirective = charArg(conf, call, "metachar", 2, '#')
|
||||
p.emit = strArg(conf, call, "emit", 3, "result.add")
|
||||
p.conc = strArg(conf, call, "conc", 4, " & ")
|
||||
p.toStr = strArg(conf, call, "tostring", 5, "$")
|
||||
p.x = newStringOfCap(120)
|
||||
var p = TTmplParser(config: conf, info: newLineInfo(conf, filename, 0, 0),
|
||||
outp: llStreamOpen(""), inp: stdin,
|
||||
subsChar: charArg(conf, call, "subschar", 1, '$'),
|
||||
nimDirective: charArg(conf, call, "metachar", 2, '#'),
|
||||
emit: strArg(conf, call, "emit", 3, "result.add"),
|
||||
conc: strArg(conf, call, "conc", 4, " & "),
|
||||
toStr: strArg(conf, call, "tostring", 5, "$"),
|
||||
x: newStringOfCap(120)
|
||||
)
|
||||
# do not process the first line which contains the directive:
|
||||
if llStreamReadLine(p.inp, p.x):
|
||||
inc p.info.line
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
# This module implements Nim's simple filters and helpers for filters.
|
||||
|
||||
import
|
||||
llstream, idents, strutils, ast, msgs, options,
|
||||
llstream, idents, ast, msgs, options,
|
||||
renderer, pathutils
|
||||
|
||||
import std/strutils
|
||||
|
||||
proc invalidPragma(conf: ConfigRef; n: PNode) =
|
||||
localError(conf, n.info,
|
||||
"'$1' not allowed here" % renderTree(n, {renderNoComments}))
|
||||
@@ -29,23 +31,30 @@ proc getArg(conf: ConfigRef; n: PNode, name: string, pos: int): PNode =
|
||||
return n[i]
|
||||
|
||||
proc charArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: char): char =
|
||||
|
||||
var x = getArg(conf, n, name, pos)
|
||||
if x == nil: result = default
|
||||
elif x.kind == nkCharLit: result = chr(int(x.intVal))
|
||||
else: invalidPragma(conf, n)
|
||||
else:
|
||||
result = default(char)
|
||||
invalidPragma(conf, n)
|
||||
|
||||
proc strArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: string): string =
|
||||
var x = getArg(conf, n, name, pos)
|
||||
if x == nil: result = default
|
||||
elif x.kind in {nkStrLit..nkTripleStrLit}: result = x.strVal
|
||||
else: invalidPragma(conf, n)
|
||||
else:
|
||||
result = ""
|
||||
invalidPragma(conf, n)
|
||||
|
||||
proc boolArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: bool): bool =
|
||||
var x = getArg(conf, n, name, pos)
|
||||
if x == nil: result = default
|
||||
elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "true") == 0: result = true
|
||||
elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "false") == 0: result = false
|
||||
else: invalidPragma(conf, n)
|
||||
else:
|
||||
result = false
|
||||
invalidPragma(conf, n)
|
||||
|
||||
proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream =
|
||||
var pattern = strArg(conf, call, "startswith", 1, "")
|
||||
|
||||
@@ -9,8 +9,14 @@
|
||||
|
||||
## Module that implements ``gorge`` for the compiler.
|
||||
|
||||
import msgs, std / sha1, os, osproc, streams, options,
|
||||
lineinfos, pathutils
|
||||
import msgs, options, lineinfos, pathutils
|
||||
|
||||
import std/[os, osproc, streams]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/syncio
|
||||
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
|
||||
proc readOutput(p: Process): (string, int) =
|
||||
result[0] = ""
|
||||
@@ -24,10 +30,11 @@ proc readOutput(p: Process): (string, int) =
|
||||
|
||||
proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (string, int) =
|
||||
let workingDir = parentDir(toFullPath(conf, info))
|
||||
result = ("", 0)
|
||||
if cache.len > 0:
|
||||
let h = secureHash(cmd & "\t" & input & "\t" & cache)
|
||||
let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string
|
||||
var f: File
|
||||
var f: File = default(File)
|
||||
if optForceFullMake notin conf.globalOptions and open(f, filename):
|
||||
result = (f.readAll, 0)
|
||||
f.close
|
||||
@@ -46,7 +53,11 @@ proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (str
|
||||
if result[1] == 0:
|
||||
writeFile(filename, result[0])
|
||||
except IOError, OSError:
|
||||
if not readSuccessful: result = ("", -1)
|
||||
if not readSuccessful:
|
||||
when defined(nimLegacyGorgeErrors):
|
||||
result = ("", -1)
|
||||
else:
|
||||
result = ("Error running startProcess: " & getCurrentExceptionMsg(), -1)
|
||||
else:
|
||||
try:
|
||||
var p = startProcess(cmd, workingDir,
|
||||
@@ -57,4 +68,7 @@ proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (str
|
||||
result = p.readOutput
|
||||
p.close()
|
||||
except IOError, OSError:
|
||||
result = ("", -1)
|
||||
when defined(nimLegacyGorgeErrors):
|
||||
result = ("", -1)
|
||||
else:
|
||||
result = ("Error running startProcess: " & getCurrentExceptionMsg(), -1)
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
import ast, astalgo, msgs, magicsys, nimsets, trees, types, renderer, idents,
|
||||
saturate, modulegraphs, options, lineinfos, int128
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
const
|
||||
someEq = {mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
|
||||
mEqStr, mEqSet, mEqCString}
|
||||
@@ -48,6 +51,10 @@ proc isLet(n: PNode): bool =
|
||||
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
|
||||
abstractInst).kind notin {tyVar}:
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc isVar(n: PNode): bool =
|
||||
n.kind == nkSym and n.sym.kind in {skResult, skVar} and
|
||||
@@ -65,9 +72,12 @@ proc isLetLocation(m: PNode, isApprox: bool): bool =
|
||||
case n.kind
|
||||
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv:
|
||||
n = n[0]
|
||||
of nkDerefExpr, nkHiddenDeref:
|
||||
of nkDerefExpr:
|
||||
n = n[0]
|
||||
inc derefs
|
||||
of nkHiddenDeref:
|
||||
n = n[0]
|
||||
if not isApprox: inc derefs
|
||||
of nkBracketExpr:
|
||||
if isConstExpr(n[1]) or isLet(n[1]) or isConstExpr(n[1].skipConv):
|
||||
n = n[0]
|
||||
@@ -130,6 +140,8 @@ proc neg(n: PNode; o: Operators): PNode =
|
||||
result = a
|
||||
elif b != nil:
|
||||
result = b
|
||||
else:
|
||||
result = nil
|
||||
else:
|
||||
# leave not (a == 4) as it is
|
||||
result = newNodeI(nkCall, n.info, 2)
|
||||
@@ -197,7 +209,7 @@ proc highBound*(conf: ConfigRef; x: PNode; o: Operators): PNode =
|
||||
nkIntLit.newIntNode(lastOrd(conf, typ))
|
||||
elif typ.kind == tySequence and x.kind == nkSym and
|
||||
x.sym.kind == skConst:
|
||||
nkIntLit.newIntNode(x.sym.ast.len-1)
|
||||
nkIntLit.newIntNode(x.sym.astdef.len-1)
|
||||
else:
|
||||
o.opAdd.buildCall(o.opLen.buildCall(x), minusOne())
|
||||
result.info = x.info
|
||||
@@ -324,6 +336,8 @@ proc usefulFact(n: PNode; o: Operators): PNode =
|
||||
result = n
|
||||
elif n[1].getMagic in someLen or n[2].getMagic in someLen:
|
||||
result = n
|
||||
else:
|
||||
result = nil
|
||||
of someLe+someLt:
|
||||
if isLetLocation(n[1], true) or isLetLocation(n[2], true):
|
||||
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
|
||||
@@ -331,12 +345,18 @@ proc usefulFact(n: PNode; o: Operators): PNode =
|
||||
elif n[1].getMagic in someLen or n[2].getMagic in someLen:
|
||||
# XXX Rethink this whole idea of 'usefulFact' for semparallel
|
||||
result = n
|
||||
else:
|
||||
result = nil
|
||||
of mIsNil:
|
||||
if isLetLocation(n[1], false) or isVar(n[1]):
|
||||
result = n
|
||||
else:
|
||||
result = nil
|
||||
of someIn:
|
||||
if isLetLocation(n[1], true):
|
||||
result = n
|
||||
else:
|
||||
result = nil
|
||||
of mAnd:
|
||||
let
|
||||
a = usefulFact(n[1], o)
|
||||
@@ -350,10 +370,14 @@ proc usefulFact(n: PNode; o: Operators): PNode =
|
||||
result = a
|
||||
elif b != nil:
|
||||
result = b
|
||||
else:
|
||||
result = nil
|
||||
of mNot:
|
||||
let a = usefulFact(n[1], o)
|
||||
if a != nil:
|
||||
result = a.neg(o)
|
||||
else:
|
||||
result = nil
|
||||
of mOr:
|
||||
# 'or' sucks! (p.isNil or q.isNil) --> hard to do anything
|
||||
# with that knowledge...
|
||||
@@ -370,6 +394,8 @@ proc usefulFact(n: PNode; o: Operators): PNode =
|
||||
result[1] = a
|
||||
result[2] = b
|
||||
result = result.neg(o)
|
||||
else:
|
||||
result = nil
|
||||
elif n.kind == nkSym and n.sym.kind == skLet:
|
||||
# consider:
|
||||
# let a = 2 < x
|
||||
@@ -378,8 +404,12 @@ proc usefulFact(n: PNode; o: Operators): PNode =
|
||||
# We make can easily replace 'a' by '2 < x' here:
|
||||
if n.sym.astdef != nil:
|
||||
result = usefulFact(n.sym.astdef, o)
|
||||
else:
|
||||
result = nil
|
||||
elif n.kind == nkStmtListExpr:
|
||||
result = usefulFact(n.lastSon, o)
|
||||
else:
|
||||
result = nil
|
||||
|
||||
type
|
||||
TModel* = object
|
||||
@@ -441,8 +471,15 @@ proc sameTree*(a, b: PNode): bool =
|
||||
proc hasSubTree(n, x: PNode): bool =
|
||||
if n.sameTree(x): result = true
|
||||
else:
|
||||
for i in 0..n.safeLen-1:
|
||||
if hasSubTree(n[i], x): return true
|
||||
case n.kind
|
||||
of nkEmpty..nkNilLit:
|
||||
result = n.sameTree(x)
|
||||
of nkFormalParams:
|
||||
result = false
|
||||
else:
|
||||
result = false
|
||||
for i in 0..<n.len:
|
||||
if hasSubTree(n[i], x): return true
|
||||
|
||||
proc invalidateFacts*(s: var seq[PNode], n: PNode) =
|
||||
# We are able to guard local vars (as opposed to 'let' variables)!
|
||||
@@ -471,6 +508,8 @@ proc invalidateFacts*(m: var TModel, n: PNode) =
|
||||
proc valuesUnequal(a, b: PNode): bool =
|
||||
if a.isValue and b.isValue:
|
||||
result = not sameValue(a, b)
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc impliesEq(fact, eq: PNode): TImplication =
|
||||
let (loc, val) = if isLocation(eq[1]): (1, 2) else: (2, 1)
|
||||
@@ -481,16 +520,26 @@ proc impliesEq(fact, eq: PNode): TImplication =
|
||||
# this is not correct; consider: a == b; a == 1 --> unknown!
|
||||
if sameTree(fact[2], eq[val]): result = impYes
|
||||
elif valuesUnequal(fact[2], eq[val]): result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
elif sameTree(fact[2], eq[loc]):
|
||||
if sameTree(fact[1], eq[val]): result = impYes
|
||||
elif valuesUnequal(fact[1], eq[val]): result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
of mInSet:
|
||||
# remember: mInSet is 'contains' so the set comes first!
|
||||
if sameTree(fact[2], eq[loc]) and isValue(eq[val]):
|
||||
if inSet(fact[1], eq[val]): result = impYes
|
||||
else: result = impNo
|
||||
of mNot, mOr, mAnd: assert(false, "impliesEq")
|
||||
else: discard
|
||||
else:
|
||||
result = impUnknown
|
||||
of mNot, mOr, mAnd:
|
||||
result = impUnknown
|
||||
assert(false, "impliesEq")
|
||||
else: result = impUnknown
|
||||
|
||||
proc leImpliesIn(x, c, aSet: PNode): TImplication =
|
||||
if c.kind in {nkCharLit..nkUInt64Lit}:
|
||||
@@ -500,13 +549,19 @@ proc leImpliesIn(x, c, aSet: PNode): TImplication =
|
||||
var value = newIntNode(c.kind, firstOrd(nil, x.typ))
|
||||
# don't iterate too often:
|
||||
if c.intVal - value.intVal < 1000:
|
||||
var i, pos, neg: int
|
||||
var i, pos, neg: int = 0
|
||||
while value.intVal <= c.intVal:
|
||||
if inSet(aSet, value): inc pos
|
||||
else: inc neg
|
||||
inc i; inc value.intVal
|
||||
if pos == i: result = impYes
|
||||
elif neg == i: result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
|
||||
proc geImpliesIn(x, c, aSet: PNode): TImplication =
|
||||
if c.kind in {nkCharLit..nkUInt64Lit}:
|
||||
@@ -517,17 +572,23 @@ proc geImpliesIn(x, c, aSet: PNode): TImplication =
|
||||
let max = lastOrd(nil, x.typ)
|
||||
# don't iterate too often:
|
||||
if max - getInt(value) < toInt128(1000):
|
||||
var i, pos, neg: int
|
||||
var i, pos, neg: int = 0
|
||||
while value.intVal <= max:
|
||||
if inSet(aSet, value): inc pos
|
||||
else: inc neg
|
||||
inc i; inc value.intVal
|
||||
if pos == i: result = impYes
|
||||
elif neg == i: result = impNo
|
||||
else: result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
|
||||
proc compareSets(a, b: PNode): TImplication =
|
||||
if equalSets(nil, a, b): result = impYes
|
||||
elif intersectSets(nil, a, b).len == 0: result = impNo
|
||||
else: result = impUnknown
|
||||
|
||||
proc impliesIn(fact, loc, aSet: PNode): TImplication =
|
||||
case fact[0].sym.magic
|
||||
@@ -538,22 +599,32 @@ proc impliesIn(fact, loc, aSet: PNode): TImplication =
|
||||
elif sameTree(fact[2], loc):
|
||||
if inSet(aSet, fact[1]): result = impYes
|
||||
else: result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
of mInSet:
|
||||
if sameTree(fact[2], loc):
|
||||
result = compareSets(fact[1], aSet)
|
||||
else:
|
||||
result = impUnknown
|
||||
of someLe:
|
||||
if sameTree(fact[1], loc):
|
||||
result = leImpliesIn(fact[1], fact[2], aSet)
|
||||
elif sameTree(fact[2], loc):
|
||||
result = geImpliesIn(fact[2], fact[1], aSet)
|
||||
else:
|
||||
result = impUnknown
|
||||
of someLt:
|
||||
if sameTree(fact[1], loc):
|
||||
result = leImpliesIn(fact[1], fact[2].pred, aSet)
|
||||
elif sameTree(fact[2], loc):
|
||||
# 4 < x --> 3 <= x
|
||||
result = geImpliesIn(fact[2], fact[1].pred, aSet)
|
||||
of mNot, mOr, mAnd: assert(false, "impliesIn")
|
||||
else: discard
|
||||
else:
|
||||
result = impUnknown
|
||||
of mNot, mOr, mAnd:
|
||||
result = impUnknown
|
||||
assert(false, "impliesIn")
|
||||
else: result = impUnknown
|
||||
|
||||
proc valueIsNil(n: PNode): TImplication =
|
||||
if n.kind == nkNilLit: impYes
|
||||
@@ -565,13 +636,19 @@ proc impliesIsNil(fact, eq: PNode): TImplication =
|
||||
of mIsNil:
|
||||
if sameTree(fact[1], eq[1]):
|
||||
result = impYes
|
||||
else:
|
||||
result = impUnknown
|
||||
of someEq:
|
||||
if sameTree(fact[1], eq[1]):
|
||||
result = valueIsNil(fact[2].skipConv)
|
||||
elif sameTree(fact[2], eq[1]):
|
||||
result = valueIsNil(fact[1].skipConv)
|
||||
of mNot, mOr, mAnd: assert(false, "impliesIsNil")
|
||||
else: discard
|
||||
else:
|
||||
result = impUnknown
|
||||
of mNot, mOr, mAnd:
|
||||
result = impUnknown
|
||||
assert(false, "impliesIsNil")
|
||||
else: result = impUnknown
|
||||
|
||||
proc impliesGe(fact, x, c: PNode): TImplication =
|
||||
assert isLocation(x)
|
||||
@@ -582,32 +659,57 @@ proc impliesGe(fact, x, c: PNode): TImplication =
|
||||
# fact: x = 4; question x >= 56? --> true iff 4 >= 56
|
||||
if leValue(c, fact[2]): result = impYes
|
||||
else: result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
elif sameTree(fact[2], x):
|
||||
if isValue(fact[1]) and isValue(c):
|
||||
if leValue(c, fact[1]): result = impYes
|
||||
else: result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
of someLt:
|
||||
if sameTree(fact[1], x):
|
||||
if isValue(fact[2]) and isValue(c):
|
||||
# fact: x < 4; question N <= x? --> false iff N <= 4
|
||||
if leValue(fact[2], c): result = impNo
|
||||
else: result = impUnknown
|
||||
# fact: x < 4; question 2 <= x? --> we don't know
|
||||
else:
|
||||
result = impUnknown
|
||||
elif sameTree(fact[2], x):
|
||||
# fact: 3 < x; question: N-1 < x ? --> true iff N-1 <= 3
|
||||
if isValue(fact[1]) and isValue(c):
|
||||
if leValue(c.pred, fact[1]): result = impYes
|
||||
else: result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
of someLe:
|
||||
if sameTree(fact[1], x):
|
||||
if isValue(fact[2]) and isValue(c):
|
||||
# fact: x <= 4; question x >= 56? --> false iff 4 <= 56
|
||||
if leValue(fact[2], c): result = impNo
|
||||
# fact: x <= 4; question x >= 2? --> we don't know
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
elif sameTree(fact[2], x):
|
||||
# fact: 3 <= x; question: x >= 2 ? --> true iff 2 <= 3
|
||||
if isValue(fact[1]) and isValue(c):
|
||||
if leValue(c, fact[1]): result = impYes
|
||||
of mNot, mOr, mAnd: assert(false, "impliesGe")
|
||||
else: discard
|
||||
else: result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
of mNot, mOr, mAnd:
|
||||
result = impUnknown
|
||||
assert(false, "impliesGe")
|
||||
else: result = impUnknown
|
||||
|
||||
proc impliesLe(fact, x, c: PNode): TImplication =
|
||||
if not isLocation(x):
|
||||
@@ -622,35 +724,59 @@ proc impliesLe(fact, x, c: PNode): TImplication =
|
||||
# fact: x = 4; question x <= 56? --> true iff 4 <= 56
|
||||
if leValue(fact[2], c): result = impYes
|
||||
else: result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
elif sameTree(fact[2], x):
|
||||
if isValue(fact[1]) and isValue(c):
|
||||
if leValue(fact[1], c): result = impYes
|
||||
else: result = impNo
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
of someLt:
|
||||
if sameTree(fact[1], x):
|
||||
if isValue(fact[2]) and isValue(c):
|
||||
# fact: x < 4; question x <= N? --> true iff N-1 <= 4
|
||||
if leValue(fact[2], c.pred): result = impYes
|
||||
else:
|
||||
result = impUnknown
|
||||
# fact: x < 4; question x <= 2? --> we don't know
|
||||
else:
|
||||
result = impUnknown
|
||||
elif sameTree(fact[2], x):
|
||||
# fact: 3 < x; question: x <= 1 ? --> false iff 1 <= 3
|
||||
if isValue(fact[1]) and isValue(c):
|
||||
if leValue(c, fact[1]): result = impNo
|
||||
|
||||
else: result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
of someLe:
|
||||
if sameTree(fact[1], x):
|
||||
if isValue(fact[2]) and isValue(c):
|
||||
# fact: x <= 4; question x <= 56? --> true iff 4 <= 56
|
||||
if leValue(fact[2], c): result = impYes
|
||||
else: result = impUnknown
|
||||
# fact: x <= 4; question x <= 2? --> we don't know
|
||||
else:
|
||||
result = impUnknown
|
||||
|
||||
elif sameTree(fact[2], x):
|
||||
# fact: 3 <= x; question: x <= 2 ? --> false iff 2 < 3
|
||||
if isValue(fact[1]) and isValue(c):
|
||||
if leValue(c, fact[1].pred): result = impNo
|
||||
else:result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
else:
|
||||
result = impUnknown
|
||||
|
||||
of mNot, mOr, mAnd: assert(false, "impliesLe")
|
||||
else: discard
|
||||
of mNot, mOr, mAnd:
|
||||
result = impUnknown
|
||||
assert(false, "impliesLe")
|
||||
else: result = impUnknown
|
||||
|
||||
proc impliesLt(fact, x, c: PNode): TImplication =
|
||||
# x < 3 same as x <= 2:
|
||||
@@ -662,6 +788,8 @@ proc impliesLt(fact, x, c: PNode): TImplication =
|
||||
let q = x.pred
|
||||
if q != x:
|
||||
result = impliesLe(fact, q, c)
|
||||
else:
|
||||
result = impUnknown
|
||||
|
||||
proc `~`(x: TImplication): TImplication =
|
||||
case x
|
||||
@@ -713,6 +841,7 @@ proc factImplies(fact, prop: PNode): TImplication =
|
||||
|
||||
proc doesImply*(facts: TModel, prop: PNode): TImplication =
|
||||
assert prop.kind in nkCallKinds
|
||||
result = impUnknown
|
||||
for f in facts.s:
|
||||
# facts can be invalidated, in which case they are 'nil':
|
||||
if not f.isNil:
|
||||
@@ -741,7 +870,7 @@ template isSub(x): untyped = x.getMagic in someSub
|
||||
template isVal(x): untyped = x.kind in {nkCharLit..nkUInt64Lit}
|
||||
template isIntVal(x, y): untyped = x.intVal == y
|
||||
|
||||
import macros
|
||||
import std/macros
|
||||
|
||||
macro `=~`(x: PNode, pat: untyped): bool =
|
||||
proc m(x, pat, conds: NimNode) =
|
||||
@@ -888,6 +1017,7 @@ proc applyReplacements(n: PNode; rep: TReplacements): PNode =
|
||||
|
||||
proc pleViaModelRec(m: var TModel; a, b: PNode): TImplication =
|
||||
# now check for inferrable facts: a <= b and b <= c implies a <= c
|
||||
result = impUnknown
|
||||
for i in 0..m.s.high:
|
||||
let fact = m.s[i]
|
||||
if fact != nil and fact.getMagic in someLe:
|
||||
@@ -933,7 +1063,7 @@ proc pleViaModel(model: TModel; aa, bb: PNode): TImplication =
|
||||
let b = fact[2]
|
||||
if a.kind == nkSym: replacements.add((a,b))
|
||||
else: replacements.add((b,a))
|
||||
var m: TModel
|
||||
var m = TModel()
|
||||
var a = aa
|
||||
var b = bb
|
||||
if replacements.len > 0:
|
||||
@@ -968,8 +1098,8 @@ proc addFactLt*(m: var TModel; a, b: PNode) =
|
||||
addFactLe(m, a, bb)
|
||||
|
||||
proc settype(n: PNode): PType =
|
||||
result = newType(tySet, ItemId(module: -1, item: -1), n.typ.owner)
|
||||
var idgen: IdGenerator
|
||||
var idgen = idGeneratorForPackage(-1'i32)
|
||||
result = newType(tySet, idgen, n.typ.owner)
|
||||
addSonSkipIntLit(result, n.typ, idgen)
|
||||
|
||||
proc buildOf(it, loc: PNode; o: Operators): PNode =
|
||||
@@ -1045,8 +1175,12 @@ proc buildProperFieldCheck(access, check: PNode; o: Operators): PNode =
|
||||
assert check.getMagic == mNot
|
||||
result = buildProperFieldCheck(access, check[1], o).neg(o)
|
||||
|
||||
proc checkFieldAccess*(m: TModel, n: PNode; conf: ConfigRef) =
|
||||
proc checkFieldAccess*(m: TModel, n: PNode; conf: ConfigRef; produceError: bool) =
|
||||
for i in 1..<n.len:
|
||||
let check = buildProperFieldCheck(n[0], n[i], m.g.operators)
|
||||
if check != nil and m.doesImply(check) != impYes:
|
||||
message(conf, n.info, warnProveField, renderTree(n[0])); break
|
||||
if produceError:
|
||||
localError(conf, n.info, "field access outside of valid case branch: " & renderTree(n[0]))
|
||||
else:
|
||||
message(conf, n.info, warnProveField, renderTree(n[0]))
|
||||
break
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#
|
||||
|
||||
# This include implements the high level optimization pass.
|
||||
# included from sem.nim
|
||||
|
||||
proc hlo(c: PContext, n: PNode): PNode
|
||||
|
||||
@@ -16,9 +17,11 @@ proc evalPattern(c: PContext, n, orig: PNode): PNode =
|
||||
# we need to ensure that the resulting AST is semchecked. However, it's
|
||||
# awful to semcheck before macro invocation, so we don't and treat
|
||||
# templates and macros as immediate in this context.
|
||||
var rule: string
|
||||
if c.config.hasHint(hintPattern):
|
||||
rule = renderTree(n, {renderNoComments})
|
||||
var rule: string =
|
||||
if c.config.hasHint(hintPattern):
|
||||
renderTree(n, {renderNoComments})
|
||||
else:
|
||||
""
|
||||
let s = n[0].sym
|
||||
case s.kind
|
||||
of skMacro:
|
||||
@@ -67,9 +70,9 @@ proc hlo(c: PContext, n: PNode): PNode =
|
||||
# already processed (special cases in semstmts.nim)
|
||||
result = n
|
||||
else:
|
||||
if n.kind in {nkFastAsgn, nkAsgn, nkIdentDefs, nkVarTuple} and
|
||||
if n.kind in {nkFastAsgn, nkAsgn, nkSinkAsgn, nkIdentDefs, nkVarTuple} and
|
||||
n[0].kind == nkSym and
|
||||
{sfGlobal, sfPure} * n[0].sym.flags == {sfGlobal, sfPure}:
|
||||
{sfGlobal, sfPure} <= n[0].sym.flags:
|
||||
# do not optimize 'var g {.global} = re(...)' again!
|
||||
return n
|
||||
result = applyPatterns(c, n)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
## A BiTable is a table that can be seen as an optimized pair
|
||||
## of (Table[LitId, Val], Table[Val, LitId]).
|
||||
## of `(Table[LitId, Val], Table[Val, LitId])`.
|
||||
|
||||
import hashes, rodfiles
|
||||
import std/hashes
|
||||
import rodfiles
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
LitId* = distinct uint32
|
||||
@@ -10,6 +14,8 @@ type
|
||||
vals: seq[T] # indexed by LitId
|
||||
keys: seq[LitId] # indexed by hash(val)
|
||||
|
||||
proc initBiTable*[T](): BiTable[T] = BiTable[T](vals: @[], keys: @[])
|
||||
|
||||
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
|
||||
result = (h + 1) and maxHash
|
||||
|
||||
@@ -30,12 +36,14 @@ proc mustRehash(length, counter: int): bool {.inline.} =
|
||||
result = (length * 2 < counter * 3) or (length - counter < 4)
|
||||
|
||||
const
|
||||
idStart = 256 ##
|
||||
## Ids do not start with 0 but with this value. The IR needs it.
|
||||
## TODO: explain why
|
||||
idStart = 1
|
||||
|
||||
template idToIdx(x: LitId): int = x.int - idStart
|
||||
|
||||
proc hasLitId*[T](t: BiTable[T]; x: LitId): bool =
|
||||
let idx = idToIdx(x)
|
||||
result = idx >= 0 and idx < t.vals.len
|
||||
|
||||
proc enlarge[T](t: var BiTable[T]) =
|
||||
var n: seq[LitId]
|
||||
newSeq(n, len(t.keys) * 2)
|
||||
@@ -86,13 +94,13 @@ proc getOrIncl*[T](t: var BiTable[T]; v: T): LitId =
|
||||
t.vals.add v
|
||||
|
||||
|
||||
proc `[]`*[T](t: var BiTable[T]; LitId: LitId): var T {.inline.} =
|
||||
let idx = idToIdx LitId
|
||||
proc `[]`*[T](t: var BiTable[T]; litId: LitId): var T {.inline.} =
|
||||
let idx = idToIdx litId
|
||||
assert idx < t.vals.len
|
||||
result = t.vals[idx]
|
||||
|
||||
proc `[]`*[T](t: BiTable[T]; LitId: LitId): lent T {.inline.} =
|
||||
let idx = idToIdx LitId
|
||||
proc `[]`*[T](t: BiTable[T]; litId: LitId): lent T {.inline.} =
|
||||
let idx = idToIdx litId
|
||||
assert idx < t.vals.len
|
||||
result = t.vals[idx]
|
||||
|
||||
@@ -111,6 +119,12 @@ proc load*[T](f: var RodFile; t: var BiTable[T]) =
|
||||
loadSeq(f, t.vals)
|
||||
loadSeq(f, t.keys)
|
||||
|
||||
proc sizeOnDisc*(t: BiTable[string]): int =
|
||||
result = 4
|
||||
for x in t.vals:
|
||||
result += x.len + 4
|
||||
result += t.keys.len * sizeof(LitId)
|
||||
|
||||
when isMainModule:
|
||||
|
||||
var t: BiTable[string]
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
## also doing cross-module dependency tracking and DCE that we don't need
|
||||
## anymore. DCE is now done as prepass over the entire packed module graph.
|
||||
|
||||
import std/[packedsets, algorithm]
|
||||
# std/intsets would give `UnusedImport`, pending https://github.com/nim-lang/Nim/issues/14246
|
||||
import std/[packedsets, algorithm, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".."/[ast, options, lineinfos, modulegraphs, cgendata, cgen,
|
||||
pathutils, extccomp, msgs]
|
||||
pathutils, extccomp, msgs, modulepaths]
|
||||
|
||||
import packed_ast, ic, dce, rodfiles
|
||||
|
||||
@@ -30,12 +33,16 @@ proc unpackTree(g: ModuleGraph; thisModule: int;
|
||||
var decoder = initPackedDecoder(g.config, g.cache)
|
||||
result = loadNodes(decoder, g.packed, thisModule, tree, n)
|
||||
|
||||
proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var AliveSyms) =
|
||||
proc setupBackendModule(g: ModuleGraph; m: var LoadedModule) =
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
|
||||
assert g.backend != nil
|
||||
var bmod = cgen.newModule(BModuleList(g.backend), m.module, g.config)
|
||||
bmod.idgen = idgenFromLoadedModule(m)
|
||||
|
||||
proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var AliveSyms) =
|
||||
var bmod = BModuleList(g.backend).modules[m.module.position]
|
||||
assert bmod != nil
|
||||
bmod.flags.incl useAliveDataFromDce
|
||||
bmod.alive = move alive[m.module.position]
|
||||
|
||||
@@ -44,6 +51,14 @@ proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var Alive
|
||||
cgen.genTopLevelStmt(bmod, n)
|
||||
|
||||
finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
|
||||
for disp in getDispatchers(g):
|
||||
genProcAux(bmod, disp)
|
||||
m.fromDisk.backendFlags = cgen.whichInitProcs(bmod)
|
||||
|
||||
proc replayTypeInfo(g: ModuleGraph; m: var LoadedModule; origin: FileIndex) =
|
||||
for x in mitems(m.fromDisk.emittedTypeInfo):
|
||||
#echo "found type ", x, " for file ", int(origin)
|
||||
g.emittedTypeInfo[x] = origin
|
||||
|
||||
proc addFileToLink(config: ConfigRef; m: PSym) =
|
||||
let filename = AbsoluteFile toFullPath(config, m.position.FileIndex)
|
||||
@@ -51,7 +66,8 @@ proc addFileToLink(config: ConfigRef; m: PSym) =
|
||||
if config.backend == backendCpp: ".nim.cpp"
|
||||
elif config.backend == backendObjc: ".nim.m"
|
||||
else: ".nim.c"
|
||||
let cfile = changeFileExt(completeCfilePath(config, withPackageName(config, filename)), ext)
|
||||
let cfile = changeFileExt(completeCfilePath(config,
|
||||
mangleModuleName(config, filename).AbsoluteFile), ext)
|
||||
let objFile = completeCfilePath(config, toObjFile(config, cfile))
|
||||
if fileExists(objFile):
|
||||
var cf = Cfile(nimname: m.name.s, cname: cfile,
|
||||
@@ -59,26 +75,69 @@ proc addFileToLink(config: ConfigRef; m: PSym) =
|
||||
flags: {CfileFlag.Cached})
|
||||
addFileToCompile(config, cf)
|
||||
|
||||
proc aliveSymsChanged(config: ConfigRef; position: int; alive: AliveSyms): bool =
|
||||
when defined(debugDce):
|
||||
import os, std/packedsets
|
||||
|
||||
proc storeAliveSymsImpl(asymFile: AbsoluteFile; s: seq[int32]) =
|
||||
var f = rodfiles.create(asymFile.string)
|
||||
f.storeHeader()
|
||||
f.storeSection aliveSymsSection
|
||||
f.storeSeq(s)
|
||||
close f
|
||||
|
||||
template prepare {.dirty.} =
|
||||
let asymFile = toRodFile(config, AbsoluteFile toFullPath(config, position.FileIndex), ".alivesyms")
|
||||
var s = newSeqOfCap[int32](alive[position].len)
|
||||
for a in items(alive[position]): s.add int32(a)
|
||||
sort(s)
|
||||
|
||||
proc storeAliveSyms(config: ConfigRef; position: int; alive: AliveSyms) =
|
||||
prepare()
|
||||
storeAliveSymsImpl(asymFile, s)
|
||||
|
||||
proc aliveSymsChanged(config: ConfigRef; position: int; alive: AliveSyms): bool =
|
||||
prepare()
|
||||
var f2 = rodfiles.open(asymFile.string)
|
||||
f2.loadHeader()
|
||||
f2.loadSection aliveSymsSection
|
||||
var oldData: seq[int32]
|
||||
var oldData: seq[int32] = @[]
|
||||
f2.loadSeq(oldData)
|
||||
f2.close
|
||||
if f2.err == ok and oldData == s:
|
||||
result = false
|
||||
else:
|
||||
when defined(debugDce):
|
||||
let oldAsSet = toPackedSet[int32](oldData)
|
||||
let newAsSet = toPackedSet[int32](s)
|
||||
echo "set of live symbols changed ", asymFile.changeFileExt("rod"), " ", position, " ", f2.err
|
||||
echo "in old but not in new ", oldAsSet.difference(newAsSet), " number of entries in old ", oldAsSet.len
|
||||
echo "in new but not in old ", newAsSet.difference(oldAsSet), " number of entries in new ", newAsSet.len
|
||||
#if execShellCmd(getAppFilename() & " rod " & quoteShell(asymFile.changeFileExt("rod"))) != 0:
|
||||
# echo "command failed"
|
||||
result = true
|
||||
var f = rodfiles.create(asymFile.string)
|
||||
f.storeHeader()
|
||||
f.storeSection aliveSymsSection
|
||||
f.storeSeq(s)
|
||||
close f
|
||||
storeAliveSymsImpl(asymFile, s)
|
||||
|
||||
proc genPackedModule(g: ModuleGraph, i: int; alive: var AliveSyms) =
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading, stored:
|
||||
assert false
|
||||
of storing, outdated:
|
||||
storeAliveSyms(g.config, g.packed[i].module.position, alive)
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
closeRodFile(g, g.packed[i].module)
|
||||
of loaded:
|
||||
if g.packed[i].loadedButAliveSetChanged:
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
else:
|
||||
addFileToLink(g.config, g.packed[i].module)
|
||||
replayTypeInfo(g, g.packed[i], FileIndex(i))
|
||||
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
registerInitProcs(BModuleList(g.backend), g.packed[i].module, g.packed[i].fromDisk.backendFlags)
|
||||
|
||||
proc generateCode*(g: ModuleGraph) =
|
||||
## The single entry point, generate C(++) code for the entire
|
||||
@@ -86,21 +145,36 @@ proc generateCode*(g: ModuleGraph) =
|
||||
resetForBackend(g)
|
||||
var alive = computeAliveSyms(g.packed, g.config)
|
||||
|
||||
for i in 0..high(g.packed):
|
||||
when false:
|
||||
for i in 0..<len(g.packed):
|
||||
echo i, " is of status ", g.packed[i].status, " ", toFullPath(g.config, FileIndex(i))
|
||||
|
||||
# First pass: Setup all the backend modules for all the modules that have
|
||||
# changed:
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading:
|
||||
of loading, stored:
|
||||
assert false
|
||||
of storing, outdated:
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
setupBackendModule(g, g.packed[i])
|
||||
of loaded:
|
||||
# Even though this module didn't change, DCE might trigger a change.
|
||||
# Consider this case: Module A uses symbol S from B and B does not use
|
||||
# S itself. A is then edited not to use S either. Thus we have to
|
||||
# recompile B in order to remove S from the final result.
|
||||
if aliveSymsChanged(g.config, g.packed[i].module.position, alive):
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
else:
|
||||
addFileToLink(g.config, g.packed[i].module)
|
||||
g.packed[i].loadedButAliveSetChanged = true
|
||||
setupBackendModule(g, g.packed[i])
|
||||
|
||||
# Second pass: Code generation.
|
||||
let mainModuleIdx = g.config.projectMainIdx2.int
|
||||
# We need to generate the main module last, because only then
|
||||
# all init procs have been registered:
|
||||
for i in 0..<len(g.packed):
|
||||
if i != mainModuleIdx:
|
||||
genPackedModule(g, i, alive)
|
||||
if mainModuleIdx >= 0:
|
||||
genPackedModule(g, mainModuleIdx, alive)
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
|
||||
## Dead code elimination (=DCE) for IC.
|
||||
|
||||
import std / [intsets, tables]
|
||||
import std/[intsets, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, options, lineinfos, types]
|
||||
|
||||
import packed_ast, ic, bitabs
|
||||
@@ -27,7 +31,7 @@ type
|
||||
proc isExportedToC(c: var AliveContext; g: PackedModuleGraph; symId: int32): bool =
|
||||
## "Exported to C" procs are special (these are marked with '.exportc') because these
|
||||
## must not be optimized away!
|
||||
let symPtr = addr g[c.thisModule].fromDisk.sh.syms[symId]
|
||||
let symPtr = unsafeAddr g[c.thisModule].fromDisk.syms[symId]
|
||||
let flags = symPtr.flags
|
||||
# due to a bug/limitation in the lambda lifting, unused inner procs
|
||||
# are not transformed correctly; issue (#411). However, the whole purpose here
|
||||
@@ -36,10 +40,14 @@ proc isExportedToC(c: var AliveContext; g: PackedModuleGraph; symId: int32): boo
|
||||
if ({sfExportc, sfCompilerProc} * flags != {}) or
|
||||
(symPtr.kind == skMethod):
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
# XXX: This used to be a condition to:
|
||||
# (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
|
||||
if sfCompilerProc in flags:
|
||||
c.compilerProcs[g[c.thisModule].fromDisk.sh.strings[symPtr.name]] = (c.thisModule, symId)
|
||||
c.compilerProcs[g[c.thisModule].fromDisk.strings[symPtr.name]] = (c.thisModule, symId)
|
||||
else:
|
||||
result = false
|
||||
|
||||
template isNotGeneric(n: NodePos): bool = ithSon(tree, n, genericParamsPos).kind == nkEmpty
|
||||
|
||||
@@ -47,17 +55,17 @@ proc followLater(c: var AliveContext; g: PackedModuleGraph; module: int; item: i
|
||||
## Marks a symbol 'item' as used and later in 'followNow' the symbol's body will
|
||||
## be analysed.
|
||||
if not c.alive[module].containsOrIncl(item):
|
||||
var body = g[module].fromDisk.sh.syms[item].ast
|
||||
var body = g[module].fromDisk.syms[item].ast
|
||||
if body != emptyNodeId:
|
||||
let opt = g[module].fromDisk.sh.syms[item].options
|
||||
if g[module].fromDisk.sh.syms[item].kind in routineKinds:
|
||||
let opt = g[module].fromDisk.syms[item].options
|
||||
if g[module].fromDisk.syms[item].kind in routineKinds:
|
||||
body = NodeId ithSon(g[module].fromDisk.bodies, NodePos body, bodyPos)
|
||||
c.stack.add((module, opt, NodePos(body)))
|
||||
|
||||
when false:
|
||||
let nid = g[module].fromDisk.sh.syms[item].name
|
||||
let nid = g[module].fromDisk.syms[item].name
|
||||
if nid != LitId(0):
|
||||
let name = g[module].fromDisk.sh.strings[nid]
|
||||
let name = g[module].fromDisk.strings[nid]
|
||||
if name in ["nimFrame", "callDepthLimitReached"]:
|
||||
echo "I was called! ", name, " body exists: ", body != emptyNodeId, " ", module, " ", item
|
||||
|
||||
@@ -66,12 +74,12 @@ proc requestCompilerProc(c: var AliveContext; g: PackedModuleGraph; name: string
|
||||
followLater(c, g, module, item)
|
||||
|
||||
proc loadTypeKind(t: PackedItemId; c: AliveContext; g: PackedModuleGraph; toSkip: set[TTypeKind]): TTypeKind =
|
||||
template kind(t: ItemId): TTypeKind = g[t.module].fromDisk.sh.types[t.item].kind
|
||||
template kind(t: ItemId): TTypeKind = g[t.module].fromDisk.types[t.item].kind
|
||||
|
||||
var t2 = translateId(t, g, c.thisModule, c.decoder.config)
|
||||
result = t2.kind
|
||||
while result in toSkip:
|
||||
t2 = translateId(g[t2.module].fromDisk.sh.types[t2.item].types[^1], g, t2.module, c.decoder.config)
|
||||
t2 = translateId(g[t2.module].fromDisk.types[t2.item].types[^1], g, t2.module, c.decoder.config)
|
||||
result = t2.kind
|
||||
|
||||
proc rangeCheckAnalysis(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
|
||||
@@ -101,13 +109,13 @@ proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: N
|
||||
discard "ignore non-sym atoms"
|
||||
of nkSym:
|
||||
# This symbol is alive and everything its body references.
|
||||
followLater(c, g, c.thisModule, n.operand)
|
||||
followLater(c, g, c.thisModule, tree[n].soperand)
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, n)
|
||||
assert n1.kind == nkInt32Lit
|
||||
assert n2.kind == nkInt32Lit
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
let m = n1.litId
|
||||
let item = n2.operand
|
||||
let item = tree[n2].soperand
|
||||
let otherModule = toFileIndexCached(c.decoder, g, c.thisModule, m).int
|
||||
followLater(c, g, otherModule, item)
|
||||
of nkMacroDef, nkTemplateDef, nkTypeSection, nkTypeOfExpr,
|
||||
@@ -123,7 +131,7 @@ proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: N
|
||||
rangeCheckAnalysis(c, g, tree, n)
|
||||
of nkProcDef, nkConverterDef, nkMethodDef, nkFuncDef, nkIteratorDef:
|
||||
if n.firstSon.kind == nkSym and isNotGeneric(n):
|
||||
let item = n.firstSon.operand
|
||||
let item = tree[n.firstSon].soperand
|
||||
if isExportedToC(c, g, item):
|
||||
# This symbol is alive and everything its body references.
|
||||
followLater(c, g, c.thisModule, item)
|
||||
@@ -145,7 +153,7 @@ proc computeAliveSyms*(g: PackedModuleGraph; conf: ConfigRef): AliveSyms =
|
||||
var c = AliveContext(stack: @[], decoder: PackedDecoder(config: conf),
|
||||
thisModule: -1, alive: newSeq[IntSet](g.len),
|
||||
options: conf.options)
|
||||
for i in countdown(high(g), 0):
|
||||
for i in countdown(len(g)-1, 0):
|
||||
if g[i].status != undefined:
|
||||
c.thisModule = i
|
||||
for p in allNodes(g[i].fromDisk.topLevel):
|
||||
|
||||
@@ -7,12 +7,8 @@ The frontend produces a set of `.rod` files. Every `.nim` module
|
||||
produces its own `.rod` file.
|
||||
|
||||
- The IR must be a faithful representation of the AST in memory.
|
||||
- The backend can do its own caching but doesn't have to.
|
||||
- We know by comparing 'nim check compiler/nim' against 'nim c compiler/nim'
|
||||
that 2/3 of the compiler's runtime is spent in the frontend. Hence we
|
||||
implement IC for the frontend first and only later for the backend. The
|
||||
backend will recompile everything until we implement its own caching
|
||||
mechanisms.
|
||||
- The backend can do its own caching but doesn't have to. In the
|
||||
current implementation the backend also caches its results.
|
||||
|
||||
Advantage of the "set of files" vs the previous global database:
|
||||
- By construction, we either read from the `.rod` file or from the
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
155
compiler/ic/integrity.nim
Normal file
155
compiler/ic/integrity.nim
Normal file
@@ -0,0 +1,155 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Integrity checking for a set of .rod files.
|
||||
## The set must cover a complete Nim project.
|
||||
|
||||
import std/sets
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, modulegraphs]
|
||||
import packed_ast, bitabs, ic
|
||||
|
||||
type
|
||||
CheckedContext = object
|
||||
g: ModuleGraph
|
||||
thisModule: int32
|
||||
checkedSyms: HashSet[ItemId]
|
||||
checkedTypes: HashSet[ItemId]
|
||||
|
||||
proc checkType(c: var CheckedContext; typeId: PackedItemId)
|
||||
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId)
|
||||
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos)
|
||||
|
||||
proc checkTypeObj(c: var CheckedContext; typ: PackedType) =
|
||||
for child in typ.types:
|
||||
checkType(c, child)
|
||||
if typ.n != emptyNodeId:
|
||||
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos typ.n)
|
||||
if typ.sym != nilItemId:
|
||||
checkForeignSym(c, typ.sym)
|
||||
if typ.owner != nilItemId:
|
||||
checkForeignSym(c, typ.owner)
|
||||
checkType(c, typ.typeInst)
|
||||
|
||||
proc checkType(c: var CheckedContext; typeId: PackedItemId) =
|
||||
if typeId == nilItemId: return
|
||||
let itemId = translateId(typeId, c.g.packed, c.thisModule, c.g.config)
|
||||
if not c.checkedTypes.containsOrIncl(itemId):
|
||||
let oldThisModule = c.thisModule
|
||||
c.thisModule = itemId.module
|
||||
checkTypeObj c, c.g.packed[itemId.module].fromDisk.types[itemId.item]
|
||||
c.thisModule = oldThisModule
|
||||
|
||||
proc checkSym(c: var CheckedContext; s: PackedSym) =
|
||||
if s.name != LitId(0):
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId s.name
|
||||
checkType c, s.typ
|
||||
if s.ast != emptyNodeId:
|
||||
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos s.ast)
|
||||
if s.owner != nilItemId:
|
||||
checkForeignSym(c, s.owner)
|
||||
|
||||
proc checkLocalSym(c: var CheckedContext; item: int32) =
|
||||
let itemId = ItemId(module: c.thisModule, item: item)
|
||||
if not c.checkedSyms.containsOrIncl(itemId):
|
||||
checkSym c, c.g.packed[c.thisModule].fromDisk.syms[item]
|
||||
|
||||
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId) =
|
||||
let itemId = translateId(symId, c.g.packed, c.thisModule, c.g.config)
|
||||
if not c.checkedSyms.containsOrIncl(itemId):
|
||||
let oldThisModule = c.thisModule
|
||||
c.thisModule = itemId.module
|
||||
checkSym c, c.g.packed[itemId.module].fromDisk.syms[itemId.item]
|
||||
c.thisModule = oldThisModule
|
||||
|
||||
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos) =
|
||||
let t = findType(tree, n)
|
||||
if t != nilItemId:
|
||||
checkType(c, t)
|
||||
case n.kind
|
||||
of nkEmpty, nkNilLit, nkType, nkNilRodNode:
|
||||
discard
|
||||
of nkIdent:
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
|
||||
of nkSym:
|
||||
checkLocalSym(c, tree[n].soperand)
|
||||
of directIntLit:
|
||||
discard
|
||||
of externIntLit, nkFloatLit..nkFloat128Lit:
|
||||
assert c.g.packed[c.thisModule].fromDisk.numbers.hasLitId n.litId
|
||||
of nkStrLit..nkTripleStrLit:
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, n)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
checkForeignSym(c, PackedItemId(module: n1.litId, item: tree[n2].soperand))
|
||||
else:
|
||||
for n0 in sonsReadonly(tree, n):
|
||||
checkNode(c, tree, n0)
|
||||
|
||||
proc checkTree(c: var CheckedContext; t: PackedTree) =
|
||||
for p in allNodes(t): checkNode(c, t, p)
|
||||
|
||||
proc checkLocalSymIds(c: var CheckedContext; m: PackedModule; symIds: seq[int32]) =
|
||||
for symId in symIds:
|
||||
assert symId >= 0 and symId < m.syms.len, $symId & " " & $m.syms.len
|
||||
|
||||
proc checkModule(c: var CheckedContext; m: PackedModule) =
|
||||
# We check that:
|
||||
# - Every symbol references existing types and symbols.
|
||||
# - Every tree node references existing types and symbols.
|
||||
for i in 0..high(m.syms):
|
||||
checkLocalSym c, int32(i)
|
||||
|
||||
checkTree c, m.toReplay
|
||||
checkTree c, m.topLevel
|
||||
|
||||
for e in m.exports:
|
||||
assert e[1] >= 0 and e[1] < m.syms.len
|
||||
assert e[0] == m.syms[e[1]].name
|
||||
|
||||
for e in m.compilerProcs:
|
||||
assert e[1] >= 0 and e[1] < m.syms.len
|
||||
assert e[0] == m.syms[e[1]].name
|
||||
|
||||
checkLocalSymIds c, m, m.converters
|
||||
checkLocalSymIds c, m, m.methods
|
||||
checkLocalSymIds c, m, m.trmacros
|
||||
checkLocalSymIds c, m, m.pureEnums
|
||||
#[
|
||||
To do: Check all these fields:
|
||||
|
||||
reexports*: seq[(LitId, PackedItemId)]
|
||||
macroUsages*: seq[(PackedItemId, PackedLineInfo)]
|
||||
|
||||
typeInstCache*: seq[(PackedItemId, PackedItemId)]
|
||||
procInstCache*: seq[PackedInstantiation]
|
||||
attachedOps*: seq[(TTypeAttachedOp, PackedItemId, PackedItemId)]
|
||||
methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
|
||||
enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
|
||||
methodsPerType*: seq[(PackedItemId, PackedItemId)]
|
||||
dispatchers*: seq[PackedItemId]
|
||||
]#
|
||||
|
||||
proc checkIntegrity*(g: ModuleGraph) =
|
||||
var c = CheckedContext(g: g)
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading:
|
||||
assert false, "cannot check integrity: Module still loading"
|
||||
of stored, storing, outdated, loaded:
|
||||
c.thisModule = int32 i
|
||||
checkModule(c, g.packed[i].fromDisk)
|
||||
183
compiler/ic/navigator.nim
Normal file
183
compiler/ic/navigator.nim
Normal file
@@ -0,0 +1,183 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Supports the "nim check --ic:on --defusages:FILE,LINE,COL"
|
||||
## IDE-like features. It uses the set of .rod files to accomplish
|
||||
## its task. The set must cover a complete Nim project.
|
||||
|
||||
import std/sets
|
||||
|
||||
from std/os import nil
|
||||
from std/private/miscdollars import toLocation
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, modulegraphs, msgs, options]
|
||||
import ".." / nir / nirlineinfos
|
||||
import packed_ast, bitabs, ic
|
||||
|
||||
type
|
||||
UnpackedLineInfo = object
|
||||
file: LitId
|
||||
line, col: int
|
||||
NavContext = object
|
||||
g: ModuleGraph
|
||||
thisModule: int32
|
||||
trackPos: UnpackedLineInfo
|
||||
alreadyEmitted: HashSet[string]
|
||||
outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
|
||||
|
||||
proc isTracked(man: LineInfoManager; current: PackedLineInfo, trackPos: UnpackedLineInfo, tokenLen: int): bool =
|
||||
let (currentFile, currentLine, currentCol) = man.unpack(current)
|
||||
if currentFile == trackPos.file and currentLine == trackPos.line:
|
||||
let col = trackPos.col
|
||||
if col >= currentCol and col < currentCol+tokenLen:
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
|
||||
result = s.name != LitId(0) and
|
||||
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[c.thisModule].fromDisk.strings[s.name].len)
|
||||
|
||||
proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
|
||||
let name = c.g.packed[s.module].fromDisk.syms[s.item].name
|
||||
result = name != LitId(0) and
|
||||
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[s.module].fromDisk.strings[name].len)
|
||||
|
||||
const
|
||||
EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
|
||||
|
||||
proc search(c: var NavContext; tree: PackedTree): ItemId =
|
||||
# We use the linear representation here directly:
|
||||
for i in 0..<len(tree):
|
||||
let i = NodePos(i)
|
||||
case tree[i].kind
|
||||
of nkSym:
|
||||
let item = tree[i].soperand
|
||||
if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree[i].info):
|
||||
return ItemId(module: c.thisModule, item: item)
|
||||
of nkModuleRef:
|
||||
let (currentFile, currentLine, currentCol) = c.g.packed[c.thisModule].fromDisk.man.unpack(tree[i].info)
|
||||
if currentLine == c.trackPos.line and currentFile == c.trackPos.file:
|
||||
let (n1, n2) = sons2(tree, i)
|
||||
assert n1.kind == nkInt32Lit
|
||||
assert n2.kind == nkInt32Lit
|
||||
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
|
||||
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
|
||||
if searchForeignSym(c, itemId, tree[i].info):
|
||||
return itemId
|
||||
else: discard
|
||||
return EmptyItemId
|
||||
|
||||
proc isDecl(tree: PackedTree; n: NodePos): bool =
|
||||
# XXX This is not correct yet.
|
||||
const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
|
||||
nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
|
||||
nkIdentDefs, nkEnumTy, nkVarTuple}
|
||||
result = n.int >= 0 and tree[n].kind in declarativeNodes
|
||||
|
||||
proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
|
||||
let (fileId, line, col) = unpack(c.g.packed[c.thisModule].fromDisk.man, info)
|
||||
var m = ""
|
||||
var file = c.g.packed[c.thisModule].fromDisk.strings[fileId]
|
||||
if c.outputSep == ' ':
|
||||
file = os.extractFilename file
|
||||
toLocation(m, file, line, col + ColOffset)
|
||||
if not c.alreadyEmitted.containsOrIncl(m):
|
||||
msgWriteln c.g.config, (if isDecl: "def" else: "usage") & c.outputSep & m
|
||||
|
||||
proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
|
||||
for i in 0..<len(tree):
|
||||
let i = NodePos(i)
|
||||
case tree[i].kind
|
||||
of nkSym:
|
||||
let item = tree[i].soperand
|
||||
if sym.item == item and sym.module == c.thisModule:
|
||||
usage(c, tree[i].info, isDecl(tree, parent(i)))
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, i)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
|
||||
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
|
||||
if itemId.item == sym.item and sym.module == itemId.module:
|
||||
usage(c, tree[i].info, isDecl(tree, parent(i)))
|
||||
else: discard
|
||||
|
||||
proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
|
||||
for i in 0..<len(g.packed):
|
||||
for k in 1..high(g.packed[i].fromDisk.includes):
|
||||
# we start from 1 because the first "include" file is
|
||||
# the module's filename.
|
||||
if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
|
||||
return i
|
||||
return -1
|
||||
|
||||
proc nav(g: ModuleGraph) =
|
||||
# translate the track position to a packed position:
|
||||
let unpacked = g.config.m.trackPos
|
||||
var mid = unpacked.fileIndex.int
|
||||
|
||||
let fullPath = toFullPath(g.config, unpacked.fileIndex)
|
||||
|
||||
if g.packed[mid].status == undefined:
|
||||
# check if 'mid' is an include file of some other module:
|
||||
mid = searchForIncludeFile(g, fullPath)
|
||||
|
||||
if mid < 0:
|
||||
localError(g.config, unpacked, "unknown file name: " & fullPath)
|
||||
return
|
||||
|
||||
let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
|
||||
|
||||
if fileId == LitId(0):
|
||||
internalError(g.config, unpacked, "cannot find a valid file ID")
|
||||
return
|
||||
|
||||
var c = NavContext(
|
||||
g: g,
|
||||
thisModule: int32 mid,
|
||||
trackPos: UnpackedLineInfo(line: unpacked.line.int, col: unpacked.col.int, file: fileId),
|
||||
outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
|
||||
)
|
||||
var symId = search(c, g.packed[mid].fromDisk.topLevel)
|
||||
if symId == EmptyItemId:
|
||||
symId = search(c, g.packed[mid].fromDisk.bodies)
|
||||
|
||||
if symId == EmptyItemId:
|
||||
localError(g.config, unpacked, "no symbol at this position")
|
||||
return
|
||||
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading:
|
||||
assert false, "cannot check integrity: Module still loading"
|
||||
of stored, storing, outdated, loaded:
|
||||
c.thisModule = int32 i
|
||||
list(c, g.packed[i].fromDisk.topLevel, symId)
|
||||
list(c, g.packed[i].fromDisk.bodies, symId)
|
||||
|
||||
proc navDefinition*(g: ModuleGraph) = nav(g)
|
||||
proc navUsages*(g: ModuleGraph) = nav(g)
|
||||
proc navDefusages*(g: ModuleGraph) = nav(g)
|
||||
|
||||
proc writeRodFiles*(g: ModuleGraph) =
|
||||
for i in 0..<len(g.packed):
|
||||
case g.packed[i].status
|
||||
of undefined, loading, stored, loaded:
|
||||
discard "nothing to do"
|
||||
of storing, outdated:
|
||||
closeRodFile(g, g.packed[i].module)
|
||||
@@ -12,10 +12,15 @@
|
||||
## use this representation directly in all the transformations,
|
||||
## it is superior.
|
||||
|
||||
import std / [hashes, tables, strtabs]
|
||||
import bitabs
|
||||
import std/[hashes, tables, strtabs]
|
||||
import bitabs, rodfiles
|
||||
import ".." / [ast, options]
|
||||
|
||||
import ".." / nir / nirlineinfos
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
SymId* = distinct int32
|
||||
ModuleId* = distinct int32
|
||||
@@ -28,21 +33,16 @@ type
|
||||
item*: int32 # same as the in-memory representation
|
||||
|
||||
const
|
||||
nilItemId* = PackedItemId(module: LitId(0), item: -1.int32)
|
||||
nilItemId* = PackedItemId(module: LitId(0), item: 0.int32)
|
||||
|
||||
const
|
||||
emptyNodeId* = NodeId(-1)
|
||||
|
||||
type
|
||||
PackedLineInfo* = object
|
||||
line*: uint16
|
||||
col*: int16
|
||||
file*: LitId
|
||||
|
||||
PackedLib* = object
|
||||
kind*: TLibKind
|
||||
generated*: bool
|
||||
isOverriden*: bool
|
||||
isOverridden*: bool
|
||||
name*: LitId
|
||||
path*: NodeId
|
||||
|
||||
@@ -60,13 +60,15 @@ type
|
||||
alignment*: int # for alignment
|
||||
options*: TOptions
|
||||
position*: int
|
||||
offset*: int
|
||||
offset*: int32
|
||||
disamb*: int32
|
||||
externalName*: LitId # instead of TLoc
|
||||
locFlags*: TLocFlags
|
||||
annex*: PackedLib
|
||||
when hasFFI:
|
||||
cname*: LitId
|
||||
constraint*: NodeId
|
||||
instantiatedFrom*: PackedItemId
|
||||
|
||||
PackedType* = object
|
||||
kind*: TTypeKind
|
||||
@@ -81,39 +83,39 @@ type
|
||||
size*: BiggestInt
|
||||
align*: int16
|
||||
paddingAtEnd*: int16
|
||||
lockLevel*: TLockLevel # lock level as required for deadlock checking
|
||||
# not serialized: loc*: TLoc because it is backend-specific
|
||||
typeInst*: PackedItemId
|
||||
nonUniqueId*: int32
|
||||
|
||||
PackedNode* = object # 20 bytes
|
||||
kind*: TNodeKind
|
||||
flags*: TNodeFlags
|
||||
operand*: int32 # for kind in {nkSym, nkSymDef}: SymId
|
||||
# for kind in {nkStrLit, nkIdent, nkNumberLit}: LitId
|
||||
# for kind in nkInt32Lit: direct value
|
||||
# for non-atom kinds: the number of nodes (for easy skipping)
|
||||
typeId*: PackedItemId
|
||||
PackedNode* = object # 8 bytes
|
||||
x: uint32
|
||||
info*: PackedLineInfo
|
||||
|
||||
PackedTree* = object ## usually represents a full Nim module
|
||||
nodes*: seq[PackedNode]
|
||||
#sh*: Shared
|
||||
|
||||
Shared* = ref object # shared between different versions of 'Module'.
|
||||
# (though there is always exactly one valid
|
||||
# version of a module)
|
||||
syms*: seq[PackedSym]
|
||||
types*: seq[PackedType]
|
||||
strings*: BiTable[string] # we could share these between modules.
|
||||
integers*: BiTable[BiggestInt]
|
||||
floats*: BiTable[BiggestFloat]
|
||||
#config*: ConfigRef
|
||||
nodes: seq[PackedNode]
|
||||
withFlags: seq[(int32, TNodeFlags)]
|
||||
withTypes: seq[(int32, PackedItemId)]
|
||||
|
||||
PackedInstantiation* = object
|
||||
key*, sym*: PackedItemId
|
||||
concreteTypes*: seq[PackedItemId]
|
||||
|
||||
const
|
||||
NodeKindBits = 8'u32
|
||||
NodeKindMask = (1'u32 shl NodeKindBits) - 1'u32
|
||||
|
||||
template kind*(n: PackedNode): TNodeKind = TNodeKind(n.x and NodeKindMask)
|
||||
template uoperand*(n: PackedNode): uint32 = (n.x shr NodeKindBits)
|
||||
template soperand*(n: PackedNode): int32 = int32(uoperand(n))
|
||||
|
||||
template toX(k: TNodeKind; operand: uint32): uint32 =
|
||||
uint32(k) or (operand shl NodeKindBits)
|
||||
|
||||
template toX(k: TNodeKind; operand: LitId): uint32 =
|
||||
uint32(k) or (operand.uint32 shl NodeKindBits)
|
||||
|
||||
template typeId*(n: PackedNode): PackedItemId = n.typ
|
||||
|
||||
proc `==`*(a, b: SymId): bool {.borrow.}
|
||||
proc hash*(a: SymId): Hash {.borrow.}
|
||||
|
||||
@@ -122,71 +124,35 @@ proc `==`*(a, b: NodePos): bool {.borrow.}
|
||||
proc `==`*(a, b: NodeId): bool {.borrow.}
|
||||
|
||||
proc newTreeFrom*(old: PackedTree): PackedTree =
|
||||
result.nodes = @[]
|
||||
result = PackedTree(nodes: @[])
|
||||
when false: result.sh = old.sh
|
||||
|
||||
when false:
|
||||
proc declareSym*(tree: var PackedTree; kind: TSymKind;
|
||||
name: LitId; info: PackedLineInfo): SymId =
|
||||
result = SymId(tree.sh.syms.len)
|
||||
tree.sh.syms.add PackedSym(kind: kind, name: name, flags: {}, magic: mNone, info: info)
|
||||
|
||||
proc litIdFromName*(tree: PackedTree; name: string): LitId =
|
||||
result = tree.sh.strings.getOrIncl(name)
|
||||
|
||||
proc add*(tree: var PackedTree; kind: TNodeKind; token: string; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(kind: kind, info: info,
|
||||
operand: int32 getOrIncl(tree.sh.strings, token))
|
||||
|
||||
proc add*(tree: var PackedTree; kind: TNodeKind; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(kind: kind, operand: 0, info: info)
|
||||
|
||||
proc throwAwayLastNode*(tree: var PackedTree) =
|
||||
tree.nodes.setLen(tree.nodes.len-1)
|
||||
|
||||
proc addIdent*(tree: var PackedTree; s: LitId; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(kind: nkIdent, operand: int32(s), info: info)
|
||||
tree.nodes.add PackedNode(x: toX(nkIdent, uint32(s)), info: info)
|
||||
|
||||
proc addSym*(tree: var PackedTree; s: int32; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(kind: nkSym, operand: s, info: info)
|
||||
|
||||
proc addModuleId*(tree: var PackedTree; s: ModuleId; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(kind: nkInt32Lit, operand: int32(s), info: info)
|
||||
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
|
||||
|
||||
proc addSymDef*(tree: var PackedTree; s: SymId; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(kind: nkSym, operand: int32(s), info: info)
|
||||
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
|
||||
|
||||
proc isAtom*(tree: PackedTree; pos: int): bool {.inline.} = tree.nodes[pos].kind <= nkNilLit
|
||||
|
||||
proc copyTree*(dest: var PackedTree; tree: PackedTree; n: NodePos) =
|
||||
# and this is why the IR is superior. We can copy subtrees
|
||||
# via a linear scan.
|
||||
let pos = n.int
|
||||
let L = if isAtom(tree, pos): 1 else: tree.nodes[pos].operand
|
||||
let d = dest.nodes.len
|
||||
dest.nodes.setLen(d + L)
|
||||
for i in 0..<L:
|
||||
dest.nodes[d+i] = tree.nodes[pos+i]
|
||||
|
||||
when false:
|
||||
proc copySym*(dest: var PackedTree; tree: PackedTree; s: SymId): SymId =
|
||||
result = SymId(dest.sh.syms.len)
|
||||
assert int(s) < tree.sh.syms.len
|
||||
let oldSym = tree.sh.syms[s.int]
|
||||
dest.sh.syms.add oldSym
|
||||
|
||||
type
|
||||
PatchPos = distinct int
|
||||
|
||||
when false:
|
||||
proc prepare*(tree: var PackedTree; kind: TNodeKind; info: PackedLineInfo): PatchPos =
|
||||
result = PatchPos tree.nodes.len
|
||||
tree.nodes.add PackedNode(kind: kind, operand: 0, info: info)
|
||||
proc addNode*(t: var PackedTree; kind: TNodeKind; operand: int32;
|
||||
typeId: PackedItemId = nilItemId; info: PackedLineInfo;
|
||||
flags: TNodeFlags = {}) =
|
||||
t.nodes.add PackedNode(x: toX(kind, cast[uint32](operand)), info: info)
|
||||
if flags != {}:
|
||||
t.withFlags.add (t.nodes.len.int32 - 1, flags)
|
||||
if typeId != nilItemId:
|
||||
t.withTypes.add (t.nodes.len.int32 - 1, typeId)
|
||||
|
||||
proc prepare*(tree: var PackedTree; kind: TNodeKind; flags: TNodeFlags; typeId: PackedItemId; info: PackedLineInfo): PatchPos =
|
||||
result = PatchPos tree.nodes.len
|
||||
tree.nodes.add PackedNode(kind: kind, flags: flags, operand: 0, info: info,
|
||||
typeId: typeId)
|
||||
tree.addNode(kind = kind, flags = flags, operand = 0, info = info, typeId = typeId)
|
||||
|
||||
proc prepare*(dest: var PackedTree; source: PackedTree; sourcePos: NodePos): PatchPos =
|
||||
result = PatchPos dest.nodes.len
|
||||
@@ -194,26 +160,30 @@ proc prepare*(dest: var PackedTree; source: PackedTree; sourcePos: NodePos): Pat
|
||||
|
||||
proc patch*(tree: var PackedTree; pos: PatchPos) =
|
||||
let pos = pos.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let k = tree.nodes[pos].kind
|
||||
assert k > nkNilLit
|
||||
let distance = int32(tree.nodes.len - pos)
|
||||
tree.nodes[pos].operand = distance
|
||||
assert distance > 0
|
||||
tree.nodes[pos].x = toX(k, cast[uint32](distance))
|
||||
|
||||
proc len*(tree: PackedTree): int {.inline.} = tree.nodes.len
|
||||
|
||||
proc `[]`*(tree: PackedTree; i: int): lent PackedNode {.inline.} =
|
||||
tree.nodes[i]
|
||||
proc `[]`*(tree: PackedTree; i: NodePos): lent PackedNode {.inline.} =
|
||||
tree.nodes[i.int]
|
||||
|
||||
template rawSpan(n: PackedNode): int = int(uoperand(n))
|
||||
|
||||
proc nextChild(tree: PackedTree; pos: var int) {.inline.} =
|
||||
if tree.nodes[pos].kind > nkNilLit:
|
||||
assert tree.nodes[pos].operand > 0
|
||||
inc pos, tree.nodes[pos].operand
|
||||
assert tree.nodes[pos].uoperand > 0
|
||||
inc pos, tree.nodes[pos].rawSpan
|
||||
else:
|
||||
inc pos
|
||||
|
||||
iterator sonsReadonly*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].operand
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
while pos < last:
|
||||
yield NodePos pos
|
||||
@@ -234,7 +204,7 @@ iterator isons*(dest: var PackedTree; tree: PackedTree;
|
||||
iterator sonsFrom1*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].operand
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
if pos < last:
|
||||
nextChild tree, pos
|
||||
@@ -248,7 +218,7 @@ iterator sonsWithoutLast2*(tree: PackedTree; n: NodePos): NodePos =
|
||||
inc count
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].operand
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
while pos < last and count > 2:
|
||||
yield NodePos pos
|
||||
@@ -258,9 +228,9 @@ iterator sonsWithoutLast2*(tree: PackedTree; n: NodePos): NodePos =
|
||||
proc parentImpl(tree: PackedTree; n: NodePos): NodePos =
|
||||
# finding the parent of a node is rather easy:
|
||||
var pos = n.int - 1
|
||||
while pos >= 0 and isAtom(tree, pos) or (pos + tree.nodes[pos].operand - 1 < n.int):
|
||||
while pos >= 0 and (isAtom(tree, pos) or (pos + tree.nodes[pos].rawSpan - 1 < n.int)):
|
||||
dec pos
|
||||
assert pos >= 0, "node has no parent"
|
||||
#assert pos >= 0, "node has no parent"
|
||||
result = NodePos(pos)
|
||||
|
||||
template parent*(n: NodePos): NodePos = parentImpl(tree, n)
|
||||
@@ -284,20 +254,32 @@ proc firstSon*(tree: PackedTree; n: NodePos): NodePos {.inline.} =
|
||||
proc kind*(tree: PackedTree; n: NodePos): TNodeKind {.inline.} =
|
||||
tree.nodes[n.int].kind
|
||||
proc litId*(tree: PackedTree; n: NodePos): LitId {.inline.} =
|
||||
LitId tree.nodes[n.int].operand
|
||||
LitId tree.nodes[n.int].uoperand
|
||||
proc info*(tree: PackedTree; n: NodePos): PackedLineInfo {.inline.} =
|
||||
tree.nodes[n.int].info
|
||||
|
||||
template typ*(n: NodePos): PackedItemId =
|
||||
tree.nodes[n.int].typeId
|
||||
template flags*(n: NodePos): TNodeFlags =
|
||||
tree.nodes[n.int].flags
|
||||
proc findType*(tree: PackedTree; n: NodePos): PackedItemId =
|
||||
for x in tree.withTypes:
|
||||
if x[0] == int32(n): return x[1]
|
||||
if x[0] > int32(n): return nilItemId
|
||||
return nilItemId
|
||||
|
||||
template operand*(n: NodePos): int32 =
|
||||
tree.nodes[n.int].operand
|
||||
proc findFlags*(tree: PackedTree; n: NodePos): TNodeFlags =
|
||||
for x in tree.withFlags:
|
||||
if x[0] == int32(n): return x[1]
|
||||
if x[0] > int32(n): return {}
|
||||
return {}
|
||||
|
||||
template typ*(n: NodePos): PackedItemId =
|
||||
tree.findType(n)
|
||||
template flags*(n: NodePos): TNodeFlags =
|
||||
tree.findFlags(n)
|
||||
|
||||
template uoperand*(n: NodePos): uint32 =
|
||||
tree.nodes[n.int].uoperand
|
||||
|
||||
proc span*(tree: PackedTree; pos: int): int {.inline.} =
|
||||
if isAtom(tree, pos): 1 else: tree.nodes[pos].operand
|
||||
if isAtom(tree, pos): 1 else: tree.nodes[pos].rawSpan
|
||||
|
||||
proc sons2*(tree: PackedTree; n: NodePos): (NodePos, NodePos) =
|
||||
assert(not isAtom(tree, n.int))
|
||||
@@ -313,6 +295,7 @@ proc sons3*(tree: PackedTree; n: NodePos): (NodePos, NodePos, NodePos) =
|
||||
result = (NodePos a, NodePos b, NodePos c)
|
||||
|
||||
proc ithSon*(tree: PackedTree; n: NodePos; i: int): NodePos =
|
||||
result = default(NodePos)
|
||||
if tree.nodes[n.int].kind > nkNilLit:
|
||||
var count = 0
|
||||
for child in sonsReadonly(tree, n):
|
||||
@@ -326,105 +309,28 @@ when false:
|
||||
|
||||
template kind*(n: NodePos): TNodeKind = tree.nodes[n.int].kind
|
||||
template info*(n: NodePos): PackedLineInfo = tree.nodes[n.int].info
|
||||
template litId*(n: NodePos): LitId = LitId tree.nodes[n.int].operand
|
||||
template litId*(n: NodePos): LitId = LitId tree.nodes[n.int].uoperand
|
||||
|
||||
template symId*(n: NodePos): SymId = SymId tree.nodes[n.int].operand
|
||||
template symId*(n: NodePos): SymId = SymId tree.nodes[n.int].soperand
|
||||
|
||||
proc firstSon*(n: NodePos): NodePos {.inline.} = NodePos(n.int+1)
|
||||
|
||||
when false:
|
||||
proc strLit*(tree: PackedTree; n: NodePos): lent string =
|
||||
assert n.kind == nkStrLit
|
||||
result = tree.sh.strings[LitId tree.nodes[n.int].operand]
|
||||
|
||||
proc strVal*(tree: PackedTree; n: NodePos): string =
|
||||
assert n.kind == nkStrLit
|
||||
result = tree.sh.strings[LitId tree.nodes[n.int].operand]
|
||||
#result = cookedStrLit(raw)
|
||||
|
||||
proc filenameVal*(tree: PackedTree; n: NodePos): string =
|
||||
case n.kind
|
||||
of nkStrLit:
|
||||
result = strVal(tree, n)
|
||||
of nkIdent:
|
||||
result = tree.sh.strings[n.litId]
|
||||
of nkSym:
|
||||
result = tree.sh.strings[tree.sh.syms[int n.symId].name]
|
||||
else:
|
||||
result = ""
|
||||
|
||||
proc identAsStr*(tree: PackedTree; n: NodePos): lent string =
|
||||
assert n.kind == nkIdent
|
||||
result = tree.sh.strings[LitId tree.nodes[n.int].operand]
|
||||
|
||||
const
|
||||
externIntLit* = {nkCharLit,
|
||||
nkIntLit,
|
||||
nkInt8Lit,
|
||||
nkInt16Lit,
|
||||
nkInt32Lit,
|
||||
nkInt64Lit,
|
||||
nkUIntLit,
|
||||
nkUInt8Lit,
|
||||
nkUInt16Lit,
|
||||
nkUInt32Lit,
|
||||
nkUInt64Lit} # nkInt32Lit is missing by design!
|
||||
nkUInt64Lit}
|
||||
|
||||
externSIntLit* = {nkIntLit, nkInt8Lit, nkInt16Lit, nkInt64Lit}
|
||||
externSIntLit* = {nkIntLit, nkInt8Lit, nkInt16Lit, nkInt32Lit, nkInt64Lit}
|
||||
externUIntLit* = {nkUIntLit, nkUInt8Lit, nkUInt16Lit, nkUInt32Lit, nkUInt64Lit}
|
||||
directIntLit* = nkInt32Lit
|
||||
|
||||
proc toString*(tree: PackedTree; n: NodePos; sh: Shared; nesting: int;
|
||||
result: var string) =
|
||||
let pos = n.int
|
||||
if result.len > 0 and result[^1] notin {' ', '\n'}:
|
||||
result.add ' '
|
||||
|
||||
result.add $tree[pos].kind
|
||||
case tree.nodes[pos].kind
|
||||
of nkNone, nkEmpty, nkNilLit, nkType: discard
|
||||
of nkIdent, nkStrLit..nkTripleStrLit:
|
||||
result.add " "
|
||||
result.add sh.strings[LitId tree.nodes[pos].operand]
|
||||
of nkSym:
|
||||
result.add " "
|
||||
result.add sh.strings[sh.syms[tree.nodes[pos].operand].name]
|
||||
of directIntLit:
|
||||
result.add " "
|
||||
result.addInt tree.nodes[pos].operand
|
||||
of externSIntLit:
|
||||
result.add " "
|
||||
result.addInt sh.integers[LitId tree.nodes[pos].operand]
|
||||
of externUIntLit:
|
||||
result.add " "
|
||||
result.add $cast[uint64](sh.integers[LitId tree.nodes[pos].operand])
|
||||
else:
|
||||
result.add "(\n"
|
||||
for i in 1..(nesting+1)*2: result.add ' '
|
||||
for child in sonsReadonly(tree, n):
|
||||
toString(tree, child, sh, nesting + 1, result)
|
||||
result.add "\n"
|
||||
for i in 1..nesting*2: result.add ' '
|
||||
result.add ")"
|
||||
#for i in 1..nesting*2: result.add ' '
|
||||
|
||||
|
||||
proc toString*(tree: PackedTree; n: NodePos; sh: Shared): string =
|
||||
result = ""
|
||||
toString(tree, n, sh, 0, result)
|
||||
|
||||
proc debug*(tree: PackedTree; sh: Shared) =
|
||||
stdout.write toString(tree, NodePos 0, sh)
|
||||
|
||||
when false:
|
||||
proc identIdImpl(tree: PackedTree; n: NodePos): LitId =
|
||||
if n.kind == nkIdent:
|
||||
result = n.litId
|
||||
elif n.kind == nkSym:
|
||||
result = tree.sh.syms[int n.symId].name
|
||||
else:
|
||||
result = LitId(0)
|
||||
|
||||
template identId*(n: NodePos): LitId = identIdImpl(tree, n)
|
||||
directIntLit* = nkNone
|
||||
|
||||
template copyInto*(dest, n, body) =
|
||||
let patchPos = prepare(dest, tree, n)
|
||||
@@ -436,28 +342,8 @@ template copyIntoKind*(dest, kind, info, body) =
|
||||
body
|
||||
patch dest, patchPos
|
||||
|
||||
when false:
|
||||
proc hasPragma*(tree: PackedTree; n: NodePos; pragma: string): bool =
|
||||
let litId = tree.sh.strings.getKeyId(pragma)
|
||||
if litId == LitId(0):
|
||||
return false
|
||||
assert n.kind == nkPragma
|
||||
for ch0 in sonsReadonly(tree, n):
|
||||
if ch0.kind == nkExprColonExpr:
|
||||
if ch0.firstSon.identId == litId:
|
||||
return true
|
||||
elif ch0.identId == litId:
|
||||
return true
|
||||
|
||||
proc getNodeId*(tree: PackedTree): NodeId {.inline.} = NodeId tree.nodes.len
|
||||
|
||||
when false:
|
||||
proc produceError*(dest: var PackedTree; tree: PackedTree; n: NodePos; msg: string) =
|
||||
let patchPos = prepare(dest, nkError, n.info)
|
||||
dest.add nkStrLit, msg, n.info
|
||||
copyTree(dest, tree, n)
|
||||
patch dest, patchPos
|
||||
|
||||
iterator allNodes*(tree: PackedTree): NodePos =
|
||||
var p = 0
|
||||
while p < tree.len:
|
||||
@@ -467,3 +353,13 @@ iterator allNodes*(tree: PackedTree): NodePos =
|
||||
|
||||
proc toPackedItemId*(item: int32): PackedItemId {.inline.} =
|
||||
PackedItemId(module: LitId(0), item: item)
|
||||
|
||||
proc load*(f: var RodFile; t: var PackedTree) =
|
||||
loadSeq f, t.nodes
|
||||
loadSeq f, t.withFlags
|
||||
loadSeq f, t.withTypes
|
||||
|
||||
proc store*(f: var RodFile; t: PackedTree) =
|
||||
storeSeq f, t.nodes
|
||||
storeSeq f, t.withFlags
|
||||
storeSeq f, t.withTypes
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
import ".." / [ast, modulegraphs, trees, extccomp, btrees,
|
||||
msgs, lineinfos, pathutils, options, cgmeth]
|
||||
|
||||
import tables
|
||||
import std/tables
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import packed_ast, ic, bitabs
|
||||
|
||||
@@ -86,6 +89,31 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
else:
|
||||
internalAssert g.config, false
|
||||
|
||||
proc replayBackendProcs*(g: ModuleGraph; module: int) =
|
||||
for it in mitems(g.packed[module].fromDisk.attachedOps):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let op = it[1]
|
||||
let tmp = translateId(it[2], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[2])
|
||||
g.attachedOps[op][key] = LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.enumToStringProcs):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let tmp = translateId(it[1], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[1])
|
||||
g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methodsPerType):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let tmp = translateId(it[1], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[1])
|
||||
g.methodsPerType.mgetOrPut(key, @[]).add LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.dispatchers):
|
||||
let tmp = translateId(it, g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it)
|
||||
g.dispatchers.add LazySym(id: symId, sym: nil)
|
||||
|
||||
proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
|
||||
## We remember the generic instantiations a module performed
|
||||
## in order to to avoid the code bloat that generic code tends
|
||||
@@ -110,18 +138,14 @@ proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
|
||||
module: module, sym: FullId(module: sym.module, packed: it.sym),
|
||||
concreteTypes: concreteTypes, inst: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methodsPerType):
|
||||
for it in mitems(g.packed[module].fromDisk.methodsPerGenericType):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let col = it[1]
|
||||
let tmp = translateId(it[2], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[2])
|
||||
g.methodsPerType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
|
||||
g.methodsPerGenericType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.enumToStringProcs):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let tmp = translateId(it[1], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[1])
|
||||
g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)
|
||||
replayBackendProcs(g, module)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methods):
|
||||
let sym = loadSymFromId(g.config, g.cache, g.packed, module,
|
||||
|
||||
@@ -7,7 +7,66 @@
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
from typetraits import supportsCopyMem
|
||||
## Low level binary format used by the compiler to store and load various AST
|
||||
## and related data.
|
||||
##
|
||||
## NB: this is incredibly low level and if you're interested in how the
|
||||
## compiler works and less a storage format, you're probably looking for
|
||||
## the `ic` or `packed_ast` modules to understand the logical format.
|
||||
|
||||
from std/typetraits import supportsCopyMem
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions]
|
||||
|
||||
## Overview
|
||||
## ========
|
||||
## `RodFile` represents a Rod File (versioned binary format), and the
|
||||
## associated data for common interactions such as IO and error tracking
|
||||
## (`RodFileError`). The file format broken up into sections (`RodSection`)
|
||||
## and preceded by a header (see: `cookie`). The precise layout, section
|
||||
## ordering and data following the section are determined by the user. See
|
||||
## `ic.loadRodFile`.
|
||||
##
|
||||
## A basic but "wrong" example of the lifecycle:
|
||||
## ---------------------------------------------
|
||||
## 1. `create` or `open` - create a new one or open an existing
|
||||
## 2. `storeHeader` - header info
|
||||
## 3. `storePrim` or `storeSeq` - save your stuff
|
||||
## 4. `close` - and we're done
|
||||
##
|
||||
## Now read the bits below to understand what's missing.
|
||||
##
|
||||
## ### Issues with the Example
|
||||
## Missing Sections:
|
||||
## This is a low level API, so headers and sections need to be stored and
|
||||
## loaded by the user, see `storeHeader` & `loadHeader` and `storeSection` &
|
||||
## `loadSection`, respectively.
|
||||
##
|
||||
## No Error Handling:
|
||||
## The API is centered around IO and prone to error, each operation checks or
|
||||
## sets the `RodFile.err` field. A user of this API needs to handle these
|
||||
## appropriately.
|
||||
##
|
||||
## API Notes
|
||||
## =========
|
||||
##
|
||||
## Valid inputs for Rod files
|
||||
## --------------------------
|
||||
## ASTs, hopes, dreams, and anything as long as it and any children it may have
|
||||
## support `copyMem`. This means anything that is not a pointer and that does not contain a pointer. At a glance these are:
|
||||
## * string
|
||||
## * objects & tuples (fields are recursed)
|
||||
## * sequences AKA `seq[T]`
|
||||
##
|
||||
## Note on error handling style
|
||||
## ----------------------------
|
||||
## A flag based approach is used where operations no-op in case of a
|
||||
## preexisting error and set the flag if they encounter one.
|
||||
##
|
||||
## Misc
|
||||
## ----
|
||||
## * 'Prim' is short for 'primitive', as in a non-sequence type
|
||||
|
||||
type
|
||||
RodSection* = enum
|
||||
@@ -16,16 +75,15 @@ type
|
||||
stringsSection
|
||||
checkSumsSection
|
||||
depsSection
|
||||
integersSection
|
||||
floatsSection
|
||||
numbersSection
|
||||
exportsSection
|
||||
hiddenSection
|
||||
reexportsSection
|
||||
compilerProcsSection
|
||||
trmacrosSection
|
||||
convertersSection
|
||||
methodsSection
|
||||
pureEnumsSection
|
||||
macroUsagesSection
|
||||
toReplaySection
|
||||
topLevelSection
|
||||
bodiesSection
|
||||
@@ -34,9 +92,16 @@ type
|
||||
typeInstCacheSection
|
||||
procInstCacheSection
|
||||
attachedOpsSection
|
||||
methodsPerTypeSection
|
||||
methodsPerGenericTypeSection
|
||||
enumToStringProcsSection
|
||||
methodsPerTypeSection
|
||||
dispatchersSection
|
||||
typeInfoSection # required by the backend
|
||||
backendFlagsSection
|
||||
aliveSymsSection # beware, this is stored in a `.alivesyms` file.
|
||||
sideChannelSection
|
||||
namespaceSection
|
||||
symnamesSection
|
||||
|
||||
RodFileError* = enum
|
||||
ok, tooBig, cannotOpen, ioFailure, wrongHeader, wrongSection, configMismatch,
|
||||
@@ -49,8 +114,8 @@ type
|
||||
# better than exceptions.
|
||||
|
||||
const
|
||||
RodVersion = 1
|
||||
cookie = [byte(0), byte('R'), byte('O'), byte('D'),
|
||||
RodVersion = 2
|
||||
defaultCookie = [byte(0), byte('R'), byte('O'), byte('D'),
|
||||
byte(sizeof(int)*8), byte(system.cpuEndian), byte(0), byte(RodVersion)]
|
||||
|
||||
proc setError(f: var RodFile; err: RodFileError) {.inline.} =
|
||||
@@ -58,6 +123,8 @@ proc setError(f: var RodFile; err: RodFileError) {.inline.} =
|
||||
#raise newException(IOError, "IO error")
|
||||
|
||||
proc storePrim*(f: var RodFile; s: string) =
|
||||
## Stores a string.
|
||||
## The len is prefixed to allow for later retreival.
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
@@ -71,6 +138,9 @@ proc storePrim*(f: var RodFile; s: string) =
|
||||
setError f, ioFailure
|
||||
|
||||
proc storePrim*[T](f: var RodFile; x: T) =
|
||||
## Stores a non-sequence/string `T`.
|
||||
## If `T` doesn't support `copyMem` and is an object or tuple then the fields
|
||||
## are written -- the user from context will need to know which `T` to load.
|
||||
if f.err != ok: return
|
||||
when supportsCopyMem(T):
|
||||
if writeBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
|
||||
@@ -88,6 +158,7 @@ proc storePrim*[T](f: var RodFile; x: T) =
|
||||
{.error: "unsupported type for 'storePrim'".}
|
||||
|
||||
proc storeSeq*[T](f: var RodFile; s: seq[T]) =
|
||||
## Stores a sequence of `T`s, with the len as a prefix for later retrieval.
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
@@ -100,6 +171,7 @@ proc storeSeq*[T](f: var RodFile; s: seq[T]) =
|
||||
storePrim(f, s[i])
|
||||
|
||||
proc loadPrim*(f: var RodFile; s: var string) =
|
||||
## Read a string, the length was stored as a prefix
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
@@ -111,6 +183,7 @@ proc loadPrim*(f: var RodFile; s: var string) =
|
||||
setError f, ioFailure
|
||||
|
||||
proc loadPrim*[T](f: var RodFile; x: var T) =
|
||||
## Load a non-sequence/string `T`.
|
||||
if f.err != ok: return
|
||||
when supportsCopyMem(T):
|
||||
if readBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
|
||||
@@ -128,6 +201,7 @@ proc loadPrim*[T](f: var RodFile; x: var T) =
|
||||
{.error: "unsupported type for 'loadPrim'".}
|
||||
|
||||
proc loadSeq*[T](f: var RodFile; s: var seq[T]) =
|
||||
## `T` must be compatible with `copyMem`, see `loadPrim`
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
@@ -137,38 +211,46 @@ proc loadSeq*[T](f: var RodFile; s: var seq[T]) =
|
||||
for i in 0..<lenPrefix:
|
||||
loadPrim(f, s[i])
|
||||
|
||||
proc storeHeader*(f: var RodFile) =
|
||||
proc storeHeader*(f: var RodFile; cookie = defaultCookie) =
|
||||
## stores the header which is described by `cookie`.
|
||||
if f.err != ok: return
|
||||
if f.f.writeBytes(cookie, 0, cookie.len) != cookie.len:
|
||||
setError f, ioFailure
|
||||
|
||||
proc loadHeader*(f: var RodFile) =
|
||||
proc loadHeader*(f: var RodFile; cookie = defaultCookie) =
|
||||
## Loads the header which is described by `cookie`.
|
||||
if f.err != ok: return
|
||||
var thisCookie: array[cookie.len, byte]
|
||||
var thisCookie: array[cookie.len, byte] = default(array[cookie.len, byte])
|
||||
if f.f.readBytes(thisCookie, 0, thisCookie.len) != thisCookie.len:
|
||||
setError f, ioFailure
|
||||
elif thisCookie != cookie:
|
||||
setError f, wrongHeader
|
||||
|
||||
proc storeSection*(f: var RodFile; s: RodSection) =
|
||||
## update `currentSection` and writes the bytes value of s.
|
||||
if f.err != ok: return
|
||||
assert f.currentSection < s
|
||||
f.currentSection = s
|
||||
storePrim(f, s)
|
||||
|
||||
proc loadSection*(f: var RodFile; expected: RodSection) =
|
||||
## read the bytes value of s, sets and error if the section is incorrect.
|
||||
if f.err != ok: return
|
||||
var s: RodSection
|
||||
var s: RodSection = default(RodSection)
|
||||
loadPrim(f, s)
|
||||
if expected != s and f.err == ok:
|
||||
setError f, wrongSection
|
||||
|
||||
proc create*(filename: string): RodFile =
|
||||
## create the file and open it for writing
|
||||
result = default(RodFile)
|
||||
if not open(result.f, filename, fmWrite):
|
||||
setError result, cannotOpen
|
||||
|
||||
proc close*(f: var RodFile) = close(f.f)
|
||||
|
||||
proc open*(filename: string): RodFile =
|
||||
## open the file for reading
|
||||
result = default(RodFile)
|
||||
if not open(result.f, filename, fmRead):
|
||||
setError result, cannotOpen
|
||||
|
||||
@@ -11,8 +11,11 @@
|
||||
# An identifier is a shared immutable string that can be compared by its
|
||||
# id. This module is essential for the compiler's performance.
|
||||
|
||||
import
|
||||
hashes, wordrecg
|
||||
import wordrecg
|
||||
import std/hashes
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
PIdent* = ref TIdent
|
||||
|
||||
@@ -10,9 +10,15 @@
|
||||
## This module implements the symbol importing mechanism.
|
||||
|
||||
import
|
||||
intsets, ast, astalgo, msgs, options, idents, lookups,
|
||||
semdata, modulepaths, sigmatch, lineinfos, sets,
|
||||
modulegraphs
|
||||
ast, astalgo, msgs, options, idents, lookups,
|
||||
semdata, modulepaths, sigmatch, lineinfos,
|
||||
modulegraphs, wordrecg
|
||||
from std/strutils import `%`, startsWith
|
||||
from std/sequtils import addUnique
|
||||
import std/[sets, tables, intsets]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
proc readExceptSet*(c: PContext, n: PNode): IntSet =
|
||||
assert n.kind in {nkImportExceptStmt, nkExportExceptStmt}
|
||||
@@ -107,7 +113,26 @@ proc rawImportSymbol(c: PContext, s, origin: PSym; importSet: var IntSet) =
|
||||
if s.owner != origin:
|
||||
c.exportIndirections.incl((origin.id, s.id))
|
||||
|
||||
proc splitPragmas(c: PContext, n: PNode): (PNode, seq[TSpecialWord]) =
|
||||
template bail = globalError(c.config, n.info, "invalid pragma")
|
||||
result = (nil, @[])
|
||||
if n.kind == nkPragmaExpr:
|
||||
if n.len == 2 and n[1].kind == nkPragma:
|
||||
result[0] = n[0]
|
||||
for ni in n[1]:
|
||||
if ni.kind == nkIdent: result[1].add whichKeyword(ni.ident)
|
||||
else: bail()
|
||||
else: bail()
|
||||
else:
|
||||
result[0] = n
|
||||
if result[0].safeLen > 0:
|
||||
(result[0][^1], result[1]) = splitPragmas(c, result[0][^1])
|
||||
|
||||
proc importSymbol(c: PContext, n: PNode, fromMod: PSym; importSet: var IntSet) =
|
||||
let (n, kws) = splitPragmas(c, n)
|
||||
if kws.len > 0:
|
||||
globalError(c.config, n.info, "unexpected pragma")
|
||||
|
||||
let ident = lookups.considerQuotedIdent(c, n)
|
||||
let s = someSym(c.graph, fromMod, ident)
|
||||
if s == nil:
|
||||
@@ -119,7 +144,7 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym; importSet: var IntSet) =
|
||||
# for an enumeration we have to add all identifiers
|
||||
if multiImport:
|
||||
# for a overloadable syms add all overloaded routines
|
||||
var it: ModuleIter
|
||||
var it: ModuleIter = default(ModuleIter)
|
||||
var e = initModuleIter(it, c.graph, fromMod, s.name)
|
||||
while e != nil:
|
||||
if e.name.id != s.name.id: internalError(c.config, n.info, "importSymbol: 3")
|
||||
@@ -163,19 +188,24 @@ proc addImport(c: PContext; im: sink ImportedModule) =
|
||||
c.imports.add im
|
||||
|
||||
template addUnnamedIt(c: PContext, fromMod: PSym; filter: untyped) {.dirty.} =
|
||||
for it in c.graph.ifaces[fromMod.position].converters:
|
||||
for it in mitems c.graph.ifaces[fromMod.position].converters:
|
||||
if filter:
|
||||
addConverter(c, it)
|
||||
for it in c.graph.ifaces[fromMod.position].patterns:
|
||||
loadPackedSym(c.graph, it)
|
||||
if sfExported in it.sym.flags:
|
||||
addConverter(c, it)
|
||||
for it in mitems c.graph.ifaces[fromMod.position].patterns:
|
||||
if filter:
|
||||
addPattern(c, it)
|
||||
for it in c.graph.ifaces[fromMod.position].pureEnums:
|
||||
loadPackedSym(c.graph, it)
|
||||
if sfExported in it.sym.flags:
|
||||
addPattern(c, it)
|
||||
for it in mitems c.graph.ifaces[fromMod.position].pureEnums:
|
||||
if filter:
|
||||
loadPackedSym(c.graph, it)
|
||||
importPureEnumFields(c, it.sym, it.sym.typ)
|
||||
|
||||
proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) =
|
||||
c.addImport ImportedModule(m: fromMod, mode: importExcept, exceptSet: exceptSet)
|
||||
addUnnamedIt(c, fromMod, it.sym.id notin exceptSet)
|
||||
addUnnamedIt(c, fromMod, it.sym.name.id notin exceptSet)
|
||||
|
||||
proc importAllSymbols*(c: PContext, fromMod: PSym) =
|
||||
c.addImport ImportedModule(m: fromMod, mode: importAll)
|
||||
@@ -201,18 +231,47 @@ proc importForwarded(c: PContext, n: PNode, exceptSet: IntSet; fromMod: PSym; im
|
||||
for i in 0..n.safeLen-1:
|
||||
importForwarded(c, n[i], exceptSet, fromMod, importSet)
|
||||
|
||||
proc importModuleAs(c: PContext; n: PNode, realModule: PSym): PSym =
|
||||
proc importModuleAs(c: PContext; n: PNode, realModule: PSym, importHidden: bool): PSym =
|
||||
result = realModule
|
||||
c.unusedImports.add((realModule, n.info))
|
||||
template createModuleAliasImpl(ident): untyped =
|
||||
createModuleAlias(realModule, c.idgen, ident, n.info, c.config.options)
|
||||
if n.kind != nkImportAs: discard
|
||||
elif n.len != 2 or n[1].kind != nkIdent:
|
||||
localError(c.config, n.info, "module alias must be an identifier")
|
||||
elif n[1].ident.id != realModule.name.id:
|
||||
# some misguided guy will write 'import abc.foo as foo' ...
|
||||
result = createModuleAlias(realModule, nextSymId c.idgen, n[1].ident, realModule.info,
|
||||
c.config.options)
|
||||
result = createModuleAliasImpl(n[1].ident)
|
||||
if result == realModule:
|
||||
# avoids modifying `realModule`, see D20201209T194412 for `import {.all.}`
|
||||
result = createModuleAliasImpl(realModule.name)
|
||||
if importHidden:
|
||||
result.options.incl optImportHidden
|
||||
c.unusedImports.add((result, n.info))
|
||||
c.importModuleMap[result.id] = realModule.id
|
||||
c.importModuleLookup.mgetOrPut(result.name.id, @[]).addUnique realModule.id
|
||||
|
||||
proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym =
|
||||
proc transformImportAs(c: PContext; n: PNode): tuple[node: PNode, importHidden: bool] =
|
||||
result = (nil, false)
|
||||
var ret = default(typeof(result))
|
||||
proc processPragma(n2: PNode): PNode =
|
||||
let (result2, kws) = splitPragmas(c, n2)
|
||||
result = result2
|
||||
for ai in kws:
|
||||
case ai
|
||||
of wImportHidden: ret.importHidden = true
|
||||
else: globalError(c.config, n.info, "invalid pragma, expected: " & ${wImportHidden})
|
||||
|
||||
if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as":
|
||||
ret.node = newNodeI(nkImportAs, n.info)
|
||||
ret.node.add n[1].processPragma
|
||||
ret.node.add n[2]
|
||||
else:
|
||||
ret.node = n.processPragma
|
||||
return ret
|
||||
|
||||
proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
|
||||
let transf = transformImportAs(c, n)
|
||||
n = transf.node
|
||||
let f = checkModuleName(c.config, n)
|
||||
if f != InvalidFileIdx:
|
||||
addImportFileDep(c, f)
|
||||
@@ -228,64 +287,80 @@ proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym =
|
||||
toFullPath(c.config, c.graph.importStack[i+1])
|
||||
c.recursiveDep = err
|
||||
|
||||
var realModule: PSym
|
||||
discard pushOptionEntry(c)
|
||||
result = importModuleAs(c, n, c.graph.importModuleCallback(c.graph, c.module, f))
|
||||
realModule = c.graph.importModuleCallback(c.graph, c.module, f)
|
||||
result = importModuleAs(c, n, realModule, transf.importHidden)
|
||||
popOptionEntry(c)
|
||||
|
||||
#echo "set back to ", L
|
||||
c.graph.importStack.setLen(L)
|
||||
# we cannot perform this check reliably because of
|
||||
# test: modules/import_in_config)
|
||||
when true:
|
||||
if result.info.fileIndex == c.module.info.fileIndex and
|
||||
result.info.fileIndex == n.info.fileIndex:
|
||||
localError(c.config, n.info, "A module cannot import itself")
|
||||
if sfDeprecated in result.flags:
|
||||
if result.constraint != nil:
|
||||
message(c.config, n.info, warnDeprecated, result.constraint.strVal & "; " & result.name.s & " is deprecated")
|
||||
# test: modules/import_in_config) # xxx is that still true?
|
||||
if realModule == c.module:
|
||||
localError(c.config, n.info, "module '$1' cannot import itself" % realModule.name.s)
|
||||
if sfDeprecated in realModule.flags:
|
||||
var prefix = ""
|
||||
if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
|
||||
message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
|
||||
let moduleName = getModuleName(c.config, n)
|
||||
if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and
|
||||
not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"):
|
||||
message(c.config, n.info, warnStdPrefix, realModule.name.s)
|
||||
|
||||
proc suggestMod(n: PNode; s: PSym) =
|
||||
if n.kind == nkImportAs:
|
||||
suggestMod(n[0], realModule)
|
||||
elif n.kind == nkInfix:
|
||||
suggestMod(n[2], s)
|
||||
else:
|
||||
message(c.config, n.info, warnDeprecated, result.name.s & " is deprecated")
|
||||
suggestSym(c.graph, n.info, result, c.graph.usageSym, false)
|
||||
suggestSym(c.graph, n.info, s, c.graph.usageSym, false)
|
||||
suggestMod(n, result)
|
||||
importStmtResult.add newSymNode(result, n.info)
|
||||
#newStrNode(toFullPath(c.config, f), n.info)
|
||||
|
||||
proc transformImportAs(c: PContext; n: PNode): PNode =
|
||||
if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as":
|
||||
result = newNodeI(nkImportAs, n.info)
|
||||
result.add n[1]
|
||||
result.add n[2]
|
||||
else:
|
||||
result = n
|
||||
result = nil
|
||||
|
||||
proc afterImport(c: PContext, m: PSym) =
|
||||
if isCachedModule(c.graph, m): return
|
||||
# fixes bug #17510, for re-exported symbols
|
||||
let realModuleId = c.importModuleMap[m.id]
|
||||
for s in allSyms(c.graph, m):
|
||||
if s.owner.id != realModuleId:
|
||||
c.exportIndirections.incl((m.id, s.id))
|
||||
|
||||
proc impMod(c: PContext; it: PNode; importStmtResult: PNode) =
|
||||
let it = transformImportAs(c, it)
|
||||
var it = it
|
||||
let m = myImportModule(c, it, importStmtResult)
|
||||
if m != nil:
|
||||
# ``addDecl`` needs to be done before ``importAllSymbols``!
|
||||
addDecl(c, m, it.info) # add symbol to symbol table of module
|
||||
importAllSymbols(c, m)
|
||||
#importForwarded(c, m.ast, emptySet, m)
|
||||
afterImport(c, m)
|
||||
|
||||
proc evalImport*(c: PContext, n: PNode): PNode =
|
||||
result = newNodeI(nkImportStmt, n.info)
|
||||
for i in 0..<n.len:
|
||||
let it = n[i]
|
||||
if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
|
||||
let sep = it[0]
|
||||
let dir = it[1]
|
||||
var imp = newNodeI(nkInfix, it.info)
|
||||
imp.add sep
|
||||
imp.add dir
|
||||
imp.add sep # dummy entry, replaced in the loop
|
||||
for x in it[2]:
|
||||
if it.kind in {nkInfix, nkPrefix} and it[^1].kind == nkBracket:
|
||||
let lastPos = it.len - 1
|
||||
var imp = copyNode(it)
|
||||
newSons(imp, it.len)
|
||||
for i in 0 ..< lastPos: imp[i] = it[i]
|
||||
imp[lastPos] = imp[0] # dummy entry, replaced in the loop
|
||||
for x in it[lastPos]:
|
||||
# transform `a/b/[c as d]` to `/a/b/c as d`
|
||||
if x.kind == nkInfix and x[0].ident.s == "as":
|
||||
let impAs = copyTree(x)
|
||||
imp[2] = x[1]
|
||||
var impAs = copyNode(x)
|
||||
newSons(impAs, 3)
|
||||
impAs[0] = x[0]
|
||||
imp[lastPos] = x[1]
|
||||
impAs[1] = imp
|
||||
impMod(c, imp, result)
|
||||
impAs[2] = x[2]
|
||||
impMod(c, impAs, result)
|
||||
else:
|
||||
imp[2] = x
|
||||
imp[lastPos] = x
|
||||
impMod(c, imp, result)
|
||||
else:
|
||||
impMod(c, it, result)
|
||||
@@ -293,7 +368,6 @@ proc evalImport*(c: PContext, n: PNode): PNode =
|
||||
proc evalFrom*(c: PContext, n: PNode): PNode =
|
||||
result = newNodeI(nkImportStmt, n.info)
|
||||
checkMinSonsLen(n, 2, c.config)
|
||||
n[0] = transformImportAs(c, n[0])
|
||||
var m = myImportModule(c, n[0], result)
|
||||
if m != nil:
|
||||
n[0] = newSymNode(m)
|
||||
@@ -304,14 +378,15 @@ proc evalFrom*(c: PContext, n: PNode): PNode =
|
||||
if n[i].kind != nkNilLit:
|
||||
importSymbol(c, n[i], m, im.imported)
|
||||
c.addImport im
|
||||
afterImport(c, m)
|
||||
|
||||
proc evalImportExcept*(c: PContext, n: PNode): PNode =
|
||||
result = newNodeI(nkImportStmt, n.info)
|
||||
checkMinSonsLen(n, 2, c.config)
|
||||
n[0] = transformImportAs(c, n[0])
|
||||
var m = myImportModule(c, n[0], result)
|
||||
if m != nil:
|
||||
n[0] = newSymNode(m)
|
||||
addDecl(c, m, n.info) # add symbol to symbol table of module
|
||||
importAllSymbolsExcept(c, m, readExceptSet(c, n))
|
||||
#importForwarded(c, m.ast, exceptSet, m)
|
||||
afterImport(c, m)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user