Compare commits

..

195 Commits

Author SHA1 Message Date
narimiran
c9f46ca8c9 bump NimVersion to 1.6.8 2022-09-26 13:18:10 +02:00
ringabout
56a15976b5 fixes #19713; Revert "Remove tlsEmulation enabled from Windows + GCC config" (#19119) (#20327)
* Revert "Remove tlsEmulation enabled from Windows + GCC config (#19119) [backport:1.6]"

This reverts commit 77b696c2c9.

* increase nimTlsSize to 48000

* enable for windows

* fixes tests

* fixes tlsEmulation:on

(cherry picked from commit 97259a5ab3)
2022-09-19 15:09:56 +02:00
Tanguy
cba0c20be8 Allow custom pragma on iterators [backport] (#20344)
Allow custom pragma on iterators

(cherry picked from commit 3a5e38ab9d)
2022-09-19 15:08:53 +02:00
ringabout
60f43fb690 fixes #19104; peg Incorrect captures [backport:1.6] (#20352)
* fixes #19104; peg Incorrect captures [backport:1.6]

* add tests

Co-authored-by: khchen <khchen@gmail.com>
(cherry picked from commit 2b80ff2374)
2022-09-19 15:08:45 +02:00
metagn
e32de02f0a fix #13515 [backport] (#20315)
(cherry picked from commit 58e6d439d8)
2022-09-19 15:08:21 +02:00
ringabout
ece219de2f fixes #20303; wasMoved expressions with side effects for ORC (#20307) [backport]
fixes #20303; wasMoved expressions with side effects

(cherry picked from commit bbbfde7341)
2022-09-08 15:00:51 +02:00
Antonis Geralis
8923e34d7f Prevent use-after-free bugs in object variants. Fixes bug #20305 (#20300) [backport]
prevent use-after-free bugs in cased objects

the bug happens specifically when deleting
an item in a seq. The item taking it's place
might not have the same case fields. Then =sink(x[i], move x[xl])
might leave the deleted fields still in memory!
If the new item switches branches again, you get a use-after-free bug.

(cherry picked from commit 8dcf367e52)
2022-09-06 12:47:22 +02:00
narimiran
113bd34b6c remove duplicate definitions of the two iterators 2022-08-31 17:09:21 +02:00
Andreas Rumpf
73e569fec9 fixes the regressions caused by the fix for #20107 [backport] (#20287)
* fixes the regressions caused by the fix for #20107 [backport]

(cherry picked from commit 5211a471c8)
2022-08-31 15:16:47 +02:00
Ivan Yonchovski
bd1ca4bb3f [nimsuggest] fix def call on identifier 2 times on the line (#20228)
- apparently TLineInfo's implementation of `==` ignores the column. After I fixed
the code to use exact TLineInfo comparison I fixed several other issues hidden
by that issue.

- Replaced `tuple[sym, info]` with `SymInfoPair`

(cherry picked from commit d4c0d35b32)
2022-08-31 11:22:57 +02:00
ringabout
0a017b208b remove unused nimfind defines (#20250)
remove unused nimfind

(cherry picked from commit 7d7886b729)
2022-08-31 11:17:03 +02:00
narimiran
d406727016 fix test 2022-08-31 08:31:55 +02:00
ringabout
09d85d8b24 std/options enables stricteffects (#19441)
(cherry picked from commit 16f6dc05fd)
2022-08-25 20:11:37 +02:00
ringabout
e5e445f042 fixes #19973; switch to poll on posix (#20212)
* fixes #19973; switch to poll on posix

* it is fd

* exclude lwip

* fixes lwip

* rename select to timeoutRead

* refactor into timeoutRead/timeoutWrite

* refactor common parts

Co-authored-by: xflywind <43030857+xflywind@users.noreply.github.com>
(cherry picked from commit 2b8f0a7971)
2022-08-23 21:33:02 +02:00
ringabout
aae2356b91 fixes #19967; reset does not work on set [backport: 1.2] (#19968)
* fixes #19967

* use case

* add testcase

* fix typos

* explictly specify other branches

Co-authored-by: xflywind <43030857+xflywind@users.noreply.github.com>
(cherry picked from commit e8556b45f5)
2022-08-23 21:32:47 +02:00
Ivan Yonchovski
5ea0e5608d Build compiler with --noNimblePath (#20168)
- Fixes https://github.com/nim-lang/Nim/issues/18840

(cherry picked from commit ec2bc2a50e)
2022-08-23 16:41:00 +02:00
ringabout
a9485f19a5 fixes #20162; locals doesn't work with ORC [backport] (#20163)
fixes #20162; locals doesn't work with ORC

(cherry picked from commit 25c6491b65)
2022-08-23 13:32:13 +02:00
Andreas Rumpf
dda6181fff fixes #20107 (#20246) [backport]
(cherry picked from commit b1fe1690c4)
2022-08-23 13:31:50 +02:00
ringabout
d79f61e54d fixes #20153; do not escape _ for mysql [backport] (#20164)
* fixes #20153; do not escape `_` for mysql

* add a test

* Update db_mysql.nim

* Update tdb_mysql.nim

Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
(cherry picked from commit 3bd935f331)
2022-08-23 13:31:33 +02:00
ringabout
33a1e3acb9 fixes #20132; fixes the broken jsondoc comand [backport] (#20135)
* fixes #20132; fixes the broken jsondoc comand

* add testcase

(cherry picked from commit 2aeb0d516b)
2022-08-23 13:31:09 +02:00
Andrey Makarov
831fed1c01 Don't require blank line before Markdown code (1.6) (#20216)
Don't require blank line before Markdown code

Fixes bug reported in https://github.com/nim-lang/Nim/pull/20189
affecting nimforum.
(manually backported version to 1.6 from devel)
2022-08-15 18:38:17 -04:00
Yuriy Glukhov
65e0906e69 Fixed compilation of void closureiters with try stmt (#20138) [backport]
(cherry picked from commit 0d734d7966)
2022-08-03 08:54:55 +02:00
quantimnot
c484943cab Change styleCheck to ignore foreign packages (#19822)
* Change `styleCheck` to ignore foreign packages

* Symbols from foreign packages are now ignored.
* Fixed `styleCheck` violations in `compiler` package.
* Added symbol ownership to custom annotation pragmas.
* Minor refactors to cleanup style check callsites.
* Minor internal documentation of reasons why a symbol isn't checked.

Style violations were fixed in the compiler after thet were exposed by
the changes. The compiler wouldn't compile otherwise.

Symbol ownership for custom pragma annotations is needed for checking
the annotation's style. A NPE was raised otherwise.

Fixes #10201
See also nim-lang/RFCs#456

* Fix a misunderstanding about excluding field style checks

I had refactored the callsites of `styleCheckUse` to apply the DRY
principle, but I misunderstood the field access handling in a template
as a general case. This corrects it.

* Fix some `styleCheck` violations in `compiler/evalffi`

The violations were exposed in CI when the compiler was built with
libffi.

* Removed some uneeded transitionary code

* Add changelog entry

Co-authored-by: quantimnot <quantimnot@users.noreply.github.com>
(cherry picked from commit 800cb006e7)
2022-08-02 16:00:11 +02:00
ringabout
000e6875bd fixes broken CI; bump macOS version to macos-11 (#20098)
* bump macOS image on Azure CI to macos-11

##[warning]The macOS-10.15 environment is deprecated, consider switching to macos-11(macos-latest), macos-12 instead. For more details see https://github.com/actions/virtual-environments/issues/5583

* fix CI error

(cherry picked from commit 8ef509b85b)
2022-07-28 07:46:26 +02:00
ringabout
17e61c75a2 fixes #20031; uint64 is an ordinal type since 1.0 (#20094)
* fixes #20031; uint64 is an ordinal type since 1.0

* Update compiler/semstmts.nim

(cherry picked from commit 5bbc5edf43)
2022-07-27 11:48:24 +02:00
ringabout
0277cd5252 Revert "Correct emscripten shortcoming" (#20082)
Revert "Correct emscripten shortcoming (#19987)"

This reverts commit 0e7138417c.
2022-07-25 17:51:13 +08:00
kraptor
621061d62f Correctly detect major version of GCC (#20059)
We were doing a very poor job detecting the major version of GCC by
parsing the output of --version.

This patches uses -dumpversion to make this parsing straightforward and
it also fixes a bunch of compiling issues on different platforms with
custom output for --version switches. For example, openSUSE first line
of the output includes the revision number and the parsing that was
being done did mix that number with the major version and breaks
building the nim compiler (as it doesn't find the 3 dots for an X.Y.Z semver
format, hence returning "false").

In this patch, we simply use -dumpversion (which has been at least from
1993, so we are safe :)

(cherry picked from commit efcb89fa70)
2022-07-25 11:42:27 +02:00
tersec
d53a057f5f Use passc and passl consistently with compiler checking (#20068)
(cherry picked from commit 1a9123eb90)
2022-07-25 11:41:52 +02:00
metagn
7d0bfc6725 fix #20067, fix #18976 [backport] (#20069)
(cherry picked from commit 685bf944aa)
2022-07-25 11:41:13 +02:00
Jacek Sieka
5771a0f9c4 epoll: correct mapping [backport] (#20058)
* epoll: correct mapping

`epoll_data` is a union and `epoll_event` is packed on `amd64`

* names

(cherry picked from commit f2e4407306)
2022-07-25 11:40:59 +02:00
narimiran
5f61f1594d re-apply the change from #19902 2022-07-18 13:50:11 +02:00
flywind
62ac3a01fa [Tiny] correct comment opcDeref => opcLdDeref (#19908)
correct comment opcDeref => opcLdDeref

(cherry picked from commit a65db5e2e9)
2022-07-18 13:47:36 +02:00
flywind
c9e7798978 [cleanup] remove unnecessary procs in vm (#19888)
remove unused procs

(cherry picked from commit 2f4900615a)
2022-07-18 13:47:24 +02:00
Andreas Rumpf
ac7efa1964 fixes #19404 by protecting the memory we borrow from. this replaces crashes with minor memory leaks which seems to be acceptable. In the longer run we need a better VM that didn't grow hacks over a decade. (#19515)
Co-authored-by: flywind <xzsflywind@gmail.com>
(cherry picked from commit ed0dce7292)
2022-07-18 13:46:02 +02:00
Jacek Sieka
8a98177025 fix pthread_mutex_t size (#20055)
(cherry picked from commit c6264ed847)
2022-07-18 07:58:15 +02:00
quantimnot
fd76c00479 Refactor and doc package handling, module name mangling (#19821)
* Refactor and doc package handling, module name mangling

* Consolidate, de-duplicate and extend package handling
* Alter how duplicate module names of a package are handled
* Alter how module names are mangled
* Fix crash when another package is named 'stdlib' (test case added)
* Doc what defines a package in the manual

Modules with duplicate names within a package used to be given 'fake'
packages to resolve conflicts. That prevented the ability to discern if
a module belonged to the current project package or a foreign package.
They now have the proper package owner and the names are mangled in a
consistent manner to prevent codegen clashes.

All module names are now mangled the same. Stdlib was treated special
before, but now it is same as any other package. This fixes a crash
when a foreign package is named 'stdlib'.

Module mangling is altered for both file paths and symbols used by the
backends.

Removed an unused module name to package mapping that may have been
intended for IC. The mapping was removed because it wasn't being used
and was complicating the issue of package modules with duplicate names
not having the proper package owner assigned.

* Fix some tests

* Refactor `packagehandling`

* Remove `packagehandling.withPackageName` and its uses
* Move module path mangling from `packagehandling` to `modulepaths`
* Move `options.toRodFile` to `ic` to break import cycle

* Changed import style to match preferred style

Co-authored-by: quantimnot <quantimnot@users.noreply.github.com>
(cherry picked from commit d30c6419a0)
2022-07-18 07:55:08 +02:00
Jacek Sieka
8786e7dddf testament: use full test name in skips [backport] (#19937)
testament: use full test name in skips
(cherry picked from commit 094d86f997)
2022-07-17 07:15:04 +02:00
flywind
3fd11d7e96 fix #18735; genDepend broken for duplicate module names in separate folders (#19988)
(cherry picked from commit 0180c6179a)
2022-07-17 07:15:04 +02:00
Ivan Yonchovski
a3c2eb04b9 Use module actual file instead of PSym.info (#19956)
After this you can do goto module from module import

(cherry picked from commit b0b9a3e5fa)
2022-07-17 07:15:04 +02:00
Ivan Yonchovski
46f0f6e47e Implement type command (#19944)
* Implement type command

- this will be mapped to textDocument/typeDefinition in LSP protocol. It will be
very useful for `nim` in particular because typically most of the time the type
is inferred.

* Update nimsuggest/nimsuggest.nim

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
(cherry picked from commit e636c211b0)
2022-07-17 07:15:04 +02:00
Mildred Ki'Lya
0e7138417c Correct emscripten shortcoming (#19987)
emscripten reports infinity for every getrlimit() requests, which does
not work when requesting the max number of file descriptors (prlimit64
syscall). This patch provides a default of 1024 which is common on Linux.

This is used in particular in ioselectors_poll.nim and te invalid value
makes it crash.
2022-07-16 17:35:02 -04:00
flywind
e9d5a9d395 [Orc] fixes "streams.readDataStr segafaults" when accepting a string literal (#20019) [backport]
fixes streams.readDataStr accept a string literal

(cherry picked from commit 286fcef68e)
2022-07-15 09:43:48 +02:00
Tanguy
9508b06513 Fix nested finally handling in closureiters [backport] (#19933)
* Fix nested finally handling in closureiters

* Fix CI

* review comment

* third time the charm

* Update compiler/closureiters.nim

Co-authored-by: Dominik Picheta <dominikpicheta@googlemail.com>

Co-authored-by: Dominik Picheta <dominikpicheta@googlemail.com>
(cherry picked from commit fb5fbf1e08)
2022-07-11 21:24:13 +02:00
Jacek Sieka
b1f325d641 sysrand: fix syscall signature [backport] (#19982)
sysrand: fix syscall signature

`syscall` is a `C` varags function

(cherry picked from commit ad0aee5354)
2022-07-07 17:24:45 +02:00
Jacek Sieka
7c7815402a once C++, always C++ [backport] (#19938)
* once C++, always C++

When using `{.compile: "file.cc".}` in a nim module, even when compiling
with `nim c` the C++ compiler should be used - once any C++ file has
been compiled, the C++ linker also needs to be used.

* more strict C++ check

* simplify code

(cherry picked from commit ad430c0daa)
2022-06-30 10:21:24 +02:00
flywind
c9a52971f4 dec inLoop after exiting the while scope in computeLiveRanges [backport] (#19918)
* dec inLoop after exiting the while scope in computeLiveRanges

* add testcase

(cherry picked from commit bcff13debc)
2022-06-30 10:21:14 +02:00
flywind
6f290fa386 [vm]fixes #15974 #12551 #19464 #16020 #16780 #16613 #14553 #19909 #18641 (#19902) [backport]
* revert #12217 since the root problem seems to have been fixed; fix #15974;fix #12551; fix #19464

* fix #16020; fix #16780

* fix tests and #16613

* fix #14553

* fix #19909; skip skipRegisterAddr

* fix #18641

(cherry picked from commit 3cb2d7af05)
2022-06-23 08:34:34 +02:00
Tanguy
1561a83c49 Fix nimRawSetjmp for VCC [backport: 1.2] (#19899)
(cherry picked from commit 40464fa762)
2022-06-20 08:34:58 +02:00
Tanguy
a1f413bcac Windows: enable nimRawSetjmp by default [backport] (#19891)
* Windows: enable nimRawSetjmp by default

See #19197. The default setjmp can randomly segfault on windows

* Attempt to disable the flag for bootstraping

* Disable styleCheck for c_setjmp

(cherry picked from commit 251bdc1d5a)
2022-06-20 08:34:18 +02:00
flywind
2064fda582 [semfold] fix #19199; properly fold uint to float conversion (#19890) [backport]
fix #19199; properly fold float conversion

(cherry picked from commit ab47707586)
2022-06-20 08:32:41 +02:00
Jake Leahy
efe5a33988 Pass headers and body correctly to FetchOptions (#19884) [backport]
* Pass headers to FetchOptions

Don't pass body if method is HttpGet or HttpHead

* Syntax fixes

* Restart CI

(cherry picked from commit 8fa2c0b532)
2022-06-20 08:32:21 +02:00
flywind
4873221429 not generate initStackBottomWith in arc/orc [backport] (#19875)
not generate initStackBottomWith in arc/orc

(cherry picked from commit eefca1b81f)
2022-06-20 08:23:59 +02:00
Ivan Yonchovski
ab0d06869e Initial implementation of nimsuggest v3 (#19826) [backport] (#19892)
* Initial implementation of nimsuggest v3 (#19826)

* Initial implementation of nimsuggest v3

Rework `nimsuggest` to use caching to make usage of ide commands more efficient.
Previously, all commands no matter what the state of the process is were causing
clean build. In the context of Language Server Protocol(LSP) and lsp clients
this was causing perf issues and overall instability. Overall, the goal of v3 is
to fit to LSP Server needs

- added two new commands:
  - `recompile` to do clean compilation
  - `changed` which can be used by the IDEs to notify that a particular file has been changed.
The later can be utilized when using LSP file watches.
  - `globalSymbols` - searching global references

- added `segfaults` dependency to allow fallback to clean build when incremental
fails. I wish the error to be propagated to the client so we can work on fixing
the incremental build failures (typically hitting pointer)

- more efficient rebuild flow. ATM incremental rebuild is triggered when the
command needs that(i. e. it is global) while the commands that work on the
current source rebuild only it

Things missing in this PR:

- Documentation
- Extensive unit testing.

Although functional I still see this more as a POC that this approach can work.

Next steps:
- Implement `sug` request.
- Rework/extend the protocol to allow better client/server communication.
Ideally we will need push events, diagnostics should be restructored to allow
per file notifications, etc.
- implement v3 test suite.
- better logging

* Add tests for v3 and implement ideSug

* Remove typeInstCache/procInstCache cleanup

* Add ideChkFile command

* Avoid contains call when adding symbol info

* Remove log

* Remove segfaults

* Fixed bad cherry-pick resolve

* modulegraphs.dependsOn does not work on transitive modules

- make sure transitive deps are marked as dirty
2022-06-19 08:50:07 +02:00
flywind
06f1828ee2 fix #19862; make widestrs consistent between refc and orc (#19874) [backport]
fix #19862; make widestrs consistent in refc and orc

(cherry picked from commit 1972005439)
2022-06-09 17:15:34 +02:00
flywind
1368316b7f style usages part one (openarray => openArray) (#19321)
* style usages (openArray)

* revert doc changes

(cherry picked from commit 9df195ef58)
2022-05-25 16:33:37 +02:00
Jacek Sieka
b0cbc9a74c std/tasks: fix spelling (#19691) [backport]
why aren't these not being caught by style check options?
--styleCheck:usages finds it.

Co-authored-by: flywind <xzsflywind@gmail.com>
(cherry picked from commit cb6ce80cb8)
2022-05-25 08:08:10 +02:00
Kaushal Modi
64689f932c tests: Fix warnings in tstrscans (#19082)
(cherry picked from commit 2e0db988e7)
2022-05-25 08:08:00 +02:00
Alfred Morgan
3e39f5bfec varargs example erroneously transformed "abc" to "def" (#19781)
(cherry picked from commit 85bc8326ac)
2022-05-24 15:28:24 +02:00
Anthony Dario
28af1e5e45 Fix typo in sequtils documentation (#19789)
Found another small typo.

(cherry picked from commit 19001c070b)
2022-05-24 15:27:28 +02:00
quantimnot
c6e3ad4ab9 Fix default testament target in docs and cli help (#19796)
Co-authored-by: quantimnot <quantimnot@users.noreply.github.com>
(cherry picked from commit a8426fc789)
2022-05-24 15:24:02 +02:00
Jacek Sieka
26707a62fc testament: include extra options in test name (#19801)
there's currently no (simple) way to disambiguate which option failed

(cherry picked from commit 63cca93ea9)
2022-05-24 15:22:51 +02:00
flywind
0b44840299 enable style:usages for stdlib tests [backport: 1.6] (#19715)
* enable style:usages for stdlib tests

* freeAddrInfo

* more tests

* importc

* bufSize

* fix more

* => parseSql and renderSql

(cherry picked from commit 98cebad7de)
2022-05-22 18:20:25 +02:00
Christoph Krybus
02d94966c9 Fix punycode.decode function (#19136)
* Refactor: rename proc to func

* Fix punycode.decode function

This function could only properly decode punycodes containing a single
encoded unicode character. As soon as there was more than one punycode
character group to decode it produced invalid output - the number of
characters was correct, but their position was not.

* Update tpunycode.nim

Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
(cherry picked from commit ade85ee91f)
2022-05-19 13:47:19 +02:00
PMunch
62d1d0516c Remove volatiles when compiling with ARC/ORC (#19545)
This removes volatiles on ARC/ORC targets in NimMain and PreMainInner.
This avoids an issue where they couldn't be optimised out on
microcontrollers leading to larger code. Since the stack bottom doesn't
have to be initialised this way when using ARC or ORC (or None, which is
also covered by this PR) these can be safely removed.

(cherry picked from commit 9a49451124)
2022-05-19 13:46:44 +02:00
narimiran
7d120b83d1 bump NimVersion to 1.6.7 2022-05-19 13:46:44 +02:00
narimiran
0565a70eab bump NimVersion to 1.6.6 2022-05-04 18:15:31 +02:00
Zoom
252df3f1c0 Add 'usages' option to the --stylechecks error msg (#19759)
(cherry picked from commit 278ecad973)
2022-05-04 18:15:00 +02:00
narimiran
608457defc use unsafeAddr 2022-05-03 09:41:45 +02:00
nc-x
4f392727c8 Fix fixAbstractType for user defined typeclasses, fixes #19730 & #18409 (#19732)
(cherry picked from commit 4680ab61c0)
2022-05-02 16:23:20 +02:00
Ivan Yonchovski
4bb2e9e921 Make sure that field usage preserves the original line info (#19751)
Currently `struct.field` will generate a node with `info` that points to the
symbol definition instead of having the actual node location.

(cherry picked from commit e4a2c2d474)
2022-05-02 16:23:07 +02:00
flywind
1788b8b991 fixes #18612; apply cache and memcmp for methods in arc/orc (#19749)
* try using endsWith

* use memcmp

* add cache

* cleanup

* better

* minor

* fix

* improve test coverage for methods with ARC

(cherry picked from commit 8bfc396a4d)
2022-05-02 16:22:47 +02:00
Andreas Rumpf
0e5bf5953e use signed comparisons for the index checking in the hope it improves the code generation (#19712)
(cherry picked from commit ef4ac5a0d2)
2022-04-27 10:33:00 +02:00
flywind
85841cd318 fix NimNode comment repr() regression [backport: 1.2] (#19726)
(cherry picked from commit 15ae9323e8)
2022-04-25 15:00:00 +02:00
Danil Yarantsev
b1045cb693 Really fix StringStream with ARC at compile-time, improve streams test (#19739)
* Fix compile-time StringStream with ARC

* make readDataStr work with ARC, improve test

(cherry picked from commit 2f32b450d3)
2022-04-25 14:58:54 +02:00
Jason Beetham
151b4cc514 Fix string stream crashing when created on nimscript due to last fix (#19717)
(cherry picked from commit dc4cc2dca5)
2022-04-25 14:57:58 +02:00
flywind
f194356d21 fix #19435; don't create TypeBoundOps for tyOpenArray, tyVarargs [backport: 1.6] (#19723)
* fix #19435; openArray wronyly registers typebounds

* add testcase

* don't create TypeBoundOps for tyOpenArray, tyVarargs

(cherry picked from commit efaa6777a4)
2022-04-25 14:51:41 +02:00
huantian
88573da12d Fix doc: list of async backends (#19741)
(cherry picked from commit 02e8aa9660)
2022-04-24 17:21:56 +02:00
flywind
2a68fa71eb fix #19680; check if stderr is static (#19709)
(cherry picked from commit 26bcf18f91)
2022-04-12 09:50:29 +02:00
Jason Beetham
fcd05bd031 StringStreams no longer errors when intialized with literals on arc/orc (#19708)
(cherry picked from commit 26acc97864)
2022-04-12 09:50:22 +02:00
flywind
d38177b11f stylecheck usages part two: stdlib cleanup (#19338)
* stylecheck usages part two: stdlib cleanup

typeinfo.nim: importCompilerProc => importcompilerproc

nre.nim: newLineFlags => newlineFlags

system.nim: JSRoot => JsRoot

ref #19319

* prefer importCompilerProc

(cherry picked from commit ae92eac060)
2022-04-08 12:07:58 +02:00
flywind
9035618347 fix stylecheck bug with nre (#19356)
* stylecheck usages part two: stdlib cleanup

typeinfo.nim: importCompilerProc => importcompilerproc

nre.nim: newLineFlags => newlineFlags

system.nim: JSRoot => JsRoot

ref #19319

* prefer importCompilerProc

* fix stylecheck error with asyncdispatch

it is a partial regression since #12842

* add tests

* don't use echo in tests

* fix stylecheck bug with nre

* Update compiler/linter.nim

* no need to check dotexpr again

* neither did let/var/const

(cherry picked from commit 00775f6880)
2022-04-08 11:24:55 +02:00
flywind
e22d494409 fix stylecheck error with asyncdispatch (#19350)
* stylecheck usages part two: stdlib cleanup

typeinfo.nim: importCompilerProc => importcompilerproc

nre.nim: newLineFlags => newlineFlags

system.nim: JSRoot => JsRoot

ref #19319

* prefer importCompilerProc

* fix stylecheck error with asyncdispatch

it is a partial regression since #12842

* add tests

* don't use echo in tests

(cherry picked from commit 92e5573b20)
2022-04-08 11:24:36 +02:00
Ivan Yonchovski
6365d8c39a [nimsuggest] return the type when on symbol in let/var (#19697) 2022-04-08 11:19:00 +02:00
flywind
b96954ef52 improve the error messages for std/tasks [backport: 1.6] (#19695)
(cherry picked from commit c8aeea9d62)
2022-04-07 21:31:49 +02:00
Miran
bb937f2357 put API changes behind a flag (#19685) 2022-04-07 20:56:39 +02:00
Miran
dcc40b9609 [backport] fix broken SSL tests (#19684)
* [backport] fix broken SSL tests

* remove a flaky one

(cherry picked from commit c322faaf38)
2022-04-06 16:19:14 +02:00
flywind
3177e16b0d fix #18986; Import/except doesn't work on devel [backport: 1.6] (#19687)
* fix #18986; Import/except doesn't work on devel [backport: 1.6]

* add testcase

(cherry picked from commit 5a995ffc53)
2022-04-06 16:19:06 +02:00
flywind
92457cbc39 Fix bug in freshVarForClosureIter. Fixes #18474 (#19675) [backport]
* Fix bug in freshVarForClosureIter. Fixes #18474.

freshVarForClosureIter was returning non-fresh symbols sometimes.
Fixed by making addField return the generated PSym.

* remove discardable

Co-authored-by: Nick Smallbone <nick@smallbone.se>
(cherry picked from commit 83dabb69ae)
2022-04-05 17:32:32 +02:00
narimiran
73c4ede283 fix wrong backport 2022-03-31 20:53:55 +02:00
narimiran
55907a8bf8 Various std net improvements (#19132)
* Variant of  that works with raw IpAddresses.

- Add doc tests for new net proc's.
- Aadd recvFrom impl
- Add recvFrom impl -- tweak handling data var

- Update lib/pure/net.nim
	Co-authored-by: Dominik Picheta <dominikpicheta@googlemail.com>

- cleaning up sendTo args
- remove extra connect test
- cleaning up sendTo args
- fix inet_ntop test
- fix test failing - byte len

* fix test failing - byte len

* debugging odd windows build failure

* debugging odd windows build failure

* more experiments to figure out the windows failure

* try manual assigment on InAddr

Co-authored-by: Jaremy Creechley <jaremy.creechley@panthalassa.com>
(cherry picked from commit 4b5cecd902)
2022-03-31 17:49:04 +02:00
Ștefan Talpalaru
9de1013c94 devel: style fix (#19318)
this allows "--styleCheck:usages --styleCheck:error"

(cherry picked from commit 35cae73aa5)
2022-03-31 14:17:45 +02:00
Jaremy Creechley
589dc2d18f Embedded Network patches - eventfd & socket getters (#19632)
(cherry picked from commit eae29e8eaf)
2022-03-28 12:50:25 +02:00
Jaremy Creechley
0adfe6c5a1 system: thread: stack dealloction on Zephyr (#19633) [backport:1.6]
Try to free the stack allocation when a thread exits. Possibly works for FreeRTOS as well.

(cherry picked from commit 4c8934305c)
2022-03-28 12:50:14 +02:00
flywind
fd2da6da54 fix #8219; nim check/dump shouldn't run single nimscript project [backport: 1.6] (#19641)
* fix #8219; nim check/dump shouldn't run single nimscript project [backport: 1.6]

(cherry picked from commit 82319ef00d)
2022-03-28 07:45:51 +02:00
John Titor
9b560747e8 Fix dial ignoring buffered parameter (#19650) [backport]
(cherry picked from commit 8cdd8867c0)
2022-03-28 07:45:41 +02:00
Andreas Rumpf
bf3a2e010d mitigates #19364 [backport]; we make this bug more unlikely to appear by producing better code to begin with; real fix will come later (#19647)
(cherry picked from commit 12a0f88a52)
2022-03-28 07:45:25 +02:00
Jaremy Creechley
9fcf1a5d4c fix no net compilation on zephyr (#19399)
Co-authored-by: Jaremy J. Creechley <jaremy.creechley@panthalassa.com>
(cherry picked from commit dc8ac66873)
2022-03-24 13:32:09 +01:00
Jaremy Creechley
4877caa462 Implement threads on Zephyr (#19156)
* pthreads setup for zephyr

- enable tweak stack size
- update lib/system/threads.nim
- Fix int/uint in casting pointer.

* add documentation and tweak flag names

* add documentation and tweak flag names

* fix configuration flag names

* fix configuration flag names

* cleanup

Co-authored-by: Jaremy Creechley <jaremy.creechley@panthalassa.com>
(cherry picked from commit 7772ca303c)
2022-03-24 13:26:05 +01:00
Jaremy Creechley
8aa045806c Implement zephyr urandom and monotime (#19142)
* implement urandom for Zephyr

* add monotime on zephyr

Co-authored-by: Jaremy Creechley <jaremy.creechley@panthalassa.com>
(cherry picked from commit 6976d18519)
2022-03-24 13:25:52 +01:00
Jaremy Creechley
b9363c8bb4 Enable customizing PageShift to set PageSize for embedded targets (#19129)
* Enable customizing PageSize (via PageShift).

This enables adjusting PageSize for embedded targets without abusing
cpu16.

* copy nimPageXYZ settings for mmpaptest

* add docs for Nim manual

* add docs for Nim manual

* docs tweaks

Co-authored-by: Jaremy Creechley <jaremy.creechley@panthalassa.com>
(cherry picked from commit 92d6fb86c6)
2022-03-24 13:25:40 +01:00
Jaremy Creechley
1dc47696c0 Add Zephyr Support (#19003)
* Porting Nim to run on Zephyr.

Includes changes to `std/net`.

Squashed commit of the following:
    tweaking more memory / malloc things
    revert back bitmasks
    tweaking nim to use kernel heap as C malloc doesn't work
    fixing socket polling on zephyr
    cleanup getting maximum sockets for process or for rtos'es
    reorganizing and fixing net for async / system
    merge netlite changes back into nativesockets
    merge netlite changes back into nativesockets
    reverting native sockets back
    tweaking nim / zephyr network
    adding option to run 'net-lite' from linux
    bridging zephyr's max connections
    fixing net errors
    fixing compilation with getAddrString
    fixing compilation with getAddrString
    experimenting with a nativesockets_lite ... getAddrString
    experimenting with a nativesockets_lite ... getAddrString
    experimenting with a nativesockets_lite ... getLocalAddr
    experimenting with a nativesockets_lite ... getLocalAddr
    experimenting with a nativesockets_lite ...
    add note regarding incorrect FreeRTOS Sockadd_in fields
    changing to NIM_STATIC_ASSERT
    cleaning up the static_assert error messages
    cleaning up the static_assert error messages
    setting up static assert ftw!
    testing compile time asserts
    reworking Sockaddr objects to more closely match various platforms
    reworking Sockaddr objects to more closely match various platforms
    reworking Sockaddr objects to more closely match various platforms
    finding missing items (issue  #18684)
    fixup posix constants (issue  #18684)
    adding plumbing for zephyr os (issue  #18684)
    adding plumbing for zephyr os (issue  #18684)

* fixing constant capitalizations

* remove extra debug prints and fix TSa_Family/cint issue

* remove extra debug prints and fix TSa_Family/cint issue

* Porting Nim to run on Zephyr.

Includes changes to `std/net`.

Squashed commit of the following:
    tweaking more memory / malloc things
    revert back bitmasks
    tweaking nim to use kernel heap as C malloc doesn't work
    fixing socket polling on zephyr
    cleanup getting maximum sockets for process or for rtos'es
    reorganizing and fixing net for async / system
    merge netlite changes back into nativesockets
    merge netlite changes back into nativesockets
    reverting native sockets back
    tweaking nim / zephyr network
    adding option to run 'net-lite' from linux
    bridging zephyr's max connections
    fixing net errors
    fixing compilation with getAddrString
    fixing compilation with getAddrString
    experimenting with a nativesockets_lite ... getAddrString
    experimenting with a nativesockets_lite ... getAddrString
    experimenting with a nativesockets_lite ... getLocalAddr
    experimenting with a nativesockets_lite ... getLocalAddr
    experimenting with a nativesockets_lite ...
    add note regarding incorrect FreeRTOS Sockadd_in fields
    changing to NIM_STATIC_ASSERT
    cleaning up the static_assert error messages
    cleaning up the static_assert error messages
    setting up static assert ftw!
    testing compile time asserts
    reworking Sockaddr objects to more closely match various platforms
    reworking Sockaddr objects to more closely match various platforms
    reworking Sockaddr objects to more closely match various platforms
    finding missing items (issue  #18684)
    fixup posix constants (issue  #18684)
    adding plumbing for zephyr os (issue  #18684)
    adding plumbing for zephyr os (issue  #18684)

* fixing constant capitalizations

* remove extra debug prints and fix TSa_Family/cint issue

* remove extra debug prints and fix TSa_Family/cint issue

* fixing PR issues

* Porting Nim to run on Zephyr.

Includes changes to `std/net`.

Squashed commit of the following:
    tweaking more memory / malloc things
    revert back bitmasks
    tweaking nim to use kernel heap as C malloc doesn't work
    fixing socket polling on zephyr
    cleanup getting maximum sockets for process or for rtos'es
    reorganizing and fixing net for async / system
    merge netlite changes back into nativesockets
    merge netlite changes back into nativesockets
    reverting native sockets back
    tweaking nim / zephyr network
    adding option to run 'net-lite' from linux
    bridging zephyr's max connections
    fixing net errors
    fixing compilation with getAddrString
    fixing compilation with getAddrString
    experimenting with a nativesockets_lite ... getAddrString
    experimenting with a nativesockets_lite ... getAddrString
    experimenting with a nativesockets_lite ... getLocalAddr
    experimenting with a nativesockets_lite ... getLocalAddr
    experimenting with a nativesockets_lite ...
    add note regarding incorrect FreeRTOS Sockadd_in fields
    changing to NIM_STATIC_ASSERT
    cleaning up the static_assert error messages
    cleaning up the static_assert error messages
    setting up static assert ftw!
    testing compile time asserts
    reworking Sockaddr objects to more closely match various platforms
    reworking Sockaddr objects to more closely match various platforms
    reworking Sockaddr objects to more closely match various platforms
    finding missing items (issue  #18684)
    fixup posix constants (issue  #18684)
    adding plumbing for zephyr os (issue  #18684)
    adding plumbing for zephyr os (issue  #18684)

* fixing constant capitalizations

* remove extra debug prints and fix TSa_Family/cint issue

* remove extra debug prints and fix TSa_Family/cint issue

* Remerge

* fixing constant capitalizations

* remove extra debug prints and fix TSa_Family/cint issue

* remove extra debug prints and fix TSa_Family/cint issue

* fixing PR issues

* fix maxDescriptors on zephyr/freertos

* move maxDescriptors to selector.nim -- fixes compile issue

* change realloc impl on zephyr to match ansi c behavior

* change realloc impl on zephyr to match ansi c behavior

* force compileOnly mode for tlwip

Co-authored-by: Jaremy J. Creechley <jaremy.creechley@wavebaselabs.com>
Co-authored-by: Jaremy Creechley <jaremy.creechley@panthalassa.com>
(cherry picked from commit 141b76e365)
2022-03-24 13:25:20 +01:00
flywind
b741f3cbd3 fix nim check nimscript [backport: 1.6] (#19444)
fix #19440; fix #3858

(cherry picked from commit 7c3c61f2f1)
2022-03-24 12:26:03 +01:00
flywind
9df55a8979 output byref types into --header file [backport: 1.6] (#19505)
* output byref types into --header file

fix #19445

* fix comments

* set targets

(cherry picked from commit 2c01c9c4c8)
2022-03-24 12:26:00 +01:00
Omar Flores
01c38610f5 Fixed formatting error for warningAsError. (#19634)
There was only a single space character between the warning and its description, so it shows up as part of the name (in bold) and with no description.
Copied the way hotCodeReloading was formatted, with the description in a new line.
2022-03-22 16:18:15 +01:00
Andreas Rumpf
40db88d0f8 fixes #19615; emit better code for integer divisions when the divisor… (#19626)
* fixes #19615; emit better code for integer divisions when the divisor is known at compile-time

* proper bugfix: unsigned numbers cannot be -1

(cherry picked from commit c4a0d4c5e3)
2022-03-22 15:46:34 +01:00
Andreas Rumpf
ff819757be fixes #19631 (#19618)
Aliasing is hard and we have to watch out not to compile 'x = f(x.a)' into 'f(x.a, addr x)'

(cherry picked from commit 731eabc930)
2022-03-22 15:45:49 +01:00
Andreas Rumpf
2d2587747f fixes #19575 (#19596) [backport]
* fixes #19575

* better bugfix

(cherry picked from commit 2beefb9aa0)
2022-03-09 16:46:15 +01:00
Andreas Rumpf
0bb7bd07d2 fixes #19569 (#19595) [backport]
* minor code refactorings

* fixes #19569

(cherry picked from commit 0d6795a771)
2022-03-09 16:46:10 +01:00
Ștefan Talpalaru
ebb140edda compile pragma: cache the result sooner (#19554)
extccomp.addExternalFileToCompile() relies on hashes to decide whether
an external C file needs recompilation or not.

Due to short-circuit evaluation of boolean expressions, the procedure
that generates a corresponding hash file is not called the first time an
external file is compiled, so an avoidable recompilation is triggered
the next build.

This patch fixes that by moving the proc call with a desired side
effect from its boolean expression, so it's executed unconditionally.

(cherry picked from commit 0c915b5e47)
2022-03-09 16:45:51 +01:00
Hamid Bluri
ec9e51abe6 fix not flushing stdout in MSYS (#19584)
discussed here https://forum.nim-lang.org/t/8975
2022-03-06 18:03:01 +01:00
VlkrS
b8f1347c99 Fix CPU detection for i386 [backport] (#19583)
See PR #19577
2022-03-06 18:02:14 +01:00
flywind
d7370ce269 fix broken CI (#19472)
* fix broken CI

* fix

* fix tests

(cherry picked from commit 56a901f9e1)
2022-02-23 12:23:48 +01:00
flywind
971b639739 setjump => setjmp [backport: 1.2] (#19496)
(cherry picked from commit d0287748fe)
2022-02-23 11:39:10 +01:00
flywind
5e13d577ac undefine C symbols in JS backend [backport:1.6] (#19437)
fix #19330; fix #19059

(cherry picked from commit 7b09fd70ab)
2022-02-23 11:38:52 +01:00
Dominik Picheta
b6024fe861 Merge pull request from GHSA-ggrq-h43f-3w7m
This fixes a CVE (currently
https://github.com/nim-lang/Nim/security/advisories/GHSA-ggrq-h43f-3w7m)

(cherry picked from commit cb894c7094)
2022-02-23 11:37:43 +01:00
rockcavera
9746d46009 Fix #19038 - making the Nim compiler work again on Windows XP (#19331)
* Update osenv.nim

* Update win_setenv.nim

* Update lib/pure/includes/osenv.nim

* Update lib/pure/includes/osenv.nim

* fixing cstring

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
(cherry picked from commit c7d5b8c83d)
2022-02-23 11:36:45 +01:00
rockcavera
82c930c364 fix 19292 (#19293)
(cherry picked from commit 77ad8b81e4)
2022-02-23 11:36:33 +01:00
rockcavera
c25b7e79cf Fix #19038 - making the Nim compiler work again on Windows XP (#19331)
* Update osenv.nim

* Update win_setenv.nim

* Update lib/pure/includes/osenv.nim

* Update lib/pure/includes/osenv.nim

* fixing cstring

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
(cherry picked from commit c7d5b8c83d)
2022-02-23 11:36:11 +01:00
rockcavera
8fe8aada87 Making TCC work again on Windows --cpu:amd64 - fix #16326 (#19221)
* fix #16326

* removing comments

(cherry picked from commit 7806ec525e)
2022-02-23 11:31:35 +01:00
narimiran
231a135563 bump NimVersion to 1.6.5 2022-02-23 11:25:24 +01:00
flywind
7994556f38 don't use a temp for addr [backport: 1.6] (#19503)
* don't use a temp for addr

fix #19497

* Update compiler/ccgcalls.nim

Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>

* add a test

Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
(cherry picked from commit 27e548140b)
2022-02-08 08:35:28 +01:00
flywind
8c9e88f520 disable nimlsp (#19499)
(cherry picked from commit 28180e47a9)
2022-02-07 19:50:59 +01:00
narimiran
7e52a57121 bump NimVersion to 1.6.4 2022-02-07 16:20:22 +01:00
flywind
35c812fda1 nvro don't touch cdecl types [backport: 1.6] (#19461)
* nvro don't touch cdecl types; fix #19342 again

(cherry picked from commit 0c3892c3c7)
2022-01-28 10:05:51 +01:00
rockcavera
47888c18f7 Update manual.rst (#19301)
(cherry picked from commit ef634cc251)
2022-01-26 18:30:56 +01:00
Andreas Rumpf
a8e040ec30 bugfix: varargs count as open arrays (#19447)
(cherry picked from commit 6ea6225523)
2022-01-26 07:57:21 +01:00
Hugo Granström
2fb1c80f42 change run command for numericalnim (#19448)
Now it makes runs the custom `nimCI` task that installs the external dependencies

(cherry picked from commit 4b723c0f53)
2022-01-25 13:12:41 +01:00
Andreas Rumpf
e1f3c74bdc RST: allow empty number-lines directives just like it was done for a decade; all my documents rely on this feature [backport (#19431)
(cherry picked from commit 15f54de5c4)
2022-01-23 08:14:23 +01:00
flywind
52d2ff601b enable weave (#19363) [backport:1.6]
* enable weave
* workaround CI

(cherry picked from commit 927fa890ec)
2022-01-20 18:06:44 +01:00
James
41b71487af Resolve cross file resolution errors in atomics (#19422) [backport:1.6]
* Resolve call undeclared routine testAndSet

* Fix undeclared field atomicType

(cherry picked from commit 851e515bba)
2022-01-20 18:06:35 +01:00
Tom
3d3b34473b Add noQuit option (#19419) [backport:1.6]
* Add noQuit option

* Add nim prefix in case of conflicts

Co-authored-by: flywind <xzsflywind@gmail.com>

Co-authored-by: flywind <xzsflywind@gmail.com>
(cherry picked from commit ce44cf03cc)
2022-01-20 18:06:26 +01:00
hlaaftana
fc0aec6f1b Optimize lent in JS [backport:1.6] (#19393)
* Optimize lent in JS [backport:1.6]

* addr on lent doesn't work anymore, don't use it

* use unsafeAddr  in test again for older versions

(cherry picked from commit 07c7a8a526)
2022-01-20 18:06:11 +01:00
flywind
7cafd22377 synchronize important_packages with devel 2022-01-17 11:23:51 +01:00
flywind
9aff19f51a mangle names in nimbase.h using cppDefine (#19395) [backport]
mangle names in nimbase.h
fix comments

(cherry picked from commit 4f6b59de96)
2022-01-17 11:16:29 +01:00
flywind
bc823b6487 nrvo shouldn't touch bycopy object[backport:1.2] (#19385)
fix nim-lang#19342

(cherry picked from commit 9b9ae8a487)
2022-01-17 07:38:58 +01:00
Leon
3d3d790c63 docs: Fix broken cross references to rfind in strutils (#19382) [backport]
Fixes three broken cross references to `rfind` in strutils.
Breakage due to signature changes of the `rfind` methods.

Co-authored-by: adigitoleo <adigitoleo@dissimulo.com>
(cherry picked from commit 5853303be0)
2022-01-17 07:38:51 +01:00
gecko
a90cabbe40 Fix remove on last node of singly-linked list [backport:1.6] (#19353)
(cherry picked from commit 955040f0f1)
2022-01-11 08:25:58 +01:00
Zachary Marquez
2539d7a862 fix nim-lang#19343 (#19344) [backport]
Ensure HttpClient onProgress is called once per second
Ensure that reported speed is accurate

(cherry picked from commit 58656aa5bb)
2022-01-11 08:25:42 +01:00
rockcavera
30737b3e7f Update net.nim (#19327) [backport]
(cherry picked from commit 5ec8b60942)
2022-01-11 08:25:20 +01:00
rockcavera
984691bb67 Fix #19314 - fixing broken DoublyLinkedList after adding empty DoublyLinkedList (#19315) [backport]
* Update lists.nim

* Update tlists.nim

(cherry picked from commit 526a32e169)
2022-01-11 08:21:16 +01:00
Andreas Rumpf
5f70b1ab53 fixes #16617 [backport] (#19300)
(cherry picked from commit ac37eed5a2)
2022-01-11 08:20:29 +01:00
rockcavera
afa4bc34b4 Fix #19297 - fixing broken list after adding empty list (#19299)
* Update lists.nim

* Update tlists.nim

* removed check `if b.tail != nil`

The tail of the list being null it is still possible to retrieve its end by going through all nodes from the head. So checking for null from `b.tail` is unnecessary. However, setting `a.tail = b.tail` only if `a.head != nil`, so you don't break a good list with an already broken one.

(cherry picked from commit dc5c88ca79)
2021-12-31 05:14:05 +01:00
Andreas Rumpf
0648cde117 fixes grammar typos [backport] (#19289)
(cherry picked from commit a61bbf7d8d)
2021-12-31 05:13:52 +01:00
Tomohiro
980ec713da Fix #19107 (#19286) [backport]
(cherry picked from commit fdbec969d8)
2021-12-31 05:13:46 +01:00
Jason Beetham
26ed4e5413 Fixed object field access of static objects in generics (#19283) [backport]
(cherry picked from commit fa96e56ad0)
2021-12-31 05:13:27 +01:00
Andreas Rumpf
161736ceb3 Revert "Update uri.nim (#19148) [backport:1.0]" (#19280)
This reverts commit a3ef5df680.

(cherry picked from commit 81d32cf7e5)
2021-12-31 05:13:22 +01:00
Jake Leahy
ce6fa79858 Extract runnables that specify doccmd (#19275) [backport:1.6]
(cherry picked from commit 4da7dbffc5)
2021-12-31 05:13:09 +01:00
Carlo Capocasa
f2e7e5d899 fix bug #14468 zero-width split (#19248) (#19269) 2021-12-20 13:10:51 +01:00
flywind
d4de5d32bc build testament in package CI (#19092)
* build testament in package CI

* Update testament/important_packages.nim

(cherry picked from commit b155864967)
2021-12-20 12:04:46 +01:00
xioren
efdb180f62 use uppercase "type" for Proxy-Authorization header (#19273)
Some servers will reject authorization requests with a lowercase "basic" type. Changing to "Basic" seems to solve these issues.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization
(cherry picked from commit b812431f83)
2021-12-20 09:42:03 +01:00
Constantine Molchanov
095202e218 Use testament to check Norm test pass (#19018)
* Use testament to check Norm test pass

This is what I actually use to test Norm, so it's better to use it.

This should not currently pass. This is expected because this is exactly the problem I want to highlight with this PR. My tests do indeed not pass at the moment.

* Remove clearNimblePath from testament command.

Co-authored-by: flywind <xzsflywind@gmail.com>
(cherry picked from commit b2edc3468c)
2021-12-19 09:19:57 +01:00
Don-Duong Quach
f4e41e6c4f Fixed typo in manual.rst unsafeAssign->uncheckedAssign. Fixes part 1 of #19266 (#19267)
(cherry picked from commit 610516e027)
2021-12-18 17:47:40 +01:00
narimiran
8aec198abc bump NimVersion to 1.6.3 2021-12-18 17:47:12 +01:00
narimiran
9084d9bc02 bump NimVersion to 1.6.2 2021-12-16 17:25:05 +01:00
Miran
48c62ca48b [backport:1.0] json: limit recursion depth (#19252)
* json: limit recursion depth

* do not run this check for JS backend

(cherry picked from commit c17baaefbc)
2021-12-14 18:19:08 +01:00
Nan Xiao
70320482be basicopt.txt: Unify the format (#19251)
(cherry picked from commit 78b86b7942)
2021-12-14 18:18:57 +01:00
Dominik Picheta
e3a07f1997 Update uri.nim (#19148) [backport:1.0]
(cherry picked from commit a3ef5df680)
2021-12-11 09:25:18 +01:00
Ștefan Talpalaru
bcf9448a75 nimc.rst: fix table markup (#19239)
(cherry picked from commit 1a92edeb89)
2021-12-11 05:47:36 +01:00
Ștefan Talpalaru
a2f5e98baa nimRawSetjmp: support Windows (#19197)
* nimRawSetjmp: support Windows

Using `_setjmp()` directly is required to avoid some rare (but very
annoying) exception-related stack corruption leading to segfaults on
Windows, with Mingw-w64 and SEH.
More details: https://github.com/status-im/nimbus-eth2/issues/3121

Also add "nimBuiltinSetjmp" - mostly for benchmarking.

* fix for Apple's Clang++

(cherry picked from commit 69aabdab80)
2021-12-11 05:47:32 +01:00
Andreas Rumpf
a3b370fa87 let Nim support Nimble 0.14 with lock-file support [backport:1.6] (#19236)
(cherry picked from commit 908fc2a22e)
2021-12-10 21:40:06 +01:00
Andreas Rumpf
b7a0c08b4f added --nimMainPrefix switch; fixes #15955; refs #16945 [backport:1.6] (#19235)
(cherry picked from commit 7ff43d07b2)
2021-12-10 21:39:58 +01:00
Andreas Rumpf
46275126b8 fixes a possible 'javascript:' protocol exploit [backport:1.0] (#19134)
* fixes a possible 'javascript:' protocol exploit [backport:1.0]

* add tests

* Update tests/stdlib/trstgen.nim

* add the same logic for hyperlinks

* move the logic into a proc

Co-authored-by: narimiran <narimiran@disroot.org>
(cherry picked from commit 9338aa2497)
2021-12-10 11:47:06 +01:00
MichalMarsalek
83c472c40d move toDeque to after addLast (#19233) [backport:1.0]
Changes the order of procs definitions in order to avoid calling an undefined proc.

(cherry picked from commit c989542339)
2021-12-10 11:46:45 +01:00
Andreas Rumpf
ac57c3193d fixes an old ARC bug: the produced copy/sink operations don't copy the hidden type field for objects with enabled inheritance; fixes #19205 [backport:1.6] (#19232)
(cherry picked from commit 32d4bf3525)
2021-12-10 11:46:35 +01:00
Andreas Rumpf
7cf5e73fb7 fixes a converter handling regression that caused private converters to leak into client modules; fixes #19213; [backport:1.6] (#19229)
(cherry picked from commit 502ac4ed5e)
2021-12-10 11:46:26 +01:00
Tanguy
c14008d77f fix #19193 (#19195) [backport:1.2]
(cherry picked from commit cd592ed85b)
2021-12-08 08:33:12 +01:00
Andreas Rumpf
168a8784f4 re-enable chronos testing once again [backport:1.2] (#19222)
(cherry picked from commit 93c8427fca)
2021-12-08 08:33:05 +01:00
Etan Kissling
ee876aee28 allow HSlice bounded by constants of distinct types (#19219) [backport:1.2]
When creating heterogenous slices of distinct types, the compiler does
not initialize the internal type's `size` before accessing it.
This then leads to this crash message:
```
compiler/int128.nim(594, 11) `false` masking only implemented for 1, 2, 4 and 8 bytes [AssertionError]
```
This patch initializes the `size` properly, fixing the problem.

(cherry picked from commit 0213c7313b)
2021-12-08 08:32:57 +01:00
Andreas Rumpf
8ed903d1d0 fixes #19159 [backport:1.6] (#19210)
(cherry picked from commit 1cbdc1573a)
2021-12-06 11:19:26 +01:00
Andreas Rumpf
bfa8188dac fixes #19198 [backport:1.6] (#19209)
* fixes #19198 [backport:1.6]

* added a test case

(cherry picked from commit f90620fb32)
2021-12-06 11:19:17 +01:00
Andreas Rumpf
56409c15c0 fixes #19015 [backport:1.6] (#19204)
(cherry picked from commit d584dd5b99)
2021-12-06 11:19:08 +01:00
Andreas Rumpf
b614d97a2d misc bugfixes [backport:1.2] (#19203)
(cherry picked from commit 23c117a950)
2021-12-06 11:18:58 +01:00
Andreas Rumpf
2bb3a85a7c renamed 'gc' switch to 'mm'; [backport:1.6] (#19187)
* renamed 'gc' switch to 'mm'; [backport:1.6]
* better docs

(cherry picked from commit a0073d2d4c)
2021-11-26 07:32:15 +01:00
flywind
1247043c90 fix marshal bugs in VM (#19161) [backport:1.6]
(cherry picked from commit fe46c8b5f1)
2021-11-22 16:30:41 +01:00
Clay Sweetser
0ba76622a3 Merge file size fields correctly on Windows (#19141)
* Merge file size fields correctly on Windows

Merge file size fields correctly on Windows

- Merge the two 32-bit file size fields from `BY_HANDLE_FILE_INFORMATION` correctly in `rawToFormalFileInfo`.
- Fixes #19135

* Update os.nim

(cherry picked from commit 0a1049881e)
2021-11-22 16:30:26 +01:00
Anuken
ab6770e77f Fix undeclared 'SYS_getrandom' on emscripten (#19144)
(cherry picked from commit 270a5a372d)
2021-11-22 16:29:54 +01:00
Andreas Rumpf
c7920e9f87 fixes .raises inference for newSeq builtin under --gc:orc [backport] (#19158)
(cherry picked from commit 309ec7167e)
2021-11-17 09:26:25 +01:00
Andreas Rumpf
167881bb83 fixes #19051 [backport:1.6] (#19133)
(cherry picked from commit c6fc3b2eae)
2021-11-17 09:26:08 +01:00
flywind
73366c015f update manual (#19130) [backport]
(cherry picked from commit 3aaa12dbe5)
2021-11-17 09:25:57 +01:00
orthoplex
cfee71e779 fixed colorNames sorting mistake (#19125) [backport]
(cherry picked from commit 528ef6c218)
2021-11-17 09:25:47 +01:00
Ryan Oldenburg
1090b0c4af Remove tlsEmulation enabled from Windows + GCC config (#19119) [backport:1.6]
This flag has a very significant performance impact on programs compiled with --threads:on. It is also apparently not needed anymore for standard circumstances. Can we remove the config? See https://github.com/nim-lang/Nim/issues/18146#issuecomment-876802676 for discussion and perf impact. [backport:1.6]

(cherry picked from commit 77b696c2c9)
2021-11-11 16:16:45 +01:00
Andreas Rumpf
3f6de926f0 fixes #14470 [backport:1.2] (#19115)
(cherry picked from commit 15157d06c3)
2021-11-11 16:16:31 +01:00
Andrey Makarov
13343180b8 fix nimindexterm in rst2tex/doc2tex [backport] (#19106)
* fix nimindexterm (rst2tex/doc2tex) [backport]

* Add support for indexing in rst

(cherry picked from commit 997ccc5889)
2021-11-11 16:16:23 +01:00
Andreas Rumpf
95dce90467 fixes #19011 [backport:1.6] (#19114)
(cherry picked from commit 6ff61766da)
2021-11-11 16:16:11 +01:00
Andreas Rumpf
f85e09633d fixes #19013 [backport:1.6] (#19111)
* fixes #19013 [backport:1.6]

* added test case

(cherry picked from commit b7c66ce860)
2021-11-11 16:16:03 +01:00
Andreas Rumpf
575450dfec fixes another effect inference bug [backport:1.6] (#19100)
* fixes another effect inference bug [backport:1.6]

(cherry picked from commit fce89cb60a)
2021-11-11 16:15:51 +01:00
Andreas Rumpf
6a2babac47 fixes #19078 [backport] (#19090)
(cherry picked from commit 9d51197aa4)
2021-11-03 15:06:53 +01:00
haxscramper
a6e192f020 [FIX] Do not break formatted string line (#19085) [backport]
Otherwise, compiler produces broken error message - `$1` is not interpolated

`Error: The $1 type doesn't have a default value. The following fields must be initialized: importGraph.`

(cherry picked from commit 4c510d5577)
2021-11-03 15:06:40 +01:00
flywind
233c6e9fb3 fix #18410 (Errors initializing an object of RootObj with the C++ backend) [backport] (#18836)
* fix #18410

* one line comment

* typo

* typo

* cover cpp

(cherry picked from commit 2f730afe9e)
2021-11-03 15:06:31 +01:00
Derek 呆
97286db546 fix #18971 (#19070) [backport:1.6]
since the example code return value from global variable, instead
of first argument, the `n.len` is 1 which causes compiler crashes.

(cherry picked from commit f755e452d2)
2021-11-03 15:06:24 +01:00
Timothy Alexander
1ac029c0f6 Fix #19052; [backport:1.6.0] (#19053)
* Fix #19052; [backport:1.6.0]

Adds a compile flag to avoid a getrandom syscall, fixing #19052.

This is neccesary when the getrandom syscall is missing, as noted in #19052, particularly in kernel versions < 3.17 when getrandom was introduced. Specifically relevant is this is missing from kernel 3.10, which is the supported kernel throughout RHEL 7 and CentOS 7, which is widely used at many organizations. Without this, versions of nim that include sysrand (i.e. versions >= 1.6.0) will not compile without modification, however with this change a compile flag may be used to fall back using /dev/urandom as done with any unknown Posix OS (preferred here as a fallback since it already supplies a cryptographically secure PRNG and existing code deals with entropy pool init, etc).

The change is placed behind a compile flag, as discussed in github ticket #19052 (summed up here):
* First, I can't seem to catch that a importc such as SYS_getrandom is declared without using it (the declared proc returns true, but compiler throws an undeclared identifier flag when referencing it).
* Second, it seemed preferable to be behaviorally explicit vs implicit when considering this is intended to be a cryptographically secure PRNG.
* Third, if I intend to compile on a kernel >= 3.17 while running the binary on at least one system < 3.17, I'll want to be able to target this without relying on a compile time determination if the getrandom syscall is available.

* Documenting compile flag for -d:nimNoGetRandom and adding changelog entry
Related to #19052 and comments in PR #19053. Also created a new changelog file since none currently exists.

Co-authored-by: Timothy Alexander <talexander@midwestlabs.com>
(cherry picked from commit dde556665a)
2021-11-03 15:05:32 +01:00
Andreas Rumpf
b18b636ea6 use two underscores for easy demangling [backport:1.6] (#19028)
(cherry picked from commit 1a45da9150)
2021-10-27 11:06:42 +02:00
narimiran
ac89e06c6e bump NimVersion to 1.6.1 2021-10-27 11:05:16 +02:00
Etan Kissling
861b625a66 allow converting static vars to openArray (#19047)
When assigning constant output to a seq, and then passing that static
seq to other functions that take `openArray`, the compiler may end up
producing errors, as it does not know how to convert `static[seq[T]]`
to `openArray[T]`. By ignoring the `static` wrapper on the type for
the purpose of determining data memory location and length, this gets
resolved cleanly. Unfortunately, it is relatively tricky to come up
with a minimal example, as there are followup problems from the failing
conversion, e.g., this may lead to `internal error: inconsistent
environment type`, instead of the relevant `openArrayLoc` error message.

(cherry picked from commit 490c4226a5)
2021-10-27 11:03:22 +02:00
narimiran
727c6378d2 bump NimVersion to 1.6.0 2021-10-18 16:36:47 +02:00
2601 changed files with 63899 additions and 147209 deletions

33
.builds/freebsd.yml Normal file
View File

@@ -0,0 +1,33 @@
## DO NO EDIT DIRECTLY! auto-generated by `nim r tools/ci_generate.nim`
# 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:
NIM_TESTAMENT_BATCH: "0_1"
CC: /usr/bin/clang
tasks:
- setup: |
set -e
cd Nim
. ci/funs.sh && nimBuildCsourcesIfNeeded
echo 'export PATH=$HOME/Nim/bin:$PATH' >> $HOME/.buildenv
- test: |
set -e
cd Nim
. ci/funs.sh && nimInternalBuildKochAndRunCI
triggers:
- action: email
condition: failure
to: Andreas Rumpf <rumpf_a@web.de>

33
.builds/openbsd_0.yml Normal file
View File

@@ -0,0 +1,33 @@
## DO NO 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: |
set -e
cd Nim
. ci/funs.sh && nimBuildCsourcesIfNeeded
echo 'export PATH=$HOME/Nim/bin:$PATH' >> $HOME/.buildenv
- test: |
set -e
cd Nim
. ci/funs.sh && nimInternalBuildKochAndRunCI
triggers:
- action: email
condition: failure
to: Andreas Rumpf <rumpf_a@web.de>

33
.builds/openbsd_1.yml Normal file
View File

@@ -0,0 +1,33 @@
## DO NO 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: |
set -e
cd Nim
. ci/funs.sh && nimBuildCsourcesIfNeeded
echo 'export PATH=$HOME/Nim/bin:$PATH' >> $HOME/.buildenv
- test: |
set -e
cd Nim
. ci/funs.sh && nimInternalBuildKochAndRunCI
triggers:
- action: email
condition: failure
to: Andreas Rumpf <rumpf_a@web.de>

53
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,53 @@
---
name: Bug report
about: Have you found an unexpected behavior? Use this template.
title: Think about the title, twice
labels: ''
assignees: ''
---
(Consider writing a PR targetting devel branch after filing this, see [contributing.html](https://nim-lang.github.io/Nim/contributing.html)).
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
```

View File

@@ -1,73 +0,0 @@
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 if possible.
Reports with a reproducible example or detailed information will likely receive fixes faster.
- type: textarea
id: nim-version
attributes:
label: Nim Version
description: |
Can be obtained from `nim -v` on the command line along with the OS/architecture.
For development versions, including the commit hash may help.
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
Describe the problem. Code example can be given here.
placeholder: Bug reports with reproducible code or detailed information will be fixed faster.
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 or 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 or detailed information will be fixed faster.
render: text
- type: textarea
id: known-workarounds
attributes:
label: Known Workarounds
description: Provide any known workarounds.
validations:
required: false
- type: textarea
id: extra-info
attributes:
label: Additional Information
description: Any additional relevant information.
validations:
required: false
- type: markdown
attributes:
value: |
- Please check whether the problem still exists in the devel branch, which can be installed via [choosenim](https://github.com/nim-lang/choosenim/), [nightlies](https://github.com/nim-lang/nightlies/), or by [creating a temporary build of the compiler](https://nim-lang.github.io/Nim/intern.html#debugging-the-compiler-building-an-instrumented-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 by [bisecting](https://nim-lang.github.io/Nim/intern.html#bisecting-for-regressions) or at least trying known past releases (e.g. `choosenim 2.0.0`).
The Nim repo also supports bisecting in issue comments by adding `!nim c`, `!nim js` etc. before a code block, see [nimrun-action](https://github.com/juancarlospaco/nimrun-action).
- [Please, consider a Donation for the Nim project.](https://nim-lang.org/donate.html)

View File

@@ -0,0 +1,35 @@
---
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.
-->

View File

@@ -1,72 +0,0 @@
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)

View File

@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

69
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
# 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

View File

@@ -1,38 +0,0 @@
# 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@v7
- name: Install OpenSSL (Windows)
if: |
runner.os == 'Windows'
run: choco install openssl.light --version=1.1.1.0 # OpenSSL 3.x removed SSL_library_init
shell: 'powershell'
# v2 wont work here, because uses "hardcoded" nim versions, action "dynamically" finds version with bug.
- 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 }}

View File

@@ -2,10 +2,10 @@ name: Nim Docs CI
on:
push:
paths:
- 'compiler/**.nim'
- 'compiler/docgen.nim'
- 'compiler/renderverbatim.nim'
- 'config/nimdoc.cfg'
- 'doc/**.rst'
- 'doc/**.md'
- 'doc/nimdoc.css'
- 'lib/**.nim'
- 'nimdoc/testproject/expected/testproject.html'
@@ -13,14 +13,13 @@ on:
- 'tools/kochdocs.nim'
- '.github/workflows/ci_docs.yml'
- 'koch.nim'
pull_request:
# Run only on changes on these files.
paths:
- 'compiler/**.nim'
- 'compiler/docgen.nim'
- 'compiler/renderverbatim.nim'
- 'config/nimdoc.cfg'
- 'doc/**.rst'
- 'doc/**.md'
- 'doc/nimdoc.css'
- 'lib/**.nim'
- 'nimdoc/testproject/expected/testproject.html'
@@ -29,10 +28,6 @@ on:
- '.github/workflows/ci_docs.yml'
- 'koch.nim'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
strategy:
@@ -41,11 +36,11 @@ jobs:
target: [linux, windows, osx]
include:
- target: linux
os: ubuntu-22.04
os: ubuntu-20.04
- target: windows
os: windows-latest
os: windows-2019
- target: osx
os: macos-15
os: macos-11
name: ${{ matrix.target }}
runs-on: ${{ matrix.os }}
@@ -53,7 +48,7 @@ jobs:
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v2
with:
fetch-depth: 2
@@ -109,7 +104,7 @@ jobs:
if: |
github.event_name == 'push' && github.ref == 'refs/heads/devel' &&
matrix.target == 'linux'
uses: crazy-max/ghaction-github-pages@v5
uses: crazy-max/ghaction-github-pages@v1
with:
build_dir: doc/html
env:

View File

@@ -1,30 +1,14 @@
name: Packages CI
on:
pull_request:
push:
branches:
- 'devel'
- 'version-2-2'
- 'version-2-0'
- 'version-1-6'
- 'version-1-2'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
batch: ["0_3", "1_3", "2_3"] # list of `index_num`
include:
- os: ubuntu-latest
cpu: amd64
- os: macos-latest
cpu: arm64
os: [ubuntu-20.04, macos-11]
cpu: [amd64]
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
@@ -33,27 +17,26 @@ jobs:
NIM_TESTAMENT_BATCH: ${{ matrix.batch }}
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v2
with:
fetch-depth: 2
- name: 'Install node.js'
uses: actions/setup-node@v7
- name: 'Install node.js 16.x'
uses: actions/setup-node@v2
with:
node-version: 24
node-version: '16.x'
- name: 'Install dependencies (Linux amd64)'
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
run: |
sudo apt-get update -qq
sudo apt-fast update -qq
DEBIAN_FRONTEND='noninteractive' \
sudo apt-get install --no-install-recommends -yq \
sudo apt-fast install --no-install-recommends -yq \
libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \
valgrind libc6-dbg libblas-dev liblapack-dev libpcre3 xorg-dev
valgrind libc6-dbg libblas-dev xorg-dev
- name: 'Install dependencies (macOS)'
if: runner.os == 'macOS'
run: brew install boehmgc make sfml gtk+3
# XXX can't find boehm and gtk on macos 13
- name: 'Install dependencies (Windows)'
if: runner.os == 'Windows'
shell: bash

View File

@@ -1,79 +0,0 @@
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-22.04]
cpu: [amd64]
name: '${{ matrix.os }}'
runs-on: ${{ matrix.os }}
steps:
- name: 'Checkout'
uses: actions/checkout@v7
with:
fetch-depth: 2
- name: 'Install node.js'
uses: actions/setup-node@v7
with:
node-version: 24
- 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: '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@v9
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);
}

View File

@@ -1,25 +0,0 @@
# 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@v11
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.

11
.gitignore vendored
View File

@@ -3,9 +3,9 @@
!*.*
# Cache
nimcache*/
rnimcache*/
dnimcache*/
nimcache/
rnimcache/
dnimcache/
*.o
!/icons/*.o
@@ -67,8 +67,6 @@ testament.db
/csources
/csources_v1
/csources_v2
/csources_v3
/dist/
# /lib/fusion # fusion is now unbundled; `git status` should reveal if it's there so users can act on it
@@ -87,7 +85,6 @@ tweeter_test.db
/tests/megatest.nim
/tests/ic/*_temp.nim
/tests/ic/*_mm/
/tests/navigator/*_temp.nim
@@ -112,5 +109,3 @@ htmldocs
nimdoc.out.css
# except here:
!/nimdoc/testproject/expected/*
pkgs/
/compiler/compiler/

62
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,62 @@
# xxx unused, out of date
image: ubuntu:18.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

View File

@@ -1,8 +1,7 @@
trigger:
branches:
include:
- 'devel'
- 'version-*'
- '*'
pr:
branches:
include:
@@ -20,32 +19,32 @@ jobs:
strategy:
matrix:
Linux_amd64:
vmImage: 'ubuntu-24.04'
vmImage: 'ubuntu-18.04'
CPU: amd64
# 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_arm64:
vmImage: 'macos-15'
CPU: arm64
OSX_arm64_cpp:
vmImage: 'macos-15'
CPU: arm64
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-11'
CPU: amd64
OSX_amd64_cpp:
vmImage: 'macOS-11'
CPU: amd64
NIM_COMPILE_TO_CPP: true
Windows_amd64_batch0_3:
vmImage: 'windows-2025'
vmImage: 'windows-2019'
CPU: amd64
# see also: `NIM_TEST_PACKAGES`
NIM_TESTAMENT_BATCH: "0_3"
Windows_amd64_batch1_3:
vmImage: 'windows-2025'
vmImage: 'windows-2019'
CPU: amd64
NIM_TESTAMENT_BATCH: "1_3"
Windows_amd64_batch2_3:
vmImage: 'windows-2025'
vmImage: 'windows-2019'
CPU: amd64
NIM_TESTAMENT_BATCH: "2_3"
@@ -73,19 +72,17 @@ jobs:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
displayName: 'Install node.js 20.x'
versionSpec: '16.x'
displayName: 'Install node.js 16.x'
condition: and(succeeded(), eq(variables['skipci'], 'false'))
- bash: |
set -e
. ci/funs.sh
echo_run sudo add-apt-repository universe
echo_run sudo apt-get update -qq
echo_run sudo apt-fast update -qq
DEBIAN_FRONTEND='noninteractive' \
echo_run sudo apt-get install --no-install-recommends -yq \
gcc-14 g++-14 libpcre3 liblapack-dev libpcre3 liblapack-dev libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev valgrind libc6-dbg
echo_run sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 60 --slave /usr/bin/g++ g++ /usr/bin/g++-14
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(succeeded(), eq(variables['skipci'], 'false'), eq(variables['Agent.OS'], 'Linux'), eq(variables['CPU'], 'amd64'))
@@ -102,16 +99,15 @@ jobs:
Pin-Priority: 1001
EOF
# echo_run sudo apt-get update -qq
echo_run sudo apt-get update -qq || echo "failed, see bug #17343"
# echo_run sudo apt-fast update -qq
echo_run sudo apt-fast update -qq || echo "failed, see bug #17343"
# `:i386` (e.g. in `libffi-dev:i386`) is needed otherwise you may get:
# `could not load: libffi.so` during dynamic loading.
DEBIAN_FRONTEND='noninteractive' \
echo_run sudo apt-get install --no-install-recommends --allow-downgrades -yq \
echo_run 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
@@ -133,7 +129,6 @@ jobs:
- bash: brew install boehmgc make sfml
displayName: 'Install dependencies (OSX)'
condition: and(succeeded(), eq(variables['skipci'], 'false'), eq(variables['Agent.OS'], 'Darwin'))
# XXX can't find boehm on macos 13
- bash: |
set -e

View File

@@ -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/debug/nim-gdb.py"
GDB_PYTHON_MODULE_PATH="$NIM_SYSROOT/tools/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

View File

@@ -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\debug\nim-gdb.py
set @GDB_PYTHON_MODULE_PATH=%NIM_ROOT%\tools\nim-gdb.py
set @NIM_GDB=gdb.exe
@echo source %@GDB_PYTHON_MODULE_PATH%> wingdbcommand.txt

View File

@@ -24,6 +24,6 @@ if not exist %nim_csources% (
cd ..
copy /y bin\nim.exe %nim_csources%
)
bin\nim.exe c --noNimblePath --skipUserCfg --skipParentCfg --hints:off koch
koch boot -d:release --skipUserCfg --skipParentCfg --hints:off
koch tools --skipUserCfg --skipParentCfg --hints:off
bin\nim.exe c --noNimblePath --skipUserCfg --skipParentCfg --hints:off koch
koch boot -d:release --skipUserCfg --skipParentCfg --hints:off
koch tools --skipUserCfg --skipParentCfg --hints:off

View File

@@ -1,160 +1,74 @@
# v2.x.x - yyyy-mm-dd
# v1.8.x - yyyy-mm-dd
## Changes affecting backward compatibility
- `-d:nimPreviewFloatRoundtrip` becomes the default. `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). Use `-d:nimLegacySprintf` to emulate old behaviors.
- The `default` parameter of `tables.getOrDefault` has been renamed to `def` to
avoid conflicts with `system.default`, so named argument usage for this
parameter like `getOrDefault(..., default = ...)` will have to be changed.
- With `-d:nimPreviewCheckedClose`, the `close` function in the `std/syncio` module now raises an IO exception in case of an error.
- Unknown warnings and hints now gives warnings `warnUnknownNotes` instead of
errors.
- With `-d:nimPreviewAsmSemSymbol`, backticked symbols are type checked in the `asm/emit` statements.
- The bare `except:` now panics on `Defect`. Use `except Exception:` or `except Defect:` to catch `Defect`. `--legacy:noPanicOnExcept` is provided for a transition period.
- With `-d:nimPreviewCStringComparisons`, comparsions (`<`, `>`, `<=`, `>=`) between cstrings switch from reference semantics to value semantics like `==` and `!=`.
- `std/parsesql` has been moved to a nimble package, use `nimble` or `atlas` to install it.
- With `-d:nimPreviewDuplicateModuleError`, importing two modules that share the same name becomes a compile-time error. This includes importing the same module more than once. Use `import foo as foo1` (or other aliases) to avoid collisions.
- Adds the switch `--mangle:nim|cpp`, which selects `nim` or `cpp` style name mangling when used with `debuginfo` on, defaults to `cpp`.
- The second parameter of `succ`, `pred`, `inc`, and `dec` in `system` now accepts `SomeInteger` (previously `Ordinal`).
- Bitshift operators (`shl`, `shr`, `ashr`) now apply bitmasking to the right operand in the C/C++/VM/JS backends.
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
- Procedure compatibility also checks the backend representation of the
parameter and result types, not just their source-level shape. Use
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
- Optional parameters in combination with `: body` syntax (RFC #405) are now opt-in via
`experimental:flexibleOptionalParams`.
## Standard library additions and changes
[//]: # "Additions:"
## Standard library additions and changes
- Added `system.readRawDataStable`, a companion to `readRawData` that returns a
raw `ptr UncheckedArray[char]` into a string's character data which stays valid
across moves and copies of the string value. It is available under every string
implementation (refc, ARC/ORC and `--strings:sso`) with the same signature, so
code can pin an interior buffer pointer today and be ready for `--strings:sso`
without `when declared` guards. Under `--strings:sso` it promotes a small inline
string to its heap representation first; under the other implementations the data
is already heap-resident, so it is equivalent to `readRawData`.
- `macros.parseExpr` and `macros.parseStmt` now accept an optional
filename argument for more informative errors.
- Module `colors` expanded with missing colors from the CSS color standard.
- Fixed `lists.SinglyLinkedList` being broken after removing the last node ([#19353](https://github.com/nim-lang/Nim/pull/19353)).
- `setutils.symmetricDifference` along with its operator version
`` setutils.`-+-` `` and in-place version `setutils.toggle` have been added
to more efficiently calculate the symmetric difference of bitsets.
- `strutils.multiReplace` overload for character set replacements in a single pass.
Useful for string sanitation. Follows existing multiReplace semantics.
- `std/files` adds:
- Exports `CopyFlag` enum and `FilePermission` type for fine-grained control of file operations
- New file operation procs with `Path` support:
- `getFilePermissions`, `setFilePermissions` for managing permissions
- `tryRemoveFile` for file deletion
- `copyFile` with configurable buffer size and symlink handling
- `copyFileWithPermissions` to preserve file attributes
- `copyFileToDir` for copying files into directories
- `std/dirs` adds:
- New directory operation procs with `Path` support:
- `copyDir` with special file handling options
- `copyDirWithPermissions` to recursively preserve attributes
- `system.setLenUninit` now supports refc, JS and VM backends.
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
Modes include `Nim` (default, fully compatible) and two new experimental modes:
`Lax` and `Gnu` for different option parsing behaviors.
- `std/symlinks.expandSymlink` now supports Windows symlinks and junctions with
POSIX-like single-hop `readlink` semantics.
- `std/nre2` is added to replace deprecated NRE.
- `system.typeof` adds a new parameter `modifierMode` to specify how type modifiers are handled.
[//]: # "Changes:"
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function.
- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation.
- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`.
- `std/re` and `std/nre` are deprecated as PCRE library is obsolete.
Use https://github.com/nitely/nim-regex or `std/nre2`.
See: https://github.com/nim-lang/Nim/issues/23668.
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
works without single-quoting.
- `std/uri`: The `?` operator now appends query parameters to an existing query
string instead of replacing it. Fixes [#19782](https://github.com/nim-lang/Nim/issues/19782).
## Language changes
- An experimental option `--experimental:typeBoundOps` has been added that
implements the RFC https://github.com/nim-lang/RFCs/issues/380.
This makes the behavior of interfaces like `hash`, `$`, `==` etc. more
reliable for nominal types across indirect/restricted imports.
- 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
# objs.nim
import std/hashes
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
Obj* = object
x*, y*: int
z*: string # to be ignored for equality
Foo = object
Bar {.multiply: 3.} = object
x, y, z: int
Baz = object
proc `==`*(a, b: Obj): bool =
a.x == b.x and a.y == b.y
# becomes
proc hash*(a: Obj): Hash =
$!(hash(a.x) &! hash(a.y))
type
Foo = object
Bar1 = object
x, y, z: int
Bar2 = object
x, y, z: int
Bar3 = object
x, y, z: int
Baz = object
```
```nim
# main.nim
{.experimental: "typeBoundOps".}
from objs import Obj # objs.hash, objs.`==` not imported
import std/tables
var t: Table[Obj, int]
t[Obj(x: 3, y: 4, z: "debug")] = 34
echo t[Obj(x: 3, y: 4, z: "ignored")] # 34
```
See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#typeminusbound-overloads)
for more information.
- Seven more Unicode characters are now parsed as operators, implementing the RFC
https://github.com/nim-lang/RFCs/issues/571: `⟑ ⟇ ⩓ ⩔ ■ □ ☆`. They all have the
same priority as `*` (multiplication). As with the other Unicode operators, Nim
only lexes them; their meaning is up to user code.
- [Case statement macros](manual.html#macros-case-statement-macros) are no longer experimental,
meaning you no longer need to enable the experimental switch `caseStmtMacros` to use them.
## Compiler changes
- Fixed a bug where `sizeof(T)` inside a `typedesc` template called from a generic type's
`when` clause would error with "'sizeof' requires '.importc' types to be '.completeStruct'".
The issue was that `hasValuelessStatics` in `semtypinst.nim` didn't recognize
`tyTypeDesc(tyGenericParam)` as an unresolved generic parameter.
- `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` now only applies to the current package.
## Tool changes
- Added `--raw` flag when generating JSON docs to not render markup.
- Added `--stdinfile` flag to name of the file used when running program from stdin (defaults to `stdinfile.nim`)
- Added `--styleCheck:warning` flag to treat style check violations as warnings.
- 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".)
## Documentation changes
- Nim now supports Nimble version 0.14 which added support for lock-files. This is done by
a simple configuration change setting that you can do yourself too. In `$nim/config/nim.cfg`
replace `pkgs` by `pkgs2`.
- Added documentation for the `completeStruct` pragma in the manual.
- There is a new switch `--nimMainPrefix:prefix` to influence the `NimMain` that the
compiler produces. This is particularly useful for generating static libraries.

31
changelogs/changelog.md Normal file
View File

@@ -0,0 +1,31 @@
# v1.xx.x - yyyy-mm-dd
## Changes affecting backward compatibility
## Standard library additions and changes
### New compile flag (`-d:nimNoGetRandom`) when building `std/sysrand` to remove dependency on 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 stdlib (sysrand in particular).
## Language changes
## Compiler changes
## Tool changes

View File

@@ -53,7 +53,7 @@
- [``joyent_http_parser``](https://github.com/nim-lang/joyent_http_parser)
- Proc [toCountTable](https://nim-lang.org/docs/tables.html#toCountTable,openArray[A])
now produces a `CountTable` with values corresponding to the number of occurrences
now produces a `CountTable` with values correspoding to the number of occurrences
of the key in the input. It used to produce a table with all values set to `1`.
Counting occurrences in a sequence used to be:
@@ -339,7 +339,7 @@ for i in a..b:
- Fixed "ReraiseError when using try/except within finally block"
([#5871](https://github.com/nim-lang/Nim/issues/5871))
- Fixed "Range type inference leads to counter-intuitive behaviour"
- Fixed "Range type inference leads to counter-intuitive behvaiour"
([#5854](https://github.com/nim-lang/Nim/issues/5854))
- Fixed "JSON % operator can fail in extern procs with dynamic types"
([#6385](https://github.com/nim-lang/Nim/issues/6385))
@@ -403,7 +403,7 @@ for i in a..b:
([#6589](https://github.com/nim-lang/Nim/issues/6589))
- Fixed "Generated c code calls function twice"
([#6292](https://github.com/nim-lang/Nim/issues/6292))
- Fixed "Range type inference leads to counter-intuitive behaviour"
- Fixed "Range type inference leads to counter-intuitive behvaiour"
([#5854](https://github.com/nim-lang/Nim/issues/5854))
- Fixed "New backward indexing is too limited"
([#6631](https://github.com/nim-lang/Nim/issues/6631))

View File

@@ -22,7 +22,7 @@
- We removed `unicode.Rune16` without any deprecation period as the name
was wrong (see the [RFC](https://github.com/nim-lang/RFCs/issues/151) for details)
and we didn't find any usage of it in the wild. If you still need it, add this
and we didn't find any usages of it in the wild. If you still need it, add this
piece of code to your project:
```nim
type

View File

@@ -11,7 +11,7 @@
* Fixed "Assertion error when running `nim check` on compiler/nim.nim" [#12281](https://github.com/nim-lang/Nim/issues/12281)
* Fixed "Compiler crash with empty array and generic instantiation with int as parameter" [#12264](https://github.com/nim-lang/Nim/issues/12264)
* Fixed "Regression in JS backend codegen "Error: request to generate code for .compileTime proc"" [#12240](https://github.com/nim-lang/Nim/issues/12240)
* Fix how `relativePath` handles case sensitivity
* Fix how `relativePath` handle case sensitiviy
* Fixed "SIGSEGV in compiler when using generic types and seqs" [#12336](https://github.com/nim-lang/Nim/issues/12336)
* Fixed "[1.0.0] weird interaction between `import os` and casting integer to char on macosx trigger bad codegen" [#12291](https://github.com/nim-lang/Nim/issues/12291)
* VM: no special casing for big endian machines
@@ -43,7 +43,7 @@
* threadpool: fix link in docs (#12258)
* Fix spellings (#12277)
* fix #12278, don't expose internal PCRE documentation
* Fixed "Documentation of quitprocs is wrong" [#12279](https://github.com/nim-lang/Nim/issues/12279)
* Fixed "Documentation of quitprocs is wrong" [#12279(https://github.com/nim-lang/Nim/issues/12279)
* Fix typo in docs
* Fix reference to parseSpec proc in readme
* [doc/tut1] removed discard discussion in comments

View File

@@ -169,7 +169,7 @@ echo f
- The Nim compiler now supports a new pragma called ``.localPassc`` to
pass specific compiler options to the C(++) backend for the C(++) file
that was produced from the current Nim module.
- The compiler now infers "sink parameters". To disable this for a specific routine,
- The compiler now inferes "sink parameters". To disable this for a specific routine,
annotate it with `.nosinks`. To disable it for a section of code, use
`{.push sinkInference: off.}`...`{.pop.}`.
- The compiler now supports a new switch `--panics:on` that turns runtime
@@ -261,7 +261,7 @@ echo f
([#12812](https://github.com/nim-lang/Nim/issues/12812))
- Fixed "Produce static/const initializations for variables when possible"
([#12216](https://github.com/nim-lang/Nim/issues/12216))
- Fixed "Assigning discriminator field leads to internal assert with --gc:destructors"
- Fixed "Assigning descriminator field leads to internal assert with --gc:destructors"
([#12821](https://github.com/nim-lang/Nim/issues/12821))
- Fixed "nimsuggest `use` command does not return all instances of symbol"
([#12832](https://github.com/nim-lang/Nim/issues/12832))

View File

@@ -282,7 +282,7 @@
- Removed the deprecated `asyncdispatch.newAsyncNativeSocket`.
- Removed the deprecated `dom.releaseEvents` and `dom.captureEvents`.
- Removed `sharedlist.initSharedList`, was deprecated and produces undefined behaviour.
- Removed `sharedlists.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)

View File

@@ -1,330 +0,0 @@
# 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 expressiveness. 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`

View File

@@ -1,560 +0,0 @@
# 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.

View File

@@ -1,248 +0,0 @@
# v2.2.0 - 2024-10-02
## Changes affecting backward compatibility
- `-d:nimStrictDelete` becomes the default. An index error is produced when the index passed to `system.delete` is out of bounds. Use `-d:nimAuditDelete` to mimic the old behavior for backward 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 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 the `system` module, use `new` instead.
- `bindMethod` in `std/jsffi` is deprecated, don't use it with closures.
- The JS backend now supports lambda lifting for closures. Use `--legacy:jsNoLambdaLifting` to emulate old behaviors.
- The JS backend now supports closure iterators.
- `owner` in `std/macros` is deprecated.
- Ambiguous type symbols in generic procs and templates now generate symchoice nodes.
Previously; in templates they would error immediately at the template definition,
and in generic procs a type symbol would arbitrarily be captured, losing the
information of the other symbols. This means that generic code can now give
errors for ambiguous type symbols, and macros operating on generic proc AST
may encounter symchoice nodes instead of the arbitrarily resolved type symbol nodes.
- Partial generic instantiation of routines is no longer allowed. Previously
it compiled in niche situations due to bugs in the compiler.
```nim
proc foo[T, U](x: T, y: U) = echo (x, y)
proc foo[T, U](x: var T, y: U) = echo "var ", (x, y)
proc bar[T]() =
foo[float](1, "abc")
bar[int]() # before: (1.0, "abc"), now: type mismatch, missing generic parameter
```
- `const` values now open a new scope for each constant, meaning symbols
declared in them can no longer be used outside or in the value of
other constants.
```nim
const foo = (var a = 1; a)
const bar = a # error
let baz = a # error
```
- The following POSIX wrappers have had their types changed from signed to
unsigned types on OSX and FreeBSD/OpenBSD to correct codegen errors:
- `Gid` (was `int32`, is now `uint32`)
- `Uid` (was `int32`, is now `uint32`)
- `Dev` (was `int32`, is now `uint32` on FreeBSD)
- `Nlink` (was `int16`, is now `uint32` on OpenBSD and `uint16` on OSX/other BSD)
- `sin6_flowinfo` and `sin6_scope_id` fields of `Sockaddr_in6`
(were `int32`, are now `uint32`)
- `n_net` field of `Tnetent` (was `int32`, is now `uint32`)
- The `Atomic[T]` type on C++ now uses C11 primitives by default instead of
`std::atomic`. To use `std::atomic` instead, `-d:nimUseCppAtomics` can be defined.
## Standard library additions and changes
[//]: # "Changes:"
- Changed `std/osfiles.copyFile` to allow specifying `bufferSize` instead of a hard-coded 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.
- Changed `std/os.copyDir` and `copyDirWithPermissions` to allow skipping special "file" objects like FIFOs, device files, etc on Unix by specifying a `skipSpecial` parameter.
[//]: # "Additions:"
- Added `newStringUninit` to the `system` module, which creates a new string of length `len` like `newString` but with uninitialized content.
- Added `setLenUninit` to the `system` module, which doesn't initialize
slots when enlarging a sequence.
- Added `hasDefaultValue` to `std/typetraits` to check if a type has a valid default value.
- Added `rangeBase` to `std/typetraits` to obtain the base type of a range type or
convert a value with a range type to its base type.
- 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`.
- `std/hashes.hash(x:string)` changed to produce a 64-bit string `Hash` (based
on Google's Farm Hash) which is also often faster than the present one. Define
`nimStringHash2` to get the old values back. `--jsbigint=off` mode always only
produces the old values. This may impact your automated tests if they depend
on hash order in some obvious or indirect way. Using `sorted` or `OrderedTable`
is often an easy workaround.
[//]: # "Deprecations:"
- Deprecates `system.newSeqUninitialized`, which is replaced by `newSeqUninit`.
[//]: # "Removals:"
## Language changes
- `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.html#constructor-initializer
- `member` can be used to attach a procedure to a C++ type.
- Inside a C++ constructor, `result` can be used to access the created object rather than `this`.
- 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"))
```
- The experimental option `--experimental:openSym` has been added to allow
captured symbols in generic routine and template bodies respectively 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, but other methods like renaming the
captured symbols should be used instead so that the code is not affected by
context changes.
Since this change may affect runtime behavior, the experimental switch
`openSym` 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.
```nim
const value = "captured"
template foo(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
body
proc old[T](): string =
foo(123):
return value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
echo old[int]() # "captured"
template oldTempl(): string =
block:
foo(123):
value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
echo oldTempl() # "captured"
{.experimental: "openSym".}
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"
template barTempl(): string =
block:
foo(123):
value
assert barTempl() == "injected" # previously it would be "captured"
template bazTempl(): string =
bind value
block:
foo(123):
value
assert bazTempl() == "captured"
```
This option also generates a new node kind `nnkOpenSym` which contains
exactly 1 `nnkSym` node. In the future this might be merged with a slightly
modified `nnkOpenSymChoice` node but macros that want to support the
experimental feature should still handle `nnkOpenSym`, as the node kind would
simply not be generated as opposed to being removed.
Another experimental switch `genericsOpenSym` exists that enables this behavior
at instantiation time, meaning templates etc can enable it specifically when
they are being called. However this does not generate `nnkOpenSym` nodes
(unless the other switch is enabled) and so doesn't reflect the regular
behavior of the switch.
```nim
const value = "captured"
template foo(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
{.push experimental: "genericsOpenSym".}
body
{.pop.}
proc bar[T](): string =
foo(123):
return value
echo bar[int]() # "injected"
template barTempl(): string =
block:
var res: string
foo(123):
res = value
res
assert barTempl() == "injected"
```
## Compiler changes
- `--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
- koch now allows bootstrapping with `-d:nimHasLibFFI`, replacing the older option of building the compiler directly w/ the `libffi` nimble package.

View File

@@ -1,4 +1,4 @@
# v2.xx.x - yyyy-mm-dd
# v1.xx.x - yyyy-mm-dd
This is an example file.
The changes should go to changelog.md!

View File

@@ -1,26 +0,0 @@
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()

View File

@@ -58,11 +58,7 @@ _nimNumCpu(){
# 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
echo $(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || 1)
}
_nimBuildCsourcesIfNeeded(){
@@ -80,8 +76,6 @@ _nimBuildCsourcesIfNeeded(){
makeX=gmake
elif [ "$unamestr" = 'CROSSOS' ]; then
makeX=gmake
elif [ "$unamestr" = 'SunOS' ]; then
makeX=gmake
else
makeX=make
fi

9
compiler.nimble Normal file
View File

@@ -0,0 +1,9 @@
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"

View File

@@ -1,128 +0,0 @@
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

View File

@@ -10,60 +10,12 @@
## Simple alias analysis for the HLO and the code generators.
import
ast, astalgo, types, trees
import std/intsets
when defined(nimPreviewSlimSystem):
import std/assertions
ast, astalgo, types, trees, intsets
type
TAnalysisResult* = enum
arNo, arMaybe, arYes
PartFlag* = enum
pfStructural ## use structural prefix-chain detection and tree-walk
pfBidirectional ## also check reverse direction per field in nkObjConstr
proc isCompileTimeOnlyNode(n: PNode): bool {.inline.} =
## `typeof` and typedesc/static values describe types at compile time; they
## do not read the runtime location that alias analysis is protecting.
n.kind == nkTypeOfExpr or (n.typ != nil and n.typ.isCompileTimeOnly)
func sameLocation(a, b: PNode): bool =
template sameConstIndex(a, b: PNode): bool =
a.kind in nkLiterals and b.kind in nkLiterals and a.intVal == b.intVal
var a = a
var b = b
while a.kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv}: a = a[1]
while b.kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv}: b = b[1]
if a.kind != b.kind: return false
case a.kind
of nkSym: result = a.sym.id == b.sym.id
of nkDotExpr, nkCheckedFieldExpr:
result = a[1].kind == nkSym and b[1].kind == nkSym and
sameLocation(a[0], b[0]) and a[1].sym.id == b[1].sym.id
of nkBracketExpr:
result = sameLocation(a[0], b[0]) and sameConstIndex(a[1], b[1])
of nkObjUpConv, nkObjDownConv, nkDerefExpr, nkHiddenDeref:
result = sameLocation(a[0], b[0])
else: result = false
proc isAccessorPrefixOf(a, b: PNode): bool =
var cur = b
while cur.kind in {nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, nkObjUpConv,
nkObjDownConv, nkHiddenDeref, nkDerefExpr,
nkHiddenStdConv, nkHiddenSubConv, nkConv}:
if sameLocation(cur, a): return true
case cur.kind
of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv,
nkHiddenDeref, nkDerefExpr:
cur = cur[0]
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
cur = cur[1]
else: discard
result = sameLocation(cur, a)
proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult
proc isPartOfAux(n: PNode, b: PType, marker: var IntSet): TAnalysisResult =
@@ -94,16 +46,14 @@ proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult =
if compareTypes(a, b, dcEqIgnoreDistinct): return arYes
case a.kind
of tyObject:
if a.baseClass != nil:
result = isPartOfAux(a.baseClass.skipTypes(skipPtrs), b, marker)
if a[0] != nil:
result = isPartOfAux(a[0].skipTypes(skipPtrs), b, marker)
if result == arNo: result = isPartOfAux(a.n, b, marker)
of tyGenericInst, tyDistinct, tyAlias, tySink:
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)
result = isPartOfAux(lastSon(a), b, marker)
of tyArray, tySet, tyTuple:
for i in 0..<a.len:
result = isPartOfAux(a[i], b, marker)
if result == arYes: return
else: discard
@@ -113,62 +63,39 @@ proc isPartOf(a, b: PType): TAnalysisResult =
# watch out: parameters reversed because I'm too lazy to change the code...
result = isPartOfAux(b, a, marker)
proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
## Checks if location `a` can be part of location `b`: i.e. whether writing to
## `b` could affect what `a` reads. We treat seqs and strings as pointers
## because the code gen often just passes them as such.
proc isPartOf*(a, b: PNode): TAnalysisResult =
## checks if location `a` can be part of location `b`. We treat seqs and
## strings as pointers because the code gen often just passes them as such.
##
## Note: `a` can only be part of `b`, if `a`'s type can be part of `b`'s
## type. Since however type analysis is more expensive, we perform it only
## if necessary.
##
## When `pfStructural` is set additional aliasing is detected:
## * a structural prefix of an accessor chain is considered part of it
## (e.g. `x.f <| x.f.g`). Normally `x.f !<| x.f.g` because the
## same-kind `nkDotExpr` comparison treats the differing field names as
## siblings, but `pfStructural` walks the chain to recognise the
## relationship.
## * Unrecognised node kinds are traversed recursively.
##
## When `pfBidirectional` is set:
## * In `nkObjConstr` the reverse direction `isPartOf(value, a)` is also
## checked per field value so that reads hidden behind calls/closures
## are detected.
##
## cases:
##
## YES-cases:
## ```
## x <| x # for general trees
## x[] <| x
## x[i] <| x
## x.f <| x
## x.f <| x.f.g # when pfStructural (prefix chain)
## ```
##
## NO-cases:
## ```
## x !<| y # depending on type and symbol kind
## x[constA] !<| x[constB]
## x.f !<| x.g # sibling fields at same level
## 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.isCompileTimeOnlyNode or b.isCompileTimeOnlyNode:
return arNo
##
if a.kind == b.kind:
case a.kind
of nkSym:
const varKinds = {skVar, skTemp, skResult, skProc, skFunc}
const varKinds = {skVar, skTemp, skProc, skFunc}
# same symbol: aliasing:
if a.sym.id == b.sym.id: result = arYes
elif a.sym.kind in varKinds or b.sym.kind in varKinds:
@@ -179,10 +106,8 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): 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], flags)
result = isPartOf(a[0], b[0])
if a.len >= 2 and b.len >= 2:
# array accesses:
if result == arYes and isDeepConstExpr(a[1]) and isDeepConstExpr(b[1]):
@@ -192,11 +117,7 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
var y = if b[1].kind == nkHiddenStdConv: b[1][1] else: b[1]
if sameValue(x, y): result = arYes
elif pfStructural in flags and isAccessorPrefixOf(a, b):
result = arYes
else: result = arNo
elif pfStructural in flags and isAccessorPrefixOf(a, b):
result = arYes
# else: maybe and no are accurate
else:
# pointer derefs:
@@ -204,26 +125,23 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
if isPartOf(a.typ, b.typ) != arNo: result = arMaybe
of nkDotExpr:
result = isPartOf(a[0], b[0], flags)
result = isPartOf(a[0], b[0])
if result != arNo:
# if the fields are different, it's not the same location
if a[1].sym.id != b[1].sym.id:
if pfStructural in flags and isAccessorPrefixOf(a, b):
result = arYes
else:
result = arNo
result = arNo
of nkHiddenDeref, nkDerefExpr:
result = isPartOf(a[0], b[0], flags)
result = isPartOf(a[0], b[0])
# weaken because of indirection:
if result != arYes:
if isPartOf(a.typ, b.typ) != arNo: result = arMaybe
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
result = isPartOf(a[1], b[1], flags)
result = isPartOf(a[1], b[1])
of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
result = isPartOf(a[0], b[0], flags)
else: result = arNo
result = isPartOf(a[0], b[0])
else: discard
# Calls return a new location, so a default of ``arNo`` is fine.
else:
# go down recursively; this is quite demanding:
@@ -235,66 +153,47 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
case b.kind
of Ix0Kinds:
# a* !<| b.f iff a* !<| b
result = isPartOf(a, b[0], flags)
result = isPartOf(a, b[0])
of DerefKinds:
# a* !<| b[] iff
result = arNo
if isPartOf(a.typ, b.typ) != arNo:
result = isPartOf(a, b[0], flags)
result = isPartOf(a, b[0])
if result == arNo: result = arMaybe
of Ix1Kinds:
# a* !<| T(b) iff a* !<| b
result = isPartOf(a, b[1], flags)
result = isPartOf(a, b[1])
of nkSym:
# b is an atom, so we have to check a:
case a.kind
of Ix0Kinds:
# a.f !<| b* iff a.f !<| b*
result = isPartOf(a[0], b, flags)
result = isPartOf(a[0], b)
of Ix1Kinds:
result = isPartOf(a[1], b, flags)
result = isPartOf(a[1], b)
of DerefKinds:
if isPartOf(a.typ, b.typ) != arNo:
result = isPartOf(a[0], b, flags)
result = isPartOf(a[0], b)
if result == arNo: result = arMaybe
else:
result = arNo
else: result = arNo
else: discard
of nkObjConstr:
result = arNo
for i in 1..<b.len:
let res = isPartOf(a, b[i][1], flags)
let res = isPartOf(a, b[i][1])
if res != arNo:
result = res
if res == arYes: break
if pfBidirectional in flags:
let res2 = isPartOf(b[i][1], a, {pfStructural})
if res2 != arNo:
result = res2
if res2 == arYes: break
of nkCallKinds:
result = arNo
for i in 1..<b.len:
# A call such as `fill(typeof(result.f))` has a compile-time-only
# argument. It must not make the object constructor look aliased with
# `result.f`; runtime arguments remain subject to the normal analysis.
if b[i].isCompileTimeOnlyNode:
continue
let res = isPartOf(a, b[i], flags)
let res = isPartOf(a, b[i])
if res != arNo:
result = res
if res == arYes: break
of nkBracket:
if b.len > 0:
result = isPartOf(a, b[0], flags)
else:
result = arNo
else:
if pfStructural in flags:
for i in 0..<b.safeLen:
if isPartOf(a, b[i], flags) != arNo: return arMaybe
result = arNo
result = isPartOf(a, b[0])
else: discard

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -12,18 +12,18 @@
# the data structures here are used in various places of the compiler.
import
ast, astyaml, options, lineinfos, idents, rodutils,
ast, hashes, intsets, strutils, options, lineinfos, ropes, idents, rodutils,
msgs
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
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
# 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,6 +31,15 @@ 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)
@@ -65,9 +74,21 @@ 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
proc mustRehash*(length, counter: int): bool
proc nextTry*(h, maxHash: Hash): Hash {.inline.}
# ------------- table[int, int] ---------------------------------------------
const
@@ -83,7 +104,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)
@@ -171,7 +192,6 @@ 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)
@@ -201,11 +221,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:
## ```nim
##
## .. code-block:: 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
@@ -214,7 +234,173 @@ proc getNamedParamFromList*(list: PNode, ident: PIdent): PSym =
proc hashNode(p: RootRef): Hash =
result = hash(cast[pointer](p))
import std/tables
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
const backrefStyle = "\e[90m"
const enumStyle = "\e[34m"
@@ -378,12 +564,14 @@ proc value(this: var DebugPrinter; value: PType) =
this.key "n"
this.value value.n
this.key "sons"
this.openBracket
for i, a in value.ikids:
if i > 0: this.comma
this.value a
this.closeBracket
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
if value.n != nil:
this.key "n"
@@ -452,32 +640,35 @@ proc value(this: var DebugPrinter; value: PNode) =
proc debug(n: PSym; conf: ConfigRef) =
var this = DebugPrinter(
visited: initTable[pointer, int](),
renderSymType: true,
useColor: not defined(windows)
)
var this: DebugPrinter
this.visited = initTable[pointer, int]()
this.renderSymType = true
this.useColor = not defined(windows)
this.value(n)
echo($this.res)
proc debug(n: PType; conf: ConfigRef) =
var this = DebugPrinter(
visited: initTable[pointer, int](),
renderSymType: true,
useColor: not defined(windows)
)
var this: DebugPrinter
this.visited = initTable[pointer, int]()
this.renderSymType = true
this.useColor = not defined(windows)
this.value(n)
echo($this.res)
proc debug(n: PNode; conf: ConfigRef) =
var this = DebugPrinter(
visited: initTable[pointer, int](),
renderSymType: false,
useColor: not defined(windows)
)
var this: DebugPrinter
this.visited = initTable[pointer, int]()
#this.renderSymType = true
this.useColor = not defined(windows)
this.value(n)
echo($this.res)
proc nextTry(h, maxHash: Hash): Hash =
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
# random-number generation for proof).
proc objectSetContains*(t: TObjectSet, obj: RootRef): bool =
# returns true whether n is in t
var h: Hash = hashNode(obj) and high(t.data) # start with real hash value
@@ -525,33 +716,113 @@ proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool =
inc(t.counter)
result = false
proc strTableContains*(t: TStrTable, n: PSym): bool =
var h: Hash = n.name.h and high(t.data) # start with real hash value
while t.data[h] != nil:
if (t.data[h] == n):
return true
h = nextTry(h, high(t.data))
result = false
proc strTableRawInsert(data: var seq[PSym], n: PSym) =
var h: Hash = n.name.h and high(data)
while data[h] != nil:
if data[h] == n:
# allowed for 'export' feature:
#InternalError(n.info, "StrTableRawInsert: " & n.name.s)
return
h = nextTry(h, high(data))
assert(data[h] == nil)
data[h] = n
proc symTabReplaceRaw(data: var seq[PSym], prevSym: PSym, newSym: PSym) =
assert prevSym.name.h == newSym.name.h
var h: Hash = prevSym.name.h and high(data)
while data[h] != nil:
if data[h] == prevSym:
data[h] = newSym
return
h = nextTry(h, high(data))
assert false
proc symTabReplace*(t: var TStrTable, prevSym: PSym, newSym: PSym) =
symTabReplaceRaw(t.data, prevSym, newSym)
proc strTableEnlarge(t: var TStrTable) =
var n: seq[PSym]
newSeq(n, t.data.len * GrowthFactor)
for i in 0..high(t.data):
if t.data[i] != nil: strTableRawInsert(n, t.data[i])
swap(t.data, n)
proc strTableAdd*(t: var TStrTable, n: PSym) =
if mustRehash(t.data.len, t.counter): strTableEnlarge(t)
strTableRawInsert(t.data, n)
inc(t.counter)
proc strTableInclReportConflict*(t: var TStrTable, n: PSym;
onConflictKeepOld = false): PSym =
# 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
while true:
var it = t.data[h]
if it == nil: break
# Semantic checking can happen multiple times thanks to templates
# and overloading: (var x=@[]; x).mapIt(it).
# So it is possible the very same sym is added multiple
# times to the symbol table which we allow here with the 'it == n' check.
if it.name.id == n.name.id:
if it == n: return nil
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 result # but return the old one
elif mustRehash(t.data.len, t.counter):
strTableEnlarge(t)
strTableRawInsert(t.data, n)
else:
assert(t.data[h] == nil)
t.data[h] = n
inc(t.counter)
result = nil
proc strTableIncl*(t: var TStrTable, n: PSym;
onConflictKeepOld = false): bool {.discardable.} =
result = strTableInclReportConflict(t, n, onConflictKeepOld) != nil
proc strTableGet*(t: TStrTable, name: PIdent): PSym =
var h: Hash = name.h and high(t.data)
while true:
result = t.data[h]
if result == nil: break
if result.name.id == name.id: break
h = nextTry(h, high(t.data))
type
TIdentIter* = object # iterator over all syms with same identifier
h*: Hash # current hash
name* {.cursor.}: PIdent
name*: PIdent
# String tables are always initialized with non-empty, power-of-two storage,
# and every probe is masked by `high(tab.data)`.
{.push boundChecks: off.}
proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
# hot spots
var h = ti.h and high(tab.data)
var start = h
var p {.cursor.} = tab.data[h]
while p != nil:
if p.name.id == ti.name.id: break
result = tab.data[h]
while result != nil:
if result.name.id == ti.name.id: break
h = nextTry(h, high(tab.data))
if h == start:
p = nil
result = nil
break
p = tab.data[h]
if p != nil:
result = p # increase the count
else:
result = nil
result = tab.data[h]
ti.h = nextTry(h, high(tab.data))
{.pop.}
proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym =
ti.h = s.h
@@ -610,75 +881,124 @@ proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym =
result = nextIter(ti, tab)
iterator items*(tab: TStrTable): PSym =
var it: TTabIter = default(TTabIter)
var it: TTabIter
var s = initTabIter(it, tab)
while s != nil:
yield s
s = nextIter(it, tab)
proc isNil(x: ItemId): bool {.inline.} =
x.module == 0 and x.item == 0
proc hasEmptySlot[T](data: TIdPairSeq[T]): bool =
proc hasEmptySlot(data: TIdPairSeq): bool =
for h in 0..high(data):
if isNil(data[h].key):
if data[h].key == nil:
return true
result = false
proc idTableRawGet[T](t: TIdTable[T], key: int): int =
proc idTableRawGet(t: TIdTable, key: int): int =
var h: Hash
h = key and high(t.data) # start with real hash value
while not isNil(t.data[h].key):
if toId(t.data[h].key) == key:
while t.data[h].key != nil:
if t.data[h].key.id == key:
return h
h = nextTry(h, high(t.data))
result = - 1
proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T =
var index = idTableRawGet(t, toId(key))
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 = default(T)
else: result = nil
template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T =
getOrDefault(t, key.itemId)
proc idTableGet(t: TIdTable, key: int): RootRef =
var index = idTableRawGet(t, key)
if index >= 0: result = t.data[index].val
else: result = nil
proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) =
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
let keyId = toId(key)
h = keyId and high(data)
while not isNil(data[h].key):
assert(toId(data[h].key) != keyId)
h = key.id and high(data)
while data[h].key != nil:
assert(data[h].key.id != key.id)
h = nextTry(h, high(data))
assert(isNil(data[h].key))
assert(data[h].key == nil)
data[h].key = key
data[h].val = val
proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) =
proc idTablePut(t: var TIdTable, key: PIdObj, val: RootRef) =
var
index: int
n: TIdPairSeq[T]
index = idTableRawGet(t, toId(key))
n: TIdPairSeq
index = idTableRawGet(t, key.id)
if index >= 0:
assert(not isNil(t.data[index].key))
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 not isNil(t.data[i].key):
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)
template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) =
t[key.itemId] = val
iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] =
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)
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
@@ -725,8 +1045,15 @@ 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 ", "

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ 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
result = t[0].sym
proc addDeclaredLoc*(result: var string, conf: ConfigRef; sym: PSym) =
result.add " [$1 declared in $2]" % [sym.kind.toHumanStr, toFileLineCol(conf, sym.info)]
@@ -24,12 +24,6 @@ proc addDeclaredLoc*(result: var string, conf: ConfigRef; typ: PType) =
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)

View File

@@ -1,160 +0,0 @@
#
#
# 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; nl: bool, indent, maxRecDepth: int)
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; nl: bool, indent, maxRecDepth: int)
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; nl: bool, indent, maxRecDepth: int)
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; nl: bool, 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)
if nl:
res.addf("\n$1", istr)
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, true, 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, true, 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$1snippet: $2", [istr, n.loc.snippet])
res.addf("\n$1lode: ", [istr])
res.treeToYamlAux(conf, n.loc.lode, marker, true, indent + 1, maxRecDepth - 1)
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; nl: bool, 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)
if nl:
res.addf("\n$1", istr)
res.addf("kind: $2", [istr, makeYamlString($n.kind)])
res.addf("\n$1sym: ", istr)
res.symToYamlAux(conf, n.sym, marker, true, indent + 1, maxRecDepth - 1)
res.addf("\n$1n: ", istr)
res.treeToYamlAux(conf, n.n, marker, true, 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:", istr)
for a in n.kids:
res.addf("\n$1 - ", istr)
res.typeToYamlAux(conf, a, marker, false, indent + 1, maxRecDepth - 1)
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; nl: bool, indent: int;
maxRecDepth: int) =
if n == nil:
res.add("null")
else:
var istr = spaces(indent * 4)
if nl:
res.addf("\n$1", istr)
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 .. nkUInt64Lit:
res.addf("\n$1intVal: $2", [istr, $(n.intVal)])
of nkFloatLit .. nkFloat128Lit:
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, true, 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, false, indent + 1, maxRecDepth - 1)
if n.typ != nil:
res.addf("\n$1typ: ", [istr])
res.typeToYamlAux(conf, n.typ, marker, true, 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, false, 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, false, 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, false, indent, maxRecDepth)

View File

@@ -10,9 +10,6 @@
# 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
@@ -26,40 +23,53 @@ const
template modElemSize(arg: untyped): untyped = arg and 7
template divElemSize(arg: untyped): untyped = arg shr 3
proc bitSetIn*(x: TBitSet, e: BiggestInt): bool =
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 =
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
@@ -86,12 +96,6 @@ const populationCount: array[uint8, uint8] = block:
arr
proc bitSetCard*(x: TBitSet): BiggestInt =
result = 0
proc bitSetCard(x: TBitSet): BiggestInt =
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))

View File

@@ -10,9 +10,6 @@
## 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)
@@ -38,7 +35,6 @@ 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:
@@ -69,10 +65,7 @@ proc copyHalf[Key, Val](h, result: Node[Key, Val]) =
result.links[j] = h.links[Mhalf + j]
else:
for j in 0..<Mhalf:
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
result.vals[j] = move h.vals[Mhalf + j]
else:
shallowCopy(result.vals[j], h.vals[Mhalf + j])
shallowCopy(result.vals[j], h.vals[Mhalf + j])
proc split[Key, Val](h: Node[Key, Val]): Node[Key, Val] =
## split node in half
@@ -92,10 +85,7 @@ 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):
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
h.vals[i] = move h.vals[i-1]
else:
shallowCopy(h.vals[i], h.vals[i-1])
shallowCopy(h.vals[i], h.vals[i-1])
h.vals[j] = val
else:
var newLink: Node[Key, Val] = nil

View File

@@ -1,164 +0,0 @@
import ropes, int128
type
Snippet* = string
Builder* = object
buf*: string
indents*: int
template newBuilder*(s: string): Builder =
Builder(buf: s)
proc extract*(builder: Builder): Snippet =
builder.buf
proc add*(builder: var Builder, s: string) =
builder.buf.add(s)
proc add*(builder: var Builder, s: char) =
builder.buf.add(s)
proc addNewline*(builder: var Builder) =
builder.add('\n')
for i in 0 ..< builder.indents:
builder.add('\t')
proc addLineEnd*(builder: var Builder, s: string) =
builder.add(s)
builder.addNewline()
proc addLineEndIndent*(builder: var Builder, s: string) =
inc builder.indents
builder.add(s)
builder.addNewline()
proc addDedent*(builder: var Builder, s: string) =
if builder.buf.len > 0 and builder.buf[^1] == '\t':
builder.buf.setLen(builder.buf.len - 1)
builder.add(s)
dec builder.indents
proc addLineEndDedent*(builder: var Builder, s: string) =
builder.addDedent(s)
builder.addNewline()
proc addLineComment*(builder: var Builder, comment: string) =
# probably no-op on nifc
builder.add("// ")
builder.add(comment)
builder.addNewline()
proc addIntValue*(builder: var Builder, val: int) =
builder.buf.addInt(val)
proc addIntValue*(builder: var Builder, val: int64) =
builder.buf.addInt(val)
proc addIntValue*(builder: var Builder, val: uint64) =
builder.buf.addInt(val)
proc addIntValue*(builder: var Builder, val: Int128) =
builder.buf.addInt128(val)
template cIntValue*(val: int): Snippet = $val
template cIntValue*(val: int64): Snippet = $val
template cIntValue*(val: uint64): Snippet = $val
template cIntValue*(val: Int128): Snippet = $val
template cUintValue*(val: uint): Snippet = $val & "U"
import std/formatfloat
proc addFloatValue*(builder: var Builder, val: float) =
builder.buf.addFloat(val)
template cFloatValue*(val: float): Snippet = $val
proc addInt64Literal*(result: var Builder; i: BiggestInt) =
if i > low(int64):
result.add "IL64($1)" % [rope(i)]
else:
result.add "(IL64(-9223372036854775807) - IL64(1))"
proc addUint64Literal*(result: var Builder; i: uint64) =
result.add rope($i & "ULL")
proc addIntLiteral*(result: var Builder; i: BiggestInt) =
if i > low(int32) and i <= high(int32):
result.addIntValue(i)
elif i == low(int32):
# Nim has the same bug for the same reasons :-)
result.add "(-2147483647 -1)"
elif i > low(int64):
result.add "IL64($1)" % [rope(i)]
else:
result.add "(IL64(-9223372036854775807) - IL64(1))"
proc addIntLiteral*(result: var Builder; i: Int128) =
addIntLiteral(result, toInt64(i))
proc cInt64Literal*(i: BiggestInt): Snippet =
if i > low(int64):
result = "IL64($1)" % [rope(i)]
else:
result = "(IL64(-9223372036854775807) - IL64(1))"
proc cUint64Literal*(i: uint64): Snippet =
result = $i & "ULL"
proc cIntLiteral*(i: BiggestInt): Snippet =
if i > low(int32) and i <= high(int32):
result = rope(i)
elif i == low(int32):
# Nim has the same bug for the same reasons :-)
result = "(-2147483647 -1)"
elif i > low(int64):
result = "IL64($1)" % [rope(i)]
else:
result = "(IL64(-9223372036854775807) - IL64(1))"
proc cIntLiteral*(i: Int128): Snippet =
result = cIntLiteral(toInt64(i))
const
NimInt* = "NI"
NimInt8* = "NI8"
NimInt16* = "NI16"
NimInt32* = "NI32"
NimInt64* = "NI64"
CInt* = "int"
NimUint* = "NU"
NimUint8* = "NU8"
NimUint16* = "NU16"
NimUint32* = "NU32"
NimUint64* = "NU64"
NimFloat* = "NF"
NimFloat32* = "NF32"
NimFloat64* = "NF64"
NimFloat128* = "NF128" # not actually defined
NimNan* = "NAN"
NimInf* = "INF"
NimBool* = "NIM_BOOL"
NimTrue* = "NIM_TRUE"
NimFalse* = "NIM_FALSE"
NimChar* = "NIM_CHAR"
CChar* = "char"
NimCstring* = "NCSTRING"
NimNil* = "NIM_NIL"
CNil* = "NULL"
NimStrlitFlag* = "NIM_STRLIT_FLAG"
CVoid* = "void"
CPointer* = "void*"
CConstPointer* = "NIM_CONST void*"
proc cIntType*(bits: BiggestInt): Snippet =
"NI" & $bits
proc cUintType*(bits: BiggestInt): Snippet =
"NU" & $bits
type
IfBuilderState* = enum
WaitingIf, WaitingElseIf, InBlock
IfBuilder* = object
state*: IfBuilderState

View File

@@ -1,668 +0,0 @@
type VarKind = enum
Local
Global
Threadvar
Const
AlwaysConst ## const even on C++
proc addVarHeader(builder: var Builder, kind: VarKind) =
## adds modifiers for given var kind:
## Local has no modifier
## Global has `static` modifier
## Const has `static NIM_CONST` modifier
## AlwaysConst has `static const` modifier (NIM_CONST is no-op on C++)
## Threadvar is unimplemented
case kind
of Local: discard
of Global:
builder.add("static ")
of Const:
builder.add("static NIM_CONST ")
of AlwaysConst:
builder.add("static const ")
of Threadvar:
doAssert false, "unimplemented"
proc addVar(builder: var Builder, kind: VarKind = Local, name: string, typ: Snippet, initializer: Snippet = "") =
## adds a variable declaration to the builder
builder.addVarHeader(kind)
builder.add(typ)
builder.add(" ")
builder.add(name)
if initializer.len != 0:
builder.add(" = ")
builder.add(initializer)
builder.addLineEnd(";")
template addVarWithType(builder: var Builder, kind: VarKind = Local, name: string, body: typed) =
## adds a variable declaration to the builder, with the `body` building the type
builder.addVarHeader(kind)
body
builder.add(" ")
builder.add(name)
builder.addLineEnd(";")
template addVarWithInitializer(builder: var Builder, kind: VarKind = Local, name: string,
typ: Snippet, initializerBody: typed) =
## adds a variable declaration to the builder, with
## `initializerBody` building the initializer. initializer must be provided
builder.addVarHeader(kind)
builder.add(typ)
builder.add(" ")
builder.add(name)
builder.add(" = ")
initializerBody
builder.addLineEnd(";")
template addVarWithTypeAndInitializer(builder: var Builder, kind: VarKind = Local, name: string,
typeBody, initializerBody: typed) =
## adds a variable declaration to the builder, with `typeBody` building the type, and
## `initializerBody` building the initializer. initializer must be provided
builder.addVarHeader(kind)
typeBody
builder.add(" ")
builder.add(name)
builder.add(" = ")
initializerBody
builder.addLineEnd(";")
proc addArrayVar(builder: var Builder, kind: VarKind = Local, name: string, elementType: Snippet, len: int, initializer: Snippet = "") =
## adds an array variable declaration to the builder
builder.addVarHeader(kind)
builder.add(elementType)
builder.add(" ")
builder.add(name)
builder.add("[")
builder.addIntValue(len)
builder.add("]")
if initializer.len != 0:
builder.add(" = ")
builder.add(initializer)
builder.addLineEnd(";")
template addArrayVarWithInitializer(builder: var Builder, kind: VarKind = Local, name: string, elementType: Snippet, len: int, body: typed) =
## adds an array variable declaration to the builder with the initializer built according to `body`
builder.addVarHeader(kind)
builder.add(elementType)
builder.add(" ")
builder.add(name)
builder.add("[")
builder.addIntValue(len)
builder.add("] = ")
body
builder.addLineEnd(";")
template addTypedef(builder: var Builder, name: string, typeBody: typed) =
## adds a typedef declaration to the builder with name `name` and type as
## built in `typeBody`
builder.add("typedef ")
typeBody
builder.add(" ")
builder.add(name)
builder.addLineEnd(";")
proc addProcTypedef(builder: var Builder, callConv: TCallingConvention, name: string, rettype, params: Snippet) =
builder.add("typedef ")
builder.add(CallingConvToStr[callConv])
builder.add("_PTR(")
builder.add(rettype)
builder.add(", ")
builder.add(name)
builder.add(")")
builder.add(params)
builder.addLineEnd(";")
template addArrayTypedef(builder: var Builder, name: string, len: BiggestInt, typeBody: typed) =
## adds an array typedef declaration to the builder with name `name`,
## length `len`, and element type as built in `typeBody`
builder.add("typedef ")
typeBody
builder.add(" ")
builder.add(name)
builder.add("[")
builder.addIntValue(len)
builder.addLineEnd("];")
type
StructInitializerKind = enum
siOrderedStruct ## struct constructor, but without named fields on C
siNamedStruct ## struct constructor, with named fields i.e. C99 designated initializer
siArray ## array constructor
siWrapper ## wrapper for a single field, generates it verbatim
StructInitializer = object
## context for building struct initializers, i.e. `{ field1, field2 }`
kind: StructInitializerKind
## if true, fields will not be named, instead values are placed in order
needsComma: bool
proc initStructInitializer(builder: var Builder, kind: StructInitializerKind): StructInitializer =
## starts building a struct initializer, i.e. braced initializer list
result = StructInitializer(kind: kind, needsComma: false)
if kind != siWrapper:
builder.add("{")
template addField(builder: var Builder, constr: var StructInitializer, name: string, valueBody: typed) =
## adds a field to a struct initializer, with the value built in `valueBody`
if constr.needsComma:
assert constr.kind != siWrapper, "wrapper constructor cannot have multiple fields"
builder.add(", ")
else:
constr.needsComma = true
case constr.kind
of siArray, siWrapper:
# no name, can just add value
valueBody
of siOrderedStruct:
# positional init - name not used in output (empty allowed for anonymous unions)
valueBody
of siNamedStruct:
# designated init - empty name for anonymous unions (skips .name = prefix)
if name.len != 0:
builder.add(".")
builder.add(name)
builder.add(" = ")
valueBody
proc finishStructInitializer(builder: var Builder, constr: StructInitializer) =
## finishes building a struct initializer
if constr.kind != siWrapper:
builder.add("}")
template addStructInitializer(builder: var Builder, constr: out StructInitializer, kind: StructInitializerKind, body: typed) =
## builds a struct initializer, i.e. `{ field1, field2 }`
## a `var StructInitializer` must be declared and passed as a parameter so
## that it can be used with `addField`
constr = builder.initStructInitializer(kind)
body
builder.finishStructInitializer(constr)
proc addField(obj: var Builder; name, typ: Snippet; isFlexArray: bool = false; initializer: Snippet = "") =
## adds a field inside a struct/union type
obj.add('\t')
obj.add(typ)
obj.add(" ")
obj.add(name)
if isFlexArray:
obj.add("[SEQ_DECL_SIZE]")
if initializer.len != 0:
obj.add(initializer)
obj.add(";\n")
proc addArrayField(obj: var Builder; name, elementType: Snippet; len: int; initializer: Snippet = "") =
## adds an array field inside a struct/union type
obj.add('\t')
obj.add(elementType)
obj.add(" ")
obj.add(name)
obj.add("[")
obj.addIntValue(len)
obj.add("]")
if initializer.len != 0:
obj.add(initializer)
obj.add(";\n")
proc addField(obj: var Builder; field: PSym; name, typ: Snippet; isFlexArray: bool = false; initializer: Snippet = "") =
## adds an field inside a struct/union type, based on an `skField` symbol
obj.add('\t')
if field.alignment > 0:
obj.add("NIM_ALIGN(")
obj.addIntValue(field.alignment)
obj.add(") ")
obj.add(typ)
if sfNoalias in field.flags:
obj.add(" NIM_NOALIAS")
obj.add(" ")
obj.add(name)
if isFlexArray:
obj.add("[SEQ_DECL_SIZE]")
if field.bitsize != 0:
obj.add(":")
obj.addIntValue(field.bitsize)
if initializer.len != 0:
obj.add(initializer)
obj.add(";\n")
proc addProcField(obj: var Builder, callConv: TCallingConvention, name: string, rettype, params: Snippet) =
obj.add(CallingConvToStr[callConv])
obj.add("_PTR(")
obj.add(rettype)
obj.add(", ")
obj.add(name)
obj.add(")")
obj.add(params)
obj.add(";\n")
type
BaseClassKind = enum
## denotes how and whether or not the base class/RTTI should be stored
bcNone, bcCppInherit, bcSupField, bcNoneRtti, bcNoneTinyRtti
StructBuilderInfo = object
## context for building `struct` types
baseKind: BaseClassKind
named: bool
preFieldsLen: int
proc structOrUnion(t: PType): Snippet =
let t = t.skipTypes({tyAlias, tySink})
if tfUnion in t.flags: "union"
else: "struct"
proc startSimpleStruct(obj: var Builder; m: BModule; name: string; baseType: Snippet): StructBuilderInfo =
result = StructBuilderInfo(baseKind: bcNone, named: name.len != 0)
obj.add("struct")
if result.named:
obj.add(" ")
obj.add(name)
if baseType.len != 0:
if m.compileToCpp:
result.baseKind = bcCppInherit
else:
result.baseKind = bcSupField
if result.baseKind == bcCppInherit:
obj.add(" : public ")
obj.add(baseType)
obj.add(" ")
obj.add("{\n")
result.preFieldsLen = obj.buf.len
if result.baseKind == bcSupField:
obj.addField(name = "Sup", typ = baseType)
proc finishSimpleStruct(obj: var Builder; m: BModule; info: StructBuilderInfo) =
if info.baseKind == bcNone and info.preFieldsLen == obj.buf.len:
# no fields were added, add dummy field
obj.addField(name = "dummy", typ = CChar)
if info.named:
obj.add("};\n")
else:
obj.add("}")
template addSimpleStruct(obj: var Builder; m: BModule; name: string; baseType: Snippet; body: typed) =
## builds a struct type not based on a Nim type with fields according to `body`,
## `name` can be empty to build as a type expression and not a statement
let info = startSimpleStruct(obj, m, name, baseType)
body
finishSimpleStruct(obj, m, info)
proc startStruct(obj: var Builder; m: BModule; t: PType; name: string; baseType: Snippet): StructBuilderInfo =
result = StructBuilderInfo(baseKind: bcNone, named: name.len != 0)
if tfPacked in t.flags:
if hasAttribute in CC[m.config.cCompiler].props:
obj.add(structOrUnion(t))
obj.add(" __attribute__((__packed__))")
else:
obj.add("#pragma pack(push, 1)\n")
obj.add(structOrUnion(t))
else:
obj.add(structOrUnion(t))
if result.named:
obj.add(" ")
obj.add(name)
if t.kind == tyObject:
if t.baseClass == nil:
if lacksMTypeField(t):
result.baseKind = bcNone
elif optTinyRtti in m.config.globalOptions:
result.baseKind = bcNoneTinyRtti
else:
result.baseKind = bcNoneRtti
elif m.compileToCpp:
result.baseKind = bcCppInherit
else:
result.baseKind = bcSupField
elif baseType.len != 0:
if m.compileToCpp:
result.baseKind = bcCppInherit
else:
result.baseKind = bcSupField
if result.baseKind == bcCppInherit:
obj.add(" : public ")
obj.add(baseType)
obj.add(" ")
obj.add("{\n")
result.preFieldsLen = obj.buf.len
case result.baseKind
of bcNone:
# rest of the options add a field or don't need it due to inheritance,
# we need to add the dummy field for uncheckedarray ahead of time
# so that it remains trailing
if t.itemId notin m.g.graph.memberProcsPerType and
t.n != nil and t.n.len == 1 and t.n[0].kind == nkSym and
t.n[0].sym.typ.skipTypes(abstractInst).kind == tyUncheckedArray:
# only consists of flexible array field, add *initial* dummy field
obj.addField(name = "dummy", typ = CChar)
of bcCppInherit: discard
of bcNoneRtti:
obj.addField(name = "m_type", typ = ptrType(cgsymValue(m, "TNimType")))
of bcNoneTinyRtti:
obj.addField(name = "m_type", typ = ptrType(cgsymValue(m, "TNimTypeV2")))
of bcSupField:
obj.addField(name = "Sup", typ = baseType)
proc finishStruct(obj: var Builder; m: BModule; t: PType; info: StructBuilderInfo) =
if info.baseKind == bcNone and info.preFieldsLen == obj.buf.len and
t.itemId notin m.g.graph.memberProcsPerType:
# no fields were added, add dummy field
obj.addField(name = "dummy", typ = CChar)
if info.named:
obj.add("};\n")
else:
obj.add("}")
if tfPacked in t.flags and hasAttribute notin CC[m.config.cCompiler].props:
obj.add("#pragma pack(pop)\n")
template addStruct(obj: var Builder; m: BModule; typ: PType; name: string; baseType: Snippet; body: typed) =
## builds a struct type directly based on `typ` with fields according to `body`,
## `name` can be empty to build as a type expression and not a statement
let info = startStruct(obj, m, typ, name, baseType)
body
finishStruct(obj, m, typ, info)
template addFieldWithStructType(obj: var Builder; m: BModule; parentTyp: PType; fieldName: string, body: typed) =
## adds a field with a `struct { ... }` type, building the fields according to `body`
obj.add('\t')
if tfPacked in parentTyp.flags:
if hasAttribute in CC[m.config.cCompiler].props:
obj.add("struct __attribute__((__packed__)) {\n")
else:
obj.add("#pragma pack(push, 1)\nstruct {")
else:
obj.add("struct {\n")
body
obj.add("} ")
obj.add(fieldName)
obj.add(";\n")
if tfPacked in parentTyp.flags and hasAttribute notin CC[m.config.cCompiler].props:
obj.add("#pragma pack(pop)\n")
template addAnonUnion(obj: var Builder; body: typed) =
## adds an anonymous union i.e. `union { ... };` with fields according to `body`
obj.add "union{\n"
body
obj.add("};\n")
template addUnionType(obj: var Builder; body: typed) =
## adds a union type i.e. `union { ... }` with fields according to `body`
obj.add "union{\n"
body
obj.add("}")
type DeclVisibility = enum
None
Extern
ExternC
ImportLib
ExportLib
ExportLibVar
Private
StaticProc
proc addVisibilityPrefix(builder: var Builder, visibility: DeclVisibility) =
# internal proc
case visibility
of None: discard
of Extern:
builder.add("extern ")
of ExternC:
builder.add("NIM_EXTERNC ")
of ImportLib:
builder.add("N_LIB_IMPORT ")
of ExportLib:
builder.add("N_LIB_EXPORT ")
of ExportLibVar:
builder.add("N_LIB_EXPORT_VAR ")
of Private:
builder.add("N_LIB_PRIVATE ")
of StaticProc:
builder.add("static ")
template addDeclWithVisibility(builder: var Builder, visibility: DeclVisibility, declBody: typed) =
## adds a declaration as in `declBody` with the given visibility
builder.addVisibilityPrefix(visibility)
declBody
type ProcParamBuilder = object
needsComma: bool
proc initProcParamBuilder(builder: var Builder): ProcParamBuilder =
result = ProcParamBuilder(needsComma: false)
builder.add("(")
proc finishProcParamBuilder(builder: var Builder, params: ProcParamBuilder) =
if params.needsComma:
builder.add(")")
else:
builder.add("void)")
template cgDeclFrmt*(s: PSym): string =
s.constraint.strVal
proc addParam(builder: var Builder, params: var ProcParamBuilder, name: string, typ: Snippet) =
if params.needsComma:
builder.add(", ")
else:
params.needsComma = true
builder.add(typ)
builder.add(" ")
builder.add(name)
proc addParam(builder: var Builder, params: var ProcParamBuilder, param: PSym, typ: Snippet) =
if params.needsComma:
builder.add(", ")
else:
params.needsComma = true
var modifiedTyp = typ
if sfNoalias in param.flags:
modifiedTyp.add(" NIM_NOALIAS")
if sfCodegenDecl notin param.flags:
builder.add(modifiedTyp)
builder.add(" ")
builder.add(param.loc.snippet)
else:
builder.add runtimeFormat(param.cgDeclFrmt, [modifiedTyp, param.loc.snippet])
proc addUnnamedParam(builder: var Builder, params: var ProcParamBuilder, typ: Snippet) =
if params.needsComma:
builder.add(", ")
else:
params.needsComma = true
builder.add(typ)
proc addProcTypedParam(builder: var Builder, paramBuilder: var ProcParamBuilder, callConv: TCallingConvention, name: string, rettype, params: Snippet) =
if paramBuilder.needsComma:
builder.add(", ")
else:
paramBuilder.needsComma = true
builder.add(CallingConvToStr[callConv])
builder.add("_PTR(")
builder.add(rettype)
builder.add(", ")
builder.add(name)
builder.add(")")
builder.add(params)
proc addVarargsParam(builder: var Builder, params: var ProcParamBuilder) =
# does not exist in NIFC, needs to be proc pragma
if params.needsComma:
builder.add(", ")
else:
params.needsComma = true
builder.add("...")
template addProcParams(builder: var Builder, params: out ProcParamBuilder, body: typed) =
params = initProcParamBuilder(builder)
body
finishProcParamBuilder(builder, params)
type SimpleProcParam = tuple
name, typ: string
proc cProcParams(params: varargs[SimpleProcParam]): Snippet =
if params.len == 0: return "(void)"
result = "("
for i in 0 ..< params.len:
if i != 0: result.add(", ")
result.add(params[i].typ)
if params[i].name.len != 0:
result.add(" ")
result.add(params[i].name)
result.add(")")
template addProcHeaderWithParams(builder: var Builder, callConv: TCallingConvention,
name: string, rettype: Snippet, paramBuilder: typed) =
# on nifc should build something like (proc name params type pragmas
# with no body given
# or enforce this with secondary builder object
builder.add(CallingConvToStr[callConv])
builder.add("(")
builder.add(rettype)
builder.add(", ")
builder.add(name)
builder.add(")")
paramBuilder
proc addProcHeader(builder: var Builder, callConv: TCallingConvention,
name: string, rettype, params: Snippet) =
# on nifc should build something like (proc name params type pragmas
# with no body given
# or enforce this with secondary builder object
addProcHeaderWithParams(builder, callConv, name, rettype):
builder.add(params)
proc addProcHeader(builder: var Builder, name: string, rettype, params: Snippet, isConstructor = false) =
# no callconv
builder.add(rettype)
builder.add(" ")
if isConstructor:
builder.add("__attribute__((constructor)) ")
builder.add(name)
builder.add(params)
proc addProcHeader(builder: var Builder, m: BModule, prc: PSym, name: string, params, rettype: Snippet, addAttributes: bool) =
# on nifc should build something like (proc name params type pragmas
# with no body given
# or enforce this with secondary builder object
let noreturn = isNoReturn(m, prc)
if sfPure in prc.flags and hasDeclspec in extccomp.CC[m.config.cCompiler].props:
builder.add("__declspec(naked) ")
if noreturn and hasDeclspec in extccomp.CC[m.config.cCompiler].props:
builder.add("__declspec(noreturn) ")
builder.add(CallingConvToStr[prc.typ.callConv])
builder.add("(")
builder.add(rettype)
builder.add(", ")
builder.add(name)
builder.add(")")
builder.add(params)
if addAttributes:
if sfPure in prc.flags and hasAttribute in extccomp.CC[m.config.cCompiler].props:
builder.add(" __attribute__((naked))")
if noreturn and hasAttribute in extccomp.CC[m.config.cCompiler].props:
builder.add(" __attribute__((noreturn))")
proc finishProcHeaderAsProto(builder: var Builder) =
builder.addLineEnd(";")
template finishProcHeaderWithBody(builder: var Builder, body: typed) =
builder.addLineEndIndent(" {")
body
builder.addLineEndDedent("}")
builder.addNewline
proc addProcVar(builder: var Builder, m: BModule, prc: PSym, name: string, params, rettype: Snippet,
isStatic = false, ignoreAttributes = false) =
# on nifc, builds full variable
if isStatic:
builder.add("static ")
let noreturn = isNoReturn(m, prc)
if not ignoreAttributes:
if sfPure in prc.flags and hasDeclspec in extccomp.CC[m.config.cCompiler].props:
builder.add("__declspec(naked) ")
if noreturn and hasDeclspec in extccomp.CC[m.config.cCompiler].props:
builder.add("__declspec(noreturn) ")
builder.add(CallingConvToStr[prc.typ.callConv])
builder.add("_PTR(")
builder.add(rettype)
builder.add(", ")
builder.add(name)
builder.add(")")
builder.add(params)
if not ignoreAttributes:
if sfPure in prc.flags and hasAttribute in extccomp.CC[m.config.cCompiler].props:
builder.add(" __attribute__((naked))")
if noreturn and hasAttribute in extccomp.CC[m.config.cCompiler].props:
builder.add(" __attribute__((noreturn))")
# ensure we are just adding a variable:
builder.addLineEnd(";")
proc addProcVar(builder: var Builder, callConv: TCallingConvention,
name: string, params, rettype: Snippet,
isStatic = false, isVolatile = false) =
# on nifc, builds full variable
if isStatic:
builder.add("static ")
builder.add(CallingConvToStr[callConv])
builder.add("_PTR(")
builder.add(rettype)
builder.add(", ")
if isVolatile:
builder.add("volatile ")
builder.add(name)
builder.add(")")
builder.add(params)
# ensure we are just adding a variable:
builder.addLineEnd(";")
proc addProcVar(builder: var Builder,
name: string, params, rettype: Snippet,
isStatic = false, isVolatile = false) =
# no callconv
if isStatic:
builder.add("static ")
builder.add(rettype)
builder.add(" (*")
if isVolatile:
builder.add("volatile ")
builder.add(name)
builder.add(")")
builder.add(params)
# ensure we are just adding a variable:
builder.addLineEnd(";")
type VarInitializerKind = enum
Assignment, CppConstructor
proc addVar(builder: var Builder, m: BModule, s: PSym, name: string, typ: Snippet, kind = Local, visibility: DeclVisibility = None, initializer: Snippet = "", initializerKind: VarInitializerKind = Assignment) =
if sfCodegenDecl in s.flags:
builder.add(runtimeFormat(s.cgDeclFrmt, [typ, name]))
if initializer.len != 0:
if initializerKind == Assignment:
builder.add(" = ")
builder.add(initializer)
builder.addLineEnd(";")
return
if s.kind in {skLet, skVar, skField, skForVar} and s.alignment > 0:
builder.add("NIM_ALIGN(" & $s.alignment & ") ")
builder.addVisibilityPrefix(visibility)
if kind == Threadvar:
if optThreads in m.config.globalOptions:
let sym = s.typ.sym
if sym != nil and sfCppNonPod in sym.flags:
builder.add("NIM_THREAD_LOCAL ")
else: builder.add("NIM_THREADVAR ")
else:
builder.addVarHeader(kind)
builder.add(typ)
if sfRegister in s.flags: builder.add(" register")
if sfVolatile in s.flags: builder.add(" volatile")
if sfNoalias in s.flags: builder.add(" NIM_NOALIAS")
builder.add(" ")
builder.add(name)
if initializer.len != 0:
if initializerKind == Assignment:
builder.add(" = ")
builder.add(initializer)
builder.addLineEnd(";")
proc addInclude(builder: var Builder, value: Snippet) =
builder.addLineEnd("#include " & value)

View File

@@ -1,259 +0,0 @@
proc constType(t: Snippet): Snippet =
# needs manipulation of `t` in nifc
"NIM_CONST " & t
proc constPtrType(t: Snippet): Snippet =
t & "* NIM_CONST"
proc ptrConstType(t: Snippet): Snippet =
"NIM_CONST " & t & "*"
proc ptrType(t: Snippet): Snippet =
t & "*"
proc cppRefType(t: Snippet): Snippet =
t & "&"
const
CallingConvToStr: array[TCallingConvention, string] = ["N_NIMCALL",
"N_STDCALL", "N_CDECL", "N_SAFECALL",
"N_SYSCALL", # this is probably not correct for all platforms,
# but one can #define it to what one wants
"N_INLINE", "N_NOINLINE", "N_FASTCALL", "N_THISCALL", "N_CLOSURE", "N_NOCONV",
"N_NOCONV" #ccMember is N_NOCONV
]
proc procPtrTypeUnnamed(rettype, params: Snippet): Snippet =
rettype & "(*)" & params
proc procPtrTypeUnnamedNimCall(rettype, params: Snippet): Snippet =
rettype & "(N_RAW_NIMCALL*)" & params
proc procPtrTypeUnnamed(callConv: TCallingConvention, rettype, params: Snippet): Snippet =
CallingConvToStr[callConv] & "_PTR(" & rettype & ", )" & params
type CppCaptureKind = enum None, ByReference, ByCopy
template addCppLambda(builder: var Builder, captures: CppCaptureKind, params: Snippet, body: typed) =
builder.add("[")
case captures
of None: discard
of ByReference: builder.add("&")
of ByCopy: builder.add("=")
builder.add("] ")
builder.add(params)
builder.addLineEndIndent(" {")
body
builder.addLineEndDedent("}")
proc cCast(typ, value: Snippet): Snippet =
"((" & typ & ") " & value & ")"
proc wrapPar(value: Snippet): Snippet =
# used for expression group, no-op on sexp
"(" & value & ")"
proc removeSinglePar(value: Snippet): Snippet =
# removes a single paren layer expected to exist, to silence Wparentheses-equality
assert value[0] == '(' and value[^1] == ')'
value[1..^2]
template addCast(builder: var Builder, typ: Snippet, valueBody: typed) =
## adds a cast to `typ` with value built by `valueBody`
builder.add "(("
builder.add typ
builder.add ") "
valueBody
builder.add ")"
proc cAddr(value: Snippet): Snippet =
"(&" & value & ")"
proc cLabelAddr(value: TLabel): Snippet =
"&&" & value
proc cDeref(value: Snippet): Snippet =
"(*" & value & ")"
proc subscript(a, b: Snippet): Snippet =
a & "[" & b & "]"
proc dotField(a, b: Snippet): Snippet =
a & "." & b
proc derefField(a, b: Snippet): Snippet =
a & "->" & b
type CallBuilder = object
needsComma: bool
proc initCallBuilder(builder: var Builder, callee: Snippet): CallBuilder =
result = CallBuilder(needsComma: false)
builder.add(callee)
builder.add("(")
const cArgumentSeparator = ", "
proc addArgumentSeparator(builder: var Builder) =
# no-op on NIFC
# used by "single argument" builders
builder.add(cArgumentSeparator)
template addArgument(builder: var Builder, call: var CallBuilder, valueBody: typed) =
if call.needsComma:
builder.addArgumentSeparator()
else:
call.needsComma = true
valueBody
proc finishCallBuilder(builder: var Builder, call: CallBuilder) =
builder.add(")")
template addCall(builder: var Builder, call: out CallBuilder, callee: Snippet, body: typed) =
call = initCallBuilder(builder, callee)
body
finishCallBuilder(builder, call)
proc addCall(builder: var Builder, callee: Snippet, args: varargs[Snippet]) =
builder.add(callee)
builder.add("(")
if args.len != 0:
builder.add(args[0])
for i in 1 ..< args.len:
builder.add(", ")
builder.add(args[i])
builder.add(")")
proc cCall(callee: Snippet, args: varargs[Snippet]): Snippet =
result = callee
result.add("(")
if args.len != 0:
result.add(args[0])
for i in 1 ..< args.len:
result.add(", ")
result.add(args[i])
result.add(")")
proc addSizeof(builder: var Builder, val: Snippet) =
builder.add("sizeof(")
builder.add(val)
builder.add(")")
proc addAlignof(builder: var Builder, val: Snippet) =
builder.add("NIM_ALIGNOF(")
builder.add(val)
builder.add(")")
proc addOffsetof(builder: var Builder, val, member: Snippet) =
builder.add("offsetof(")
builder.add(val)
builder.add(", ")
builder.add(member)
builder.add(")")
template cSizeof(val: Snippet): Snippet =
"sizeof(" & val & ")"
template cAlignof(val: Snippet): Snippet =
"NIM_ALIGNOF(" & val & ")"
template cOffsetof(val, member: Snippet): Snippet =
"offsetof(" & val & ", " & member & ")"
type TypedBinaryOp = enum
Add, Sub, Mul, Div, Mod
Shr, Shl, BitAnd, BitOr, BitXor
const typedBinaryOperators: array[TypedBinaryOp, string] = [
Add: "+",
Sub: "-",
Mul: "*",
Div: "/",
Mod: "%",
Shr: ">>",
Shl: "<<",
BitAnd: "&",
BitOr: "|",
BitXor: "^"
]
type TypedUnaryOp = enum
Neg, BitNot
const typedUnaryOperators: array[TypedUnaryOp, string] = [
Neg: "-",
BitNot: "~",
]
type UntypedBinaryOp = enum
LessEqual, LessThan, GreaterEqual, GreaterThan, Equal, NotEqual
And, Or
const untypedBinaryOperators: array[UntypedBinaryOp, string] = [
LessEqual: "<=",
LessThan: "<",
GreaterEqual: ">=",
GreaterThan: ">",
Equal: "==",
NotEqual: "!=",
And: "&&",
Or: "||"
]
type UntypedUnaryOp = enum
Not
const untypedUnaryOperators: array[UntypedUnaryOp, string] = [
Not: "!"
]
proc addOp(builder: var Builder, binOp: TypedBinaryOp, t: Snippet, a, b: Snippet) =
builder.add('(')
builder.add(a)
builder.add(' ')
builder.add(typedBinaryOperators[binOp])
builder.add(' ')
builder.add(b)
builder.add(')')
proc addOp(builder: var Builder, unOp: TypedUnaryOp, t: Snippet, a: Snippet) =
builder.add('(')
builder.add(typedUnaryOperators[unOp])
builder.add('(')
builder.add(a)
builder.add("))")
proc addOp(builder: var Builder, binOp: UntypedBinaryOp, a, b: Snippet) =
builder.add('(')
builder.add(a)
builder.add(' ')
builder.add(untypedBinaryOperators[binOp])
builder.add(' ')
builder.add(b)
builder.add(')')
proc addOp(builder: var Builder, unOp: UntypedUnaryOp, a: Snippet) =
builder.add('(')
builder.add(untypedUnaryOperators[unOp])
builder.add('(')
builder.add(a)
builder.add("))")
template cOp(binOp: TypedBinaryOp, t: Snippet, a, b: Snippet): Snippet =
'(' & a & ' ' & typedBinaryOperators[binOp] & ' ' & b & ')'
template cOp(binOp: TypedUnaryOp, t: Snippet, a: Snippet): Snippet =
'(' & typedUnaryOperators[binOp] & '(' & a & "))"
template cOp(binOp: UntypedBinaryOp, a, b: Snippet): Snippet =
'(' & a & ' ' & untypedBinaryOperators[binOp] & ' ' & b & ')'
template cOp(binOp: UntypedUnaryOp, a: Snippet): Snippet =
'(' & untypedUnaryOperators[binOp] & '(' & a & "))"
template cIfExpr(cond, a, b: Snippet): Snippet =
# XXX used for `min` and `max`, maybe add nifc primitives for these
"(" & cond & " ? " & a & " : " & b & ")"
template cUnlikely(val: Snippet): Snippet =
"NIM_UNLIKELY(" & val & ")"

View File

@@ -1,329 +0,0 @@
template addAssignmentWithValue(builder: var Builder, lhs: Snippet, valueBody: typed) =
builder.add(lhs)
builder.add(" = ")
valueBody
builder.addLineEnd(";")
template addFieldAssignmentWithValue(builder: var Builder, lhs: Snippet, name: string, valueBody: typed) =
builder.add(lhs)
builder.add("." & name & " = ")
valueBody
builder.addLineEnd(";")
template addAssignment(builder: var Builder, lhs, rhs: Snippet) =
builder.addAssignmentWithValue(lhs):
builder.add(rhs)
template addFieldAssignment(builder: var Builder, lhs: Snippet, name: string, rhs: Snippet) =
builder.addFieldAssignmentWithValue(lhs, name):
builder.add(rhs)
template addMutualFieldAssignment(builder: var Builder, lhs, rhs: Snippet, name: string) =
builder.addFieldAssignmentWithValue(lhs, name):
builder.add(rhs)
builder.add("." & name)
template addAssignment(builder: var Builder, lhs: Snippet, rhs: int | int64 | uint64 | Int128) =
builder.addAssignmentWithValue(lhs):
builder.addIntValue(rhs)
template addFieldAssignment(builder: var Builder, lhs: Snippet, name: string, rhs: int | int64 | uint64 | Int128) =
builder.addFieldAssignmentWithValue(lhs, name):
builder.addIntValue(rhs)
template addDerefFieldAssignment(builder: var Builder, lhs: Snippet, name: string, rhs: Snippet) =
builder.add(lhs)
builder.add("->" & name & " = ")
builder.add(rhs)
builder.addLineEnd(";")
template addSubscriptAssignment(builder: var Builder, lhs: Snippet, index: Snippet, rhs: Snippet) =
builder.add(lhs)
builder.add("[" & index & "] = ")
builder.add(rhs)
builder.addLineEnd(";")
template addStmt(builder: var Builder, stmtBody: typed) =
## makes an expression built by `stmtBody` into a statement
stmtBody
builder.addLineEnd(";")
proc addCallStmt(builder: var Builder, callee: Snippet, args: varargs[Snippet]) =
builder.addStmt():
builder.addCall(callee, args)
# XXX blocks need indent tracker in `Builder` object
template addSingleIfStmt(builder: var Builder, cond: Snippet, body: typed) =
builder.add("if (")
builder.add(cond)
builder.addLineEndIndent(") {")
body
builder.addLineEndDedent("}")
template addSingleIfStmtWithCond(builder: var Builder, condBody: typed, body: typed) =
builder.add("if (")
condBody
builder.addLineEndIndent(") {")
body
builder.addLineEndDedent("}")
proc initIfStmt(builder: var Builder): IfBuilder =
IfBuilder(state: WaitingIf)
proc finishIfStmt(builder: var Builder, stmt: IfBuilder) =
assert stmt.state != InBlock
builder.addNewline()
template addIfStmt(builder: var Builder, stmt: out IfBuilder, body: typed) =
stmt = initIfStmt(builder)
body
finishIfStmt(builder, stmt)
proc initElifBranch(builder: var Builder, stmt: var IfBuilder, cond: Snippet) =
case stmt.state
of WaitingIf:
builder.add("if (")
of WaitingElseIf:
builder.add(" else if (")
else: assert false, $stmt.state
builder.add(cond)
builder.addLineEndIndent(") {")
stmt.state = InBlock
proc initElseBranch(builder: var Builder, stmt: var IfBuilder) =
assert stmt.state == WaitingElseIf, $stmt.state
builder.addLineEndIndent(" else {")
stmt.state = InBlock
proc finishBranch(builder: var Builder, stmt: var IfBuilder) =
builder.addDedent("}")
stmt.state = WaitingElseIf
template addElifBranch(builder: var Builder, stmt: var IfBuilder, cond: Snippet, body: typed) =
initElifBranch(builder, stmt, cond)
body
finishBranch(builder, stmt)
template addElseBranch(builder: var Builder, stmt: var IfBuilder, body: typed) =
initElseBranch(builder, stmt)
body
finishBranch(builder, stmt)
proc initForRange(builder: var Builder, i, start, bound: Snippet, inclusive: bool = false) =
builder.add("for (")
builder.add(i)
builder.add(" = ")
builder.add(start)
builder.add("; ")
builder.add(i)
if inclusive:
builder.add(" <= ")
else:
builder.add(" < ")
builder.add(bound)
builder.add("; ")
builder.add(i)
builder.addLineEndIndent("++) {")
proc initForStep(builder: var Builder, i, start, bound, step: Snippet, inclusive: bool = false) =
builder.add("for (")
builder.add(i)
builder.add(" = ")
builder.add(start)
builder.add("; ")
builder.add(i)
if inclusive:
builder.add(" <= ")
else:
builder.add(" < ")
builder.add(bound)
builder.add("; ")
builder.add(i)
builder.add(" += ")
builder.add(step)
builder.addLineEndIndent(") {")
proc finishFor(builder: var Builder) {.inline.} =
builder.addLineEndDedent("}")
template addForRangeExclusive(builder: var Builder, i, start, bound: Snippet, body: typed) =
initForRange(builder, i, start, bound, false)
body
finishFor(builder)
template addForRangeInclusive(builder: var Builder, i, start, bound: Snippet, body: typed) =
initForRange(builder, i, start, bound, true)
body
finishFor(builder)
template addSwitchStmt(builder: var Builder, val: Snippet, body: typed) =
builder.add("switch (")
builder.add(val)
builder.addLineEnd(") {") # no indent
body
builder.addLineEnd("}")
template addSingleSwitchCase(builder: var Builder, val: Snippet, body: typed) =
builder.add("case ")
builder.add(val)
builder.addLineEndIndent(":")
body
builder.addLineEndDedent("")
type
SwitchCaseState = enum
None, Of, Else, Finished
SwitchCaseBuilder = object
state: SwitchCaseState
proc addCase(builder: var Builder, info: var SwitchCaseBuilder, val: Snippet) =
if info.state != Of:
assert info.state == None
info.state = Of
builder.add("case ")
builder.add(val)
builder.addLineEndIndent(":")
proc addCaseRange(builder: var Builder, info: var SwitchCaseBuilder, first, last: Snippet) =
if info.state != Of:
assert info.state == None
info.state = Of
builder.add("case ")
builder.add(first)
builder.add(" ... ")
builder.add(last)
builder.addLineEndIndent(":")
proc addCaseElse(builder: var Builder, info: var SwitchCaseBuilder) =
assert info.state == None
info.state = Else
builder.addLineEndIndent("default:")
template addSwitchCase(builder: var Builder, info: out SwitchCaseBuilder, caseBody, body: typed) =
info = SwitchCaseBuilder(state: None)
caseBody
info.state = Finished
body
builder.addLineEndDedent("")
template addSwitchElse(builder: var Builder, body: typed) =
builder.addLineEndIndent("default:")
body
builder.addLineEndDedent("")
proc addBreak(builder: var Builder) =
builder.addLineEnd("break;")
type ScopeBuilder = object
inside: bool
proc initScope(builder: var Builder): ScopeBuilder =
builder.addLineEndIndent("{")
result = ScopeBuilder(inside: true)
proc finishScope(builder: var Builder, scope: var ScopeBuilder) =
assert scope.inside, "scope not inited"
builder.addLineEndDedent("}")
scope.inside = false
template addScope(builder: var Builder, body: typed) =
builder.addLineEndIndent("{")
body
builder.addLineEndDedent("}")
type WhileBuilder = object
inside: bool
proc initWhileStmt(builder: var Builder, cond: Snippet): WhileBuilder =
builder.add("while (")
builder.add(cond)
builder.addLineEndIndent(") {")
result = WhileBuilder(inside: true)
proc finishWhileStmt(builder: var Builder, stmt: var WhileBuilder) =
assert stmt.inside, "while stmt not inited"
builder.addLineEndDedent("}")
stmt.inside = false
template addWhileStmt(builder: var Builder, cond: Snippet, body: typed) =
builder.add("while (")
builder.add(cond)
builder.addLineEndIndent(") {")
body
builder.addLineEndDedent("}")
proc addLabel(builder: var Builder, name: TLabel) =
builder.add(name)
builder.addLineEnd(": ;")
proc addReturn(builder: var Builder) =
builder.addLineEnd("return;")
proc addReturn(builder: var Builder, value: Snippet) =
builder.add("return ")
builder.add(value)
builder.addLineEnd(";")
proc addGoto(builder: var Builder, label: TLabel) =
builder.add("goto ")
builder.add(label)
builder.addLineEnd(";")
proc addComputedGoto(builder: var Builder, value: Snippet) =
builder.add("goto *")
builder.add(value)
builder.addLineEnd(";")
proc addIncr(builder: var Builder, val: Snippet) =
builder.add(val)
builder.addLineEnd("++;")
proc addDecr(builder: var Builder, val: Snippet) =
builder.add(val)
builder.addLineEnd("--;")
proc addInPlaceOp(builder: var Builder, binOp: TypedBinaryOp, t: Snippet, a, b: Snippet) =
builder.add(a)
builder.add(' ')
builder.add(typedBinaryOperators[binOp])
builder.add("= ")
builder.add(b)
builder.addLineEnd(";")
proc addInPlaceOp(builder: var Builder, binOp: UntypedBinaryOp, a, b: Snippet) =
builder.add(a)
builder.add(' ')
builder.add(untypedBinaryOperators[binOp])
builder.add("= ")
builder.add(b)
builder.addLineEnd(";")
proc cInPlaceOp(binOp: TypedBinaryOp, t: Snippet, a, b: Snippet): Snippet =
result = ""
result.add(a)
result.add(' ')
result.add(typedBinaryOperators[binOp])
result.add("= ")
result.add(b)
result.add(";\n")
proc cInPlaceOp(binOp: UntypedBinaryOp, a, b: Snippet): Snippet =
result = ""
result.add(a)
result.add(' ')
result.add(untypedBinaryOperators[binOp])
result.add("= ")
result.add(b)
result.add(";\n")
template addCPragma(builder: var Builder, val: Snippet) =
builder.addNewline()
builder.add("#pragma ")
builder.add(val)
builder.addNewline()
proc addDiscard(builder: var Builder, val: Snippet) =
builder.add("(void)")
builder.add(val)
builder.addLineEnd(";")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,295 +16,98 @@
## implementation.
template detectVersion(field, corename) =
if m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
result = 2
else:
result = 1
if m.g.field == 0:
let core = getCompilerProc(m.g.graph, corename)
if core == nil or core.kind != skConst:
m.g.field = 1
else:
m.g.field = toInt(ast.getInt(core.ast))
result = m.g.field
proc detectStrVersion(m: BModule): int =
if m.g.config.usesSso() and
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
result = 3
else:
detectVersion(strVersion, "nimStrVersion")
detectVersion(strVersion, "nimStrVersion")
proc detectSeqVersion(m: BModule): int =
detectVersion(seqVersion, "nimSeqVersion")
# ----- Version 1: GC'ed strings and seqs --------------------------------
proc genStringLiteralDataOnlyV1(m: BModule, s: string; result: var Rope) =
cgsym(m, "TGenericSeq")
let tmp = getTempName(m)
result.add tmp
var res = newBuilder("")
res.addVarWithTypeAndInitializer(AlwaysConst, name = tmp):
res.addSimpleStruct(m, name = "", baseType = ""):
res.addField(name = "Sup", typ = "TGenericSeq")
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
do:
var strInit: StructInitializer
res.addStructInitializer(strInit, kind = siOrderedStruct):
res.addField(strInit, name = "Sup"):
var seqInit: StructInitializer
res.addStructInitializer(seqInit, kind = siOrderedStruct):
res.addField(seqInit, name = "len"):
res.addIntValue(s.len)
res.addField(seqInit, name = "reserved"):
res.add(cCast(NimInt, cOp(BitOr, NimUint, cCast(NimUint, cIntValue(s.len)), NimStrlitFlag)))
res.addField(strInit, name = "data"):
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
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 genStringLiteralV1(m: BModule; n: PNode; result: var Builder) =
proc genStringLiteralV1(m: BModule; n: PNode): Rope =
if s.isNil:
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))
result = ropecg(m, "((#NimStringDesc*) NIM_NIL)", [])
else:
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var name: string = ""
if id == m.labels:
# string literal not found in the cache:
genStringLiteralDataOnlyV1(m, n.strVal, name)
result = ropecg(m, "((#NimStringDesc*) &$1)",
[genStringLiteralDataOnlyV1(m, n.strVal)])
else:
name = m.tmpBase & $id
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), cAddr(name)))
result = ropecg(m, "((#NimStringDesc*) &$1$2)",
[m.tmpBase, id])
# ------ Version 2: destructor based strings and seqs -----------------------
proc genStringLiteralDataOnlyV2(m: BModule, s: string; result: Rope; isConst: bool) =
var res = newBuilder("")
res.addVarWithTypeAndInitializer(
if isConst: AlwaysConst else: Global,
name = result):
res.addSimpleStruct(m, name = "", baseType = ""):
res.addField(name = "cap", typ = NimInt)
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
do:
var structInit: StructInitializer
res.addStructInitializer(structInit, kind = siOrderedStruct):
res.addField(structInit, name = "cap"):
res.add(cOp(BitOr, NimInt, cIntValue(s.len), NimStrlitFlag))
res.addField(structInit, name = "data"):
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
m.s[cfsData].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; result: var Builder) =
proc genStringLiteralV2(m: BModule; n: PNode; isConst: bool): Rope =
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var litName: string
if id == m.labels:
cgsym(m, "NimStrPayload")
cgsym(m, "NimStringV2")
let pureLit = getTempName(m)
genStringLiteralDataOnlyV2(m, n.strVal, pureLit, isConst)
result = getTempName(m)
discard cgsym(m, "NimStrPayload")
discard cgsym(m, "NimStringV2")
# string literal not found in the cache:
litName = getTempName(m)
genStringLiteralDataOnlyV2(m, n.strVal, litName, isConst)
m.s[cfsData].addf("static $4 NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n",
[result, rope(n.strVal.len), pureLit, rope(if isConst: "const" else: "")])
else:
litName = m.tmpBase & $id
let tmp = getTempName(m)
result.add tmp
var res = newBuilder("")
res.addVarWithInitializer(
if isConst: AlwaysConst else: Global,
name = tmp,
typ = "NimStringV2"):
var strInit: StructInitializer
res.addStructInitializer(strInit, kind = siOrderedStruct):
res.addField(strInit, name = "len"):
res.addIntValue(n.strVal.len)
res.addField(strInit, name = "p"):
res.add(cCast(ptrType("NimStrPayload"), cAddr(litName)))
m.s[cfsStrData].add(extract(res))
result = getTempName(m)
m.s[cfsData].addf("static $4 NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n",
[result, rope(n.strVal.len), m.tmpBase & rope(id),
rope(if isConst: "const" else: "")])
proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Builder) =
proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool): Rope =
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var pureLit: Rope
if id == m.labels:
pureLit = getTempName(m)
cgsym(m, "NimStrPayload")
cgsym(m, "NimStringV2")
discard cgsym(m, "NimStrPayload")
discard cgsym(m, "NimStringV2")
# string literal not found in the cache:
genStringLiteralDataOnlyV2(m, n.strVal, pureLit, isConst)
else:
pureLit = m.tmpBase & rope(id)
var strInit: StructInitializer
result.addStructInitializer(strInit, kind = siOrderedStruct):
result.addField(strInit, name = "len"):
result.addIntValue(n.strVal.len)
result.addField(strInit, name = "p"):
result.add(cCast(ptrType("NimStrPayload"), cAddr(pureLit)))
proc ssoBytesLit(m: BModule; s: string; slen: int): string =
## Compute the `bytes` field value for the new SmallString layout.
## byte 0 = slen, bytes 1-7 = inline chars 0-6 (zero-padded).
## On LE: slen in bits 0-7, char[i] in bits (i+1)*8..(i+1)*8+7.
## On BE: slen in bits 56-63, char[i] in bits (6-i)*8..(6-i)*8+7.
const AlwaysAvail = 7
var val: uint64
if CPU[m.g.config.target.targetCPU].endian == littleEndian:
val = uint64(slen)
for i in 0..<min(s.len, AlwaysAvail):
val = val or (uint64(s[i]) shl (uint(i + 1) * 8))
else:
val = uint64(slen) shl 56
for i in 0..<min(s.len, AlwaysAvail):
val = val or (uint64(s[i]) shl (uint(AlwaysAvail - 1 - i) * 8))
# Cast to NU (C name for Nim's uint, = NU64 on 64-bit). NU64 = uint64_t.
result = cCast("NU", $val & "ULL")
proc ssoMoreLit(m: BModule; s: string): string =
## For medium string literals (AlwaysAvail < len <= PayloadSize), encode
## chars[AlwaysAvail..ptrSize-1] in the 'more' pointer field bit-pattern.
## The last pointer byte is always '\0' (null terminator), guaranteed by
## PayloadSize = AlwaysAvail + ptrSize - 1. slen <= PayloadSize guards
## prevent any code from dereferencing this as an actual pointer.
const AlwaysAvail = 7
let ptrSize = m.g.config.target.ptrSize
var val: uint64 = 0
for i in 0..<ptrSize:
let ch: uint64 = if AlwaysAvail + i < s.len: uint64(s[AlwaysAvail + i]) else: 0
if CPU[m.g.config.target.targetCPU].endian == littleEndian:
val = val or (ch shl (uint(i) * 8))
else:
val = val or (ch shl (uint(ptrSize - 1 - i) * 8))
result = cCast(ptrType("LongString"), "(uintptr_t)" & $val)
proc genStringLiteralV3Const(m: BModule; n: PNode; isConst: bool; result: var Builder) =
# Inline SmallString struct initializer for use inside const aggregate types.
# Layout: {bytes: NimUint, more: ptr LongString}
# bytes = slen (low byte) | char[0]<<8 | char[1]<<16 | ... | char[6]<<56
const AlwaysAvail = 7
let s = n.strVal
cgsym(m, "SmallString")
cgsym(m, "LongString")
let payloadSize = AlwaysAvail + m.g.config.target.ptrSize - 1
var si: StructInitializer
result.addStructInitializer(si, kind = siOrderedStruct):
if s.len <= AlwaysAvail:
result.addField(si, name = "bytes"):
result.add(ssoBytesLit(m, s, s.len))
result.addField(si, name = "more"):
result.add(NimNil)
elif s.len <= payloadSize:
# Medium string: bytes holds slen + chars 0-6; more holds chars 7..PayloadSize-1.
result.addField(si, name = "bytes"):
result.add(ssoBytesLit(m, s, s.len))
result.addField(si, name = "more"):
result.add(ssoMoreLit(m, s))
else:
# Emit the LongString block into cfsStrData and reference it inline.
let dataName = getTempName(m)
var res = newBuilder("")
res.addVarWithTypeAndInitializer(
if isConst: AlwaysConst else: Global,
name = dataName):
res.addSimpleStruct(m, name = "", baseType = ""):
res.addField(name = "rc", typ = NimInt)
res.addField(name = "fullLen", typ = NimInt)
res.addField(name = "capImpl", typ = NimInt)
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
do:
var di: StructInitializer
res.addStructInitializer(di, kind = siOrderedStruct):
res.addField(di, name = "fullLen"):
res.addIntValue(s.len)
res.addField(di, name = "rc"):
res.addIntValue(1)
res.addField(di, name = "capImpl"):
res.addIntValue(0) # static, never freed
res.addField(di, name = "data"):
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
# slen = StaticSlen (254): marks this as a static (never-freed) long string.
result.addField(si, name = "bytes"):
result.add(ssoBytesLit(m, s, 254))
result.addField(si, name = "more"):
result.add(cCast(ptrType("LongString"), cAddr(dataName)))
# ------ Version 3: SmallString (SSO) strings --------------------------------
proc genStringLiteralV3(m: BModule; n: PNode; isConst: bool; result: var Builder) =
# SmallString literal. Always generate a fresh SmallString variable (like v2
# always generates a fresh outer NimStringV2). For long strings, cache the
# LongString payload to avoid duplicates within a module.
const AlwaysAvail = 7 # must match strs_v3.nim
let s = n.strVal
let tmp = getTempName(m)
result.add tmp
cgsym(m, "SmallString")
cgsym(m, "LongString")
let payloadSize = AlwaysAvail + m.g.config.target.ptrSize - 1
var res = newBuilder("")
if s.len <= AlwaysAvail:
# Short: bytes holds slen + all chars (zero-padded), more = NULL.
res.addVarWithInitializer(
if isConst: AlwaysConst else: Global,
name = tmp, typ = "SmallString"):
var si: StructInitializer
res.addStructInitializer(si, kind = siOrderedStruct):
res.addField(si, name = "bytes"):
res.add(ssoBytesLit(m, s, s.len))
res.addField(si, name = "more"):
res.add(NimNil)
elif s.len <= payloadSize:
# Medium: bytes holds slen + chars 0-6; more holds chars 7..PayloadSize-1 as raw bits.
res.addVarWithInitializer(
if isConst: AlwaysConst else: Global,
name = tmp, typ = "SmallString"):
var si: StructInitializer
res.addStructInitializer(si, kind = siOrderedStruct):
res.addField(si, name = "bytes"):
res.add(ssoBytesLit(m, s, s.len))
res.addField(si, name = "more"):
res.add(ssoMoreLit(m, s))
else:
# Long: cache the LongString block to emit it only once per module per string.
# Always generate a fresh SmallString pointing at the (possibly cached) block.
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var dataName: string
if id == m.labels:
dataName = getTempName(m)
res.addVarWithTypeAndInitializer(
if isConst: AlwaysConst else: Global,
name = dataName):
res.addSimpleStruct(m, name = "", baseType = ""):
res.addField(name = "rc", typ = NimInt)
res.addField(name = "fullLen", typ = NimInt)
res.addField(name = "capImpl", typ = NimInt)
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
do:
var di: StructInitializer
res.addStructInitializer(di, kind = siOrderedStruct):
res.addField(di, name = "fullLen"):
res.addIntValue(s.len)
res.addField(di, name = "rc"):
res.addIntValue(1)
res.addField(di, name = "capImpl"):
res.addIntValue(0) # bit 0 = 0: static, never freed
res.addField(di, name = "data"):
res.add(makeCString(s))
else:
dataName = m.tmpBase & $id
# slen = StaticSlen (254): marks this as a static (never-freed) long string.
res.addVarWithInitializer(
if isConst: AlwaysConst else: Global,
name = tmp, typ = "SmallString"):
var si: StructInitializer
res.addStructInitializer(si, kind = siOrderedStruct):
res.addField(si, name = "bytes"):
res.add(ssoBytesLit(m, s, 254))
res.addField(si, name = "more"):
res.add(cCast(ptrType("LongString"), cAddr(dataName)))
m.s[cfsStrData].add(extract(res))
result = "{$1, (NimStrPayload*)&$2}" % [rope(n.strVal.len), pureLit]
# ------ Version selector ---------------------------------------------------
proc genNilStringLiteral(m: BModule; info: TLineInfo; result: var Builder) =
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))
proc genStringLiteral(m: BModule; n: PNode; result: var Builder) =
proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo;
isConst: bool): Rope =
case detectStrVersion(m)
of 0, 1: genStringLiteralV1(m, n, result)
of 2: genStringLiteralV2(m, n, isConst = true, result)
of 3: genStringLiteralV3(m, n, isConst = true, result)
of 0, 1: result = genStringLiteralDataOnlyV1(m, s)
of 2:
result = getTempName(m)
genStringLiteralDataOnlyV2(m, s, result, isConst)
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 genStringLiteral(m: BModule; n: PNode): Rope =
case detectStrVersion(m)
of 0, 1: result = genStringLiteralV1(m, n)
of 2: result = genStringLiteralV2(m, n, isConst = true)
else:
localError(m.config, n.info, "cannot determine how to produce code for string literal")

View File

@@ -19,11 +19,13 @@ 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",
@@ -32,8 +34,11 @@ 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",
cfsDynLibInit: "NIM_merge_DYNLIB_INIT"
cfsDebugInit: "NIM_merge_DEBUG_INIT",
cfsDynLibInit: "NIM_merge_DYNLIB_INIT",
cfsDynLibDeinit: "NIM_merge_DYNLIB_DEINIT",
]
CProcSectionNames: array[TCProcSection, string] = [
cpsLocals: "NIM_merge_PROC_LOCALS",

View File

@@ -24,31 +24,28 @@ 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.snippet == "": fillObjectFields(p.module, typ)
if disc.loc.r == nil: fillObjectFields(p.module, typ)
if disc.loc.t == nil:
internalError(p.config, n.info, "specializeResetN()")
let discField = dotField(accessor, disc.loc.snippet)
p.s(cpsStmts).addSwitchStmt(discField):
for i in 1..<n.len:
let branch = n[i]
assert branch.kind in {nkOfBranch, nkElse}
var caseBuilder: SwitchCaseBuilder
p.s(cpsStmts).addSwitchCase(caseBuilder):
if branch.kind == nkOfBranch:
genCaseRange(p, branch, caseBuilder)
else:
p.s(cpsStmts).addCaseElse(caseBuilder)
do:
specializeResetN(p, accessor, lastSon(branch), typ)
p.s(cpsStmts).addBreak()
specializeResetT(p, discField, disc.loc.t)
lineF(p, cpsStmts, "switch ($1.$2) {$n", [accessor, disc.loc.r])
for i in 1..<n.len:
let branch = n[i]
assert branch.kind in {nkOfBranch, nkElse}
if branch.kind == nkOfBranch:
genCaseRange(p, branch)
else:
lineF(p, cpsStmts, "default:$n", [])
specializeResetN(p, accessor, lastSon(branch), typ)
lineF(p, cpsStmts, "break;$n", [])
lineF(p, cpsStmts, "} $n", [])
specializeResetT(p, "$1.$2" % [accessor, disc.loc.r], disc.loc.t)
of nkSym:
let field = n.sym
if field.typ.kind == tyVoid: return
if field.loc.snippet == "": fillObjectFields(p.module, typ)
if field.loc.r == nil: fillObjectFields(p.module, typ)
if field.loc.t == nil:
internalError(p.config, n.info, "specializeResetN()")
specializeResetT(p, dotField(accessor, field.loc.snippet), field.loc.t)
specializeResetT(p, "$1.$2" % [accessor, field.loc.r], field.loc.t)
else: internalError(p.config, n.info, "specializeResetN()")
proc specializeResetT(p: BProc, accessor: Rope, typ: PType) =
@@ -57,79 +54,53 @@ proc specializeResetT(p: BProc, accessor: Rope, typ: PType) =
case typ.kind
of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred,
tySink, tyOwned:
specializeResetT(p, accessor, skipModifier(typ))
specializeResetT(p, accessor, lastSon(typ))
of tyArray:
let arraySize = lengthOrd(p.config, typ.indexType)
var i: TLoc = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt))
p.s(cpsStmts).addForRangeExclusive(i.snippet, cIntValue(0), cIntValue(arraySize)):
specializeResetT(p, subscript(accessor, i.snippet), typ.elementType)
let arraySize = lengthOrd(p.config, typ[0])
var i: TLoc
getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt), i)
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])
lineF(p, cpsStmts, "}$n", [])
of tyObject:
var x = typ.baseClass
if x != nil: x = x.skipTypes(skipPtrs)
specializeResetT(p, accessor.parentObj(p.module), x)
if typ.n != nil:
if typ.sym != nil and sfImportc in typ.sym.flags:
# imported C struct, nimZeroMem
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
cCast(ptrType(CPointer), cAddr(accessor)),
cSizeof(getTypeDesc(p.module, typ)))
else:
specializeResetN(p, accessor, typ.n, typ)
if isCaseObj(typ.n):
# The active branch was released above. Clear the complete object so
# stale bytes from overlapping branches cannot be traced by the GC.
# type
# Foo = object
# case kind: bool
# of true:
# a: ref Bar # 8 bytes (pointer)
# of false:
# b: int # 4 bytes
# specializeResetT for b emits accessor.b = 0 — writes 4 bytes
# But the union is 8 bytes wide (sized by the largest branch)
# The remaining 4 bytes where a used to live are untouched
# Those stale bytes could contain a heap pointer the GC traces → crash
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
cCast(CPointer, cAddr(accessor)),
cSizeof(getTypeDesc(p.module, typ)))
for i in 0..<typ.len:
var x = typ[i]
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, a in typ.ikids:
specializeResetT(p, dotField(accessor, "Field" & $i), a)
for i in 0..<typ.len:
specializeResetT(p, ropecg(p.module, "$1.Field$2", [accessor, i]), typ[i])
of tyString, tyRef, tySequence:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "unsureAsgnRef"),
cCast(ptrType(CPointer), cAddr(accessor)),
NimNil)
lineCg(p, cpsStmts, "#unsureAsgnRef((void**)&$1, NIM_NIL);$n", [accessor])
of tyProc:
if typ.callConv == ccClosure:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "unsureAsgnRef"),
cCast(ptrType(CPointer), cAddr(dotField(accessor, "ClE_0"))),
NimNil)
p.s(cpsStmts).addFieldAssignment(accessor, "ClP_0", NimNil)
lineCg(p, cpsStmts, "#unsureAsgnRef((void**)&$1.ClE_0, NIM_NIL);$n", [accessor])
lineCg(p, cpsStmts, "$1.ClP_0 = NIM_NIL;$n", [accessor])
else:
p.s(cpsStmts).addAssignment(accessor, NimNil)
of tyChar, tyBool, tyEnum, tyRange, tyInt..tyUInt64:
p.s(cpsStmts).addAssignment(accessor, cIntValue(0))
lineCg(p, cpsStmts, "$1 = NIM_NIL;$n", [accessor])
of tyChar, tyBool, tyEnum, tyInt..tyUInt64:
lineCg(p, cpsStmts, "$1 = 0;$n", [accessor])
of tyCstring, tyPointer, tyPtr, tyVar, tyLent:
p.s(cpsStmts).addAssignment(accessor, NimNil)
lineCg(p, cpsStmts, "$1 = NIM_NIL;$n", [accessor])
of tySet:
case mapSetType(p.config, typ)
of ctArray:
let t = getTypeDesc(p.module, typ)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
accessor,
cSizeof(t))
lineCg(p, cpsStmts, "#nimZeroMem($1, sizeof($2));$n",
[accessor, getTypeDesc(p.module, typ)])
of ctInt8, ctInt16, ctInt32, ctInt64:
p.s(cpsStmts).addAssignment(accessor, cIntValue(0))
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, tyError, tyBuiltInTypeClass, tyUserTypeClass,
doAssert false, "unexpected set type kind"
of {tyNone, tyEmpty, tyNil, tyUntyped, tyTyped, tyGenericInvocation,
tyGenericParam, tyOrdinal, tyRange, tyOpenArray, tyForward, tyVarargs,
tyUncheckedArray, tyProxy, tyBuiltInTypeClass, tyUserTypeClass,
tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot,
tyAnything, tyStatic, tyFromExpr, tyConcept, tyVoid, tyIterable:
tyAnything, tyStatic, tyFromExpr, tyConcept, tyVoid, tyIterable}:
discard
proc specializeReset(p: BProc, a: TLoc) =

File diff suppressed because it is too large Load Diff

View File

@@ -19,12 +19,9 @@ proc accessThreadLocalVar(p: BProc, s: PSym) =
if emulatedThreadVars(p.config) and threadVarAccessed notin p.flags:
p.flags.incl threadVarAccessed
incl p.module.flags, usesThreadVars
p.procSec(cpsLocals).addVar(kind = Local,
name = "NimTV_",
typ = ptrType("NimThreadVars"))
p.procSec(cpsInit).addAssignment("NimTV_",
cCast(ptrType("NimThreadVars"),
cCall(cgsymValue(p.module, "GetThreadLocalVars"))))
p.procSec(cpsLocals).addf("\tNimThreadVars* NimTV_;$n", [])
p.procSec(cpsInit).add(
ropecg(p.module, "\tNimTV_ = (NimThreadVars*) #GetThreadLocalVars();$n", []))
proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) =
if emulatedThreadVars(m.config):
@@ -33,51 +30,30 @@ proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) =
# allocator for it :-(
if not containsOrIncl(m.g.nimtvDeclared, s.id):
m.g.nimtvDeps.add(s.loc.t)
m.g.nimtv.addField(name = s.loc.snippet, typ = getTypeDesc(m, s.loc.t))
m.g.nimtv.addf("$1 $2;$n", [getTypeDesc(m, s.loc.t), s.loc.r])
else:
let vis =
if isExtern: Extern
elif lfExportLib in s.loc.flags: ExportLibVar
else: Private
if m.config.cmd == cmdNifC and vis == Private and not isExtern:
# A `{.threadvar.}`/`{.global.}` thread-local declared inside a routine is
# emitted by every module that emit-everywhere's its enclosing routine
# (e.g. libp2p's `var keys {.global.}: HashSet`), so its content-addressed
# name collides at link. Same fix as a plain global (genGlobalVarDecl):
# `extern` declaration + a droppable `'d'` definition unit the merge stage
# assigns one owner. The thread-local storage class rides on both.
let cname = stripCnifMarks(s.loc.snippet)
let td = getTypeDesc(m, s.loc.t)
# `extern` declaration via the full `addVar` overload — it knows the
# thread-local storage class (`NIM_THREADVAR`); the simple `addVar`'s
# `addVarHeader` does not implement `Threadvar`.
m.s[cfsVars].addVar(m, s, name = s.loc.snippet, typ = td,
kind = Threadvar, visibility = Extern)
m.s[cfsVars].add(cnifDefDirective(cname, "d", icNifName(m, s)))
m.s[cfsVars].addVar(m, s,
name = s.loc.snippet, typ = td, kind = Threadvar, visibility = vis)
m.s[cfsVars].add(cnifEndDefs())
else:
m.s[cfsVars].addVar(m, s,
name = s.loc.snippet,
typ = getTypeDesc(m, s.loc.t),
kind = Threadvar,
visibility = vis)
if isExtern: m.s[cfsVars].add("extern ")
elif lfExportLib in s.loc.flags: m.s[cfsVars].add("N_LIB_EXPORT_VAR ")
else: m.s[cfsVars].add("N_LIB_PRIVATE ")
if optThreads in m.config.globalOptions:
let sym = s.typ.sym
if sym != nil and sfCppNonPod in sym.flags:
m.s[cfsVars].add("NIM_THREAD_LOCAL ")
else: m.s[cfsVars].add("NIM_THREADVAR ")
m.s[cfsVars].add(getTypeDesc(m, s.loc.t))
m.s[cfsVars].addf(" $1;$n", [s.loc.r])
proc generateThreadLocalStorage(m: BModule) =
if m.g.nimtv.buf.len != 0 and (usesThreadVars in m.flags or sfMainModule in m.module.flags):
if m.g.nimtv != nil 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].addTypedef(name = "NimThreadVars"):
m.s[cfsSeqTypes].addSimpleStruct(m, name = "", baseType = ""):
m.s[cfsSeqTypes].add(extract(m.g.nimtv))
m.s[cfsSeqTypes].addf("typedef struct {$1} NimThreadVars;$n", [m.g.nimtv])
proc generateThreadVarsSize(m: BModule) =
if m.g.nimtv.buf.len != 0:
if m.g.nimtv != nil:
let externc = if m.config.backend == backendCpp or
sfCompileToCpp in m.module.flags: ExternC
else: None
m.s[cfsProcs].addDeclWithVisibility(externc):
m.s[cfsProcs].addProcHeader("NimThreadVarsSize", NimInt, cProcParams())
m.s[cfsProcs].finishProcHeaderWithBody():
m.s[cfsProcs].addReturn(cCast(NimInt, cSizeof("NimThreadVars")))
sfCompileToCpp in m.module.flags: "extern \"C\" "
else: ""
m.s[cfsProcs].addf(
"$#NI NimThreadVarsSize(){return (NI)sizeof(NimThreadVars);}$n",
[externc.rope])

View File

@@ -16,15 +16,12 @@ type
p: BProc
visitorFrmt: string
const
visitorFrmt = "#nimGCvisit((void*)$1, $2);$n"
proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType)
proc genCaseRange(p: BProc, branch: PNode, info: var SwitchCaseBuilder)
proc getTemp(p: BProc, t: PType, needsInit=false): TLoc
proc visit(p: BProc, data, visitor: Snippet) =
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCvisit"),
cCast(CPointer, data),
visitor)
proc genCaseRange(p: BProc, branch: PNode)
proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false)
proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode;
typ: PType) =
@@ -37,35 +34,32 @@ 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.snippet == "": fillObjectFields(c.p.module, typ)
if disc.loc.r == nil: fillObjectFields(c.p.module, typ)
if disc.loc.t == nil:
internalError(c.p.config, n.info, "genTraverseProc()")
let discField = dotField(accessor, disc.loc.snippet)
p.s(cpsStmts).addSwitchStmt(discField):
for i in 1..<n.len:
let branch = n[i]
assert branch.kind in {nkOfBranch, nkElse}
var caseBuilder: SwitchCaseBuilder
p.s(cpsStmts).addSwitchCase(caseBuilder):
if branch.kind == nkOfBranch:
genCaseRange(c.p, branch, caseBuilder)
else:
p.s(cpsStmts).addCaseElse(caseBuilder)
do:
genTraverseProc(c, accessor, lastSon(branch), typ)
p.s(cpsStmts).addBreak()
lineF(p, cpsStmts, "switch ($1.$2) {$n", [accessor, disc.loc.r])
for i in 1..<n.len:
let branch = n[i]
assert branch.kind in {nkOfBranch, nkElse}
if branch.kind == nkOfBranch:
genCaseRange(c.p, branch)
else:
lineF(p, cpsStmts, "default:$n", [])
genTraverseProc(c, accessor, lastSon(branch), typ)
lineF(p, cpsStmts, "break;$n", [])
lineF(p, cpsStmts, "} $n", [])
of nkSym:
let field = n.sym
if field.typ.kind == tyVoid: return
if field.loc.snippet == "": fillObjectFields(c.p.module, typ)
if field.loc.r == nil: fillObjectFields(c.p.module, typ)
if field.loc.t == nil:
internalError(c.p.config, n.info, "genTraverseProc()")
genTraverseProc(c, dotField(accessor, field.loc.snippet), field.loc.t)
genTraverseProc(c, "$1.$2" % [accessor, field.loc.r], field.loc.t)
else: internalError(c.p.config, n.info, "genTraverseProc()")
proc parentObj(accessor: Rope; m: BModule): Rope {.inline.} =
if not m.compileToCpp:
result = dotField(accessor, "Sup")
result = "$1.Sup" % [accessor]
else:
result = accessor
@@ -77,156 +71,126 @@ proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType) =
case typ.kind
of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred,
tySink, tyOwned:
genTraverseProc(c, accessor, skipModifier(typ))
genTraverseProc(c, accessor, lastSon(typ))
of tyArray:
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)
var oldLen, newLen: int
p.s(cpsStmts).addForRangeExclusive(i.snippet, cIntValue(0), cIntValue(arraySize)):
oldLen = p.s(cpsStmts).buf.len
genTraverseProc(c, subscript(accessor, i.snippet), typ.elementType)
newLen = p.s(cpsStmts).buf.len
if oldLen == newLen:
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)
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])
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:
var x = typ.baseClass
if x != nil: x = x.skipTypes(skipPtrs)
genTraverseProc(c, accessor.parentObj(c.p.module), x)
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)
if typ.n != nil: genTraverseProc(c, accessor, typ.n, typ)
of tyTuple:
let typ = getUniqueType(typ)
for i, a in typ.ikids:
genTraverseProc(c, dotField(accessor, "Field" & $i), a)
for i in 0..<typ.len:
genTraverseProc(c, ropecg(c.p.module, "$1.Field$2", [accessor, i]), typ[i])
of tyRef:
visit(p, accessor, c.visitorFrmt)
lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
of tySequence:
if optSeqDestructors notin c.p.module.config.globalOptions:
visit(p, accessor, c.visitorFrmt)
elif containsGarbageCollectedRef(typ.elementType):
lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
elif containsGarbageCollectedRef(typ.lastSon):
# destructor based seqs are themselves not traced but their data is, if
# they contain a GC'ed type:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCvisitSeq"),
cCast(CPointer, accessor),
c.visitorFrmt)
lineCg(p, cpsStmts, "#nimGCvisitSeq((void*)$1, $2);$n", [accessor, c.visitorFrmt])
#genTraverseProcSeq(c, accessor, typ)
of tyString:
if tfHasAsgn notin typ.flags:
visit(p, accessor, c.visitorFrmt)
lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
of tyProc:
if typ.callConv == ccClosure:
visit(p, dotField(accessor, "ClE_0"), c.visitorFrmt)
lineCg(p, cpsStmts, visitorFrmt, [ropecg(c.p.module, "$1.ClE_0", [accessor]), c.visitorFrmt])
else:
discard
proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType) =
var p = c.p
assert typ.kind == tySequence
var i = getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo, tyInt))
var oldCode = p.s(cpsStmts)
var oldLen, newLen: int
var a = TLoc(snippet: accessor)
let le = lenExpr(c.p, a)
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
p.s(cpsStmts).addForRangeExclusive(i.snippet, cIntValue(0), le):
oldLen = p.s(cpsStmts).buf.len
genTraverseProc(c, subscript(dataField(c.p, accessor), i.snippet), typ.elementType)
newLen = p.s(cpsStmts).buf.len
if newLen == oldLen:
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])
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", [])
proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash): Rope =
var c: TTraversalClosure
var p = newProc(nil, m)
result = "Marker_" & getTypeName(m, origTyp, sig)
let
hcrOn = m.hcrOn
typ = origTyp.skipTypes(abstractInstOwned)
markerName = if hcrOn: result & "_actual" else: result
header = "static N_NIMCALL(void, $1)(void* p, NI op)" % [markerName]
t = getTypeDesc(m, typ)
p.s(cpsLocals).addVar(kind = Local, name = "a", typ = t)
p.s(cpsInit).addAssignment("a", cCast(t, "p"))
lineF(p, cpsLocals, "$1 a;$n", [t])
lineF(p, cpsInit, "a = ($1)p;$n", [t])
var c = TTraversalClosure(p: p,
visitorFrmt: "op" # "#nimGCvisit((void*)$1, op);$n"
)
c.p = p
c.visitorFrmt = "op" # "#nimGCvisit((void*)$1, op);$n"
assert typ.kind != tyTypeDesc
if typ.kind == tySequence:
genTraverseProcSeq(c, "a".rope, typ)
else:
if skipTypes(typ.elementType, typedescInst+{tyOwned}).kind == tyArray:
if skipTypes(typ[0], typedescInst+{tyOwned}).kind == tyArray:
# C's arrays are broken beyond repair:
genTraverseProc(c, "a".rope, typ.elementType)
genTraverseProc(c, "a".rope, typ[0])
else:
genTraverseProc(c, cDeref("a"), typ.elementType)
genTraverseProc(c, "(*a)".rope, typ[0])
var headerBuilder = newBuilder("")
headerBuilder.addProcHeaderWithParams(ccNimCall, markerName, CVoid):
var paramBuilder: ProcParamBuilder
headerBuilder.addProcParams(paramBuilder):
headerBuilder.addParam(paramBuilder, name = "p", typ = CPointer)
headerBuilder.addParam(paramBuilder, name = "op", typ = NimInt)
let header = extract(headerBuilder)
let generatedProc = "$1 {$n$2$3$4}\n" %
[header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)]
m.s[cfsProcHeaders].addDeclWithVisibility(StaticProc):
m.s[cfsProcHeaders].add(header)
m.s[cfsProcHeaders].finishProcHeaderAsProto()
m.s[cfsProcs].addDeclWithVisibility(StaticProc):
m.s[cfsProcs].add(header)
m.s[cfsProcs].finishProcHeaderWithBody():
m.s[cfsProcs].add(extract(p.s(cpsLocals)))
m.s[cfsProcs].add(extract(p.s(cpsInit)))
m.s[cfsProcs].add(extract(p.s(cpsStmts)))
m.s[cfsProcHeaders].addf("$1;\n", [header])
m.s[cfsProcs].add(generatedProc)
if hcrOn:
var desc = newBuilder("")
var unnamedParamBuilder: ProcParamBuilder
desc.addProcParams(unnamedParamBuilder):
desc.addUnnamedParam(unnamedParamBuilder, CPointer)
desc.addUnnamedParam(unnamedParamBuilder, NimInt)
let unnamedParams = extract(desc)
m.s[cfsProcHeaders].addProcVar(ccNimCall, result, unnamedParams, CVoid)
m.s[cfsDynLibInit].addAssignmentWithValue(result):
m.s[cfsDynLibInit].addCast(procPtrTypeUnnamed(ccNimCall, CVoid, unnamedParams)):
m.s[cfsDynLibInit].addCall("hcrRegisterProc",
getModuleDllPath(m),
'"' & result & '"',
cCast(CPointer, markerName))
m.s[cfsProcHeaders].addf("N_NIMCALL_PTR(void, $1)(void*, NI);\n", [result])
m.s[cfsDynLibInit].addf("\t$1 = (N_NIMCALL_PTR(void, )(void*, NI)) hcrRegisterProc($3, \"$1\", (void*)$2);\n",
[result, markerName, getModuleDllPath(m)])
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)
if sfThread in s.flags and emulatedThreadVars(m.config):
accessThreadLocalVar(p, s)
sLoc = derefField("NimTV_", sLoc)
var c = TTraversalClosure(p: p,
visitorFrmt: cIntValue(0) # "#nimGCvisit((void*)$1, 0);$n"
)
sLoc = "NimTV_->" & sLoc
c.visitorFrmt = "0" # "#nimGCvisit((void*)$1, 0);$n"
c.p = p
let header = "static N_NIMCALL(void, $1)(void)" % [result]
genTraverseProc(c, sLoc, s.loc.t)
var headerBuilder = newBuilder("")
headerBuilder.addProcHeaderWithParams(ccNimCall, result, CVoid):
var paramBuilder: ProcParamBuilder
headerBuilder.addProcParams(paramBuilder):
# (void)
discard
let header = extract(headerBuilder)
let generatedProc = "$1 {$n$2$3$4}$n" %
[header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)]
m.s[cfsProcHeaders].addDeclWithVisibility(StaticProc):
m.s[cfsProcHeaders].add(header)
m.s[cfsProcHeaders].finishProcHeaderAsProto()
m.s[cfsProcs].addDeclWithVisibility(StaticProc):
m.s[cfsProcs].add(header)
m.s[cfsProcs].finishProcHeaderWithBody():
m.s[cfsProcs].add(extract(p.s(cpsLocals)))
m.s[cfsProcs].add(extract(p.s(cpsInit)))
m.s[cfsProcs].add(extract(p.s(cpsStmts)))
m.s[cfsProcHeaders].addf("$1;$n", [header])
m.s[cfsProcs].add(generatedProc)

File diff suppressed because it is too large Load Diff

View File

@@ -10,27 +10,19 @@
# This module declares some helpers for the C code generator.
import
ast, types, msgs, wordrecg,
platform, trees, options, cgendata, mangleutils, renderer, modulegraphs
import std/[hashes, strutils, formatfloat]
when defined(nimPreviewSlimSystem):
import std/assertions
ast, types, hashes, strutils, msgs, wordrecg,
platform, trees, options, cgendata
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:
result = nil
else: discard
proc stmtsContainPragma*(n: PNode, w: TSpecialWord): bool =
result = getPragmaStmt(n, w) != nil
@@ -58,7 +50,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](uint(a))
result = cast[Hash](a)
template getUniqueType*(key: PType): PType = key
@@ -68,6 +60,53 @@ 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"
else:
result.add("X" & toHex(ord(c), 2))
requiresUnderscore = true
if requiresUnderscore:
result.add "_"
proc mapSetType(conf: ConfigRef; typ: PType): TCTypeKind =
case int(getSize(conf, typ))
of 1: result = ctInt8
@@ -80,10 +119,7 @@ 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
if tfByRef in pt.flags: return true
elif tfByCopy in pt.flags: return false
case pt.kind
of tyObject:
@@ -106,91 +142,3 @@ proc ccgIntroducedPtr*(conf: ConfigRef; s: PSym, retType: PType): bool =
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
# keep backend-minted ids out of the `_u` namespace; their item counter
# restarts at 0 and would collide with loaded symbols' ids
if s.itemId.isBackendMinted:
result.add "_c"
if (s.disamb and HookDisambBit) != 0'i32:
# A backend-minted sym whose `disamb` is content-derived (setHookDisamb gave
# it HookDisambBit) — e.g. the `rttiDestroy` wrapper. Its `itemId.item` is a
# PER-PROCESS backend counter, so using it makes the C name diverge across
# the emit-everywhere processes: the type's RTTI table (emit-everywhere,
# merge-deduped) ends up referencing one process's `_c<item>` while the
# wrapper is defined with another's -> undefined at link (`rttiDestroy_c23`).
# The content-derived disamb is stable across processes, so use it.
result.add $s.disamb
else:
result.add $s.itemId.item
else:
result.add "_u"
# Mirror `mangleProcNameExt`: use the per-(module,name) `disamb`, NOT
# `itemId.item`. Under the per-module IC backend the same symbol is loaded
# from a NIF in many processes and `itemId.item` is a fresh, load-order
# dependent counter — so a method base would mangle to `_u1` in one module,
# `_u3` in another and clean at its owner, none of which link. `disamb` is
# assigned deterministically per (module, name) and is serialized, so every
# process that touches the symbol derives the identical C name.
result.add $s.disamb
# module suffix LAST (a strippable trailing token; see `mangleProcNameExt`)
result.add "__"
result.add m.g.graph.ifaces[s.itemId.module].uniqueName
proc encodeSym*(m: BModule; s: PSym; makeUnique: bool = false; extra: string = ""): string =
#Module::Type
var name = s.name.s & extra
if makeUnique:
name = makeUnique(m, s, name)
"N" & encodeName(s.skipGenericOwner.name.s) & encodeName(name) & "E"
proc encodeType*(m: BModule; t: PType; staticLists: var string): 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], staticLists)
result.add "E"
of tySequence, tyOpenArray, tyArray, tyVarargs, tyTuple, tyProc, tySet, tyTypeDesc,
tyPtr, tyRef, tyVar, tyLent, tySink, 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, staticLists)
result.add "E"
of tyStatic:
if t.n != nil:
staticLists.add "_s" & renderTree(t.n)
else:
raiseAssert "unreachable"
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, staticLists)
else:
assert false, "encodeType " & $t.kind

File diff suppressed because it is too large Load Diff

View File

@@ -10,14 +10,13 @@
## This module contains the data structures for the C code generation phase.
import
ast, ropes, options,
lineinfos, pathutils, modulegraphs, cbuilderbase
import std/[intsets, tables, sets]
ast, ropes, options, intsets,
tables, ndi, lineinfos, pathutils, modulegraphs, 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
@@ -25,17 +24,21 @@ 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,
@@ -43,12 +46,12 @@ type
ctUInt, ctUInt8, ctUInt16, ctUInt32, ctUInt64,
ctArray, ctPtrToArray, ctStruct, ctPtr, ctNimStr, ctNimSeq, ctProc,
ctCString
TCFileSections* = array[TCFileSection, Builder] # represents a generated C file
TCFileSections* = array[TCFileSection, Rope] # represents a generated C file
TCProcSection* = enum # the sections a generated C proc consists of
cpsLocals, # section of local variables for C proc
cpsInit, # section for init of variables for C proc
cpsStmts # section of local statements for C proc
TCProcSections* = array[TCProcSection, Builder] # represents a generated C proc
TCProcSections* = array[TCProcSection, Rope] # represents a generated C proc
BModule* = ref TCGen
BProc* = ref TCProc
TBlock* = object
@@ -75,13 +78,10 @@ type
flags*: set[TCProcFlag]
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
currLineInfo*: TLineInfo # AST codegen will make this superfluous
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]]
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
# in how many nested try statements we are
# (the vars must be volatile then)
# `inExcept` is true when we are in the except part of a try block.
# `isHidden` is true for compiler-injected `nkHiddenTryStmt` wrappers
# (e.g. ARC's destructor try/finally around `except T as e:` bodies);
# finallyActions walks past such wrappers to reach the user's try.
# bool is true when are in the except part of a try block
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
# using return in finally statements
labels*: Natural # for generating unique labels in the C proc
@@ -91,7 +91,6 @@ type
options*: TOptions # options that should be used for code
# generation; this is the same as prc.options
# unless prc == nil
optionsStack*: seq[(TOptions, TNoteKinds)]
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
@@ -100,7 +99,6 @@ 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]
@@ -118,11 +116,11 @@ type
# computing alive data on our own.
BModuleList* = ref object of RootObj
mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Builder
mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Rope
mapping*: Rope # the generated mapping file (if requested)
mods*: seq[BModule] # list of all compiled modules
modules*: seq[BModule] # list of all compiled modules
modulesClosed*: seq[BModule] # list of the same compiled modules, but in the order they were closed
forwardedProcs*: seq[PSym] # procs that did not yet have a body
forwardedProcs*: seq[PSym] # proc:s that did not yet have a body
generatedHeader*: BModule
typeInfoMarker*: TypeCacheWithOwner
typeInfoMarkerV2*: TypeCacheWithOwner
@@ -130,7 +128,7 @@ type
graph*: ModuleGraph
strVersion*, seqVersion*: int # version of the string/seq implementation to use
nimtv*: Builder # Nim thread vars; the struct body
nimtv*: Rope # Nim thread vars; the struct body
nimtvDeps*: seq[PType] # type deps: every module needs whole struct
nimtvDeclared*: IntSet # so that every var/field exists only once
# in the struct
@@ -140,7 +138,6 @@ 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
@@ -154,83 +151,54 @@ 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 occurrence in code
# 1 ABI check per occurence 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
emittedContentDefs*: HashSet[string]
# cmdNifC per-module backend: content-addressed C names (generic
# instances and synthesized hooks) whose body this TU already emitted.
# Distinct symbols (minted in different source modules) can share one
# `_i<disamb>` name; `declaredThings` keys on symbol id and lets the
# second one through, so we dedup the body by name here instead.
queue*: seq[PSym] # queue of procs to generate
alive*: IntSet # symbol IDs of alive data as computed by `dce.nim`
headerFiles*: seq[string] # needed headers to include
typeInfoMarker*: TypeCache # needed for generating type information
typeInfoMarkerV2*: TypeCache
initProc*: BProc # code for init procedure
preInitProc*: BProc # code executed before the init proc
hcrCreateTypeInfosProc*: Builder # type info globals are in here when HCR=on
hcrCreateTypeInfosProc*: Rope # type info globals are in here when HCR=on
inHcrInitGuard*: bool # We are currently within a HCR reloading guard.
hcrInitGuard*: IfBuilder
typeStack*: TTypeSeq # used for type generation
dataCache*: TNodeTable
typeNodes*, nimTypes*: int # used for type info generation
typeNodesName*, nimTypesName*: Rope # used for type info generation
labels*: Natural # for generating unique module-scope names
extensionLoaders*: array['0'..'9', Builder] # special procs for the
extensionLoaders*: array['0'..'9', Rope] # special procs for the
# OpenGL wrapper
sigConflicts*: CountTable[SigHash]
icImplMods*: IntSet # module ids whose routine BODIES this TU
# embeds (redirected defs, shared instances,
# hooks); recorded as the artifact's cdeps so
# the reuse gate can check their impl cookies
icDataDefs*: seq[tuple[cname, nifname: string]]
# C names of data definitions (consts, globals,
# RTTI) this TU embeds plus their NIF symbol
# names (empty for RTTI, which has no symbol);
# recorded in the cnif artifact so a later run
# can reuse the TU and re-demand definitions
# that cached TUs still reference
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 delayedCodegen*(m: BModule): bool {.inline.} =
useAliveDataFromDce in m.flags or m.config.globalOptions.contains(optCompress)
proc includeHeader*(this: BModule; header: string) =
if not this.headerFiles.contains header:
this.headerFiles.add header
proc s*(p: BProc, s: TCProcSection): var Builder {.inline.} =
proc s*(p: BProc, s: TCProcSection): var Rope {.inline.} =
# section in the current block
result = p.blocks[^1].sections[s]
proc procSec*(p: BProc, s: TCProcSection): var Builder {.inline.} =
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] = newBuilder("")
proc newProc*(prc: PSym, module: BModule): BProc =
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}
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]()
proc newModuleList*(g: ModuleGraph): BModuleList =
BModuleList(typeInfoMarker: initTable[SigHash, tuple[str: Rope, owner: int32]](),

View File

@@ -10,15 +10,8 @@
## This module implements code generation for methods.
import
options, ast, msgs, idents, renderer, types, magicsys,
sempass2, modulegraphs, lineinfos, astalgo
import std/intsets
when defined(nimPreviewSlimSystem):
import std/assertions
import std/[tables]
intsets, options, ast, msgs, idents, renderer, types, magicsys,
sempass2, strutils, modulegraphs, lineinfos
proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
var dest = skipTypes(d, abstractPtrs)
@@ -47,15 +40,12 @@ 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:
@@ -67,25 +57,22 @@ 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.signatureLen != b.typ.signatureLen:
if a.typ.len != b.typ.len:
return
var i = 0
for x, y in paramTypePairs(a.typ, b.typ):
inc i
var aa = x
var bb = y
for i in 1..<a.typ.len:
var aa = a.typ[i]
var bb = b.typ[i]
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.elementType
bb = bb.elementType
aa = aa.lastSon
bb = bb.lastSon
else:
break
if sameType(x, y):
if sameType(a.typ[i], b.typ[i]):
if aa.kind == tyObject and result != Invalid:
result = Yes
elif aa.kind == tyObject and bb.kind == tyObject and (i == 1 or multiMethods):
@@ -103,11 +90,10 @@ proc sameMethodBucket(a, b: PSym; multiMethods: bool): MethodResult =
return No
if result == Yes:
# check for return type:
# 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:
if not sameTypeOrNil(a.typ[0], b.typ[0]):
if b.typ[0] != nil and b.typ[0].kind == tyUntyped:
# infer 'auto' from the base to make it consistent:
b.typ.setReturnType a.typ.returnType
b.typ[0] = a.typ[0]
else:
return No
@@ -122,21 +108,21 @@ proc attachDispatcher(s: PSym, dispatcher: PNode) =
s.ast[dispatcherPos] = dispatcher
proc createDispatcher(s: PSym; g: ModuleGraph; idgen: IdGenerator): PSym =
var disp = copySym(s, idgen)
incl(disp, sfDispatcher)
excl(disp, sfExported)
var disp = copySym(s, nextSymId(idgen))
incl(disp.flags, sfDispatcher)
excl(disp.flags, sfExported)
let old = disp.typ
disp.typ = copyType(disp.typ, idgen, disp.typ.owner)
disp.typ = copyType(disp.typ, nextTypeId(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.locImpl.snippet = ""
if s.typ.returnType != nil:
disp.loc.r = nil
if s.typ[0] != nil:
if disp.ast.len > resultPos:
disp.ast[resultPos].sym = copySym(s.ast[resultPos].sym, idgen)
disp.ast[resultPos].sym = copySym(s.ast[resultPos].sym, nextSymId(idgen))
else:
# We've encountered a method prototype without a filled-in
# resultPos slot. We put a placeholder in there that will
@@ -157,26 +143,25 @@ proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
disp.ast[resultPos].kind == nkEmpty:
disp.ast[resultPos] = copyTree(meth.ast[resultPos])
proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: 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") and sfFromGeneric notin s.flags:
# `sfFromGeneric` excepted: this is the same-module restriction for vtable
# slot placement, and it must be judged on the GENERIC method, not on an
# instance. The generic `method skip[T](x: Input[T])` never reaches here
# (`semMethodPrototype` registers generic methods via `addMethodToGeneric`,
# bypassing `methodDef`); only its instance `skip[string]` does, and that
# instance's first-param type `Input[string]` is owned by whichever module
# first instantiated it (`tparsecombnum`, which `import parsecomb`s and uses
# it), NOT by `Input[T]`'s defining module — so the comparison spuriously
# fails for a method that is perfectly legal at the generic level. (Concrete
# methods, `sfFromGeneric notin flags`, are still checked.)
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")
# 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
for i in 0..<g.methods.len:
let disp = g.methods[i].dispatcher
case sameMethodBucket(disp, s, multimethods = optMultiMethods in g.config.globalOptions)
@@ -190,19 +175,12 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
g.methods[i].methods[0] != s:
# already exists due to forwarding definition?
localError(g.config, s.info, "method is not a base")
logMethodDef(g, s)
return
of No: discard
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)))
logMethodDef(g, s)
#echo "adding ", s.info
if witness != nil:
localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
@@ -210,9 +188,8 @@ 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):
@@ -221,8 +198,7 @@ proc relevantCol*(methods: seq[PSym], col: int): bool =
return true
proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
result = 0
for col in FirstParamAt..<a.typ.signatureLen:
for col in 1..<a.typ.len:
if contains(relevantCols, col):
var aa = skipTypes(a.typ[col], skipPtrs)
var bb = skipTypes(b.typ[col], skipPtrs)
@@ -230,7 +206,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
@@ -249,16 +225,16 @@ proc sortBucket*(a: var seq[PSym], relevantCols: IntSet) =
a[j] = v
if h == 1: break
proc genIfDispatcher*(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idgen: IdGenerator): PSym =
proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PSym =
var base = methods[0].ast[dispatcherPos].sym
result = base
var paramLen = base.typ.signatureLen
var paramLen = base.typ.len
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 FirstParamAt..<paramLen:
for col in 1..<paramLen:
if contains(relevantCols, col):
let param = base.typ.n[col].sym
if param.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
@@ -267,7 +243,7 @@ proc genIfDispatcher*(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet;
for meth in 0..high(methods):
var curr = methods[meth] # generate condition:
var cond: PNode = nil
for col in FirstParamAt..<paramLen:
for col in 1..<paramLen:
if contains(relevantCols, col):
var isn = newNodeIT(nkCall, base.info, boolType)
isn.add newSymNode(iss)
@@ -282,7 +258,7 @@ proc genIfDispatcher*(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet;
cond = a
else:
cond = isn
let retTyp = base.typ.returnType
let retTyp = base.typ[0]
let call = newNodeIT(nkCall, base.info, retTyp)
call.add newSymNode(curr)
for col in 1..<paramLen:
@@ -308,13 +284,14 @@ proc genIfDispatcher*(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet;
nilchecks.flags.incl nfTransf # should not be further transformed
result.ast[bodyPos] = nilchecks
proc generateIfMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator) =
proc generateMethodDispatchers*(g: ModuleGraph): PNode =
result = newNode(nkStmtList)
for bucket in 0..<g.methods.len:
var relevantCols = initIntSet()
for col in FirstParamAt..<g.methods[bucket].methods[0].typ.signatureLen:
for col in 1..<g.methods[bucket].methods[0].typ.len:
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)
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, idgen)
result.add newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols))

File diff suppressed because it is too large Load Diff

View File

@@ -7,13 +7,11 @@
# distribution, for details about the copyright.
#
## Helpers for binaries that use compiler passes, e.g.: nim, nimsuggest
## Helpers for binaries that use compiler passes, e.g.: nim, nimsuggest, nimfix
import
options, idents, nimconf, extccomp, commands, msgs,
lineinfos, modulegraphs, condsyms, pathutils
import std/[os, parseopt]
lineinfos, modulegraphs, condsyms, os, pathutils, parseopt
proc prependCurDir*(f: AbsoluteFile): AbsoluteFile =
when defined(unix):
@@ -53,16 +51,10 @@ proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) =
proc loadConfigsAndProcessCmdLine*(self: NimProg, cache: IdentCache; conf: ConfigRef;
graph: ModuleGraph): bool =
if self.suggestMode:
conf.setCmd cmdCheck
conf.ideActive = true
conf.setCmd cmdIdeTools
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")

View File

@@ -1,726 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## The "cnif" artifact: the C code generator's output as a NIF file.
##
## This is deliberately *not* NIFC: the C text is kept verbatim (Nim's
## C-level machinery — exception handling in particular — is more refined
## than what NIFC models today; the gap can be closed incrementally later).
## The only structure the artifact adds is the part dead code elimination
## and generic-instance merging need:
##
## - raw C text as string literals
## - every *global* entity's C name as a `Symbol` token
## - every emitted proc definition as a `(cdef SymbolDef flags ...)` group
##
## The C generator marks names with control characters at the single place
## a global's C name is minted (`fillBackendName`) and emits a definition
## directive at the single place finished procs are appended; the marks then
## ride through all of the snippet composition untouched. This module turns
## the final marked module text into the `.c.nif` artifact and strips the
## marks for the actual `.c` output. Rendering C from the artifact is a
## plain token walk: string literals verbatim, symbols by name — which is
## also where a later merge step redirects losing generic instances.
##
## Marker scheme (cannot collide: C string literals escape control chars,
## and `\1`/`\31`/`\23` of cgen's postprocess directives are distinct):
## \2 name \3 a global's C name
## \4 name \31 flags \31 nif \5 start of the definition of `name`;
## `nif` is the defining symbol's NIF name
## (empty for backend-minted symbols) so a
## later run can re-demand the definition
## \4 \5 end of the definitions section
import std / [tables, sets, os, assertions, syncio, algorithm]
import "../dist/nimony/src/lib" / [nifbuilder, nifcoreparse]
const
CnifSymStart* = '\2'
CnifSymEnd* = '\3'
CnifDefStart* = '\4'
CnifDefSep* = '\31' # same separator char as cgen's postprocess directives
CnifDefEnd* = '\5'
proc markCName*(name: string): string {.inline.} =
CnifSymStart & name & CnifSymEnd
proc hasCnifMarks*(s: string): bool =
for c in s:
if c in {CnifSymStart, CnifSymEnd, CnifDefStart}: return true
false
proc stripCnifMarks*(s: string): string =
## Removes the symbol marks (keeping the names) and the definition
## directives (entirely) so the result is plain C.
if not hasCnifMarks(s): return s
result = newStringOfCap(s.len)
var i = 0
while i < s.len:
case s[i]
of CnifSymStart, CnifSymEnd:
inc i
of CnifDefStart:
while i < s.len and s[i] != CnifDefEnd: inc i
inc i # skip CnifDefEnd
else:
result.add s[i]
inc i
const
CnifVersion* = "4"
## Artifact format version, stored in the meta head. Artifacts written
## by an older compiler lack the NIF names and the cref group the
## def-retention check needs (v2), the cdeps group the fine-grained
## reuse gate needs (v3), or the type NIF names and cnif-marked extern
## RTTI references the typeinfo flavor of the def-retention check
## needs (v4); `readCnifHeads` reports them as invalid so their TUs
## simply regenerate once.
proc cnifDefDirective*(name, flags, nifName: string): string =
CnifDefStart & name & CnifDefSep & flags & CnifDefSep & nifName & CnifDefEnd
proc cnifEndDefs*(): string =
CnifDefStart & CnifDefEnd
proc writeCnifArtifact*(code: string; outfile: string;
initRequired = false; datInitRequired = false;
dataDefs: openArray[tuple[cname, nifname: string]] = [];
semmedNif = ""; moduleBase = "";
implDeps: openArray[string] = []) =
## Splits the marked module text into the `.c.nif` artifact.
## The artifact starts with a `(meta <flags> "semmedNif" "moduleBase"
## "version")` head — whether the module has an init/datInit proc
## ('i'/'d'), which semmed NIF it was generated from and the module's
## mangled base name (what `registerModuleToMain` and the reuse decision
## need when the TU is reused in a later run, possibly without the module
## ever being loaded again) — a `(cdata (SymbolDef StrLit)*)` group naming
## the data definitions (consts, globals, RTTI) the TU embeds together
## with their NIF names, a `(cref Ident*)` group naming every C name
## the TU references but does not define itself (what the def-retention
## check consults when some *other* TU regenerates), and a
## `(cdeps Ident*)` group naming the modules whose routine *bodies* this
## TU embeds (redirected defs, shared instances, hooks): the fine-grained
## reuse gate checks their `.impl.nif` cookies on top of the direct
## imports' `.iface.nif` cookies.
# pre-pass: every marked name is a use, every definition directive (and
# every data def) is a definition; external references = uses - defs
var uses = initHashSet[string]()
var defs = initHashSet[string]()
block prePass:
var i = 0
while i < code.len:
case code[i]
of CnifSymStart:
inc i
var name = ""
while i < code.len and code[i] != CnifSymEnd:
name.add code[i]
inc i
inc i
uses.incl name
of CnifDefStart:
inc i
var payload = ""
while i < code.len and code[i] != CnifDefEnd:
payload.add code[i]
inc i
inc i
let sep = find(payload, CnifDefSep)
if sep > 0: defs.incl payload[0..<sep]
elif payload.len > 0: defs.incl payload
else:
inc i
for d in dataDefs: defs.incl d.cname
var crefs: seq[string] = @[]
for u in uses:
if u notin defs: crefs.add u
sort crefs
var b = nifbuilder.open(outfile)
b.withTree "stmts":
b.withTree "meta":
var metaFlags = ""
if initRequired: metaFlags.add 'i'
if datInitRequired: metaFlags.add 'd'
if metaFlags.len > 0: b.addIdent metaFlags
else: b.addEmpty
b.addStrLit semmedNif
b.addStrLit moduleBase
b.addStrLit CnifVersion
b.withTree "cdata":
for d in dataDefs:
b.addSymbolDef d.cname
b.addStrLit d.nifname
b.withTree "cref":
for r in crefs:
b.addIdent r
b.withTree "cdeps":
for s in implDeps:
b.addIdent s
var raw = ""
var inDef = false
template flushRaw() =
if raw.len > 0:
b.addStrLit raw
raw.setLen 0
var i = 0
while i < code.len:
case code[i]
of CnifSymStart:
flushRaw()
inc i
var name = ""
while i < code.len and code[i] != CnifSymEnd:
name.add code[i]
inc i
inc i # skip CnifSymEnd
b.addSymbol name, ""
of CnifDefStart:
flushRaw()
inc i
var payload = ""
while i < code.len and code[i] != CnifDefEnd:
payload.add code[i]
inc i
inc i # skip CnifDefEnd
if inDef:
b.endTree()
inDef = false
if payload.len > 0:
let sep = find(payload, CnifDefSep)
let name = if sep >= 0: payload[0..<sep] else: payload
var flags = if sep >= 0: payload[sep+1..^1] else: ""
var nifName = ""
let sep2 = find(flags, CnifDefSep)
if sep2 >= 0:
nifName = flags[sep2+1..^1]
flags = flags[0..<sep2]
b.addTree "cdef"
b.addSymbolDef name
if flags.len > 0: b.addIdent flags
else: b.addEmpty
b.addStrLit nifName
inDef = true
else:
raw.add code[i]
inc i
flushRaw()
if inDef:
b.endTree()
b.close()
proc renderMarkedC*(code: string; live: HashSet[string]; dropped: var int): string =
## Renders the final C text from the marked module text: symbol marks are
## removed (keeping the names — a later merge step substitutes them here),
## and definitions whose name is not in `live` are dropped entirely. Each
## definition is self-delimiting (genProcAux emits an end directive right
## after the proc's text), so text written by other emitters is never part
## of a definition's span and survives unconditionally.
result = newStringOfCap(code.len)
var i = 0
while i < code.len:
case code[i]
of CnifSymStart, CnifSymEnd:
inc i
of CnifDefStart:
var payload = ""
inc i
while i < code.len and code[i] != CnifDefEnd:
payload.add code[i]
inc i
inc i # skip CnifDefEnd
if payload.len > 0:
let sep = find(payload, CnifDefSep)
let name = if sep >= 0: payload[0..<sep] else: payload
if name notin live:
inc dropped
# drop the definition's text: everything up to its end directive
while i < code.len and code[i] != CnifDefStart: inc i
else:
result.add code[i]
inc i
# ---- Liveness over the artifact -------------------------------------------
proc symOrIdentName(c: Cursor): string {.inline.} =
if c.kind == Ident: strVal(c) else: symName(c)
type
CnifHeads* = object
## The cheap-to-parse part of an artifact that a later run needs in
## order to reuse the TU without regenerating it.
valid*: bool ## file parsed, carries the meta head and has
## the current format version
initRequired*: bool
datInitRequired*: bool
semmedNif*: string ## the semmed NIF this TU was generated from
moduleBase*: string ## the module's mangled base name
cdefs*: seq[tuple[cname, nifname: string]] ## the proc definitions
cdata*: seq[tuple[cname, nifname: string]] ## the data definitions
crefs*: seq[string] ## C names referenced but not defined here
cdeps*: seq[string] ## module suffixes whose routine bodies this
## TU embeds (impl-cookie gated on reuse)
proc readCnifHeads*(f: string): CnifHeads =
## Reads `(meta ...)`, `(cdata ...)`, `(cref ...)` and the `(cdef ...)`
## head names from an artifact. Artifacts written by an older compiler
## (no meta head or a different format version) report `valid=false`.
result = CnifHeads()
if not fileExists(f): return
var pool = newPool()
var tags = newTagPool()
let stmtsTag = tags.registerTag("stmts")
let cdefTag = tags.registerTag("cdef")
let cdataTag = tags.registerTag("cdata")
let crefTag = tags.registerTag("cref")
let cdepsTag = tags.registerTag("cdeps")
let metaTag = tags.registerTag("meta")
var buf = parseFromFile(f, 1000, pool, tags)
var c = beginRead(buf)
if c.kind != TagLit or c.cursorTagId != stmtsTag:
endRead(c)
return
var version = ""
var sawMeta = false
c.loopInto:
if c.kind == TagLit:
if c.cursorTagId == metaTag:
sawMeta = true
var strIdx = 0
c.loopInto:
if c.kind == Ident:
for ch in strVal(c):
if ch == 'i': result.initRequired = true
elif ch == 'd': result.datInitRequired = true
inc c
elif c.kind == StrLit:
if strIdx == 0: result.semmedNif = strVal(c)
elif strIdx == 1: result.moduleBase = strVal(c)
elif strIdx == 2: version = strVal(c)
inc strIdx
inc c
else:
skip c
elif c.cursorTagId == cdataTag:
c.loopInto:
if c.kind == SymbolDef:
result.cdata.add (symName(c), "")
inc c
elif c.kind == StrLit:
if result.cdata.len > 0:
result.cdata[^1].nifname = strVal(c)
inc c
else:
skip c
elif c.cursorTagId == crefTag:
c.loopInto:
if c.kind in {Ident, Symbol, SymbolDef}:
result.crefs.add symOrIdentName(c)
inc c
else:
skip c
elif c.cursorTagId == cdepsTag:
c.loopInto:
if c.kind in {Ident, Symbol, SymbolDef}:
result.cdeps.add symOrIdentName(c)
inc c
else:
skip c
elif c.cursorTagId == cdefTag:
# fixed head: SymbolDef, flags (Ident or empty), NIF name StrLit;
# everything after that is the definition's body text
var state = 0
c.loopInto:
if c.kind == SymbolDef:
result.cdefs.add (symName(c), "")
state = 1
inc c
elif state == 1: # the flags field
state = 2
skip c
elif state == 2: # the NIF name
if c.kind == StrLit and result.cdefs.len > 0:
result.cdefs[^1].nifname = strVal(c)
state = 3
skip c
else:
skip c
else:
skip c
else:
skip c
endRead(c)
result.valid = sawMeta and version == CnifVersion
type
CnifLiveness* = object
defs*: int ## proc definitions emitted across all modules
liveDefs*: int ## of those, reachable from the roots
live*: HashSet[string] ## live C names
broken*: bool
proc computeLiveFromCArtifacts*(files: openArray[string]): CnifLiveness =
## dce1-style mark&sweep over the C-shaped artifacts: a `(cdef ...)`
## group is a definition (flags 'x'/'c'/'m' — exportc, compilerproc,
## method/dispatcher — make it a root), names at the top level (data,
## globals, init code) are roots, names inside a group are its uses.
## Because the artifact is *fully lowered* output, no conservative
## modelling is needed: every call the C code contains is a token here.
##
## NB: mangled C names contain no dots, so NIF's text reader classifies
## them as `Ident` rather than `Symbol`; the dialect therefore treats
## Ident tokens as name uses. Inside a `(cdef ...)` the flags ident is
## the one immediately following the SymbolDef; everything after is a use.
result = CnifLiveness(live: initHashSet[string]())
var pool = newPool()
var tags = newTagPool()
let stmtsTag = tags.registerTag("stmts")
let cdefTag = tags.registerTag("cdef")
let cdataTag = tags.registerTag("cdata")
let crefTag = tags.registerTag("cref")
let cdepsTag = tags.registerTag("cdeps")
let metaTag = tags.registerTag("meta")
var uses = initTable[string, HashSet[string]]()
var roots = initHashSet[string]()
var defs = initHashSet[string]()
for f in files:
if not fileExists(f):
result.broken = true
return
var buf = parseFromFile(f, 1000, pool, tags)
var c = beginRead(buf)
if c.kind != TagLit or c.cursorTagId != stmtsTag:
result.broken = true
endRead(c)
return
c.loopInto:
case c.kind
of Symbol, Ident:
roots.incl symOrIdentName(c)
inc c
of TagLit:
if c.cursorTagId == metaTag or c.cursorTagId == cdataTag or
c.cursorTagId == crefTag or c.cursorTagId == cdepsTag:
# bookkeeping for TU reuse, irrelevant for liveness
skip c
elif c.cursorTagId == cdefTag:
var owner = ""
var flagsSeen = false
c.loopInto:
case c.kind
of SymbolDef:
owner = symName(c)
defs.incl owner
flagsSeen = false
inc c
of Symbol, Ident:
let name = symOrIdentName(c)
if not flagsSeen:
# the flags field right after the SymbolDef
flagsSeen = true
for ch in name:
# 'd' marks a data definition (const/RTTI): never DCE'd, so it
# is a root whose body keeps its referenced procs live
if ch in {'x', 'c', 'm', 'd'}:
roots.incl owner
break
else:
uses.mgetOrPut(owner, initHashSet[string]()).incl name
inc c
of DotToken:
flagsSeen = true # empty flags field
inc c
else:
skip c
else:
c.loopInto:
if c.kind in {Symbol, Ident}:
roots.incl symOrIdentName(c)
inc c
else:
skip c
else:
skip c
endRead(c)
# mark & sweep
var work = newSeqOfCap[string](roots.len)
for r in roots: work.add r
while work.len > 0:
let s = work.pop()
if not result.live.containsOrIncl(s):
if uses.hasKey(s):
for dep in uses[s]:
if dep notin result.live:
work.add dep
result.defs = defs.len
for d in defs:
if d in result.live: inc result.liveDefs
# ---- The merge stage: liveness + owner assignment -------------------------
type
MergeDecision* = object
## What the per-module backend's `merge` stage computes from every
## module's `.c.nif` and what its `emit` stage consumes to render the
## final `.c` of one module.
live*: HashSet[string] ## globally reachable C names (dead cdefs
## are dropped from every module)
owners*: Table[string, string] ## for each `'u'`-flagged (unique,
## externally-linked) definition, the single
## artifact base name allowed to embed its
## body; every other module prototypes it
broken*: bool ## an artifact was missing or unparsable —
## the caller should fall back / regenerate
defs*, liveDefs*: int
proc computeMergeDecision*(files: openArray[string]): MergeDecision =
## One pass over every `.c.nif`: the same mark&sweep as
## `computeLiveFromCArtifacts` plus, per definition, owner assignment.
##
## Each `cg` process emits the body of every definition it demands
## (emit-everywhere), so the same externally-linked definition appears in
## several artifacts. A `'u'` flag on the `(cdef ...)` marks those that need
## exactly one owner, assigned here across processes: the owner is the
## lexicographically smallest artifact that emits it — a pure function of the
## claimant set, hence stable across rebuilds. Definitions without `'u'`
## (inline procs, dispatchers) are `static`/main-only and emitted into every
## using TU, so they get no owner entry and are never deduplicated.
result = MergeDecision(live: initHashSet[string](),
owners: initTable[string, string]())
var pool = newPool()
var tags = newTagPool()
let stmtsTag = tags.registerTag("stmts")
let cdefTag = tags.registerTag("cdef")
let cdataTag = tags.registerTag("cdata")
let crefTag = tags.registerTag("cref")
let cdepsTag = tags.registerTag("cdeps")
let metaTag = tags.registerTag("meta")
var uses = initTable[string, HashSet[string]]()
var roots = initHashSet[string]()
var defs = initHashSet[string]()
for f in files:
if not fileExists(f):
result.broken = true
return
let owner = extractFilename(f)
var buf = parseFromFile(f, 1000, pool, tags)
var c = beginRead(buf)
if c.kind != TagLit or c.cursorTagId != stmtsTag:
result.broken = true
endRead(c)
return
c.loopInto:
case c.kind
of Symbol, Ident:
roots.incl symOrIdentName(c)
inc c
of TagLit:
if c.cursorTagId == metaTag or c.cursorTagId == cdataTag or
c.cursorTagId == crefTag or c.cursorTagId == cdepsTag:
skip c
elif c.cursorTagId == cdefTag:
var ownerName = ""
var flagsSeen = false
var needsOwner = false
c.loopInto:
case c.kind
of SymbolDef:
ownerName = symName(c)
defs.incl ownerName
flagsSeen = false
inc c
of Symbol, Ident:
let name = symOrIdentName(c)
if not flagsSeen:
flagsSeen = true
for ch in name:
if ch in {'x', 'c', 'm'}: roots.incl ownerName
# 'u' = unique proc (DCE'd), 'd' = data (never DCE'd, hence a
# root); both need a single owner across the emit-everywhere
# processes
elif ch == 'u': needsOwner = true
elif ch == 'd':
needsOwner = true
roots.incl ownerName
else:
uses.mgetOrPut(ownerName, initHashSet[string]()).incl name
inc c
of DotToken:
flagsSeen = true # empty flags field
inc c
else:
skip c
if needsOwner and ownerName.len > 0:
# smallest claimant wins; ties impossible (one entry per name)
let prev = result.owners.getOrDefault(ownerName, "")
if prev.len == 0 or owner < prev:
result.owners[ownerName] = owner
else:
c.loopInto:
if c.kind in {Symbol, Ident}:
roots.incl symOrIdentName(c)
inc c
else:
skip c
else:
skip c
endRead(c)
var work = newSeqOfCap[string](roots.len)
for r in roots: work.add r
while work.len > 0:
let s = work.pop()
if not result.live.containsOrIncl(s):
if uses.hasKey(s):
for dep in uses[s]:
if dep notin result.live:
work.add dep
result.defs = defs.len
for d in defs:
if d in result.live: inc result.liveDefs
const MergeDecisionFile* = "ic.backend.merge.nif"
## Fixed name of the merge stage's output in the nimcache, read by `emit`.
proc writeMergeDecision*(outfile: string; d: MergeDecision) =
## Serializes the merge decision: `(merge (live Symbol*) (owners (own
## Symbol StrLit)*))`. C names are mangled (no dots) so they serialize as
## symbols; owner artifact base names go in string literals.
var live: seq[string] = @[]
for n in d.live: live.add n
sort live
var keys: seq[string] = @[]
for k in d.owners.keys: keys.add k
sort keys
var b = nifbuilder.open(outfile)
b.withTree "merge":
b.withTree "live":
for n in live: b.addSymbol n, ""
b.withTree "owners":
for k in keys:
b.withTree "own":
b.addSymbol k, ""
b.addStrLit d.owners[k]
b.close()
proc readMergeDecision*(f: string): MergeDecision =
## Reads back a `writeMergeDecision` file; `broken=true` if absent/unparsable.
result = MergeDecision(live: initHashSet[string](),
owners: initTable[string, string]())
if not fileExists(f):
result.broken = true
return
var pool = newPool()
var tags = newTagPool()
let mergeTag = tags.registerTag("merge")
let liveTag = tags.registerTag("live")
let ownersTag = tags.registerTag("owners")
let ownTag = tags.registerTag("own")
var buf = parseFromFile(f, 1000, pool, tags)
var c = beginRead(buf)
if c.kind != TagLit or c.cursorTagId != mergeTag:
result.broken = true
endRead(c)
return
c.loopInto:
if c.kind == TagLit and c.cursorTagId == liveTag:
c.loopInto:
if c.kind in {Symbol, Ident}:
result.live.incl symOrIdentName(c)
inc c
else:
skip c
elif c.kind == TagLit and c.cursorTagId == ownersTag:
c.loopInto:
if c.kind == TagLit and c.cursorTagId == ownTag:
var key = ""
c.loopInto:
if c.kind in {Symbol, Ident}:
key = symOrIdentName(c)
inc c
elif c.kind == StrLit:
if key.len > 0: result.owners[key] = strVal(c)
inc c
else:
skip c
else:
skip c
else:
skip c
endRead(c)
proc renderCFromArtifact*(artifact: string; d: MergeDecision; ownerId: string;
dropped: var int): string =
## The per-module backend's `emit` stage: render one module's final `.c` from
## its `.c.nif` and the merge decision. String literals are emitted verbatim,
## symbols by name; a `(cdef ...)` body is dropped when the name is dead, or
## when it is a `'u'` unique definition this module does not own. The body's
## prototype lives in the surrounding raw text (cgen emits a forward
## declaration for every *used* proc, independent of where the body lands), so
## a dropped body still leaves a valid declaration — no synthesis needed. The
## head groups (meta/cdata/cref/cdeps) carry no C text.
result = ""
if not fileExists(artifact): return
var pool = newPool()
var tags = newTagPool()
let stmtsTag = tags.registerTag("stmts")
let cdefTag = tags.registerTag("cdef")
var buf = parseFromFile(artifact, 1000, pool, tags)
var c = beginRead(buf)
if c.kind != TagLit or c.cursorTagId != stmtsTag:
endRead(c)
return
c.loopInto:
case c.kind
of StrLit:
result.add strVal(c)
inc c
of Symbol, Ident:
result.add symOrIdentName(c)
inc c
of TagLit:
if c.cursorTagId == cdefTag:
# fixed head: SymbolDef, flags (Ident or empty), nifname StrLit; the
# rest is the definition's body text. `state` counts past the head.
var name = ""
var isUnique = false
var isData = false
var keep = true
var state = 0
c.loopInto:
if state == 0 and c.kind == SymbolDef:
name = symName(c)
state = 1
inc c
elif state == 1: # the flags field (one token: Ident/Symbol or empty)
if c.kind in {Ident, Symbol}:
for ch in symOrIdentName(c):
if ch == 'u': isUnique = true
elif ch == 'd': isData = true
state = 2
inc c
elif state == 2: # the NIF name (one StrLit) — decide keep here
let owned = d.owners.getOrDefault(name, ownerId) == ownerId
keep =
if isData: owned # data: kept by its owner only
elif isUnique: (name in d.live) and owned
else: name in d.live # inline/dispatcher: per-TU
if not keep: inc dropped
state = 3
inc c
else: # body tokens
if keep:
if c.kind == StrLit: result.add strVal(c)
elif c.kind in {Symbol, Ident}: result.add symOrIdentName(c)
inc c
else:
# head groups (meta/cdata/cref/cdeps) carry no C text
skip c
else:
inc c
endRead(c)

View File

@@ -24,18 +24,13 @@ bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep")
bootSwitch(usedGoGC, defined(gogc), "--gc:go")
bootSwitch(usedNoGC, defined(nogc), "--gc:none")
import std/[setutils, sets, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
import std/[setutils, os, strutils, parseutils, parseopt, sequtils, strtabs]
import
msgs, options, nversion, condsyms, extccomp, platform,
wordrecg, nimblecmd, lineinfos, pathutils
import std/pathnorm
wordrecg, nimblecmd, lineinfos, pathutils, 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(usedFFI, hasFFI, "-d:nimHasLibFFI")
@@ -118,7 +113,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', 'warning', 'error' or 'usages' 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 % "-")
@@ -186,7 +181,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 notes: set[TMsgKind] = {}
var notes: set[TMsgKind]
var isBracket = false
if i < arg.len and arg[i] == '[':
isBracket = true
@@ -203,16 +198,11 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
else: invalidCmdLineOption(conf, pass, orig, info)
let isSomeHint = state in {wHint, wHintAsError}
let isSomeWarning = state in {wWarning, wWarningAsError}
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 or isSomeWarning:
message(conf, info, warnUnknownNotes, "unknown $#: $#" % [name, id])
else:
localError(conf, info, "unknown $#: $#" % [name, 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}
@@ -245,12 +235,10 @@ proc processCompile(conf: ConfigRef; filename: string) =
extccomp.addExternalFileToCompile(conf, found)
const
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'yrc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found"
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console' or 'lib' expected, but '$1' found"
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
errInvalidFeatureButXFound = Feature.toSeq.map(proc(val:Feature): string = "'$1'" % $val).join(", ") & " expected, but '$1' found"
errDefaultOrSsoExpectedButXFound = "'default' or 'sso' expected, but '$1' found"
template warningOptionNoop(switch: string) =
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
@@ -267,23 +255,18 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
of "destructors", "arc": result = conf.selectedGC == gcArc
of "orc": result = conf.selectedGC == gcOrc
of "yrc": result = conf.selectedGC == gcYrc
of "hooks": result = conf.selectedGC == gcHooks
of "go": result = conf.selectedGC == gcGo
of "none": result = conf.selectedGC == gcNone
of "stack", "regions": result = conf.selectedGC == gcRegions
of "atomicarc": result = conf.selectedGC == gcAtomicArc
else:
result = false
localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg)
of "v2", "generational": warningOptionNoop(arg)
else: 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:
result = false
localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg)
else: localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg)
of "verbosity": result = $conf.verbosity == arg
of "app":
case arg.normalize
@@ -293,9 +276,7 @@ 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:
result = false
localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
of "dynliboverride":
result = isDynlibOverride(conf, arg)
of "exceptions":
@@ -304,25 +285,8 @@ 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:
result = false
localError(conf, info, errInvalidExceptionSystem % arg)
of "strings":
case arg.normalize
of "default": result = conf.selectedStrings == stringDefault
of "sso": result = conf.selectedStrings == stringSso
else:
result = false
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
of "experimental":
try:
result = conf.features.contains parseEnum[Feature](arg)
except ValueError:
result = false
localError(conf, info, errInvalidFeatureButXFound % arg)
else:
result = false
invalidCmdLineOption(conf, passCmd1, switch, info)
else: localError(conf, info, errInvalidExceptionSystem % arg)
else: invalidCmdLineOption(conf, passCmd1, switch, info)
proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool =
case switch.normalize
@@ -360,7 +324,6 @@ 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)
@@ -368,15 +331,8 @@ 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)
result = false
of "panics": result = contains(conf.globalOptions, optPanics)
of "jsbigint64": result = contains(conf.globalOptions, optJsBigInt64)
of "mangle": result = contains(conf.globalOptions, optItaniumMangle)
else:
result = false
invalidCmdLineOption(conf, passCmd1, switch, info)
of "nilseqs", "nilchecks", "taintmode": warningOptionNoop(switch)
else: invalidCmdLineOption(conf, passCmd1, switch, info)
proc processPath(conf: ConfigRef; path: string, info: TLineInfo,
notRelativeToProj = false): AbsoluteDir =
@@ -418,8 +374,7 @@ proc makeAbsolute(s: string): AbsoluteFile =
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
var ln, col: int
if parseUtils.parseInt(line, ln) <= 0:
localError(conf, info, errInvalidNumber % line)
if parseUtils.parseInt(column, col) <= 0:
@@ -469,7 +424,7 @@ template handleStdinOrCmdInput =
conf.outDir = getNimcacheDir(conf)
proc handleStdinInput*(conf: ConfigRef) =
conf.projectName = conf.stdinFile.string
conf.projectName = "stdinfile"
conf.projectIsStdin = true
handleStdinOrCmdInput()
@@ -483,9 +438,7 @@ proc parseCommand*(command: string): Command =
of "cpp", "compiletocpp": cmdCompileToCpp
of "objc", "compiletooc": cmdCompileToOC
of "js", "compiletojs": cmdCompileToJS
of "nif": cmdCompileToNif
of "r": cmdCrun
of "m": cmdM
of "run": cmdTcc
of "check": cmdCheck
of "e": cmdNimscript
@@ -493,8 +446,6 @@ proc parseCommand*(command: string): Command =
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
@@ -503,13 +454,10 @@ proc parseCommand*(command: string): Command =
of "gendepend": cmdGendepend
of "dump": cmdDump
of "parse": cmdParse
of "rod": cmdRod
of "secret": cmdInteractive
of "nop", "help": cmdNop
of "jsonscript": cmdJsonscript
of "nifc": cmdNifC # generate C from NIF files
of "ic": cmdIc # generate .build.nif for nifmake
of "icconfig": cmdIcConfig # produce the precompiled config artifact
of "track": cmdTrack # IDE goto-def / find-usages over `nim ic`'s NIF output
else: cmdUnknown
proc setCmd*(conf: ConfigRef, cmd: Command) =
@@ -521,12 +469,6 @@ proc setCmd*(conf: ConfigRef, cmd: Command) =
of cmdCompileToCpp: conf.backend = backendCpp
of cmdCompileToOC: conf.backend = backendObjc
of cmdCompileToJS: conf.backend = backendJs
of cmdCompileToNif: conf.backend = backendNif
of cmdNifC:
conf.backend = backendC # NIF to C compilation
of cmdM:
# cmdM requires optCompress for proper IC handling (include files, etc.)
conf.globalOptions.incl optCompress
else: discard
proc setCommandEarly*(conf: ConfigRef, command: string) =
@@ -535,8 +477,7 @@ 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, cmdMd2html, cmdMd2tex:
# xxx see whether to add others: cmdGendepend, etc.
of cmdRst2html, cmdRst2tex: # xxx see whether to add others: cmdGendepend, etc.
conf.foreignPackageNotes = {hintSuccessX}
else:
conf.foreignPackageNotes = foreignPackageNotesDefault
@@ -556,117 +497,10 @@ proc specialDefine(conf: ConfigRef, key: string; pass: TCmdLinePass) =
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, "gcyrc")
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 "yrc":
conf.selectedGC = gcYrc
defineSymbol(conf.symbols, "gcyrc")
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 = ""
var val = ""
# Record config-file switches so the `nim ic` driver can serialise them into a
# precompiled-config artifact and have its per-module child processes replay
# them instead of re-parsing the `nim.cfg` chain (and re-running `config.nims`
# in the VM) on every invocation. Only `passPP` (config-file) switches are
# captured; command-line switches are forwarded by the build graph as usual.
# Path-search switches are skipped: their net effect already lives in the
# resolved `searchPaths` the driver forwards as `--path`, and replaying their
# raw (often relative-to-config-dir) arguments here would misresolve.
if pass == passPP and switch.normalize notin
["path", "p", "nimblepath", "lazypath", "excludepath",
"nonimblepath", "clearnimblepath", "nimcache"]:
conf.icConfigSwitches.add (switch, arg)
var
key, val: string
case switch.normalize
of "eval":
expectArg(conf, switch, arg, pass, info)
@@ -681,17 +515,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":
of "nimblepath", "babelpath":
if switch.normalize == "babelpath": deprecatedAlias(switch, "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":
of "nonimblepath", "nobabelpath":
if switch.normalize == "nobabelpath": deprecatedAlias(switch, "nonimblepath")
expectNoArg(conf, switch, arg, pass, info)
disableNimblePath(conf)
of "clearnimblepath":
@@ -708,7 +542,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
# 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)
conf.nimcacheDir = processPath(conf, arg, info, notRelativeToProj=true)
of "out", "o":
expectArg(conf, switch, arg, pass, info)
let f = splitFile(processPath(conf, arg, info, notRelativeToProj=true).string)
@@ -719,14 +553,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.outDir = processPath(conf, arg, info, notRelativeToProj=true)
of "usenimcache":
processOnOffSwitchG(conf, {optUseNimcache}, arg, pass, info)
of "ideimports":
# nimsuggest: where the import closure comes from. IC is opt-in.
# nif|on load unchanged imports from precompiled NIF (cmdM)
# source|off (default) recompile the whole closure from source (cmdCheck)
case arg.normalize
of "nif", "on", "": conf.ideImportsFromNif = true
of "source", "off": conf.ideImportsFromNif = false
else: localError(conf, info, "'--ideImports' expects 'nif' or 'source', got: '$1'" % arg)
of "docseesrcurl":
expectArg(conf, switch, arg, pass, info)
conf.docSeeSrcUrl = arg
@@ -735,11 +561,6 @@ 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":
@@ -775,22 +596,60 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
processOnOffSwitchG(conf, {optForceFullMake}, arg, pass, info)
of "project":
processOnOffSwitchG(conf, {optWholeProject, optGenIndex}, arg, pass, info)
of "gc":
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 "strings":
of "gc", "mm":
if conf.backend == backendJs: return # for: bug #16033
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
case arg.normalize
of "default":
conf.selectedStrings = stringDefault
of "sso":
conf.selectedStrings = stringSso
defineSymbol(conf.symbols, "nimsso")
else:
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
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)
of "warnings", "w":
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
@@ -816,18 +675,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.globalOptions.excl optCDebug
else:
localError(conf, info, "expected native|gdb|on|off but found " & arg)
of "mangle":
case arg.normalize
of "nim":
conf.globalOptions.excl optItaniumMangle
of "cpp":
conf.globalOptions.incl optItaniumMangle
else:
localError(conf, info, "expected nim|cpp but found " & arg)
of "compress":
conf.globalOptions.incl optCompress
of "genbif":
processOnOffSwitchG(conf, {optGenBif}, arg, pass, info)
of "g": # alias for --debugger:native
conf.globalOptions.incl optCDebug
conf.options.incl optLineDir
@@ -843,7 +690,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
of "hotcodereloading":
processOnOffSwitchG(conf, {optHotCodeReloading}, arg, pass, info)
if conf.hcrOn:
warningDeprecated(conf, info, "hotCodeReloading is deprecated, see https://github.com/nim-lang/RFCs/issues/573 for further information")
defineSymbol(conf.symbols, "hotcodereloading")
defineSymbol(conf.symbols, "useNimRtl")
# hardcoded linking with dynamic runtime for MSVC for smaller binaries
@@ -871,13 +717,10 @@ 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 or conf.cmd == cmdNimscript: discard
if conf.backend == backendJs: discard
else: processOnOffSwitchG(conf, {optThreads}, arg, pass, info)
#if optThreads in conf.globalOptions: conf.setNote(warnGcUnsafe)
of "tlsemulation":
processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info)
if optTlsEmulation in conf.globalOptions:
conf.legacyFeatures.incl emitGenerics
of "tlsemulation": processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info)
of "implicitstatic":
processOnOffSwitch(conf, {optImplicitStatic}, arg, pass, info)
of "patterns", "trmacros":
@@ -914,7 +757,6 @@ 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")
@@ -934,79 +776,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 arg
conf.cLinkedLibs.add processPath(conf, arg, info).string
of "header":
if conf != nil: conf.headerFile = arg
incl(conf.globalOptions, optGenIndex)
of "nimbasepattern":
if conf != nil: conf.nimbasePattern = arg
of "index":
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 "ismainmodule":
# `nim m` (IC) only: marks the single module being checked as the program's
# real entry point so that `isMainModule` and `when isMainModule:` resolve
# correctly even though every module is compiled with `sfMainModule` set.
conf.isMainModule = switchOn(arg)
of "icgroup":
# `nim m` only: register a module that belongs to the current strongly-
# connected import group, so it is compiled from source (not loaded from a
# precompiled NIF) and gets its own NIF written. `deps.nim` emits one
# `--icGroup:<path>` per member of a dependency cycle. The argument is an
# absolute .nim path produced by the dependency scanner.
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
conf.icGroup.incl(canonicalizePath(conf, AbsoluteFile arg).string)
of "icproject":
# `nim m`/`nim nifc` only: the ORIGINAL project file (see options.icProject)
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
conf.icProject = canonicalizePath(conf, AbsoluteFile arg).string
of "icpreparsedconfig":
# `nim m`/`nim nifc` only: path of the precompiled-config artifact (see
# options.icPreparsedConfig). Read in `passCmd1`, before `loadConfigs`, so
# config loading can replay it instead of re-parsing the `nim.cfg` chain.
expectArg(conf, switch, arg, pass, info)
conf.icPreparsedConfig = arg
of "icconfigout":
# `nim icconfig` only: where to write the precompiled config artifact (see
# options.icConfigOut). The `nim ic` driver spawns the producer with this.
expectArg(conf, switch, arg, pass, info)
conf.icConfigOut = arg
of "icbackendstage":
# `nim nifc` only: per-module backend stage, one of cg|merge|emit (see
# options.icBackendStage). Empty (switch unused) keeps the whole-program
# backend. Emitted by `deps.nim`'s backend build file.
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
conf.icBackendStage = arg
of "icbackendmodule":
# `nim nifc` only: the NIF module suffix the cg/emit stage operates on (see
# options.icBackendModule).
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
conf.icBackendModule = arg
processOnOffSwitchG(conf, {optGenIndex}, arg, pass, info)
of "import":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
let m = findModule(conf, arg, toFullPath(conf, info)).string
if m.len == 0:
localError(conf, info, "Cannot resolve filename: " & arg)
else:
conf.implicitImports.add(if arg.startsWith(stdPrefix): arg else: m)
conf.implicitImports.add findModule(conf, arg, toFullPath(conf, info)).string
of "include":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
let m = findModule(conf, arg, toFullPath(conf, info)).string
if m.len == 0:
localError(conf, info, "Cannot resolve filename: " & arg)
else:
conf.implicitIncludes.add m
conf.implicitIncludes.add findModule(conf, arg, toFullPath(conf, info)).string
of "listcmd":
processOnOffSwitchG(conf, {optListCmd}, arg, pass, info)
of "asm":
@@ -1031,38 +814,21 @@ 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)
var value: int = 10_000_000
discard parseSaturatedNatural(arg, value)
if value <= 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
conf.maxLoopIterationsVM = value
of "maxcalldepthvm":
expectArg(conf, switch, arg, pass, info)
var value: int = 2_000
discard parseSaturatedNatural(arg, value)
if value <= 0: localError(conf, info, "maxCallDepthVM must be a positive integer greater than zero")
conf.maxCallDepthVM = value
conf.maxLoopIterationsVM = parseInt(arg)
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
var value: int = 0
discard parseSaturatedNatural(arg, value)
conf.errorMax = if value == 0: high(int) else: value
of "stdinfile":
expectArg(conf, switch, arg, pass, info)
conf.stdinFile = if os.isAbsolute(arg): AbsoluteFile(arg)
else: AbsoluteFile(getCurrentDir() / arg)
let ret = parseInt(arg)
conf.errorMax = if ret == 0: high(int) else: ret
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]
@@ -1072,9 +838,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.mainPackageNotes = conf.notes
of "parallelbuild":
expectArg(conf, switch, arg, pass, info)
var value: int = 0
discard parseSaturatedNatural(arg, value)
conf.numberOfProcessors = value
conf.numberOfProcessors = parseInt(arg)
of "version", "v":
expectNoArg(conf, switch, arg, pass, info)
writeVersionInfo(conf, pass)
@@ -1092,8 +856,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
# xxx maybe also ic, since not in help?
if pass in {passCmd2, passPP}:
case arg.normalize
of "on": conf.ic = true
of "legacy": conf.symbolFiles = v2Sf
of "on": conf.symbolFiles = v2Sf
of "off": conf.symbolFiles = disabledSf
of "writeonly": conf.symbolFiles = writeOnlySf
of "readonly": conf.symbolFiles = readOnlySf
@@ -1113,8 +876,6 @@ 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)
@@ -1198,12 +959,6 @@ 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 "raw":
expectNoArg(conf, switch, arg, pass, info)
docRawOutput(conf)
of "exceptions":
case arg.normalize
of "cpp": conf.exc = excCpp
@@ -1235,10 +990,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
defineSymbol(conf.symbols, "nimSeqsV2")
of "stylecheck":
case arg.normalize
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError, optStyleWarning}
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError, optStyleWarning}
of "warning": conf.globalOptions = conf.globalOptions + {optStyleWarning} - {optStyleHint, optStyleError}
of "error": conf.globalOptions = conf.globalOptions + {optStyleError} - {optStyleHint, optStyleWarning}
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":
@@ -1259,6 +1013,25 @@ 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":
@@ -1272,8 +1045,6 @@ 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
@@ -1281,15 +1052,14 @@ 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", "symbol", "taintmode", "cs", "deadcodeelim": warningOptionNoop(switch)
of "nilseqs", "nilchecks", "mainmodule", "m", "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 = ""
var arg = ""
var cmd, arg: string
splitSwitch(config, switch, cmd, arg, pass, gCmdLineInfo)
processSwitch(cmd, arg, pass, gCmdLineInfo, config)
@@ -1316,10 +1086,7 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser;
config.projectName = unixToNativePath(p.key)
config.arguments = cmdLineRest(p)
result = true
elif pass != passCmd2:
setCommandEarly(config, p.key)
result = false
else: result = false
elif pass != passCmd2: setCommandEarly(config, p.key)
else:
if pass == passCmd1: config.commandArgs.add p.key
if argsCount == 1:
@@ -1328,16 +1095,6 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser;
# support UNIX style filenames everywhere for portable build scripts:
if config.projectName.len == 0:
config.projectName = unixToNativePath(p.key)
if config.cmd == cmdTrack:
# `nim track PROJ --def:...`: unlike a normal command (where everything
# after the project file is passed to the compiled program), `track`
# accepts its IDE-query switches AFTER the project — the natural,
# nimsuggest-like invocation form. So don't swallow the rest of the line
# into `arguments`; keep parsing the remaining tokens as switches.
result = false
else:
config.arguments = cmdLineRest(p)
result = true
else:
result = false
config.arguments = cmdLineRest(p)
result = true
inc argsCount

View File

@@ -1,28 +0,0 @@
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"

View File

@@ -11,15 +11,12 @@
## for details. Note this is a first implementation and only the "Concept matching"
## section has been implemented.
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
import ast, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer, types, intsets
import std/sets
when defined(nimPreviewSlimSystem):
import std/assertions
from magicsys import addSonSkipIntLit
const
logBindings = when defined(debugConcepts): true else: false
logBindings = false
## Code dealing with Concept declarations
## --------------------------------------
@@ -27,12 +24,24 @@ const
proc declareSelf(c: PContext; info: TLineInfo) =
## Adds the magical 'Self' symbols to the current scope.
let ow = getCurrOwner(c)
let s = newSym(skType, getIdent(c.cache, "Self"), c.idgen, ow, info)
s.typ = newType(tyTypeDesc, c.idgen, ow)
s.typ.incl {tfUnresolved, tfPacked}
s.typ.add newType(tyEmpty, c.idgen, ow)
let s = newSym(skType, getIdent(c.cache, "Self"), nextSymId(c.idgen), ow, info)
s.typ = newType(tyTypeDesc, nextTypeId(c.idgen), ow)
s.typ.flags.incl {tfUnresolved, tfPacked}
s.typ.add newType(tyEmpty, nextTypeId(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 (possibly empty) lists of statements
@@ -49,8 +58,6 @@ 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))
result = n
@@ -70,413 +77,150 @@ proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
## ----------------
type
MatchFlags* = enum
mfDontBind # Do not bind generic parameters
mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand
ConceptTypePair = tuple[conceptId, typeId: ItemId]
## Pair of (concept type id, implementation type id) used for cycle detection
MatchCon = object ## Context we pass around during concept matching.
bindings: LayeredIdTable
marker: HashSet[ConceptTypePair] ## Tracks (concept, type) pairs being checked to detect cycles.
inferred: seq[(PType, PType)] ## we need a seq here so that we can easily undo inferences \
## that turned out to be wrong.
marker: IntSet ## Some protection against wild runaway recursions.
potentialImplementation: PType ## the concrete type that might match the concept we try to match.
magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and
## cannot be fixed that easily.
## Thus we special case it here.
concpt: PType ## current concept being evaluated
flags: set[MatchFlags]
MatchKind = enum
mkNoMatch, mkSubset, mkSame
const
asymmetricConceptParamMods = {tyVar, tySink, tyLent, tyOwned, tyAlias, tyInferred} # param modifiers that to not have to match implementation -> concept
bindableTypes = {tyGenericParam, tyOr, tyTypeDesc}
proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool
proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool
proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool
proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool
proc existingBinding(m: MatchCon; key: PType): PType =
## checks if we bound the type variable 'key' already to some
## concrete type.
result = m.bindings.lookup(key)
if result == nil:
result = key
for i in 0..<m.inferred.len:
if m.inferred[i][0] == key: return m.inferred[i][1]
return nil
const
ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyAlias, tyInferred}
proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool
proc unrollGenericParam(param: PType): PType =
result = param.skipTypes(ignorableForArgType)
while result.kind in {tyGenericParam, tyTypeDesc} and result.hasElementType and result.elementType.kind != tyNone:
result = result.elementType
proc bindParam(c: PContext, m: var MatchCon; key, v: PType): bool {. discardable .} =
if v.kind == tyTypeDesc:
return false
var value = unrollGenericParam(v)
if value.kind == tyGenericParam:
value = existingBinding(m, value)
if value.kind == tyGenericParam:
if value.hasElementType:
value = value.elementType
else:
return true
if value.kind == tyStatic:
return false
if m.magic in {mArrPut, mArrGet} and value.kind in arrPutGetMagicApplies:
value = value.last
let old = existingBinding(m, key)
if old != key:
# check previously bound value
if not matchType(c, old, value, m):
return false
elif key.hasElementType and not key.elementType.isNil and key.elementType.kind != tyNone:
# check constaint
if matchType(c, unrollGenericParam(key), value, m) == false:
return false
when logBindings: echo "bind table adding '", key, "', ", value
assert value != nil
assert value.kind != tyVoid
m.bindings.put(key, value)
return true
proc defSignatureType(n: PNode): PType = n[0].sym.typ
proc conceptBody*(n: PType): PNode = n.n.lastSon
proc acceptsAllTypes(t: PType): bool=
result = false
if t.kind == tyAnything:
result = true
elif t.kind == tyGenericParam:
if tfImplicitTypeParam in t.flags:
result = true
if not t.hasElementType or t.elementType.kind == tyNone:
result = true
proc procDefSignature(s: PSym): PNode {. deprecated .} =
var nc = s.ast.copyNode()
for i in 0 .. 5:
nc.add s.ast[i]
nc
proc matchKids(c: PContext; f, a: PType; m: var MatchCon, start=0): bool=
result = true
for i in start ..< f.kidsLen - ord(f.kind in {tyGenericInst, tyGenericInvocation}):
if not matchType(c, f[i], a[i], m): return false
iterator traverseTyOr(t: PType): PType {. closure .}=
for i in t.kids:
case i.kind:
of tyGenericParam:
if i.hasElementType:
for s in traverseTyOr(i.elementType):
yield s
else:
yield i
else:
yield i
proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon): bool =
assert not(potentialImpl.reduceToBase.kind == tyConcept)
let concpt = f.reduceToBase
# Handle self-referential concepts: when a concept references itself in its body
# (e.g., `A = concept; proc test(x: Self, y: A)`), the inner type A has n=nil.
# We detect this by checking if the concept has the same symbol name as the
# one we're currently matching and has no body (n=nil).
if concpt.n.isNil:
if concpt.sym != nil and m.concpt.sym != nil and
concpt.sym == m.concpt.sym:
# Self-reference: check if potentialImpl matches what we're already checking
return potentialImpl.id == m.potentialImplementation.id
# Concept without body that's not a self-reference - cannot match
return false
# Cycle detection: track (concept, type) pairs to prevent infinite recursion.
# Returns true on cycle (coinductive semantics) to support co-dependent concepts.
let pair: ConceptTypePair = (concpt.itemId, potentialImpl.itemId)
if pair in m.marker:
return true
m.marker.incl pair
var efPot = potentialImpl
if potentialImpl.isSelf:
if m.concpt.n == concpt.n:
m.marker.excl pair
return true
efPot = m.potentialImplementation
var oldBindings = m.bindings
m.bindings = newTypeMapLayer(m.bindings)
let oldPotentialImplementation = m.potentialImplementation
m.potentialImplementation = efPot
let oldConcept = m.concpt
m.concpt = concpt
var invocation: PType = nil
if f.kind in {tyGenericInvocation, tyGenericInst}:
invocation = f
result = processConcept(c, concpt, invocation, oldBindings, m)
m.potentialImplementation = oldPotentialImplementation
m.concpt = oldConcept
m.bindings = oldBindings
m.marker.excl pair
proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool=
if fn.kind != an.kind:
return false
if fn[namePos].sym.name != an[namePos].sym.name:
return false
let
ft = fn.defSignatureType
at = an.defSignatureType
if ft.len != at.len:
return false
for i in 1 ..< ft.n.len:
m.bindings = m.bindings.newTypeMapLayer()
let aType = at.n[i].typ
let fType = ft.n[i].typ
if aType.isSelf and fType.isSelf:
continue
if not matchType(c, fType, aType, m):
m.bindings.setToPreviousLayer()
return false
result = true
if not matchReturnType(c, ft.returnType, at.returnType, m):
m.bindings.setToPreviousLayer()
result = false
proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind =
# XXX: In the future this may need extra parameters to carry info for container types
if fc.n == ac.n:
# This will have to take generic parameters into account at some point
return mkSame
let
fn = fc.conceptBody
an = ac.conceptBody
var match = false
for fdef in fn:
for ia, ndef in an:
match = cmpConceptDefs(c, fdef, ndef, m)
if match:
break
if not match:
return mkNoMatch
return mkSubset
proc isObjectSubtype(f, a: PType): bool =
var t = a
result = false
while t != nil:
t = t.baseClass
if t == nil:
break
t = t.skipTypes({tyPtr,tyRef})
if t == nil:
break
if t.kind != tyObject:
break
if sameObjectTypes(f, t):
result = true
break
proc matchType(c: PContext; fo, ao: PType; 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
## routine inside the concept that we're looking for. 'a' is the formal parameter
## of a routine that might match.
var
a = ao
f = fo
if a.isSelf:
if m.magic in {mArrPut, mArrGet}:
return false
a = m.potentialImplementation
if a.kind in bindableTypes:
a = existingBinding(m, ao)
if a == ao and a.kind == tyGenericParam and a.hasElementType and a.elementType.kind != tyNone:
a = a.elementType
if f.isConcept:
if a.acceptsAllTypes:
return false
if a.skipTypes(ignorableForArgType).isConcept:
# if f is a subset of a then any match to a will also match f. Not the other way around
return conceptsMatch(c, a.reduceToBase, f.reduceToBase, m) >= mkSubset
else:
return matchConceptToImpl(c, f, a, m)
result = false
const
ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred}
case f.kind
of tyAlias:
result = matchType(c, f.skipModifier, a, m)
result = matchType(c, f.lastSon, a, m)
of tyTypeDesc:
if isSelf(f):
if m.magic in {mArrPut, mArrGet}:
if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies:
bindParam(c, m, a, last m.potentialImplementation)
result = true
else:
result = matchType(c, a.skipTypes(ignorableForArgType), m.potentialImplementation, m)
#let oldLen = m.inferred.len
result = matchType(c, a, m.potentialImplementation, m)
#echo "self is? ", result, " ", a.kind, " ", a, " ", m.potentialImplementation, " ", m.potentialImplementation.kind
#m.inferred.setLen oldLen
#echo "A for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)
else:
if a.kind == tyTypeDesc:
if not(a.hasElementType) or a.elementType.kind == tyNone:
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
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:
if not matchType(c, f[i], a[i], m): return false
return true
of tyGenericParam:
let ak = a.skipTypes({tyVar, tySink, tyLent, tyOwned})
if ak.kind in {tyTypeDesc, tyStatic} and not isSelf(ak):
result = false
else:
let old = existingBinding(m, f)
if old == nil:
if f.len > 0 and f[0].kind != tyNone:
# also check the generic's constraints:
let oldLen = m.inferred.len
result = matchType(c, f[0], 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}:
when logBindings: echo "B adding ", f, " ", lastSon ak
m.inferred.add((f, lastSon ak))
result = true
elif f.hasElementType:
result = matchType(c, f.elementType, a.elementType, m)
else:
when logBindings: echo "C adding ", f, " ", ak
m.inferred.add((f, ak))
#echo "binding ", typeToString(ak), " to ", typeToString(f)
result = true
elif not m.marker.containsOrIncl(old.id):
result = matchType(c, old, ak, m)
if m.magic == mArrPut and ak.kind == tyGenericParam:
result = true
#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.elementType, a.elementType, m)
result = matchType(c, f.sons[0], a.sons[0], m)
elif m.magic == mArrPut:
result = matchType(c, f.elementType, a, m)
of tyEnum, tyObject, tyDistinct:
if a.kind in ignorableForArgType:
result = matchType(c, f, a.skipTypes(ignorableForArgType), m)
result = matchType(c, f.sons[0], a, m)
else:
if a.kind == tyGenericInst:
# tyOr does this to generic typeclasses
result = a.base.sym == f.sym
else:
result = sameType(f, a)
if not result and f.kind == tyObject and a.kind == tyObject:
result = isObjectSubtype(f, a)
result = false
of tyEnum, tyObject, tyDistinct:
result = sameType(f, a)
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.hasElementType and ak.elementType.kind == tyOrdinal)
of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr:
if f.kind == tyArray and f.kidsLen == 3 and a.kind == tyArray:
# XXX: this is a work-around!
# system.nim creates these for the magic array typeclass
result = true
else:
let ak = a.skipTypes(ignorableForArgType - {f.kind})
if ak.kind == f.kind:
if f.base.kind == tyNone:
result = true
elif f.kidsLen == ak.kidsLen:
result = matchKids(c, f, ak, m)
of tyGenericInvocation, tyGenericInst:
result = false
let ea = a.skipTypes(ignorableForArgType)
if ea.kind in {tyGenericInst, tyGenericInvocation}:
var
k1 = f.kidsLen - ord(f.kind == tyGenericInst)
k2 = ea.kidsLen - ord(ea.kind == tyGenericInst)
if sameType(f.genericHead, ea.genericHead) and k1 == k2:
result = true
for i in 1 ..< k2:
if not matchType(c, f[i], ea[i], m):
result = false
break
elif f.kind == tyGenericInvocation:
# bind potential generic constraints into body
let body = f.base
for i in 1 ..< len(f):
bindParam(c,m,body[i-1], f[i])
result = matchType(c, body, a, m)
else: # tyGenericInst
result = matchType(c, f.last, a, m)
of tyOrdinal:
result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
of tyStatic:
var scomp = f.base
if scomp.kind == tyGenericParam:
if f.base.kidsLen > 0:
scomp = scomp.base
if a.kind == tyStatic:
result = matchType(c, scomp, a.base, m)
else:
result = matchType(c, scomp, a, m)
of tyGenericParam:
if a.acceptsAllTypes:
discard bindParam(c, m, f, a)
result = f.acceptsAllTypes
else:
result = bindParam(c, m, f, a)
of tyAnything:
result = true
of tyNot:
if a.kind == tyNot:
result = matchType(c, f.elementType, a.elementType, m)
else:
m.bindings = m.bindings.newTypeMapLayer()
result = not matchType(c, f.elementType, a, m)
m.bindings.setToPreviousLayer()
of tyAnd:
m.bindings = m.bindings.newTypeMapLayer()
result = true
for ff in traverseTyOr(f):
let r = matchType(c, ff, a, m)
if not r:
m.bindings.setToPreviousLayer()
result = false
break
of tyGenericBody:
var ak = a
if a.kind == tyGenericBody:
ak = last(a)
result = matchType(c, last(f), ak, m)
of tyCompositeTypeClass:
if a.kind == tyCompositeTypeClass:
result = matchKids(c, f, a, m)
else:
result = matchType(c, last(f), a, m)
of tyBuiltInTypeClass:
let target = f.genericHead.kind
result = a.skipTypes(ignorableForArgType).reduceToBase.kind == target
(ak.kind == tyGenericParam and ak.len > 0 and ak[0].kind == tyOrdinal)
of tyConcept:
let oldLen = m.inferred.len
let oldPotentialImplementation = m.potentialImplementation
m.potentialImplementation = a
result = conceptMatchNode(c, f.n.lastSon, m)
m.potentialImplementation = oldPotentialImplementation
if not result:
m.inferred.setLen oldLen
of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr,
tyGenericInst:
let ak = a.skipTypes(ignorableForArgType - {f.kind})
if ak.kind == f.kind and f.len == ak.len:
for i in 0..<ak.len:
if not matchType(c, f[i], ak[i], m): return false
return true
of tyOr:
let oldLen = m.inferred.len
if a.kind == tyOr:
# say the concept requires 'int|float|string' if the potentialImplementation
# says 'int|string' that is good enough.
var covered = 0
for ff in traverseTyOr(f):
for aa in traverseTyOr(a):
m.bindings = m.bindings.newTypeMapLayer()
let r = matchType(c, ff, aa, m)
for i in 0..<f.len:
for j in 0..<a.len:
let oldLenB = m.inferred.len
let r = matchType(c, f[i], a[j], m)
if r:
inc covered
break
m.bindings.setToPreviousLayer()
m.inferred.setLen oldLenB
result = covered >= a.kidsLen
result = covered >= a.len
if not result:
m.inferred.setLen oldLen
else:
for ff in f.kids:
m.bindings = m.bindings.newTypeMapLayer()
result = matchType(c, ff, a, m)
for i in 0..<f.len:
result = matchType(c, f[i], a, m)
if result: break # and remember the binding!
m.bindings.setToPreviousLayer()
of tySet:
result = false
if a.kind == tySet:
result = matchType(c, f.elementType, a.elementType, m)
m.inferred.setLen oldLen
of tyNot:
if a.kind == tyNot:
result = matchType(c, f[0], a[0], m)
else:
let oldLen = m.inferred.len
result = not matchType(c, f[0], a, m)
m.inferred.setLen oldLen
of tyAnything:
result = true
of tyOrdinal:
result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
else:
result = false
if result and ao.kind == tyGenericParam:
let bf = if f.isSelf: m.potentialImplementation else: f
if bindParam(c, m, ao, bf):
when logBindings: echo " ^ reverse binding"
proc checkConstraint(c: PContext; f, a: PType; m: var MatchCon): bool =
result = matchType(c, f, a, m) or matchType(c, a, f, m)
proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool =
## Like 'matchType' but with extra logic dealing with proc return types
@@ -486,38 +230,30 @@ proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool =
elif a == nil:
result = false
else:
result = checkConstraint(c, f, a, m)
result = matchType(c, f, a, m)
proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool =
## Checks if 'candidate' matches 'n' from the concept body. 'n' is a nkProcDef
## or similar.
# watch out: only add bindings after a completely successful match.
m.bindings = m.bindings.newTypeMapLayer()
let oldLen = m.inferred.len
let can = candidate.typ.n
let con = defSignatureType(n).n
let con = n[0].sym.typ.n
if can.len < con.len:
# too few arguments, cannot be a match:
return false
if can.len > con.len:
# too many arguments (not optional)
for i in con.len ..< can.len:
if can[i].sym.ast == nil:
return false
when defined(debugConcepts):
echo "considering: ", renderTree(candidate.procDefSignature), " ", candidate.magic
let common = min(can.len, con.len)
for i in 1 ..< common:
if not checkConstraint(c, con[i].typ, can[i].typ, m):
m.bindings.setToPreviousLayer()
if not matchType(c, con[i].typ, can[i].typ, m):
m.inferred.setLen oldLen
return false
if not matchReturnType(c, n.defSignatureType.returnType, candidate.typ.returnType, m):
m.bindings.setToPreviousLayer()
if not matchReturnType(c, n[0].sym.typ.sons[0], candidate.typ.sons[0], m):
m.inferred.setLen oldLen
return false
# all other parameters have to be optional parameters:
@@ -525,7 +261,7 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool =
assert can[i].kind == nkSym
if can[i].sym.ast == nil:
# has too many arguments one of which is not optional:
m.bindings.setToPreviousLayer()
m.inferred.setLen oldLen
return false
return true
@@ -533,14 +269,12 @@ 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.
result = false
var candidates = searchScopes(c, n[namePos].sym.name, kinds)
searchImportsAll(c, n[namePos].sym.name, kinds, candidates)
let candidates = searchInScopesFilterBy(c, n[namePos].sym.name, kinds)
for candidate in candidates:
#echo "considering ", typeToString(candidate.typ), " ", candidate.magic
m.magic = candidate.magic
if matchSym(c, candidate, n, m):
result = true
break
if matchSym(c, candidate, n, m): return true
result = false
proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
## Traverse the concept's AST ('n') and see if every declaration inside 'n'
@@ -567,72 +301,39 @@ 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 fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; m: var MatchCon) =
# invocation != nil means we have a non-atomic concept:
if invocation != nil and invocation.kind == tyGenericInvocation:
assert concpt.sym.typ.kind == tyGenericBody
for i in 0 .. concpt.sym.typ.len - 1:
let thisSym = concpt.sym.typ[i]
if lookup(bindings, thisSym) != nil:
# dont trust the bindings over existing ones
continue
let found = m.bindings.lookup(thisSym)
if found != nil:
when logBindings: echo "Invocation bind: ", thisSym, " ", found
bindings.put(thisSym, found)
# bind even more generic parameters
let genBody = invocation.base
assert genBody.kind == tyGenericBody
for i in FirstGenericParamAt ..< invocation.kidsLen:
let bpram = genBody[i - 1]
if lookup(bindings, invocation[i]) != nil:
# dont trust the bindings over existing ones
continue
let boundV = lookup(bindings, bpram)
when logBindings: echo "generic body bind: '", invocation[i], "' '", boundV, "'"
if boundV != nil:
bindings.put(invocation[i], boundV)
bindings.put(concpt, m.potentialImplementation)
proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool =
m.bindings = m.bindings.newTypeMapLayer()
if invocation != nil and invocation.kind == tyGenericInst:
let genericBody = invocation.base
for i in 1..<invocation.kidsLen-1:
# instGenericContainer can bind `tyVoid`
if invocation[i].kind != tyVoid:
bindParam(c, m, genericBody[i-1], invocation[i])
result = conceptMatchNode(c, concpt.conceptBody, m)
if result and mfDontBind notin m.flags:
fixBindings(bindings, concpt, invocation, m)
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType, flags: set[MatchFlags] = {}): bool =
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var TIdTable; 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 fulfill the
## we extract its AST via 'concpt.n.lastSon'). 'arg' is the type that might fullfill 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(bindings: bindings, potentialImplementation: arg, concpt: concpt, flags: flags, marker: initHashSet[ConceptTypePair]())
if arg.isConcept:
result = conceptsMatch(c, concpt.reduceToBase, arg.reduceToBase, m) >= mkSubset
elif arg.acceptsAllTypes:
# XXX: I think this is wrong, or at least partially wrong. Can still test ambiguous types
result = false
elif mfCheckGeneric in m.flags:
# prioritize concepts the least. Specifically if the arg is not a catch all as per above
result = true
else:
result = processConcept(c, concpt, invocation, bindings, m)
var m = MatchCon(inferred: @[], potentialImplementation: arg)
result = conceptMatchNode(c, concpt.n.lastSon, m)
if result:
for (a, b) in m.inferred:
if b.kind == tyGenericParam:
var dest = b
while true:
dest = existingBinding(m, dest)
if dest == nil or dest.kind != tyGenericParam: break
if dest != nil:
bindings.idTablePut(a, dest)
when logBindings: echo "A bind ", a, " ", dest
else:
bindings.idTablePut(a, b)
when logBindings: echo "B bind ", a, " ", b
# 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:
# bind even more generic parameters
assert invocation.kind == tyGenericInvocation
for i in 1 ..< invocation.len:
bindings.idTablePut(invocation[i], arg[i])

View File

@@ -10,7 +10,7 @@
# This module handles the conditional symbols.
import
std/strtabs
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 in keys(symbols):
for key, val in pairs(symbols):
yield key
proc countDefinedSymbols*(symbols: StringTableRef): int =
@@ -45,8 +45,10 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimNewTypedesc") # deadcode
defineSymbol("nimrequiresnimframe") # deadcode
defineSymbol("nimparsebiggestfloatmagic") # deadcode
defineSymbol("nimalias") # deadcode
defineSymbol("nimlocks") # deadcode
defineSymbol("nimnode") # deadcode
defineSymbol("nimnode") # deadcode pending `nimnode` reference in opengl package
# refs https://github.com/nim-lang/opengl/pull/79
defineSymbol("nimvarargstyped") # deadcode
defineSymbol("nimtypedescfixed") # deadcode
defineSymbol("nimKnowsNimvm") # deadcode
@@ -68,7 +70,7 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimVmExportFixed") # deadcode
defineSymbol("nimHasSymOwnerInMacro") # deadcode
defineSymbol("nimNewRuntime") # deadcode
defineSymbol("nimIncrSeqV3") # deadcode
defineSymbol("nimIncrSeqV3") # xxx: turn this into deadcode
defineSymbol("nimAshr") # deadcode
defineSymbol("nimNoNilSeqs") # deadcode
defineSymbol("nimNoNilSeqs2") # deadcode
@@ -82,24 +84,10 @@ 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)
@@ -110,18 +98,31 @@ 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") # deadcode
defineSymbol("nimHasLibFFIEnabled")
defineSymbol("nimHasStacktraceMsgs") # deadcode
defineSymbol("nimHasSinkInference")
defineSymbol("nimNewIntegerOps")
defineSymbol("nimHasInvariant")
defineSymbol("nimHasStacktraceMsgs")
defineSymbol("nimDoesntTrackDefects")
defineSymbol("nimHasLentIterators") # deadcode
defineSymbol("nimHasDeclaredMagic") # deadcode
defineSymbol("nimHasStacktracesModule") # deadcode
defineSymbol("nimHasLentIterators")
defineSymbol("nimHasDeclaredMagic")
defineSymbol("nimHasStacktracesModule")
defineSymbol("nimHasEffectTraitsModule")
defineSymbol("nimHasCastPragmaBlocks")
defineSymbol("nimHasDeclaredLocs")
@@ -132,48 +133,10 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasCustomLiterals")
defineSymbol("nimHasUnifiedTuple")
defineSymbol("nimHasIterable")
defineSymbol("nimHasTypeofVoid") # deadcode
defineSymbol("nimHasDragonBox") # deadcode
defineSymbol("nimHasTypeofVoid")
defineSymbol("nimHasDragonBox")
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") # deadcode
defineSymbol("nimHasNolineTooLong")
defineSymbol("nimHasCastExtendedVm")
defineSymbol("nimHasWarnStdPrefix")
defineSymbol("nimHasVtables")
defineSymbol("nimHasGenericsOpenSym2")
defineSymbol("nimHasGenericsOpenSym3")
defineSymbol("nimHasJsNoLambdaLifting")
defineSymbol("nimHasDefaultFloatRoundtrip")
defineSymbol("nimHasXorSet")
defineSymbol("nimHasSetLengthSeqUninitMagic")
defineSymbol("nimHasPreviewDuplicateModuleError")
defineSymbol("nimHasImplicitRangeConversion")

View File

@@ -54,19 +54,3 @@ proc isCompilerDebug*(): bool =
{.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

View File

@@ -9,18 +9,13 @@
# This module implements a dependency file generator.
import options, ast, ropes, pathutils, msgs, lineinfos
import options, ast, ropes, passes, pathutils, msgs, lineinfos
import modulegraphs
import std/[os, parseutils]
import std/strutils except addf
import std/[os, strutils, parseutils]
import std/private/globs
when defined(nimPreviewSlimSystem):
import std/assertions
type
TGen = object of PPassContext
module: PSym
@@ -42,7 +37,7 @@ proc toNimblePath(s: string, isStdlib: bool): string =
let sub = "lib/"
var start = s.find(sub)
if start < 0:
raiseAssert "unreachable"
doAssert false
else:
start += sub.len
let base = s[start..^1]
@@ -63,16 +58,12 @@ proc toNimblePath(s: string, isStdlib: bool): string =
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]
result = s
else:
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
@@ -83,7 +74,7 @@ proc addDependency(c: PPassContext, g: PGen, b: Backend, n: PNode) =
let child = nativeToUnixPath(path.dir / path.name).toNimblePath(belongsToStdlib(g.graph, n.sym))
addDependencyAux(b, parent, child)
proc addDotDependency*(c: PPassContext, n: PNode): PNode =
proc addDotDependency(c: PPassContext, n: PNode): PNode =
result = n
let g = PGen(c)
let b = Backend(g.graph.backend)
@@ -104,7 +95,18 @@ proc generateDot*(graph: ModuleGraph; project: AbsoluteFile) =
rope(project.splitFile.name), b.dotGraph],
changeFileExt(project, "dot"))
proc setupDependPass*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
result = PGen(module: module, config: graph.config, graph: graph)
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
if graph.backend == nil:
graph.backend = Backend(dotGraph: "")
graph.backend = Backend(dotGraph: nil)
result = g
const gendependPass* = makePass(open = myOpen, process = addDotDependency)

File diff suppressed because it is too large Load Diff

View File

@@ -9,34 +9,37 @@
## 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 3 different branching
## make this easier to handle: There are only 2 different branching
## instructions: 'goto X' is an unconditional goto, 'fork X'
## is a conditional goto (either the next instruction or 'X' can be
## taken), 'loop X' is the only jump that jumps back.
##
## Exhaustive case statements are translated
## taken). 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 GraphFree Approach to DataFlow Analysis" by Markus Mohnen.
## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf
import ast, lineinfos, renderer, aliasanalysis
import ast, intsets, lineinfos, renderer
import std/private/asciitables
import std/intsets
when defined(nimPreviewSlimSystem):
import std/assertions
type
InstrKind* = enum
goto, loop, fork, def, use
goto, fork, def, use
Instr* = object
n*: PNode # contains the def/use location.
case kind*: InstrKind
of goto, fork, loop: dest*: int
of def, use:
n*: PNode # contains the def/use location.
of goto, fork: dest*: int
else: discard
ControlFlowGraph* = seq[Instr]
@@ -46,26 +49,24 @@ 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, interestingInstructions: int
inTryStmt: 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, loop}:
if c[i].kind in {goto, fork}:
jumpTargets.incl(i+c[i].dest)
var i = start
while i <= last:
@@ -76,12 +77,12 @@ proc codeListing(c: ControlFlowGraph, start = 0; last = -1): string =
case c[i].kind
of def, use:
result.add renderTree(c[i].n)
result.add("\t#")
result.add($c[i].n.info.line)
result.add("\n")
of goto, fork, loop:
of goto, fork:
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")
@@ -89,13 +90,181 @@ 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): TPosition =
proc forkI(c: var Con; n: PNode): TPosition =
result = TPosition(c.code.len)
c.code.add Instr(kind: fork, dest: 0)
c.code.add Instr(n: n, kind: fork, dest: 0)
proc gotoI(c: var Con): TPosition =
proc gotoI(c: var Con; n: PNode): TPosition =
result = TPosition(c.code.len)
c.code.add Instr(kind: goto, dest: 0)
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!
]#
proc genLabel(c: Con): TPosition = TPosition(c.code.len)
@@ -103,8 +272,8 @@ template checkedDistance(dist): int =
doAssert low(int) div 2 + 1 < dist and dist < high(int) div 2
dist
proc jmpBack(c: var Con, p = TPosition(0)) =
c.code.add Instr(kind: loop, dest: checkedDistance(p.int - c.code.len))
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 patch(c: var Con, p: TPosition) =
# patch with current index
@@ -113,13 +282,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()
var exits: seq[TPosition]
exits.add c.gotoI(newNode(nkEmpty))
for f in c.blocks[oldLen].breakFixups:
c.patch(f[0])
for finale in f[1]:
c.gen(finale)
exits.add c.gotoI()
exits.add c.gotoI(newNode(nkEmpty))
for e in exits:
c.patch e
c.blocks.setLen(oldLen)
@@ -130,29 +299,95 @@ template withBlock(labl: PSym; body: untyped) =
body
popBlock(c, oldLen)
template forkT(body) =
let lab1 = c.forkI()
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.)
#
# EDIT: Actually, we only need to unroll 2 times
# because Nim doesn't have a way of breaking/goto-ing into
# a loop iteration. Unrolling 2 times is much better for compile
# times of nested loops than 3 times, so we do that here.
#[
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..1:
c.gen(n[1])
else:
withBlock(nil):
var endings: array[2, TPosition]
for i in 0..1:
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)
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) =
#[
@@ -181,32 +416,34 @@ 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:
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])
forkT(it[1]):
c.gen(it[1])
endings.add c.gotoI(it[1])
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:
forkT(n):
c.gen(n[2])
proc genCase(c: var Con; n: PNode) =
@@ -221,40 +458,39 @@ proc genCase(c: var Con; n: PNode) =
# elsePart
# Lend:
let isExhaustive = skipTypes(n[0].typ,
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
# 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:
forkT(it.lastSon):
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])
if endings.len != 0:
c.patch(endings[^1])
endings.add c.gotoI(it.lastSon)
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()
let lab1 = c.gotoI(n)
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
@@ -262,7 +498,6 @@ 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:
@@ -293,9 +528,9 @@ proc genTry(c: var Con; n: PNode) =
for i in 1..<n.len:
let it = n[i]
if it.kind != nkFinally:
forkT:
forkT(it):
c.gen(it.lastSon)
endings.add c.gotoI()
endings.add c.gotoI(it)
for i in countdown(endings.high, 0):
c.patch(endings[i])
@@ -303,28 +538,26 @@ proc genTry(c: var Con; n: PNode) =
if fin.kind == nkFinally:
c.gen(fin[0])
template genNoReturn(c: var Con) =
template genNoReturn(c: var Con; n: PNode) =
# leave the graph
c.code.add Instr(kind: goto, dest: high(int) - c.code.len)
c.code.add Instr(n: n, 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)
genNoReturn(c, n)
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:
@@ -333,6 +566,124 @@ 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
@@ -351,9 +702,8 @@ proc genUse(c: var Con; orig: PNode) =
let n = c.skipTrivials(orig)
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
if n.sym.kind in InterestingSyms:
c.code.add Instr(n: orig, kind: use)
else:
gen(c, n)
@@ -361,9 +711,7 @@ proc genDef(c: var Con; orig: PNode) =
let n = c.skipTrivials(orig)
if n.kind == nkSym and n.sym.kind in InterestingSyms:
if n.sym == c.root:
c.code.add Instr(kind: def, n: orig)
inc c.interestingInstructions
c.code.add Instr(n: orig, kind: def)
proc genCall(c: var Con; n: PNode) =
gen(c, n[0])
@@ -371,17 +719,18 @@ proc genCall(c: var Con; n: PNode) =
if t != nil: t = t.skipTypes(abstractInst)
for i in 1..<n.len:
gen(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])
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])
# 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:
forkT:
# join F1
forkT(n):
for i in countdown(c.blocks.high, 0):
if c.blocks[i].isTryBlock:
genBreakOrRaiseAux(c, i, n)
@@ -419,11 +768,11 @@ proc gen(c: var Con; n: PNode) =
else:
genCall(c, n)
if sfNoReturn in n[0].sym.flags:
genNoReturn(c)
genNoReturn(c, n)
else:
genCall(c, n)
of nkCharLit..nkNilLit: discard
of nkAsgn, nkFastAsgn, nkSinkAsgn:
of nkAsgn, nkFastAsgn:
gen(c, n[1])
if n[0].kind in PathKinds0:
@@ -439,8 +788,8 @@ proc gen(c: var Con; n: PNode) =
genUse(c, n)
of nkIfStmt, nkIfExpr: genIf(c, n)
of nkWhenStmt:
# This is "when nimvm" node. Chose the second branch.
gen(c, n[1][0])
# This is "when nimvm" node. Chose the first branch.
gen(c, n[0][1])
of nkCaseStmt: genCase(c, n)
of nkWhileStmt: genWhile(c, n)
of nkBlockExpr, nkBlockStmt: genBlock(c, n)
@@ -457,35 +806,16 @@ 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: raiseAssert "dfa construction pass requires the elimination of 'defer'"
of nkDefer: doAssert false, "dfa construction pass requires the elimination of 'defer'"
else: discard
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 =
proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
## constructs a control flow graph for ``body``.
var c = Con(code: @[], blocks: @[], owner: s, root: root)
var c = Con(code: @[], blocks: @[], owner: s)
withBlock(s):
gen(c, body)
if root.kind == skResult:
genImplicitReturn(c)
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
genImplicitReturn(c)
when defined(gcArc) or defined(gcOrc):
result = c.code # will move
else:
shallowCopy(result, c.code)
when false:
optimizeJumps result

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
# semantic checking.
import
options, ast, msgs, docgen, lineinfos, pathutils, packages
options, ast, msgs, passes, docgen, lineinfos, pathutils, packages
from modulegraphs import ModuleGraph, PPassContext
@@ -29,6 +29,7 @@ proc shouldProcess(g: PGen): bool =
template closeImpl(body: untyped) {.dirty.} =
var g = PGen(p)
let useWarning = sfMainModule notin g.module.flags
let groupedToc = true
if shouldProcess(g):
finishGenerateDoc(g.doc)
body
@@ -37,27 +38,25 @@ template closeImpl(body: untyped) {.dirty.} =
except IOError:
discard
proc closeDoc*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
result = nil
proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
closeImpl:
writeOutput(g.doc, useWarning, true)
writeOutput(g.doc, useWarning, groupedToc)
proc closeJson*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
result = nil
proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
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, g.config)
generateDoc(g.doc, n, n)
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, g.config, false)
generateJson(g.doc, n, false)
template myOpenImpl(ext: untyped) {.dirty.} =
var g: PGen
@@ -65,15 +64,25 @@ 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, hasToc = true)
graph.cache, graph.config, ext, module)
d.hasToc = true
g.doc = d
result = g
proc openHtml*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
proc myOpen(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
myOpenImpl(HtmlExt)
proc openTex*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
proc myOpenTex(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
myOpenImpl(TexExt)
proc openJson*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
proc myOpenJson(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
myOpenImpl(JsonExt)
const docgen2Pass* = makePass(open = myOpen, process = processNode, close = close)
const docgen2TexPass* = makePass(open = myOpenTex, process = processNode,
close = close)
const docgen2JsonPass* = makePass(open = myOpenJson, process = processNodeJson,
close = closeJson)
proc finishDoc2Pass*(project: string) =
discard

View File

@@ -1,20 +1,16 @@
import ast, idents, lineinfos, modulegraphs, magicsys
when defined(nimPreviewSlimSystem):
import std/assertions
proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym =
result = newSym(skProc, getIdent(g.cache, "$"), idgen, t.owner, info)
result = newSym(skProc, getIdent(g.cache, "$"), nextSymId idgen, t.owner, info)
let dest = newSym(skParam, getIdent(g.cache, "e"), idgen, result, info)
let dest = newSym(skParam, getIdent(g.cache, "e"), nextSymId idgen, result, info)
dest.typ = t
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
let res = newSym(skResult, getIdent(g.cache, "result"), nextSymId idgen, result, info)
res.typ = getSysType(g, info, tyString)
result.typ = newType(tyProc, idgen, t.owner)
result.typ = newType(tyProc, nextTypeId idgen, t.owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)
@@ -30,13 +26,9 @@ 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, newIntTypeNode(field.position, t),
caseStmt.add newTree(nkOfBranch, newSymNode(field),
newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), newStrNode(val, info))))
#newIntTypeNode(nkIntLit, field.position, t)
# safety branch for invalid data:
caseStmt.add newTree(nkElse,
newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res),
newStrNode("", info))))
body.add(caseStmt)
@@ -47,5 +39,67 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
n[bodyPos] = body
n[resultPos] = newSymNode(res)
result.ast = n
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
setHookDisamb(g, result, "$enumtostr", t)
incl result.flags, sfFromGeneric
incl result.flags, sfNeverRaises
proc searchObjCaseImpl(obj: PNode; field: PSym): PNode =
case obj.kind
of nkSym:
result = nil
of nkElse, nkOfBranch:
result = searchObjCaseImpl(obj.lastSon, field)
else:
if obj.kind == nkRecCase and obj[0].kind == nkSym and obj[0].sym == field:
result = obj
else:
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)
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)
let dest = newSym(skParam, getIdent(g.cache, "e"), nextSymId idgen, result, info)
dest.typ = field.typ
let res = newSym(skResult, getIdent(g.cache, "result"), nextSymId idgen, result, info)
res.typ = getSysType(g, info, tyUInt8)
result.typ = newType(tyProc, nextTypeId idgen, t.owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)
result.typ.addParam dest
var body = newNodeI(nkStmtList, info)
var caseStmt = newNodeI(nkCaseStmt, info)
caseStmt.add(newSymNode dest)
let subObj = searchObjCase(t, field)
for i in 1..<subObj.len:
let ofBranch = subObj[i]
var newBranch = newNodeI(ofBranch.kind, ofBranch.info)
for j in 0..<ofBranch.len-1:
newBranch.add ofBranch[j]
newBranch.add newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), newIntNode(nkInt8Lit, i)))
caseStmt.add newBranch
body.add(caseStmt)
var n = newNodeI(nkProcDef, info, bodyPos+2)
for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
n[namePos] = newSymNode(result)
n[paramsPos] = result.typ.n
n[bodyPos] = body
n[resultPos] = newSymNode(res)
result.ast = n
incl result.flags, sfFromGeneric
incl result.flags, sfNeverRaises

View File

@@ -10,11 +10,7 @@
## 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
import ast, renderer, options, strutils, types
type
ErrorKind* = enum ## expand as you need.
@@ -41,8 +37,7 @@ proc newError*(wrongNode: PNode; k: ErrorKind; args: varargs[PNode]): PNode =
let innerError = errorSubNode(wrongNode)
if innerError != nil:
return innerError
var idgen = idGeneratorForPackage(-1'i32)
result = newNodeIT(nkError, wrongNode.info, newType(tyError, idgen, nil))
result = newNodeIT(nkError, wrongNode.info, newType(tyError, ItemId(module: -1, item: -1), nil))
result.add wrongNode
result.add newIntNode(nkIntLit, ord(k))
for a in args: result.add a
@@ -52,8 +47,7 @@ proc newError*(wrongNode: PNode; msg: string): PNode =
let innerError = errorSubNode(wrongNode)
if innerError != nil:
return innerError
var idgen = idGeneratorForPackage(-1'i32)
result = newNodeIT(nkError, wrongNode.info, newType(tyError, idgen, nil))
result = newNodeIT(nkError, wrongNode.info, newType(tyError, ItemId(module: -1, item: -1), nil))
result.add wrongNode
result.add newIntNode(nkIntLit, ord(CustomError))
result.add newStrNode(msg, wrongNode.info)

View File

@@ -9,11 +9,9 @@
## This file implements the FFI part of the evaluator for Nim code.
import ast, types, options, msgs, lineinfos
from std/os import getAppFilename
import libffi/libffi
import std/[tables, dynlib]
import ast, types, options, tables, dynlib, msgs, lineinfos
from os import getAppFilename
import pkg/libffi
when defined(windows):
const libcDll = "msvcrt.dll"
@@ -39,10 +37,9 @@ 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)
@@ -64,23 +61,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 = nil
var theAddr: pointer
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.cstring)
theAddr = gExeHandle.symAddr(name)
# then try libc:
if theAddr.isNil:
let dllhandle = getDll(conf, gDllCache, libcDll, sym.info)
theAddr = dllhandle.symAddr(name.cstring)
theAddr = dllhandle.symAddr(name)
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.cstring)
theAddr = dllhandle.symAddr(name)
if theAddr.isNil: globalError(conf, sym.info,
"cannot import symbol: " & name & " from " & libPathMsg)
result.intVal = cast[int](theAddr)
result.intVal = cast[ByteAddress](theAddr)
proc mapType(conf: ConfigRef, t: ast.PType): ptr libffi.Type =
if t == nil: return addr libffi.type_void
@@ -99,7 +96,7 @@ proc mapType(conf: ConfigRef, t: ast.PType): ptr libffi.Type =
tyTyped, tyTypeDesc, tyProc, tyArray, tyStatic, tyNil:
result = addr libffi.type_pointer
of tyDistinct, tyAlias, tySink:
result = mapType(conf, t.skipModifier)
result = mapType(conf, t[0])
else:
result = nil
# too risky:
@@ -111,13 +108,12 @@ 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(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[int](x) + y)
cast[pointer](cast[ByteAddress](x) + y)
proc packSize(conf: ConfigRef, v: PNode, typ: PType): int =
## computes the size of the blob
@@ -126,18 +122,16 @@ 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.elementType)
result = sizeof(pointer) + packSize(conf, v[0], typ.lastSon)
of tyDistinct, tyGenericInst, tyAlias, tySink:
result = packSize(conf, v, typ.skipModifier)
result = packSize(conf, v, typ[0])
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.elementType)
else:
result = 0
result = v.len * packSize(conf, v[0], typ[1])
else:
result = getSize(conf, typ).int
@@ -146,7 +140,6 @@ 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
@@ -161,8 +154,7 @@ 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: result = nil
else: result = nil
else: discard
proc packObject(conf: ConfigRef, x: PNode, typ: PType, res: pointer) =
internalAssert conf, x.kind in {nkObjConstr, nkPar, nkTupleConstr}
@@ -234,19 +226,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.elementType, res +! sizeof(pointer))
pack(conf, v[0], typ.lastSon, res +! sizeof(pointer))
dec packRecCheck
awr(pointer, res +! sizeof(pointer))
of tyArray:
let baseSize = getSize(conf, typ.elementType)
let baseSize = getSize(conf, typ[1])
for i in 0..<v.len:
pack(conf, v[i], typ.elementType, res +! i * baseSize)
pack(conf, v[i], typ[1], res +! i * baseSize)
of tyObject, tyTuple:
packObject(conf, v, typ, res)
of tyNil:
discard
of tyDistinct, tyGenericInst, tyAlias, tySink:
pack(conf, v, typ.skipModifier, res)
pack(conf, v, typ[0], res)
else:
globalError(conf, v.info, "cannot map value to FFI " & typeToString(v.typ))
@@ -304,9 +296,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.elementType)
let baseSize = getSize(conf, typ[1])
for i in 0..<result.len:
result[i] = unpack(conf, x +! i * baseSize, typ.elementType, result[i])
result[i] = unpack(conf, x +! i * baseSize, typ[1], result[i])
proc canonNodeKind(k: TNodeKind): TNodeKind =
case k
@@ -364,7 +356,6 @@ 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))
@@ -378,19 +369,18 @@ 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[int](p))
awi(nkPtrLit, cast[ByteAddress](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[int](p))
awi(nkPtrLit, cast[ByteAddress](p))
elif n != nil and n.len == 1:
internalAssert(conf, n.kind == nkRefTy)
n[0] = unpack(conf, p, typ.elementType, n[0])
n[0] = unpack(conf, p, typ.lastSon, 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)
@@ -405,10 +395,9 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
of tyNil:
setNil()
of tyDistinct, tyGenericInst, tyAlias, tySink:
result = unpack(conf, x, typ.skipModifier, n)
result = unpack(conf, x, typ.lastSon, 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 =
@@ -434,8 +423,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 = default(TCif)
var sig: ParamList = default(ParamList)
var cif: TCif
var sig: ParamList
# use the arguments' types for varargs support:
for i in 1..<call.len:
sig[i-1] = mapType(conf, call[i].typ)
@@ -444,24 +433,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.returnType), sig) != OK:
mapType(conf, typ[0]), sig) != OK:
globalError(conf, call.info, "error in FFI call")
var args: ArgList = default(ArgList)
var args: 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.returnType): pointer(nil)
else: alloc(getSize(conf, typ.returnType).int)
let retVal = if isEmptyType(typ[0]): pointer(nil)
else: alloc(getSize(conf, typ[0]).int)
libffi.call(cif, fn, retVal, args)
if retVal.isNil:
result = newNode(nkEmpty)
else:
result = unpack(conf, retVal, typ.returnType, nil)
result = unpack(conf, retVal, typ[0], nil)
result.info = call.info
if retVal != nil: dealloc retVal
@@ -474,8 +463,8 @@ proc callForeignFunction*(conf: ConfigRef, fn: PNode, fntyp: PType,
info: TLineInfo): PNode =
internalAssert conf, fn.kind == nkPtrLit
var cif: TCif = default(TCif)
var sig: ParamList = default(ParamList)
var cif: TCif
var sig: ParamList
for i in 0..len-1:
var aTyp = args[i+start].typ
if aTyp.isNil:
@@ -489,7 +478,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 = default(ArgList)
var cargs: ArgList
let fn = cast[pointer](fn.intVal)
for i in 0..len-1:
let t = args[i+start].typ

View File

@@ -9,16 +9,16 @@
## Template evaluation engine. Now hygienic.
import options, ast, astalgo, msgs, renderer, lineinfos, idents, trees
import std/strutils
import
strutils, options, ast, astalgo, msgs, renderer, lineinfos, idents
type
TemplCtx = object
owner, genSymOwner: PSym
instLines: bool # use the instantiation lines numbers
isDeclarative: bool
mapping: SymMapping # every gensym'ed symbol needs to be mapped to some
# new symbol
mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
# new symbol
config: ConfigRef
ic: IdentCache
instID: int
@@ -26,26 +26,13 @@ type
proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
result = copyNode(a)
if ctx.instLines: setInfoRecursive(result, b.info)
if ctx.instLines: result.info = b.info
proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
template handleParam(param) =
let x = param
if x.kind == nkArgList:
for y in items(x): result.add(y)
elif nfDefaultRefsParam in x.flags:
# value of default param needs to be evaluated like template body
# if it contains other template params
var res: PNode
if isAtom(x):
res = newNodeI(nkPar, x.info)
evalTemplateAux(x, actual, c, res)
if res.len == 1: res = res[0]
else:
res = copyNode(x)
for i in 0..<x.safeLen:
evalTemplateAux(x[i], actual, c, res)
result.add res
else:
result.add copyTree(x)
@@ -57,19 +44,18 @@ 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.signatureLen + s.position - 1]
handleParam actual[s.owner.typ.len + s.position - 1]
else:
internalAssert c.config, sfGenSym in s.flags or s.kind == skType
var x = idTableGet(c.mapping, s)
var x = PSym(idTableGet(c.mapping, s))
if x == nil:
x = copySym(s, c.idgen)
x = copySym(s, nextSymId(c.idgen))
# sem'check needs to set the owner properly later, see bug #9476
setOwner(x, nil) # c.genSymOwner
x.owner = nil # c.genSymOwner
#if x.kind == skParam and x.owner.kind == skModule:
# internalAssert c.config, false
idTablePut(c.mapping, s, x)
if sfGenSym in s.flags:
# TODO: getIdent(c.ic, "`" & x.name.s & "`gensym" & $c.instID)
result.add newIdentNode(getIdent(c.ic, x.name.s & "`gensym" & $c.instID),
if c.instLines: actual.info else: templ.info)
else:
@@ -130,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.paramsLen
expectedRegularParams = s.typ.len-1
givenRegularParams = totalParams - genericParams
if givenRegularParams < 0: givenRegularParams = 0
@@ -196,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(owner: tmpl,
genSymOwner: genSymOwner,
config: conf,
ic: ic,
mapping: initSymMapping(),
instID: instID[],
idgen: idgen
)
var ctx: TemplCtx
ctx.owner = tmpl
ctx.genSymOwner = genSymOwner
ctx.config = conf
ctx.ic = ic
initIdTable(ctx.mapping)
ctx.instID = instID[]
ctx.idgen = idgen
let body = tmpl.ast[bodyPos]
#echo "instantion of ", renderTree(body, {renderIds})
@@ -218,7 +204,7 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
result = copyNode(body)
ctx.instLines = sfCallsite in tmpl.flags
if ctx.instLines:
setInfoRecursive(result, n.info)
result.info = n.info
for i in 0..<body.safeLen:
evalTemplateAux(body[i], args, ctx, result)
result.flags.incl nfFromTemplate

View File

@@ -1,131 +0,0 @@
#
#
# 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, tyError, tyBuiltInTypeClass,
tyUserTypeClass, tyUserTypeClassInst, tyCompositeTypeClass,
tyAnd, tyOr, tyNot, tyAnything, tyConcept, tyIterable, tyForward:
result = newZero(t, info, nkEmpty) # bug indicator

View File

@@ -14,14 +14,7 @@
import ropes, platform, condsyms, options, msgs, lineinfos, pathutils, modulepaths
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
import std/[os, strutils, osproc, sha1, streams, sequtils, times, strtabs, json, jsonutils, sugar, parseutils]
type
TInfoCCProp* = enum # properties of the C compiler:
@@ -33,7 +26,6 @@ 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
@@ -63,7 +55,7 @@ type
# Configuration settings for various compilers.
# When adding new compilers, the cmake sources could be a good reference:
# https://cmake.org/gitweb?p=cmake.git;a=tree;f=Modules/Platform;
# http://cmake.org/gitweb?p=cmake.git;a=tree;f=Modules/Platform;
template compiler(name, settings: untyped): untyped =
proc name: TInfoCC {.compileTime.} = settings
@@ -91,12 +83,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++17 -funsigned-char",
cppXsupport: "-std=gnu++14 -funsigned-char",
props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
hasAttribute, hasBuiltinUnreachable})
hasAttribute})
# GNU C and C++ Compiler
compiler nintendoSwitchGCC:
@@ -121,9 +113,9 @@ compiler nintendoSwitchGCC:
asmStmtFrmt: "asm($1);$n",
structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
produceAsm: gnuAsmListing,
cppXsupport: "-std=gnu++17 -funsigned-char",
cppXsupport: "-std=gnu++14 -funsigned-char",
props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
hasAttribute, hasBuiltinUnreachable})
hasAttribute})
# LLVM Frontend for GCC/G++
compiler llvmGcc:
@@ -158,15 +150,11 @@ compiler vcc:
compileTmpl: "/c$vccplatform $options $include /nologo /Fo$objfile $file",
buildGui: " /SUBSYSTEM:WINDOWS user32.lib ",
buildDll: " /LD",
buildLib: "vccexe --command:lib$vccplatform /nologo /OUT:$libfile $objfiles",
buildLib: "lib /OUT:$libfile $objfiles",
linkerExe: "cl",
linkTmpl: "$builddll$vccplatform /Fe$exefile $objfiles $buildgui /nologo $options",
includeCmd: " /I",
# HACK: we call `cl` so we have to pass `/link` for linker options,
# but users may still want to pass arguments to `cl` (see #14221)
# to deal with this, we add `/link` before each `/LIBPATH`,
# the linker ignores extra `/link`s since it's an unrecognized argument
linkDirCmd: " /link /LIBPATH:",
linkDirCmd: " /LIBPATH:",
linkLibCmd: " $1.lib",
debug: " /RTC1 /Z7 ",
pic: "",
@@ -176,22 +164,6 @@ compiler vcc:
cppXsupport: "",
props: {hasCpp, hasAssume, hasDeclspec})
# Nvidia CUDA NVCC Compiler
compiler nvcc:
result = gcc()
result.name = "nvcc"
result.compilerExe = "nvcc"
result.cppCompiler = "nvcc"
result.compileTmpl = "-c -x cu -Xcompiler=\"$options\" $include -o $objfile $file"
result.linkTmpl = "$buildgui $builddll -o $exefile $objfiles -Xcompiler=\"$options\""
# AMD HIPCC Compiler (rocm/cuda)
compiler hipcc:
result = clang()
result.name = "hipcc"
result.compilerExe = "hipcc"
result.cppCompiler = "hipcc"
compiler clangcl:
result = vcc()
result.name = "clang_cl"
@@ -257,8 +229,8 @@ compiler tcc:
linkerExe: "tcc",
linkTmpl: "-o $exefile $options $buildgui $builddll $objfiles",
includeCmd: " -I",
linkDirCmd: " -L",
linkLibCmd: " -l$1",
linkDirCmd: "", # XXX: not supported yet
linkLibCmd: "", # XXX: not supported yet
debug: " -g ",
pic: "",
asmStmtFrmt: "asm($1);$n",
@@ -305,9 +277,7 @@ const
envcc(),
icl(),
icc(),
clangcl(),
hipcc(),
nvcc()]
clangcl()]
hExt* = ".h"
@@ -341,7 +311,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, backendNif: discard
of backendC: discard
of backendInvalid:
# during parsing of cfg files; we don't know the backend yet, no point in
# guessing wrong thing
@@ -353,9 +323,7 @@ proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
platform.OS[conf.target.targetOS].name & '.' &
CC[c].name & fullSuffix
result = getConfigVar(conf, fullCCname)
if existsConfigVar(conf, fullCCname):
result = getConfigVar(conf, fullCCname)
else:
if result.len == 0:
# not overridden for this cross compilation setting?
result = getConfigVar(conf, CC[c].name & fullSuffix)
else:
@@ -463,22 +431,11 @@ proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} =
# really: Cross compilation from Linux to Linux for example is entirely
# reasonable.
# `optGenMapping` is included here for niminst.
# We use absolute paths for vcc / cl, see issue #19883.
let options =
if conf.cCompiler == ccVcc:
{optGenMapping}
else:
{optGenScript, optGenMapping}
result = conf.globalOptions * options != {}
result = conf.globalOptions * {optGenScript, optGenMapping} != {}
proc cFileSpecificOptions(conf: ConfigRef; nimname, fullNimFile: string): string =
result = conf.compileOptions
if (conf.cCompiler == ccGcc or conf.cCompiler == ccCLang) and
conf.selectedGC == gcRefc:
# bug #10625
addOpt(result, "-fno-omit-frame-pointer")
for option in conf.compileOptionsCmd:
if strutils.find(result, option, 0) < 0:
addOpt(result, option)
@@ -514,10 +471,6 @@ 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 & " "
@@ -564,9 +517,9 @@ proc ccHasSaneOverflow*(conf: ConfigRef): bool =
var exe = getConfigVar(conf, conf.cCompiler, ".exe")
if exe.len == 0: exe = CC[conf.cCompiler].compilerExe
# NOTE: should we need the full version, use -dumpfullversion
let (s, exitCode) = try: execCmdEx(exe & " -dumpversion") except IOError, OSError, ValueError: ("", 1)
let (s, exitCode) = try: execCmdEx(exe & " -dumpversion") except: ("", 1)
if exitCode == 0:
var major: int = 0
var major: int
discard parseInt(s, major)
result = major >= 5
else:
@@ -612,7 +565,7 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
compilePattern = joinPath(conf.cCompilerPath, exe)
else:
compilePattern = exe
compilePattern = getCompilerExe(conf, c, isCpp)
includeCmd.add(join([CC[c].includeCmd, quoteShell(conf.projectPath.string)]))
@@ -654,7 +607,7 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
"for the selected C compiler: " & CC[conf.cCompiler].name)
result.add(' ')
strutils.addf(result, CC[c].compileTmpl, [
result.addf(CC[c].compileTmpl, [
"dfile", dfile,
"file", cfsh, "objfile", quoteShell(objfile),
"options", options, "include", includeCmd,
@@ -676,7 +629,7 @@ proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
let hashFile = toGeneratedFile(conf, conf.mangleModuleName(cfile.cname).AbsoluteFile, "sha1")
let currentHash = footprint(conf, cfile)
var f: File = default(File)
var f: File
if open(f, hashFile.string, fmRead):
let oldHash = parseSecureHash(f.readLine())
close(f)
@@ -712,8 +665,7 @@ proc getLinkCmd(conf: ConfigRef; output: AbsoluteFile,
if removeStaticFile:
removeFile output # fixes: bug #16947
result = CC[conf.cCompiler].buildLib % ["libfile", quoteShell(output),
"objfiles", objfiles,
"vccplatform", vccplatform(conf)]
"objfiles", objfiles]
else:
var linkerExe = getConfigVar(conf, conf.cCompiler, ".linkerexe")
if linkerExe.len == 0: linkerExe = getLinkerExe(conf, conf.cCompiler)
@@ -746,7 +698,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 ' '
strutils.addf(result, linkTmpl, ["builddll", builddll,
result.addf(linkTmpl, ["builddll", builddll,
"mapfile", mapfile,
"buildgui", buildgui, "options", linkOptions,
"objfiles", objfiles, "exefile", exefile,
@@ -786,7 +738,7 @@ proc getLinkCmd(conf: ConfigRef; output: AbsoluteFile,
# https://blog.molecular-matters.com/2017/05/09/deleting-pdb-files-locked-by-visual-studio/
# and a bit about the .pdb format in case that is ever needed:
# https://github.com/crosire/blink
# https://www.debuginfo.com/articles/debuginfomatch.html#pdbfiles
# http://www.debuginfo.com/articles/debuginfomatch.html#pdbfiles
if conf.hcrOn and isVSCompatible(conf):
let t = now()
let pdb = output.string & "." & format(t, "MMMM-yyyy-HH-mm-") & $t.nanosecond & ".pdb"
@@ -810,61 +762,7 @@ template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body
(ose.msg & " " & $ose.errorCode))
raise
proc createMacAppBundle(conf: ConfigRef; exefile: AbsoluteFile) =
let (dir, name, _) = splitFile(exefile.string)
let appBundleName = name & ".app"
let appBundlePath = dir / appBundleName
let contentsPath = appBundlePath / "Contents"
let macosPath = contentsPath / "MacOS"
createDir(macosPath)
let bundleExePath = macosPath / name
copyFileWithPermissions(exefile.string, bundleExePath)
let infoPlistPath = contentsPath / "Info.plist"
proc xmlEscape(s: string): string =
result = newStringOfCap(s.len)
for c in items(s):
case c:
of '<': result.add("&lt;")
of '>': result.add("&gt;")
of '&': result.add("&amp;")
of '"': result.add("&quot;")
of '\'': result.add("&apos;")
else:
if ord(c) < 32:
result.add("&#" & $ord(c) & ';')
else:
result.add(c)
let escapedName = xmlEscape(name)
let infoPlistContent = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>$1</string>
<key>CFBundleIdentifier</key>
<string>com.nim.$1</string>
<key>CFBundleName</key>
<string>$1</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSUIElement</key>
<string>1</string>
</dict>
</plist>""" % [escapedName]
writeFile(infoPlistPath, infoPlistContent)
removeFile(exefile.string)
rawMessage(conf, hintUserRaw, "Created Mac app bundle: " & appBundlePath)
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
@@ -920,22 +818,10 @@ proc linkViaResponseFile(conf: ConfigRef; cmd: string) =
else:
writeFile(linkerArgs, args)
try:
when defined(macosx):
execLinkCmd(conf, "xargs " & cmd.substr(0, last) & " < " & linkerArgs)
else:
execLinkCmd(conf, cmd.substr(0, last) & " @" & linkerArgs)
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
@@ -947,38 +833,23 @@ 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] % (demangleModuleName(path.splitFile.name) & ": " & compileCmd)
else:
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 = ""
var cmds: TStringSeq = default(TStringSeq)
var prettyCmds: TStringSeq = default(TStringSeq)
var script: Rope = nil
var cmds: TStringSeq
var prettyCmds: TStringSeq
let prettyCb = proc (idx: int) = writePrettyCmdsStderr(prettyCmds[idx])
for idx, it in conf.toCompile:
@@ -1039,18 +910,21 @@ proc callCCompiler*(conf: ConfigRef) =
objfiles.add(' ')
objfiles.add(quoteShell(objFile))
let mainOutput = if optGenScript notin conf.globalOptions: conf.prepareToWriteOutput
else: AbsoluteFile(conf.outFile)
else: AbsoluteFile(conf.projectName)
linkCmd = getLinkCmd(conf, mainOutput, objfiles, removeStaticFile = true)
extraCmds = getExtraCmds(conf, mainOutput)
if optCompileOnly notin conf.globalOptions:
preventLinkCmdMaxCmdLen(conf, linkCmd)
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)
for cmd in extraCmds:
execExternalProgram(conf, cmd, hintExecuting)
# create Mac app bundle for GUI apps on macOS
when defined(macosx):
if conf.globalOptions * {optGenGuiApp, optGenDynLib, optGenStaticLib} == {optGenGuiApp}:
createMacAppBundle(conf, mainOutput)
else:
linkCmd = ""
if optGenScript in conf.globalOptions:
@@ -1066,11 +940,10 @@ proc jsonBuildInstructionsFile*(conf: ConfigRef): AbsoluteFile =
# works out of the box with `hashMainCompilationParams`.
result = getNimcacheDir(conf) / conf.outFile.changeFileExt("json")
const cacheVersion = "D20240927T193831" # update when `BuildCache` spec changes
const cacheVersion = "D20210525T193831" # update when `BuildCache` spec changes
type BuildCache = object
cacheVersion: string
outputFile: string
outputLastModificationTime: string
compile: seq[(string, string)]
link: seq[string]
linkcmd: string
@@ -1084,7 +957,7 @@ type BuildCache = object
depfiles: seq[(string, string)]
nimexe: string
proc writeJsonBuildInstructions*(conf: ConfigRef; deps: StringTableRef) =
proc writeJsonBuildInstructions*(conf: ConfigRef) =
var linkFiles = collect(for it in conf.externalToLink:
var it = it
if conf.noAbsolutePaths: it = it.extractFilename
@@ -1105,24 +978,17 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; deps: StringTableRef) =
currentDir: getCurrentDir())
if optRun in conf.globalOptions or isDefined(conf, "nimBetterRun"):
bcache.cmdline = conf.commandLine
for it in conf.m.fileInfos:
bcache.depfiles = collect(for it in conf.m.fileInfos:
let path = it.fullPath.string
if isAbsolute(path): # TODO: else?
if path in deps:
bcache.depfiles.add (path, deps[path])
else: # backup for configs etc.
bcache.depfiles.add (path, $secureHashFile(path))
(path, $secureHashFile(path)))
bcache.nimexe = hashNimExe()
if fileExists(bcache.outputFile):
bcache.outputLastModificationTime = $getLastModificationTime(bcache.outputFile)
conf.jsonBuildFile = conf.jsonBuildInstructionsFile
conf.jsonBuildFile.string.writeFile(bcache.toJson.pretty)
proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile): bool =
result = false
if not fileExists(jsonFile) or not fileExists(conf.absOutFile): return true
var bcache: BuildCache = default(BuildCache)
var bcache: BuildCache
try: bcache.fromJson(jsonFile.string.parseFile)
except IOError, OSError, ValueError:
stderr.write "Warning: JSON processing failed for: $#\n" % jsonFile.string
@@ -1136,13 +1002,11 @@ proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; jsonFile: Absolute
# xxx optimize by returning false if stdin input was the same
for (file, hash) in bcache.depfiles:
if $secureHashFile(file) != hash: return true
if bcache.outputLastModificationTime != $getLastModificationTime(bcache.outputFile):
return true
proc runJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile) =
var bcache: BuildCache = default(BuildCache)
var bcache: BuildCache
try: bcache.fromJson(jsonFile.string.parseFile)
except ValueError, KeyError, JsonKindError:
except:
let e = getCurrentException()
conf.quitOrRaise "\ncaught exception:\n$#\nstacktrace:\n$#error evaluating JSON file: $#" %
[e.msg, e.getStackTrace(), jsonFile.string]
@@ -1153,18 +1017,16 @@ proc runJsonBuildInstructions*(conf: ConfigRef; jsonFile: AbsoluteFile) =
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)
var cmds, prettyCmds: 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)
execLinkCmd(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)])

View File

@@ -10,11 +10,9 @@
# This module implements Nim's standard template filter.
import
llstream, ast, msgs, options,
llstream, strutils, ast, msgs, options,
filters, lineinfos, pathutils
import std/strutils
type
TParseState = enum
psDirective, psTempl
@@ -203,15 +201,17 @@ proc parseLine(p: var TTmplParser) =
proc filterTmpl*(conf: ConfigRef, stdin: PLLStream, filename: AbsoluteFile,
call: PNode): PLLStream =
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)
)
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)
# do not process the first line which contains the directive:
if llStreamReadLine(p.inp, p.x):
inc p.info.line

View File

@@ -10,11 +10,9 @@
# This module implements Nim's simple filters and helpers for filters.
import
llstream, idents, ast, msgs, options,
llstream, idents, strutils, 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}))
@@ -31,30 +29,23 @@ 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:
result = default(char)
invalidPragma(conf, n)
else: 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:
result = ""
invalidPragma(conf, n)
else: 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:
result = false
invalidPragma(conf, n)
else: invalidPragma(conf, n)
proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream =
var pattern = strArg(conf, call, "startswith", 1, "")

View File

@@ -9,14 +9,8 @@
## Module that implements ``gorge`` for the compiler.
import msgs, options, lineinfos, pathutils
import std/[os, osproc, streams]
when defined(nimPreviewSlimSystem):
import std/syncio
import ../dist/checksums/src/checksums/sha1
import msgs, std / sha1, os, osproc, streams, options,
lineinfos, pathutils
proc readOutput(p: Process): (string, int) =
result[0] = ""
@@ -30,11 +24,10 @@ 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 = default(File)
var f: File
if optForceFullMake notin conf.globalOptions and open(f, filename):
result = (f.readAll, 0)
f.close
@@ -53,11 +46,7 @@ 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:
when defined(nimLegacyGorgeErrors):
result = ("", -1)
else:
result = ("Error running startProcess: " & getCurrentExceptionMsg(), -1)
if not readSuccessful: result = ("", -1)
else:
try:
var p = startProcess(cmd, workingDir,
@@ -68,7 +57,4 @@ proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (str
result = p.readOutput
p.close()
except IOError, OSError:
when defined(nimLegacyGorgeErrors):
result = ("", -1)
else:
result = ("Error running startProcess: " & getCurrentExceptionMsg(), -1)
result = ("", -1)

View File

@@ -12,9 +12,6 @@
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}
@@ -46,15 +43,11 @@ proc isLocation(n: PNode): bool = not n.isValue
proc isLet(n: PNode): bool =
if n.kind == nkSym:
if n.sym.kind in {skLet, skConst, skTemp, skForVar}: # guard immutable variables
if n.sym.kind in {skLet, skTemp, skForVar}:
result = true
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
@@ -72,12 +65,9 @@ proc isLetLocation(m: PNode, isApprox: bool): bool =
case n.kind
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv:
n = n[0]
of nkDerefExpr:
of nkDerefExpr, nkHiddenDeref:
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]
@@ -140,8 +130,6 @@ 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)
@@ -209,7 +197,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.astdef.len-1)
nkIntLit.newIntNode(x.sym.ast.len-1)
else:
o.opAdd.buildCall(o.opLen.buildCall(x), minusOne())
result.info = x.info
@@ -336,8 +324,6 @@ 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'
@@ -345,18 +331,12 @@ 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)
@@ -370,14 +350,10 @@ 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...
@@ -394,8 +370,6 @@ 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
@@ -404,12 +378,8 @@ 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
@@ -475,9 +445,8 @@ proc hasSubTree(n, x: PNode): bool =
of nkEmpty..nkNilLit:
result = n.sameTree(x)
of nkFormalParams:
result = false
discard
else:
result = false
for i in 0..<n.len:
if hasSubTree(n[i], x): return true
@@ -508,8 +477,6 @@ 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)
@@ -520,26 +487,16 @@ 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
else:
result = impUnknown
of mNot, mOr, mAnd:
result = impUnknown
assert(false, "impliesEq")
else: result = impUnknown
of mNot, mOr, mAnd: assert(false, "impliesEq")
else: discard
proc leImpliesIn(x, c, aSet: PNode): TImplication =
if c.kind in {nkCharLit..nkUInt64Lit}:
@@ -549,19 +506,13 @@ 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 = 0
var i, pos, neg: int
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}:
@@ -572,23 +523,17 @@ 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 = 0
var i, pos, neg: int
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
@@ -599,32 +544,22 @@ 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)
else:
result = impUnknown
of mNot, mOr, mAnd:
result = impUnknown
assert(false, "impliesIn")
else: result = impUnknown
of mNot, mOr, mAnd: assert(false, "impliesIn")
else: discard
proc valueIsNil(n: PNode): TImplication =
if n.kind == nkNilLit: impYes
@@ -636,19 +571,13 @@ 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)
else:
result = impUnknown
of mNot, mOr, mAnd:
result = impUnknown
assert(false, "impliesIsNil")
else: result = impUnknown
of mNot, mOr, mAnd: assert(false, "impliesIsNil")
else: discard
proc impliesGe(fact, x, c: PNode): TImplication =
assert isLocation(x)
@@ -659,57 +588,32 @@ 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
else: result = impUnknown
else:
result = impUnknown
else:
result = impUnknown
of mNot, mOr, mAnd:
result = impUnknown
assert(false, "impliesGe")
else: result = impUnknown
of mNot, mOr, mAnd: assert(false, "impliesGe")
else: discard
proc impliesLe(fact, x, c: PNode): TImplication =
if not isLocation(x):
@@ -724,59 +628,35 @@ 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:
result = impUnknown
assert(false, "impliesLe")
else: result = impUnknown
of mNot, mOr, mAnd: assert(false, "impliesLe")
else: discard
proc impliesLt(fact, x, c: PNode): TImplication =
# x < 3 same as x <= 2:
@@ -788,8 +668,6 @@ 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
@@ -841,7 +719,6 @@ 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:
@@ -870,7 +747,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 std/macros
import macros
macro `=~`(x: PNode, pat: untyped): bool =
proc m(x, pat, conds: NimNode) =
@@ -1017,7 +894,6 @@ 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:
@@ -1063,7 +939,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:
@@ -1098,8 +974,8 @@ proc addFactLt*(m: var TModel; a, b: PNode) =
addFactLe(m, a, bb)
proc settype(n: PNode): PType =
var idgen = idGeneratorForPackage(-1'i32)
result = newType(tySet, idgen, n.typ.owner)
result = newType(tySet, ItemId(module: -1, item: -1), n.typ.owner)
var idgen: IdGenerator
addSonSkipIntLit(result, n.typ, idgen)
proc buildOf(it, loc: PNode; o: Operators): PNode =
@@ -1165,12 +1041,8 @@ proc buildProperFieldCheck(access, check: PNode; o: Operators): PNode =
if check[1].kind == nkCurly:
result = copyTree(check)
if access.kind == nkDotExpr:
# change the access to the discriminator field access
var a = copyTree(access)
# set field name to discriminator field name
a[1] = check[2]
# set discriminator field type: important for `neg`
a.typ = check[2].typ
result[2] = a
# 'access.kind != nkDotExpr' can happen for object constructors
# which we don't check yet
@@ -1179,12 +1051,8 @@ 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; produceError: bool) =
proc checkFieldAccess*(m: TModel, n: PNode; conf: ConfigRef) =
for i in 1..<n.len:
let check = buildProperFieldCheck(n[0], n[i], m.g.operators)
if check != nil and m.doesImply(check) != impYes:
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
message(conf, n.info, warnProveField, renderTree(n[0])); break

View File

@@ -8,20 +8,17 @@
#
# This include implements the high level optimization pass.
# included from sem.nim
proc hlo(c: PContext, n: PNode, loopDetector: int): PNode
proc hlo(c: PContext, n: PNode): PNode
proc evalPattern(c: PContext, n, orig: PNode): PNode =
internalAssert c.config, n.kind == nkCall and n[0].kind == nkSym
# 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):
renderTree(n, {renderNoComments})
else:
""
var rule: string
if c.config.hasHint(hintPattern):
rule = renderTree(n, {renderNoComments})
let s = n[0].sym
case s.kind
of skMacro:
@@ -61,19 +58,18 @@ proc applyPatterns(c: PContext, n: PNode): PNode =
# activate this pattern again:
c.patterns[i] = pattern
proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
proc hlo(c: PContext, n: PNode): PNode =
inc(c.hloLoopDetector)
# simply stop and do not perform any further transformations:
if loopDetector > 300:
message(c.config, n.info, warnUser, "term rewrite macro instantiation too nested")
return n
if c.hloLoopDetector > 300: return n
case n.kind
of nkMacroDef, nkTemplateDef, procDefs:
# already processed (special cases in semstmts.nim)
result = n
else:
if n.kind in {nkFastAsgn, nkAsgn, nkSinkAsgn, nkIdentDefs, nkVarTuple} and
if n.kind in {nkFastAsgn, nkAsgn, nkIdentDefs, nkVarTuple} and
n[0].kind == nkSym and
{sfGlobal, sfPure} <= n[0].sym.flags:
{sfGlobal, sfPure} * n[0].sym.flags == {sfGlobal, sfPure}:
# do not optimize 'var g {.global} = re(...)' again!
return n
result = applyPatterns(c, n)
@@ -81,7 +77,7 @@ proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
# no optimization applied, try subtrees:
for i in 0..<result.safeLen:
let a = result[i]
let h = hlo(c, a, loopDetector)
let h = hlo(c, a)
if h != a: result[i] = h
else:
# perform type checking, so that the replacement still fits:
@@ -91,15 +87,17 @@ proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
result = fitNode(c, n.typ, result, n.info)
# optimization has been applied so check again:
result = commonOptimizations(c.graph, c.idgen, c.module, result)
result = hlo(c, result, loopDetector + 1)
result = hlo(c, result)
result = commonOptimizations(c.graph, c.idgen, c.module, result)
proc hloBody(c: PContext, n: PNode): PNode =
# fast exit:
if c.patterns.len == 0 or optTrMacros notin c.config.options: return n
result = hlo(c, n, 0)
c.hloLoopDetector = 0
result = hlo(c, n)
proc hloStmt(c: PContext, n: PNode): PNode =
# fast exit:
if c.patterns.len == 0 or optTrMacros notin c.config.options: return n
result = hlo(c, n, 0)
c.hloLoopDetector = 0
result = hlo(c, n)

168
compiler/ic/bitabs.nim Normal file
View File

@@ -0,0 +1,168 @@
## A BiTable is a table that can be seen as an optimized pair
## of (Table[LitId, Val], Table[Val, LitId]).
import hashes, rodfiles
type
LitId* = distinct uint32
BiTable*[T] = object
vals: seq[T] # indexed by LitId
keys: seq[LitId] # indexed by hash(val)
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash
template maxHash(t): untyped = high(t.keys)
template isFilled(x: LitId): bool = x.uint32 > 0'u32
proc `$`*(x: LitId): string {.borrow.}
proc `<`*(x, y: LitId): bool {.borrow.}
proc `<=`*(x, y: LitId): bool {.borrow.}
proc `==`*(x, y: LitId): bool {.borrow.}
proc hash*(x: LitId): Hash {.borrow.}
proc len*[T](t: BiTable[T]): int = t.vals.len
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
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
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)
swap(t.keys, n)
for i in 0..high(n):
let eh = n[i]
if isFilled(eh):
var j = hash(t.vals[idToIdx eh]) and maxHash(t)
while isFilled(t.keys[j]):
j = nextTry(j, maxHash(t))
t.keys[j] = move n[i]
proc getKeyId*[T](t: BiTable[T]; v: T): LitId =
let origH = hash(v)
var h = origH and maxHash(t)
if t.keys.len != 0:
while true:
let litId = t.keys[h]
if not isFilled(litId): break
if t.vals[idToIdx t.keys[h]] == v: return litId
h = nextTry(h, maxHash(t))
return LitId(0)
proc getOrIncl*[T](t: var BiTable[T]; v: T): LitId =
let origH = hash(v)
var h = origH and maxHash(t)
if t.keys.len != 0:
while true:
let litId = t.keys[h]
if not isFilled(litId): break
if t.vals[idToIdx t.keys[h]] == v: return litId
h = nextTry(h, maxHash(t))
# not found, we need to insert it:
if mustRehash(t.keys.len, t.vals.len):
enlarge(t)
# recompute where to insert:
h = origH and maxHash(t)
while true:
let litId = t.keys[h]
if not isFilled(litId): break
h = nextTry(h, maxHash(t))
else:
setLen(t.keys, 16)
h = origH and maxHash(t)
result = LitId(t.vals.len + idStart)
t.keys[h] = result
t.vals.add v
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
assert idx < t.vals.len
result = t.vals[idx]
proc hash*[T](t: BiTable[T]): Hash =
## as the keys are hashes of the values, we simply use them instead
var h: Hash = 0
for i, n in pairs t.keys:
h = h !& hash((i, n))
result = !$h
proc store*[T](f: var RodFile; t: BiTable[T]) =
storeSeq(f, t.vals)
storeSeq(f, t.keys)
proc load*[T](f: var RodFile; t: var BiTable[T]) =
loadSeq(f, t.vals)
loadSeq(f, t.keys)
when isMainModule:
var t: BiTable[string]
echo getOrIncl(t, "hello")
echo getOrIncl(t, "hello")
echo getOrIncl(t, "hello3")
echo getOrIncl(t, "hello4")
echo getOrIncl(t, "helloasfasdfdsa")
echo getOrIncl(t, "hello")
echo getKeyId(t, "hello")
echo getKeyId(t, "none")
for i in 0 ..< 100_000:
discard t.getOrIncl($i & "___" & $i)
for i in 0 ..< 100_000:
assert t.getOrIncl($i & "___" & $i).idToIdx == i + 4
echo "begin"
echo t.vals.len
echo t.vals[0]
echo t.vals[1004]
echo "middle"
var tf: BiTable[float]
discard tf.getOrIncl(0.4)
discard tf.getOrIncl(16.4)
discard tf.getOrIncl(32.4)
echo getKeyId(tf, 32.4)
var f2 = open("testblah.bin", fmWrite)
echo store(f2, tf)
f2.close
var f1 = open("testblah.bin", fmRead)
var t2: BiTable[float]
echo f1.load(t2)
echo t2.vals.len
echo getKeyId(t2, 32.4)
echo "end"
f1.close

175
compiler/ic/cbackend.nim Normal file
View File

@@ -0,0 +1,175 @@
#
#
# The Nim Compiler
# (c) Copyright 2021 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## New entry point into our C/C++ code generator. Ideally
## somebody would rewrite the old backend (which is 8000 lines of crufty Nim code)
## to work on packed trees directly and produce the C code as an AST which can
## then be rendered to text in a very simple manner. Unfortunately nobody wrote
## this code. So instead we wrap the existing cgen.nim and its friends so that
## we call directly into the existing code generation logic but avoiding the
## naive, outdated `passes` design. Thus you will see some
## `useAliveDataFromDce in flags` checks in the old code -- the old code is
## 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, tables
import ".."/[ast, options, lineinfos, modulegraphs, cgendata, cgen,
pathutils, extccomp, msgs, modulepaths]
import packed_ast, ic, dce, rodfiles
proc unpackTree(g: ModuleGraph; thisModule: int;
tree: PackedTree; n: NodePos): PNode =
var decoder = initPackedDecoder(g.config, g.cache)
result = loadNodes(decoder, g.packed, thisModule, tree, n)
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]
for p in allNodes(m.fromDisk.topLevel):
let n = unpackTree(g, m.module.position, m.fromDisk.topLevel, p)
cgen.genTopLevelStmt(bmod, n)
finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
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)
let ext =
if config.backend == backendCpp: ".nim.cpp"
elif config.backend == backendObjc: ".nim.m"
else: ".nim.c"
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,
obj: objFile,
flags: {CfileFlag.Cached})
addFileToCompile(config, cf)
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]
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
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
## Nim program aka `ModuleGraph`.
resetForBackend(g)
var alive = computeAliveSyms(g.packed, g.config)
when false:
for i in 0..high(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..high(g.packed):
# 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:
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):
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..high(g.packed):
if i != mainModuleIdx:
genPackedModule(g, i, alive)
if mainModuleIdx >= 0:
genPackedModule(g, mainModuleIdx, alive)

161
compiler/ic/dce.nim Normal file
View File

@@ -0,0 +1,161 @@
#
#
# The Nim Compiler
# (c) Copyright 2021 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Dead code elimination (=DCE) for IC.
import std/[intsets, tables]
import ".." / [ast, options, lineinfos, types]
import packed_ast, ic, bitabs
type
AliveSyms* = seq[IntSet]
AliveContext* = object ## Purpose is to fill the 'alive' field.
stack: seq[(int, TOptions, NodePos)] ## A stack for marking symbols as alive.
decoder: PackedDecoder ## We need a PackedDecoder for module ID address translations.
thisModule: int ## The module we're currently analysing for DCE.
alive: AliveSyms ## The final result of our computation.
options: TOptions
compilerProcs: Table[string, (int, int32)]
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 = 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
# is to eliminate unused procs. So there is no special logic required for this case.
if sfCompileTime notin flags:
if ({sfExportc, sfCompilerProc} * flags != {}) or
(symPtr.kind == skMethod):
result = true
# 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.strings[symPtr.name]] = (c.thisModule, symId)
template isNotGeneric(n: NodePos): bool = ithSon(tree, n, genericParamsPos).kind == nkEmpty
proc followLater(c: var AliveContext; g: PackedModuleGraph; module: int; item: int32) =
## 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.syms[item].ast
if body != emptyNodeId:
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.syms[item].name
if nid != LitId(0):
let name = g[module].fromDisk.strings[nid]
if name in ["nimFrame", "callDepthLimitReached"]:
echo "I was called! ", name, " body exists: ", body != emptyNodeId, " ", module, " ", item
proc requestCompilerProc(c: var AliveContext; g: PackedModuleGraph; name: string) =
let (module, item) = c.compilerProcs[name]
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.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.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) =
## Replicates the logic of `ccgexprs.genRangeChck`.
## XXX Refactor so that the duplicated logic is avoided. However, for now it's not clear
## the approach has enough merit.
var dest = loadTypeKind(n.typ, c, g, abstractVar)
if optRangeCheck notin c.options or dest in {tyUInt..tyUInt64}:
discard "no need to generate a check because it was disabled"
else:
let n0t = loadTypeKind(n.firstSon.typ, c, g, {})
if n0t in {tyUInt, tyUInt64}:
c.requestCompilerProc(g, "raiseRangeErrorNoArgs")
else:
let raiser =
case loadTypeKind(n.typ, c, g, abstractVarRange)
of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU"
of tyFloat..tyFloat128: "raiseRangeErrorF"
else: "raiseRangeErrorI"
c.requestCompilerProc(g, raiser)
proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
## Marks the symbols we encounter when we traverse the AST at `tree[n]` as alive, unless
## it is purely in a declarative context (type section etc.).
case n.kind
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
discard "ignore non-sym atoms"
of nkSym:
# This symbol is alive and everything its body references.
followLater(c, g, c.thisModule, n.operand)
of nkModuleRef:
let (n1, n2) = sons2(tree, n)
assert n1.kind == nkInt32Lit
assert n2.kind == nkInt32Lit
let m = n1.litId
let item = n2.operand
let otherModule = toFileIndexCached(c.decoder, g, c.thisModule, m).int
followLater(c, g, otherModule, item)
of nkMacroDef, nkTemplateDef, nkTypeSection, nkTypeOfExpr,
nkCommentStmt, nkIncludeStmt,
nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
nkFromStmt, nkStaticStmt:
discard
of nkVarSection, nkLetSection, nkConstSection:
# XXX ignore the defining local variable name?
for son in sonsReadonly(tree, n):
aliveCode(c, g, tree, son)
of nkChckRangeF, nkChckRange64, nkChckRange:
rangeCheckAnalysis(c, g, tree, n)
of nkProcDef, nkConverterDef, nkMethodDef, nkFuncDef, nkIteratorDef:
if n.firstSon.kind == nkSym and isNotGeneric(n):
let item = n.firstSon.operand
if isExportedToC(c, g, item):
# This symbol is alive and everything its body references.
followLater(c, g, c.thisModule, item)
else:
for son in sonsReadonly(tree, n):
aliveCode(c, g, tree, son)
proc followNow(c: var AliveContext; g: PackedModuleGraph) =
## Mark all entries in the stack. Marking can add more entries
## to the stack but eventually we have looked at every alive symbol.
while c.stack.len > 0:
let (modId, opt, ast) = c.stack.pop()
c.thisModule = modId
c.options = opt
aliveCode(c, g, g[modId].fromDisk.bodies, ast)
proc computeAliveSyms*(g: PackedModuleGraph; conf: ConfigRef): AliveSyms =
## Entry point for our DCE algorithm.
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):
if g[i].status != undefined:
c.thisModule = i
for p in allNodes(g[i].fromDisk.topLevel):
aliveCode(c, g, g[i].fromDisk.topLevel, p)
followNow(c, g)
result = move(c.alive)
proc isAlive*(a: AliveSyms; module: int, item: int32): bool =
## Backends use this to query if a symbol is `alive` which means
## we need to produce (C/C++/etc) code for it.
result = a[module].contains(item)

56
compiler/ic/design.rst Normal file
View File

@@ -0,0 +1,56 @@
====================================
Incremental Recompilations
====================================
We split the Nim compiler into a frontend and a backend.
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. 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
`.nim` file, there can be no inconsistency. There can also be no
partial updates.
- No dependency to external packages (SQLite). SQLite simply is too
slow and the old way of serialization was too slow too. We use a
format designed for Nim and expect to base further tools on this
file format.
References to external modules must be (moduleId, symId) pairs.
The symbol IDs are module specific. This way no global ID increment
mechanism needs to be implemented that we could get wrong. ModuleIds
are rod-file specific too.
Global state
------------
There is no global state.
Rod File Format
---------------
It's a simple binary file format. `rodfiles.nim` contains some details.
Backend
-------
Nim programmers have to come to enjoy whole-program dead code elimination,
by default. Since this is a "whole program" optimization, it does break
modularity. However, thanks to the packed AST representation we can perform
this global analysis without having to unpack anything. This is basically
a mark&sweep GC algorithm:
- Start with the top level statements. Every symbol that is referenced
from a top level statement is not "dead" and needs to be compiled by
the backend.
- Every symbol referenced from a referenced symbol also has to be
compiled.
Caching logic: Only if the set of alive symbols is different from the
last run, the module has to be regenerated.

File diff suppressed because it is too large Load Diff

1222
compiler/ic/ic.nim Normal file

File diff suppressed because it is too large Load Diff

149
compiler/ic/integrity.nim Normal file
View File

@@ -0,0 +1,149 @@
#
#
# 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 sets
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) =
if tree[n.int].typeId != nilItemId:
checkType(c, tree[n.int].typeId)
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.nodes[n.int].operand)
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 == nkInt32Lit
assert n2.kind == nkInt32Lit
checkForeignSym(c, PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand))
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)]
methodsPerType*: seq[(PackedItemId, int, PackedItemId)]
enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
]#
proc checkIntegrity*(g: ModuleGraph) =
var c = CheckedContext(g: g)
for i in 0..high(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)

167
compiler/ic/navigator.nim Normal file
View File

@@ -0,0 +1,167 @@
#
#
# 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 sets
from os import nil
from std/private/miscdollars import toLocation
import ".." / [ast, modulegraphs, msgs, options]
import packed_ast, bitabs, ic
type
NavContext = object
g: ModuleGraph
thisModule: int32
trackPos: PackedLineInfo
alreadyEmitted: HashSet[string]
outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
proc isTracked(current, trackPos: PackedLineInfo, tokenLen: int): bool =
if current.file == trackPos.file and current.line == trackPos.line:
let col = trackPos.col
if col >= current.col and col < current.col+tokenLen:
return true
proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
result = s.name != LitId(0) and
isTracked(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(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..high(tree.nodes):
case tree.nodes[i].kind
of nkSym:
let item = tree.nodes[i].operand
if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree.nodes[i].info):
return ItemId(module: c.thisModule, item: item)
of nkModuleRef:
if tree.nodes[i].info.line == c.trackPos.line and tree.nodes[i].info.file == c.trackPos.file:
let (n1, n2) = sons2(tree, NodePos i)
assert n1.kind == nkInt32Lit
assert n2.kind == nkInt32Lit
let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
if searchForeignSym(c, itemId, tree.nodes[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.int].kind in declarativeNodes
proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
var m = ""
var file = c.g.packed[c.thisModule].fromDisk.strings[info.file]
if c.outputSep == ' ':
file = os.extractFilename file
toLocation(m, file, info.line.int, info.col.int + 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..high(tree.nodes):
case tree.nodes[i].kind
of nkSym:
let item = tree.nodes[i].operand
if sym.item == item and sym.module == c.thisModule:
usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
of nkModuleRef:
let (n1, n2) = sons2(tree, NodePos i)
assert n1.kind == nkInt32Lit
assert n2.kind == nkInt32Lit
let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
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.nodes[i].info, isDecl(tree, parent(NodePos i)))
else: discard
proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
for i in 0..high(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: PackedLineInfo(line: unpacked.line, col: unpacked.col, 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..high(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..high(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)

417
compiler/ic/packed_ast.nim Normal file
View File

@@ -0,0 +1,417 @@
#
#
# The Nim Compiler
# (c) Copyright 2020 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Packed AST representation, mostly based on a seq of nodes.
## For IC support. Far future: Rewrite the compiler passes to
## use this representation directly in all the transformations,
## it is superior.
import hashes, tables, strtabs
import bitabs
import ".." / [ast, options]
type
SymId* = distinct int32
ModuleId* = distinct int32
NodePos* = distinct int
NodeId* = distinct int32
PackedItemId* = object
module*: LitId # 0 if it's this module
item*: int32 # same as the in-memory representation
const
nilItemId* = PackedItemId(module: LitId(0), item: -1.int32)
const
emptyNodeId* = NodeId(-1)
type
PackedLineInfo* = object
line*: uint16
col*: int16
file*: LitId
PackedLib* = object
kind*: TLibKind
generated*: bool
isOverriden*: bool
name*: LitId
path*: NodeId
PackedSym* = object
kind*: TSymKind
name*: LitId
typ*: PackedItemId
flags*: TSymFlags
magic*: TMagic
info*: PackedLineInfo
ast*: NodeId
owner*: PackedItemId
guard*: PackedItemId
bitsize*: int
alignment*: int # for alignment
options*: TOptions
position*: int
offset*: int
externalName*: LitId # instead of TLoc
locFlags*: TLocFlags
annex*: PackedLib
when hasFFI:
cname*: LitId
constraint*: NodeId
PackedType* = object
kind*: TTypeKind
callConv*: TCallingConvention
#nodekind*: TNodeKind
flags*: TTypeFlags
types*: seq[PackedItemId]
n*: NodeId
#nodeflags*: TNodeFlags
sym*: PackedItemId
owner*: PackedItemId
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 # 28 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
info*: PackedLineInfo
PackedTree* = object ## usually represents a full Nim module
nodes*: seq[PackedNode]
PackedInstantiation* = object
key*, sym*: PackedItemId
concreteTypes*: seq[PackedItemId]
proc `==`*(a, b: SymId): bool {.borrow.}
proc hash*(a: SymId): Hash {.borrow.}
proc `==`*(a, b: NodePos): bool {.borrow.}
#proc `==`*(a, b: PackedItemId): bool {.borrow.}
proc `==`*(a, b: NodeId): bool {.borrow.}
proc newTreeFrom*(old: PackedTree): PackedTree =
result.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)
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)
proc addSymDef*(tree: var PackedTree; s: SymId; info: PackedLineInfo) =
tree.nodes.add PackedNode(kind: nkSym, operand: int32(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 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)
proc prepare*(dest: var PackedTree; source: PackedTree; sourcePos: NodePos): PatchPos =
result = PatchPos dest.nodes.len
dest.nodes.add source.nodes[sourcePos.int]
proc patch*(tree: var PackedTree; pos: PatchPos) =
let pos = pos.int
assert tree.nodes[pos].kind > nkNilLit
let distance = int32(tree.nodes.len - pos)
tree.nodes[pos].operand = distance
proc len*(tree: PackedTree): int {.inline.} = tree.nodes.len
proc `[]`*(tree: PackedTree; i: int): lent PackedNode {.inline.} =
tree.nodes[i]
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
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
inc pos
while pos < last:
yield NodePos pos
nextChild tree, pos
iterator sons*(dest: var PackedTree; tree: PackedTree; n: NodePos): NodePos =
let patchPos = prepare(dest, tree, n)
for x in sonsReadonly(tree, n): yield x
patch dest, patchPos
iterator isons*(dest: var PackedTree; tree: PackedTree;
n: NodePos): (int, NodePos) =
var i = 0
for ch0 in sons(dest, tree, n):
yield (i, ch0)
inc i
iterator sonsFrom1*(tree: PackedTree; n: NodePos): NodePos =
var pos = n.int
assert tree.nodes[pos].kind > nkNilLit
let last = pos + tree.nodes[pos].operand
inc pos
if pos < last:
nextChild tree, pos
while pos < last:
yield NodePos pos
nextChild tree, pos
iterator sonsWithoutLast2*(tree: PackedTree; n: NodePos): NodePos =
var count = 0
for child in sonsReadonly(tree, n):
inc count
var pos = n.int
assert tree.nodes[pos].kind > nkNilLit
let last = pos + tree.nodes[pos].operand
inc pos
while pos < last and count > 2:
yield NodePos pos
dec count
nextChild tree, pos
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)):
dec pos
#assert pos >= 0, "node has no parent"
result = NodePos(pos)
template parent*(n: NodePos): NodePos = parentImpl(tree, n)
proc hasXsons*(tree: PackedTree; n: NodePos; x: int): bool =
var count = 0
if tree.nodes[n.int].kind > nkNilLit:
for child in sonsReadonly(tree, n): inc count
result = count == x
proc hasAtLeastXsons*(tree: PackedTree; n: NodePos; x: int): bool =
if tree.nodes[n.int].kind > nkNilLit:
var count = 0
for child in sonsReadonly(tree, n):
inc count
if count >= x: return true
return false
proc firstSon*(tree: PackedTree; n: NodePos): NodePos {.inline.} =
NodePos(n.int+1)
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
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
template operand*(n: NodePos): int32 =
tree.nodes[n.int].operand
proc span*(tree: PackedTree; pos: int): int {.inline.} =
if isAtom(tree, pos): 1 else: tree.nodes[pos].operand
proc sons2*(tree: PackedTree; n: NodePos): (NodePos, NodePos) =
assert(not isAtom(tree, n.int))
let a = n.int+1
let b = a + span(tree, a)
result = (NodePos a, NodePos b)
proc sons3*(tree: PackedTree; n: NodePos): (NodePos, NodePos, NodePos) =
assert(not isAtom(tree, n.int))
let a = n.int+1
let b = a + span(tree, a)
let c = b + span(tree, b)
result = (NodePos a, NodePos b, NodePos c)
proc ithSon*(tree: PackedTree; n: NodePos; i: int): NodePos =
if tree.nodes[n.int].kind > nkNilLit:
var count = 0
for child in sonsReadonly(tree, n):
if count == i: return child
inc count
assert false, "node has no i-th child"
when false:
proc `@`*(tree: PackedTree; lit: LitId): lent string {.inline.} =
tree.sh.strings[lit]
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 symId*(n: NodePos): SymId = SymId tree.nodes[n.int].operand
proc firstSon*(n: NodePos): NodePos {.inline.} = NodePos(n.int+1)
when false:
# xxx `nkStrLit` or `nkStrLit..nkTripleStrLit:` below?
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,
nkInt64Lit,
nkUIntLit,
nkUInt8Lit,
nkUInt16Lit,
nkUInt32Lit,
nkUInt64Lit} # nkInt32Lit is missing by design!
externSIntLit* = {nkIntLit, nkInt8Lit, nkInt16Lit, nkInt64Lit}
externUIntLit* = {nkUIntLit, nkUInt8Lit, nkUInt16Lit, nkUInt32Lit, nkUInt64Lit}
directIntLit* = nkInt32Lit
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)
template copyInto*(dest, n, body) =
let patchPos = prepare(dest, tree, n)
body
patch dest, patchPos
template copyIntoKind*(dest, kind, info, body) =
let patchPos = prepare(dest, kind, info)
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:
yield NodePos(p)
let s = span(tree, p)
inc p, s
proc toPackedItemId*(item: int32): PackedItemId {.inline.} =
PackedItemId(module: LitId(0), item: item)

View File

@@ -14,16 +14,12 @@
import ".." / [ast, modulegraphs, trees, extccomp, btrees,
msgs, lineinfos, pathutils, options, cgmeth]
import std/tables
import tables
when defined(nimPreviewSlimSystem):
import std/assertions
import packed_ast, ic, bitabs
proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
## `list` is an `nkStmtList` of `nkReplayAction` nodes (macro-cache puts/incs/
## adds/incls and a few pragmas) recorded for `module`. Under the NIF backend a
## loaded module's `ast` is never reconstructed, so the caller passes the replay
## actions it parsed out of the module's NIF directly.
proc replayStateChanges*(module: PSym; g: ModuleGraph) =
let list = module.ast
assert list != nil
assert list.kind == nkStmtList
for n in list:
@@ -67,9 +63,8 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
g.cacheTables[destKey] = initBTree[string, PNode]()
if not contains(g.cacheTables[destKey], key):
g.cacheTables[destKey].add(key, val)
# else: the same key was already replayed. Under IC the import closure is
# replayed (direct module + transitive deps), so the same registration can
# legitimately be reached twice; re-applying it is a no-op, not an error.
else:
internalError(g.config, n.info, "key already exists: " & key)
of "incl":
let destKey = n[1].strVal
let val = n[2]
@@ -91,36 +86,62 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
else:
internalAssert g.config, false
proc replayBackendActions*(g: ModuleGraph; module: PSym; list: PNode) =
## Applies the backend-relevant replay actions (C compile/link directives)
## found in a NIF-loaded module's top-level statement list. The `nifc`
## backend loads modules without going through sem's `replayStateChanges`,
## so e.g. math's `{.passL: "-lm".}` was lost and the final link failed
## with undefined references. VM cache actions are deliberately NOT
## replayed here — codegen does not run macros.
if list == nil: return
for n in list:
if n.kind == nkReplayAction and n.len >= 2 and
n[0].kind == nkStrLit and n[1].kind == nkStrLit:
case n[0].strVal
of "compile":
if n.len == 4 and n[2].kind == nkStrLit:
let cname = AbsoluteFile n[1].strVal
var cf = Cfile(nimname: splitFile(cname).name, cname: cname,
obj: AbsoluteFile n[2].strVal,
flags: {CfileFlag.External},
customArgs: n[3].strVal)
extccomp.addExternalFileToCompile(g.config, cf)
of "link":
extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal)
of "passl":
extccomp.addLinkOption(g.config, n[1].strVal)
of "passc":
extccomp.addCompileOption(g.config, n[1].strVal)
of "localpassc":
extccomp.addLocalCompileOption(g.config, n[1].strVal,
toFullPathConsiderDirty(g.config, module.info.fileIndex))
of "cppdefine":
options.cppDefine(g.config, n[1].strVal)
else:
discard
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
## to imply. This is cheaper than deduplication of identical
## generic instantiations. However, deduplication is more
## powerful and general and I hope to implement it soon too
## (famous last words).
assert g.packed[module].status == loaded
for it in g.packed[module].fromDisk.typeInstCache:
let key = translateId(it[0], g.packed, module, g.config)
g.typeInstCache.mgetOrPut(key, @[]).add LazyType(id: FullId(module: module, packed: it[1]), typ: nil)
for it in mitems(g.packed[module].fromDisk.procInstCache):
let key = translateId(it.key, g.packed, module, g.config)
let sym = translateId(it.sym, g.packed, module, g.config)
var concreteTypes = newSeq[FullId](it.concreteTypes.len)
for i in 0..high(it.concreteTypes):
let tmp = translateId(it.concreteTypes[i], g.packed, module, g.config)
concreteTypes[i] = FullId(module: tmp.module, packed: it.concreteTypes[i])
g.procInstCache.mgetOrPut(key, @[]).add LazyInstantiation(
module: module, sym: FullId(module: sym.module, packed: it.sym),
concreteTypes: concreteTypes, inst: nil)
for it in mitems(g.packed[module].fromDisk.methodsPerType):
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))
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.methods):
let sym = loadSymFromId(g.config, g.cache, g.packed, module,
PackedItemId(module: LitId(0), item: it))
methodDef(g, g.idgen, sym)
when false:
# not used anymore:
for it in mitems(g.packed[module].fromDisk.compilerProcs):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it[1]))
g.lazyCompilerprocs[g.packed[module].fromDisk.sh.strings[it[0]]] = symId
for it in mitems(g.packed[module].fromDisk.converters):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
g.ifaces[module].converters.add LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.trmacros):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
g.ifaces[module].patterns.add LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.pureEnums):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
g.ifaces[module].pureEnums.add LazySym(id: symId, sym: nil)

247
compiler/ic/rodfiles.nim Normal file
View File

@@ -0,0 +1,247 @@
#
#
# The Nim Compiler
# (c) Copyright 2020 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## 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 typetraits import supportsCopyMem
## 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 preceeded 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
versionSection
configSection
stringsSection
checkSumsSection
depsSection
numbersSection
exportsSection
hiddenSection
reexportsSection
compilerProcsSection
trmacrosSection
convertersSection
methodsSection
pureEnumsSection
macroUsagesSection
toReplaySection
topLevelSection
bodiesSection
symsSection
typesSection
typeInstCacheSection
procInstCacheSection
attachedOpsSection
methodsPerTypeSection
enumToStringProcsSection
typeInfoSection # required by the backend
backendFlagsSection
aliveSymsSection # beware, this is stored in a `.alivesyms` file.
RodFileError* = enum
ok, tooBig, cannotOpen, ioFailure, wrongHeader, wrongSection, configMismatch,
includeFileChanged
RodFile* = object
f*: File
currentSection*: RodSection # for error checking
err*: RodFileError # little experiment to see if this works
# better than exceptions.
const
RodVersion = 1
cookie = [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.} =
f.err = err
#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
return
var lenPrefix = int32(s.len)
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
if s.len != 0:
if writeBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
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):
setError f, ioFailure
elif T is tuple:
for y in fields(x):
storePrim(f, y)
elif T is object:
for y in fields(x):
when y is seq:
storeSeq(f, y)
else:
storePrim(f, y)
else:
{.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
return
var lenPrefix = int32(s.len)
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
for i in 0..<s.len:
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):
setError f, ioFailure
else:
s = newString(lenPrefix)
if lenPrefix > 0:
if readBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
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):
setError f, ioFailure
elif T is tuple:
for y in fields(x):
loadPrim(f, y)
elif T is object:
for y in fields(x):
when y is seq:
loadSeq(f, y)
else:
loadPrim(f, y)
else:
{.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):
setError f, ioFailure
else:
s = newSeq[T](lenPrefix)
for i in 0..<lenPrefix:
loadPrim(f, s[i])
proc storeHeader*(f: var RodFile) =
## 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) =
## Loads the header which is described by `cookie`.
if f.err != ok: return
var thisCookie: 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
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
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
if not open(result.f, filename, fmRead):
setError result, cannotOpen

View File

@@ -1,292 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Precompiled config for the incremental compiler (`nim ic`).
##
## `nim ic` builds the program by spawning one `nim m` child per module (or
## strongly-connected import group) plus a final `nim nifc`. Each child is a
## full Nim process, so each would normally re-read the whole `nim.cfg` chain
## *and* re-run `config.nims` through the VM — work that is identical for every
## child and, because of the VM run, far from free. With ~85 modules in the
## compiler itself that config work is paid ~85 times during `koch bootic`.
##
## The fix mirrors Nimony's `.cfg.nif`: the driver parses config once, records
## the net effect, and the children replay it. Every config-file switch funnels
## through `processSwitch(..., passPP, ...)` (`nimconf.parseAssignment` and the
## `switch()` callback in `scriptconfig`), so the recorded sequence of those
## switches, replayed in order, reproduces an identical `ConfigRef` without any
## file read or VM run. The one config side effect that does not go through
## `processSwitch` is `cppDefine` (it mutates `conf.cppDefines` directly), so the
## resolved set is serialised alongside.
##
## Path-search switches are deliberately excluded from the recording (see
## `commands.processSwitch`): their resolved result already lives in
## `conf.searchPaths`, which the driver forwards to every child as absolute
## `--path` arguments; replaying their raw, config-dir-relative arguments here
## would misresolve.
import options, commands, lineinfos, pathutils, msgs
import std/[algorithm, os, sets, osproc, times, streams, syncio]
import "../dist/nimony/src/lib" / [nifbuilder, nifcoreparse]
const
IcConfigVersion* = "2"
## Artifact format version. Bump on any layout change here so a child built
## by an older compiler rejects a stale artifact and falls back to normal
## config loading instead of replaying a format it cannot parse.
proc writeIcConfig*(conf: ConfigRef; outfile: string) =
## Serialise the resolved config (the config-file switches recorded during
## `loadConfigs`, the resolved `cppDefines`/`searchPaths`, the nimcache dir, and
## the list of config *source* files for staleness detection) into `outfile`.
## `OnlyIfChanged`: when the content is byte-identical to what is already on
## disk the file is left untouched so its mtime does not advance — otherwise
## every `nim ic` run would re-fire the whole nifmake graph (see `nifler`'s
## `produceConfig`, whose model this mirrors).
var b = nifbuilder.open(outfile, writeMode = OnlyIfChanged)
b.withTree "stmts":
b.withTree "meta":
b.addStrLit IcConfigVersion
b.withTree "sources":
# Every config file read while loading (nim.cfg chain + config.nims), so a
# later run can decide via mtimes whether this artifact is still current
# (see `sourcesChanged`).
for f in conf.configFiles:
b.addStrLit f.string
b.withTree "nimcache":
# Resolved build nimcache. Recorded (unlike the path-search switches) so the
# driver, which replays this artifact instead of parsing `nim.cfg`, still
# learns a `--nimcache:` set inside `nim.cfg` and builds in the right place.
b.addStrLit conf.nimcacheDir.string
b.withTree "cppdefines":
# HashSet iteration order is unspecified; sort so the artifact is
# byte-stable across runs (nifmake keys rebuilds off content changes).
var defs: seq[string] = @[]
for d in conf.cppDefines: defs.add d
sort defs
for d in defs: b.addStrLit d
b.withTree "searchpaths":
# The resolved (absolute) search paths. Path-search *switches* are skipped
# below because their raw arguments are config-dir-relative; the net effect
# lives here instead, so a replayer with no `--path` command-line arguments
# (the `nim ic` driver itself) still resolves imports. `nim m`/`nim nifc`
# children also receive these as forwarded `--path` args; the dedup on
# replay makes the overlap harmless.
for p in conf.searchPaths:
b.addStrLit p.string
b.withTree "switches":
for sw in conf.icConfigSwitches:
b.addTree "sw"
b.addStrLit sw.switch
b.addStrLit sw.arg
b.endTree()
b.close()
proc applyIcConfig*(conf: ConfigRef; infile: string): bool =
## Replay the precompiled config into `conf`. Returns false (and applies
## nothing meaningful) when the artifact is missing or written by a compiler
## with an incompatible format version, so the caller can fall back to reading
## the config files normally.
if not fileExists(infile): return false
var pool = newPool()
var tags = newTagPool()
let
stmtsTag = tags.registerTag("stmts")
metaTag = tags.registerTag("meta")
sourcesTag = tags.registerTag("sources")
nimcacheTag = tags.registerTag("nimcache")
cppTag = tags.registerTag("cppdefines")
pathsTag = tags.registerTag("searchpaths")
switchesTag = tags.registerTag("switches")
swTag = tags.registerTag("sw")
var buf = parseFromFile(infile, 1000, pool, tags)
var c = beginRead(buf)
if c.kind != TagLit or c.cursorTagId != stmtsTag:
endRead(c)
return false
var version = ""
var sawMeta = false
let info = unknownLineInfo
c.loopInto:
if c.kind == TagLit:
if c.cursorTagId == metaTag:
sawMeta = true
c.loopInto:
if c.kind == StrLit:
version = strVal(c)
inc c
else:
skip c
elif c.cursorTagId == nimcacheTag:
c.loopInto:
if c.kind == StrLit:
let nc = strVal(c)
# Only when nimcache was not already pinned on the command line: a
# `--nimcache:` argument the driver/child was launched with must win
# over whatever `nim.cfg` recorded into the artifact.
if nc.len > 0 and conf.nimcacheDir.isEmpty:
conf.nimcacheDir = AbsoluteDir(nc)
inc c
else:
skip c
elif c.cursorTagId == sourcesTag:
# Replay does not need the source list; it exists only for
# `sourcesChanged`. Skip the whole section.
skip c
elif c.cursorTagId == cppTag:
c.loopInto:
if c.kind == StrLit:
cppDefine(conf, strVal(c))
inc c
else:
skip c
elif c.cursorTagId == pathsTag:
c.loopInto:
if c.kind == StrLit:
# Append preserving the serialised order (which already reflects the
# driver's addPath insert-at-front sequence), deduping against any
# path a child already received via a forwarded `--path` argument.
let d = AbsoluteDir(strVal(c))
if not conf.searchPaths.contains(d): conf.searchPaths.add d
inc c
else:
skip c
elif c.cursorTagId == switchesTag:
c.loopInto:
if c.kind == TagLit and c.cursorTagId == swTag:
var sw = ""
var arg = ""
var idx = 0
c.loopInto:
if c.kind == StrLit:
if idx == 0: sw = strVal(c)
else: arg = strVal(c)
inc idx
inc c
else:
skip c
processSwitch(sw, arg, passPP, info, conf)
else:
skip c
else:
skip c
else:
skip c
endRead(c)
result = sawMeta and version == IcConfigVersion
proc sourcesChanged*(configFile: string): bool =
## True when the precompiled config at `configFile` is missing, malformed,
## written by an incompatible version, or any recorded config *source* file is
## newer than it (or has vanished) — i.e. the artifact must be regenerated.
## Mirrors nifler's `sourcesChanged`: the source list lives inside the artifact
## so this needs no out-of-band knowledge of which `nim.cfg`s were read.
if not fileExists(configFile): return true
let modtime = getLastModificationTime(configFile)
var pool = newPool()
var tags = newTagPool()
let
stmtsTag = tags.registerTag("stmts")
metaTag = tags.registerTag("meta")
sourcesTag = tags.registerTag("sources")
var buf = parseFromFile(configFile, 1000, pool, tags)
var c = beginRead(buf)
if c.kind != TagLit or c.cursorTagId != stmtsTag:
endRead(c)
return true
var version = ""
var depsChanged = false
c.loopInto:
if c.kind == TagLit and c.cursorTagId == metaTag:
c.loopInto:
if c.kind == StrLit:
version = strVal(c)
inc c
else:
skip c
elif c.kind == TagLit and c.cursorTagId == sourcesTag:
c.loopInto:
if c.kind == StrLit:
let dep = strVal(c)
if not fileExists(dep) or getLastModificationTime(dep) >= modtime:
depsChanged = true
inc c
else:
skip c
else:
skip c
endRead(c)
result = depsChanged or version != IcConfigVersion
proc produceIcConfig*(conf: ConfigRef) =
## The `cmdIcConfig` command. By the time it runs, the normal pipeline has
## already fully parsed the `nim.cfg` chain and run `config.nims`, so the
## resolved config is sitting in `conf`; just serialise it to `--o`.
let outPath = conf.icConfigOut
if outPath.len == 0:
rawMessage(conf, errGenerated, "icconfig: missing output path (--icConfigOut)")
return
createDir(parentDir(outPath))
writeIcConfig(conf, outPath)
proc ensureIcConfig*(conf: ConfigRef) =
## Driver-side (`cmdIc`). Make sure an up-to-date precompiled config exists,
## (re)producing it in a *separate* process when missing or stale, then point
## `conf.icPreparsedConfig` at it so the driver replays the very same config its
## `nim m`/`nim nifc` children will — perfect speed (config parsed at most once,
## skipped entirely when nothing changed) and consistency (one producer, every
## process replays its output). The artifact lives in the nimcache derived from
## the command line (pre-config-parse), which is the one the children are told;
## a `--nimcache:` set inside `nim.cfg` is recovered from the artifact itself.
let cacheDir = getNimcacheDir(conf).string
# Start from a clean cache when the on-disk NIF format stamp is absent or stale
# (see `icFormatVersion`). This must happen HERE, before the config artifact is
# produced — `commandIc` performs the same check later, but by then the artifact
# would already live in the cache and the wipe would delete it.
createDir(cacheDir)
let versionFile = cacheDir / "ic.version"
let stamp = if fileExists(versionFile): readFile(versionFile) else: ""
if stamp != icFormatVersion:
removeDir(cacheDir)
createDir(cacheDir)
writeFile(versionFile, icFormatVersion)
let outPath = cacheDir / "ic_config.cfg.nif"
if not fileExists(outPath) or sourcesChanged(outPath):
createDir(cacheDir)
# Re-invoke ourselves as the config producer: reuse this process's command
# line, dropping the command argument (`ic`/`track`) in favour of `icconfig`
# and the explicit output path. Every switch must land BEFORE the project
# file, because anything after the project is swallowed into
# `config.arguments` by `cmdLineRest` (and a non-empty `arguments` without
# `--run` is a hard error). Callers may legitimately put switches after the
# project — `nim track PROJ --def:...` — so we re-order rather than replay
# verbatim: all `-`-prefixed switches first (in encounter order), then the
# non-switch project token(s). The producer re-reads `nim.cfg` itself.
var pargs = @["icconfig", "--icConfigOut:" & outPath]
var rest: seq[string] = @[]
var droppedCmd = false
for a in commandLineParams():
if a.len == 0: continue
if a[0] == '-':
pargs.add a
elif not droppedCmd:
droppedCmd = true # drop the original command token (`ic`/`track`)
else:
rest.add a # project file (and any further non-switch tokens) go last
for a in rest: pargs.add a
let p = startProcess(getAppFilename(), args = pargs,
options = {poStdErrToStdOut})
let outp = p.outputStream.readAll()
let code = p.waitForExit()
p.close()
if code != 0 or not fileExists(outPath):
rawMessage(conf, errGenerated,
"failed to produce precompiled config (exit code " & $code & "):\n" & outp)
return
conf.icPreparsedConfig = outPath

View File

@@ -1,55 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Nim's OWN module-suffix, replacing nimony's `gear2/modnames.moduleSuffix`.
##
## nimony's version hashes a path made RELATIVE to `getCurrentDir()` (or the
## shortest search-path-relative form), so the produced suffix depends on the
## current working directory AND the searchPath set. Under `nim ic` the
## DISCOVERY pass (`deps.nim`, in the driver process) and the COMPILE pass
## (`nifgen`/`typekeys`, in a child `nim m` process) can run with different CWDs
## or `--path` sets, so the SAME file hashes to two different suffixes: e.g.
## `std/staticos` became `sta5rk8sn1` at discovery but `sta4c0qxk` at compile, so
## every importer waited forever for a `.s.bif` that was actually written under
## the other name — a cold `nim ic` build (of anything pulling in `std/os`, whose
## `oscommon` does `from std/staticos import PathComponent`) never converged.
##
## Hashing the CANONICAL ABSOLUTE path makes the suffix a pure function of the
## file, identical across every process and call site. The base-name prefix +
## base-36 `uhash` layout is kept byte-for-byte compatible with the old scheme so
## nothing but the hashed string changes.
import std/os
import "../dist/nimony/src/lib" / tinyhashes
const
PrefixLen = 3 # keep it short: the suffix ends up in every mangled C name
Base36 = "0123456789abcdefghijklmnopqrstuvwxyz"
proc moduleSuffix*(path: string; searchPaths: openArray[string]): string =
## `searchPaths` is accepted for signature-compatibility with the replaced
## `modnames.moduleSuffix` but is deliberately IGNORED — the suffix must not
## depend on the search-path set or the CWD (see the module doc).
# Absolute inputs (the norm at every call site: `toFullPath`/`projectFull`)
# pass straight through `normalizedPath` with no `getCurrentDir` involvement;
# a stray relative path is made absolute against the CWD only as a fallback.
var f = path
if not isAbsolute(f):
try: f = absolutePath(f)
except CatchableError: discard
f = normalizedPath(f)
let m = splitFile(f).name
var id = uhash(f)
result = newStringOfCap(10)
for i in 0 ..< min(m.len, PrefixLen):
result.add m[i]
# base-36 of the hash, low digit first (order is irrelevant for identity).
while id > 0'u32:
result.add Base36[int(id mod 36'u32)]
id = id div 36'u32

View File

@@ -1,252 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## nifcore-based IC serialization helpers — Stage 1 of porting the IC backend
## from the old `nifstreams`/`nifcursors` NIF stack to `nifcore` (see
## `doc/ic_nifcore_port.md`).
##
## It hosts:
## * the process-wide shared `Pool`/`TagPool` that stands in for the old global
## `nifstreams.pool`,
## * `writeFileStable`, the content-stable file writer mirroring
## `nifcursors.writeFile(..., OnlyIfChanged)`,
## * the first ported writer (`writeSemDeps`), used as the migration spike.
##
## No `nifstreams`/`nifcursors` types cross this module's boundary: callers pass
## plain Nim values (config, ids, string lists), so it can coexist with the
## still-old-API `ast2nif.nim` during the migration.
import std / [syncio, algorithm]
from std / os import removeFile, moveFile
import options, pathutils, typekeys
import "../dist/nimony/src/lib" / [nifcore, nifcoreparse, nifreader, bif]
# One shared literals pool + tag pool for the whole process — the nifcore
# analogue of the old global `nifstreams.pool`. A single shared pool keeps
# string/symbol/file ids stable across every TokenBuf the IC backend builds,
# preserving the old global-pool semantics during the migration. (Stage 6 may
# move to fresh per-file pools for bif's fast path; see doc/ic_nifcore_port.md.)
let icPool* = newPool()
let icTags* = newTagPool()
proc createIcBuf*(cap = 16): TokenBuf {.inline.} =
## A `TokenBuf` bound to the shared IC pools.
createTokenBuf(cap, icPool, icTags)
proc tagId*(s: string): TagId {.inline.} =
## Intern a tag name in the shared tag pool.
icTags.registerTag(s)
type
IcBuilder* = object
## A thin nifcore `TokenBuf` builder whose surface is *primitive types only*
## (strings/ints/floats). It lets the still-old-API `ast2nif.nim` drive a
## nifcore buffer without any nifcore type crossing the module boundary —
## the bridge that routes IC output onto the nifcore serializer (Stage 2).
buf*: TokenBuf
proc newIcBuilder*(cap = 16): IcBuilder = IcBuilder(buf: createIcBuf(cap))
proc openTag*(b: var IcBuilder; tag: string) {.inline.} = b.buf.openTag(tagId(tag))
proc closeTag*(b: var IcBuilder) {.inline.} = b.buf.closeTag()
proc addSymUse*(b: var IcBuilder; s: string) {.inline.} = b.buf.addSymUse(s)
proc addSymDef*(b: var IcBuilder; s: string) {.inline.} = b.buf.addSymDef(s)
proc addIdent*(b: var IcBuilder; s: string) {.inline.} = b.buf.addIdent(s)
proc addStrLit*(b: var IcBuilder; s: string) {.inline.} = b.buf.addStrLit(s)
proc addIntLit*(b: var IcBuilder; v: int64) {.inline.} = b.buf.addIntLit(v)
proc addUIntLit*(b: var IcBuilder; v: uint64) {.inline.} = b.buf.addUIntLit(v)
proc addFloatLit*(b: var IcBuilder; v: float64) {.inline.} = b.buf.addFloatLit(v)
proc addCharLit*(b: var IcBuilder; c: char) {.inline.} = b.buf.addCharLit(c)
proc addDotToken*(b: var IcBuilder) {.inline.} = b.buf.addDotToken()
proc lineInfo*(b: var IcBuilder; file: string; line, col: int32; comment = "") =
## Attach line info (+ optional `#comment#`) to the head just emitted. No-op
## when `file` is empty (matches the old "emit only when info is valid").
## Strings are interned in the shared pools; the file/comment ids reproduce
## the old `pool.files`/`pool.strings` entries by string value.
if file.len == 0: return
let fid = icPool.filenames.getOrIncl(file)
let cid = if comment.len > 0: icPool.strings.getOrIncl(comment) else: StrId(0)
b.buf.appendLineInfo(fid, line, col, cid)
proc writeFileStable*(b: var TokenBuf; path: string; onlyIfChanged = false) =
## Serialize `b` to canonical module NIF text and write it. Mirrors
## `nifcursors.writeFile`: the module suffix is derived from `path`
## (`"." & extractModuleSuffix`), and `onlyIfChanged` skips the write when the
## on-disk bytes already match — the content-stability nifmake's incremental
## rebuild depends on.
let content = toModuleString(b, "." & extractModuleSuffix(path))
if onlyIfChanged:
let existing =
try: readFile(path)
except CatchableError: ""
if existing == content: return
writeFile(path, content)
proc writeStable*(b: var IcBuilder; path: string; onlyIfChanged = false) {.inline.} =
writeFileStable(b.buf, path, onlyIfChanged)
proc cursorPool*(c: Cursor): Pool {.inline.} = nifcore.pool(c)
## The literals pool the cursor's buffer was built against. `ast2nif.nim`
## imports `nifcore` with `except pool` (to keep nifstreams' global `pool`
## var the writer uses), so the reader reaches a cursor's pool through here —
## needed once `bif`-loaded buffers carry their OWN fresh pool rather than the
## shared `icPool`.
proc freshModuleCopy(b: var IcBuilder): TokenBuf =
## Re-home `b.buf` into a PRIVATE, module-local pool via `addSubtree` (which
## re-interns only the literals/tags this buffer actually uses). `b.buf` is bound
## to the process-wide shared `icPool`/`icTags`; storing it directly would embed
## the WHOLE shared pool (correct but huge — see `bif.storeToFile`). The copy's
## fresh-pool reload reproduces ids verbatim (the bif fresh-pool INVARIANT).
result = createTokenBuf(b.buf.len, newPool(), newTagPool())
var c = b.buf.beginRead()
while c.hasMore:
addSubtree(result, c)
skip c
proc storeBif*(b: var IcBuilder; path: string; dottedSuffix: string) =
## Persist the buffer as a compact, self-contained binary NIF (`.bif`).
var fresh = freshModuleCopy(b)
bif.store(fresh, path, dottedSuffix)
proc storeBifStable*(b: var IcBuilder; path: string; dottedSuffix: string) =
## Content-stable `bif` write — the binary analogue of `writeFileStable`'s
## `onlyIfChanged`: only replace `path` when the encoded bytes differ, so an
## unchanged sidecar keeps its mtime and nifmake prunes the dependent rebuild
## cascade. Used for the iface/impl cookies + dep sidecars whose byte-stability
## gates incremental builds. (bif encoding is deterministic for a given buffer
## under fresh pools, so equal content ⇒ equal bytes.)
var fresh = freshModuleCopy(b)
let tmp = path & ".tmp"
bif.store(fresh, tmp, dottedSuffix)
let newBytes = readFile(tmp)
let oldBytes =
try: readFile(path)
except CatchableError: ""
if newBytes == oldBytes:
removeFile(tmp)
else:
moveFile(tmp, path)
# --- subtree splicing (shared pool, so a raw subtree copy is exact) ----------
proc addAll*(dest: var IcBuilder; src: var IcBuilder) =
## Append every top-level subtree of `src` into `dest` — the nifcore analogue
## of the old `dest.add wholeBuffer` splice.
var c = src.buf.beginRead()
while c.hasMore:
addSubtree(dest.buf, c)
skip c
proc addStmtsBody*(dest: var IcBuilder; src: var IcBuilder) =
## Append the BODY of a `(stmts . . <body> )` builder into `dest`, dropping the
## wrapper tag and its two leading dot slots (flags/type) — the nifcore
## analogue of the old `for i in 3 ..< content.len-1: dest.add content[i]`.
var c = src.buf.beginRead() # at (stmts
c.into:
skip c # flags dot
skip c # type dot
while c.hasMore:
addSubtree(dest.buf, c)
skip c
# --- cookie input: a line-info-free logical token list of the module ---------
# The cookie hashers (ast2nif) need a flat, ParRi-bearing, index-addressable
# view of the serialized module. nifcore has no ParRi kind and variable-width
# tokens, so we flatten the buffer here (in the clean nifcore world) into a
# neutral `CookieTok` list — no nifcore type crosses into ast2nif.
type
CookieKind* = enum
ckParLe, ckParRi, ckSym, ckSymDef, ckIdent, ckStr, ckInt, ckUInt, ckFloat, ckChar, ckDot
CookieTok* = object
kind*: CookieKind
tag*: string # ckParLe
name*: string # ckSym / ckSymDef
sym*: uint32 # ckSym / ckSymDef id (identity key)
str*: string # ckIdent / ckStr
ival*: int64
uval*: uint64
fval*: float64
cval*: uint32
proc flattenGo(c: var Cursor; b: TokenBuf; acc: var seq[CookieTok]) =
while c.hasMore:
case c.kind
of TagLit:
acc.add CookieTok(kind: ckParLe, tag: b.tags.tagName(c.cursorTagId))
c.into:
flattenGo(c, b, acc)
acc.add CookieTok(kind: ckParRi)
of Symbol:
acc.add CookieTok(kind: ckSym, name: symName(c, b.pool), sym: uint32(symId(c, b.pool)))
skip c
of SymbolDef:
acc.add CookieTok(kind: ckSymDef, name: symName(c, b.pool), sym: uint32(symId(c, b.pool)))
skip c
of Ident:
acc.add CookieTok(kind: ckIdent, str: strVal(c, b.pool)); skip c
of StrLit:
acc.add CookieTok(kind: ckStr, str: strVal(c, b.pool)); skip c
of IntLit:
acc.add CookieTok(kind: ckInt, ival: intVal(c)); skip c
of UIntLit:
acc.add CookieTok(kind: ckUInt, uval: uintVal(c)); skip c
of FloatLit:
acc.add CookieTok(kind: ckFloat, fval: floatVal(c)); skip c
of CharLit:
acc.add CookieTok(kind: ckChar, cval: uint32(ord(charLit(c)))); skip c
of DotToken:
acc.add CookieTok(kind: ckDot); skip c
else:
skip c # LineInfoLit / ExtendedSuffix ride on heads, never standalone
proc flattenForCookie*(b: var IcBuilder): seq[CookieTok] =
## Flatten the nifcore module buffer to the cookie hashers' flat token list.
result = newSeqOfCap[CookieTok](b.buf.len)
var cur = b.buf.beginRead()
flattenGo(cur, b.buf, result)
proc collectBifStrLits*(path: string): seq[string] =
## Read a small `(tag "s" "s" …)` bif sidecar (`semdeps`/`edges`) and return every
## string literal it holds, in order — the binary analogue of the old nifstreams
## scan that collected `StrLit`s. Keeps nifcore types out of `deps.nim`, which
## only needs the recorded string list.
##
## Uses `loadFromFile` (a full read into owned memory) rather than the mmap-backed
## `bif.load`, then CLOSES the handle. `bif.load` intentionally leaves the mapping
## resident for the process lifetime; for the `nim ic` driver that reads these
## sidecars while `nim m` children rewrite them, a lingering read mapping is a
## Windows sharing violation: the child's `open(path, fmWrite)` fails with
## `IOError: cannot open`. These sidecars are tiny, so the zero-copy mmap buys
## nothing here anyway.
result = @[]
var f = open(path, fmRead)
var m = bif.loadFromFile(f)
close(f)
var c = m.buf.beginRead()
while c.hasMore:
if c.kind == StrLit: result.add strVal(c)
inc c
proc writeSemDeps*(config: ConfigRef; thisModule: int32; importPaths: seq[string]) =
## Stage 1 spike: the nifcore port of `ast2nif.writeSemDeps`. Serializes the
## module's resolved direct imports as `(semdeps "path" ...)`. Byte-identical
## to the old writer (verified), so `nim ic` build graphs are unaffected.
let selfSuffix = modname(thisModule, config)
var paths = importPaths
sort paths
var dest = newIcBuilder(4 + 2*paths.len)
dest.openTag "semdeps"
for p in paths:
dest.addStrLit p
dest.closeTag()
let path = toGeneratedFile(config, AbsoluteFile(selfSuffix), ".s.deps.bif").string
storeBifStable(dest, path, "." & extractModuleSuffix(path))

View File

@@ -11,11 +11,8 @@
# An identifier is a shared immutable string that can be compared by its
# id. This module is essential for the compiler's performance.
import wordrecg
import std/hashes
when defined(nimPreviewSlimSystem):
import std/assertions
import
hashes, wordrecg
type
PIdent* = ref TIdent

Some files were not shown because too many files have changed in this diff Show More