Compare commits

...

446 Commits

Author SHA1 Message Date
Daan Leijen
599f97eadf merge from dev-trace 2022-04-20 17:36:00 -07:00
Daan Leijen
36814189ff merge from dev-slice 2022-04-20 17:35:30 -07:00
Daan Leijen
cacb387a61 Merge branch 'dev' into dev-slice 2022-04-20 17:34:56 -07:00
Daan Leijen
343a747f2f merge from dev 2022-04-20 17:34:47 -07:00
Daan Leijen
31473c8e37 merge from dev 2022-04-20 17:34:06 -07:00
Daan Leijen
7e8dc812a9 merge from dev-trace 2022-04-20 17:29:58 -07:00
Daan Leijen
9605e2317a merge from dev-slice 2022-04-20 17:27:45 -07:00
Daan Leijen
24ef590532 Call SymInitialize at process start as it is single threaded 2022-04-20 17:25:24 -07:00
Daan Leijen
f2a2eb4ad0 merge from dev 2022-04-20 17:16:25 -07:00
Daan Leijen
83d84b8703 increase max alignment limit to 16MiB (issue #576) 2022-04-20 09:54:24 -07:00
Daan Leijen
c48c275a8f Merge branch 'dev' into dev-slice 2022-04-19 20:16:59 -07:00
Daan Leijen
9459513813 Merge branch 'dev' into dev-slice 2022-04-19 19:59:51 -07:00
Daan Leijen
a90b98a144 update to vs2022 2022-04-19 19:57:57 -07:00
Daan Leijen
eb5deccea8 Merge branch 'dev' into dev-slice 2022-04-19 19:57:00 -07:00
Daan Leijen
413141ae29 merge from dev 2022-04-19 19:55:03 -07:00
Daan Leijen
487b401b26 Merge branch 'dev' into dev-slice 2022-04-19 18:43:32 -07:00
Daan Leijen
a949c9321c update vs2022 solution 2022-04-19 11:17:53 -07:00
Daan Leijen
5c64f51503 Merge branch 'dev' into dev-slice 2022-04-19 11:07:41 -07:00
Daan Leijen
44695c33d9 Merge branch 'dev-slice' into dev-slice-trace 2022-04-14 17:00:00 -07:00
Daan Leijen
b9e44dfa78 Merge branch 'dev-trace' of https://github.com/microsoft/mimalloc into dev-trace 2022-04-14 16:59:43 -07:00
Daan Leijen
b2fe83fa2c Merge branch 'dev' into dev-trace 2022-04-14 16:59:36 -07:00
Daan Leijen
f2712f4a8f Merge branch 'dev' into dev-slice 2022-04-14 16:54:04 -07:00
Daan Leijen
f819dbb4e4 fix trailing comma 2022-04-14 16:12:02 -07:00
Daan Leijen
12a3a4c51a merge from dev 2022-04-14 16:11:29 -07:00
Daan Leijen
f9416ce71c merge from dev 2022-04-14 16:09:12 -07:00
Daan Leijen
b86bbbff00 merge from dev 2022-04-14 16:07:57 -07:00
Daan
ab39eadbaa Merge branch 'dev-trace' into dev-slice-trace 2022-04-14 13:51:51 -07:00
Daan
6d852d9ff5 fix trace on windows if symbol initialization fails 2022-04-14 13:51:32 -07:00
Daan
cc4274cb5e Merge branch 'dev-slice' into dev-slice-trace 2022-04-14 11:34:26 -07:00
Daan
dd929659ab fix wrong assertion 2022-04-14 11:28:40 -07:00
Daan Leijen
9aec70ee02 fix assertion 2022-04-10 13:27:43 -07:00
Daan Leijen
b4ca31bcd0 merge from dev-trace 2022-04-10 13:22:35 -07:00
Daan Leijen
0e22d46b11 merge from dev-slice 2022-04-10 13:21:49 -07:00
Daan Leijen
1270eec6c0 merge from dev 2022-04-10 13:19:26 -07:00
Daan Leijen
4b95e8ea1d Merge branch 'dev' into dev-slice 2022-04-10 13:02:38 -07:00
Daan Leijen
a3ced56b18 merge from dev 2022-04-09 16:22:10 -07:00
Daan Leijen
0a1d0bbcbf Merge branch 'dev' into dev-slice 2022-04-09 15:59:11 -07:00
Daan Leijen
7e492f4420 merge from dev 2022-04-09 15:07:07 -07:00
Daan Leijen
157c9b0966 Merge branch 'dev' into dev-slice 2022-04-09 14:08:36 -07:00
Daan Leijen
12c91999ac Merge branch 'dev' into dev-slice 2022-04-09 13:48:30 -07:00
Daan Leijen
774d12f12e merge from dev 2022-04-09 13:26:38 -07:00
Daan Leijen
ea0f5b8779 use new MI_ATOMIC_VAR_INIT 2022-04-08 14:52:15 -07:00
Daan Leijen
2d8f13fb93 Merge branch 'dev-slice' of https://github.com/microsoft/mimalloc into dev-slice 2022-04-08 14:46:33 -07:00
Daan
862f07bc76 Merge branch 'dev' into dev-slice 2022-04-08 14:44:35 -07:00
Daan
131b62283b Merge branch 'dev' into dev-slice 2022-04-08 14:10:08 -07:00
Daan
e856b71cc7 Merge branch 'dev-trace' into dev-slice-trace 2022-04-08 13:50:16 -07:00
Daan
4ab716d229 Merge branch 'dev-slice' into dev-slice-trace 2022-04-08 13:50:10 -07:00
Daan
0dafa1e0a0 Merge branch 'dev' into dev-trace 2022-04-08 13:48:57 -07:00
daan
984e946f76 Merge branch 'dev' into dev-slice 2022-04-07 20:26:43 -07:00
daan
196ceeac59 merge from dev 2022-04-07 20:18:52 -07:00
Daan Leijen
6431176f4e Merge branch 'dev' into dev-slice 2022-04-07 19:09:39 -07:00
Daan
2a4a3dfa23 Merge branch 'dev' into dev-slice 2022-04-07 16:12:30 -07:00
Daan
0075a81879 Merge branch 'dev' into dev-slice 2022-04-07 13:02:53 -07:00
Daan Leijen
88f9c94101 Merge branch 'dev' into dev-slice 2022-04-07 12:35:34 -07:00
Daan Leijen
0cda8b02d5 fix stats for large objects that were off by the block size padding 2022-04-07 11:08:54 -07:00
Daan Leijen
332346b685 remove unneeded MI_HUGE_OBJ_SIZE_MAX 2022-04-07 10:38:31 -07:00
Daan Leijen
1e4f0c58dc Merge branch 'dev' into dev-slice 2022-04-07 10:22:08 -07:00
Daan Leijen
8509ce2096 Merge branch 'dev' into dev-slice 2022-04-07 10:19:33 -07:00
Daan Leijen
9f6cbc50ee use heap_stat_decrease when possible 2022-04-07 09:48:08 -07:00
Daan Leijen
5a90a2a9a1 merge from dev 2022-04-04 17:40:29 -07:00
Daan Leijen
1f089e99f6 Merge branch 'dev' into dev-slice 2022-04-02 11:42:02 -07:00
Daan
18c1891708 Merge branch 'dev' into dev-slice 2022-02-22 16:46:06 -08:00
Daan
10da1af59b merge from dev 2022-02-14 16:48:30 -08:00
Daan
b89b4fd18a fix v2.0.5 version 2022-02-14 16:44:33 -08:00
Daan
19edc880da merge from dev 2022-02-14 16:36:03 -08:00
Daan
a1310047c4 Merge branch 'dev-slice' of https://github.com/microsoft/mimalloc into dev-slice 2022-02-14 16:16:30 -08:00
Daan
e91ee4c384 Merge branch 'dev' into dev-slice 2022-02-14 16:16:03 -08:00
daan
26695dc582 Merge branch 'dev' into dev-slice 2022-02-14 15:45:10 -08:00
daan
221f96ac2c Merge branch 'dev' into dev-slice 2022-02-10 11:59:28 -08:00
daan
96008c55d0 fix ubsan warning on huge allocations (issue #543) 2022-02-10 11:57:30 -08:00
daan
352d8be237 Merge branch 'dev' into dev-slice 2022-02-10 11:46:43 -08:00
daan
e87b1d2298 add extra huge allocation test 2022-02-10 11:08:13 -08:00
daan
8fa9600e98 Merge branch 'dev-slice' into dev-slice-trace 2022-02-05 17:55:10 -08:00
daan
04ab0f639e Merge branch 'dev' into dev-trace 2022-02-05 17:54:39 -08:00
daan
f2b6938d64 fix start adjustment for the commit mask 2022-02-05 17:36:14 -08:00
daan
47f8caad4d improve commit chunk alignment 2022-02-05 17:23:28 -08:00
daan
8ec83f6945 increase min commit to 2 mib 2022-02-05 11:21:47 -08:00
daan
e11100a137 add minimal commit size for increased efficiency (decommit fine grained, commit coarse grained) 2022-02-05 10:57:15 -08:00
daan
9ca363d0e4 merge from dev 2022-02-04 16:13:12 -08:00
daan
0e2df71829 increase minimal commit size to 8*slice-size and add decommit_extend_delay as option 2022-02-04 16:11:38 -08:00
daan
fb418831df only delay eager commit after the first thread 2022-02-04 16:10:51 -08:00
Daan
c56f3e0bb3 Merge branch 'dev-trace' into dev-slice-trace 2022-02-03 19:16:37 -08:00
Daan
5fcb2615a8 add overflow test 2022-02-03 19:16:23 -08:00
Daan
80c86e7cba Merge branch 'dev-trace' into dev-slice-trace 2022-02-03 19:15:30 -08:00
Daan
95a8196490 fix compilation on macOS 2022-02-03 19:15:10 -08:00
Daan
cc83f82c55 Merge branch 'dev-trace' into dev-slice-trace 2022-02-03 16:01:05 -08:00
Daan
6ea598f1c4 merge from dev-slice 2022-02-03 16:00:59 -08:00
Daan
0dd5a2e0a5 Merge branch 'dev' into dev-slice 2022-02-03 15:59:49 -08:00
Daan
636931874f merge from dev 2022-02-03 15:59:32 -08:00
Daan
0e1beb0018 check for decommit allowed before purging the segment cache 2022-02-03 15:51:27 -08:00
Daan
cbcee4dce4 merge from dev 2022-02-03 15:49:27 -08:00
daan
741d39a004 fix over aggressive decommit of abandoned pages 2022-02-03 14:26:56 -08:00
Daan
b365623b13 merge from dev 2022-02-02 19:21:15 -08:00
Daan
4e65b5018f clean up options 2022-02-02 19:01:41 -08:00
Daan
932f866105 decommit segment cache on force collect 2022-02-02 18:28:02 -08:00
Daan
ccfe005731 decommit in abandoned pages on mi_collect 2022-02-02 17:08:05 -08:00
Daan
bd2ac3c92e collect segment cache on mi_collect 2022-02-02 16:17:21 -08:00
Daan
05aa7648bb merge from dev 2022-02-02 16:17:06 -08:00
Daan
bfea3e2fc2 Merge branch 'dev' into dev-slice 2022-01-22 13:12:40 -08:00
Daan
3b93554ce6 merge from dev 2022-01-22 13:09:18 -08:00
Daan Leijen
1718fc811e merge from dev 2022-01-16 12:41:23 -08:00
Daan Leijen
44e7eb12d6 Merge branch 'dev' into dev-slice 2022-01-12 17:00:04 -08:00
Daan Leijen
df01e463b6 Merge branch 'dev' into dev-slice 2022-01-11 15:42:36 -08:00
Daan Leijen
e115a655dc Merge branch 'dev' into dev-slice 2022-01-10 16:57:23 -08:00
daan
d4ae01bcd0 merge from dev-trace 2022-01-10 16:44:52 -08:00
daan
f690711539 fix debug freed bytes fill non-owning thread free 2022-01-10 16:44:12 -08:00
daan
5b0a07d6b6 merge from dev-slice 2022-01-10 16:24:58 -08:00
daan
99c113d573 merge from dev-trace 2022-01-10 16:23:57 -08:00
daan
ad47cab97c merge from dev 2022-01-10 16:22:34 -08:00
daan
a74c05c6c0 Merge branch 'dev' into dev-slice 2022-01-10 16:21:15 -08:00
Daan Leijen
a763b6310d merge from dev 2022-01-10 15:40:22 -08:00
daan
ae1c06d940 merge from dev 2022-01-10 15:29:49 -08:00
Daan
f317225a70 ignore reset_decommits option in the 2.x / dev-slice version 2022-01-10 12:10:18 -08:00
Daan
0842004b61 Merge branch 'dev' into dev-slice 2022-01-10 12:04:47 -08:00
Daan
9f9c77e6b6 Merge branch 'dev' into dev-slice 2022-01-10 11:41:12 -08:00
daan
3eac4a912c Merge branch 'dev' into dev-slice 2022-01-01 16:24:41 -08:00
Daan Leijen
c4b934c2ae Merge branch 'dev' into dev-slice 2021-12-20 12:34:13 -08:00
Daan
43ed851006 Merge branch 'dev' into dev-slice 2021-12-19 15:37:57 -08:00
daan
af854570cd Merge branch 'dev' into dev-slice 2021-12-18 16:36:58 -08:00
daan
72a33c37ef merge from dev 2021-12-18 11:34:02 -08:00
Daan Leijen
78e2e580f8 Merge branch 'dev' into dev-slice 2021-12-18 11:11:54 -08:00
daan
3d35147aba Merge branch 'dev' into dev-slice 2021-12-17 13:25:44 -08:00
daan
abbff9c030 merge from dev (MI_ALIGNED_MAX) 2021-12-17 13:23:24 -08:00
daan
e6400bcc27 Merge branch 'dev' into dev-slice 2021-12-16 15:36:03 -08:00
daan
7f7ae1a749 Merge branch 'dev' into dev-slice 2021-12-16 15:35:04 -08:00
daan
8d9336dfa6 Merge branch 'dev' into dev-slice 2021-12-16 15:11:58 -08:00
daan
bc79abb7d5 Merge branch 'dev-slice' of https://github.com/microsoft/mimalloc into dev-slice 2021-12-15 19:29:12 -08:00
daan
2af1db7f3a Merge branch 'dev' into dev-slice 2021-12-15 19:29:04 -08:00
Daan
f21841e926 Merge branch 'dev' into dev-slice 2021-12-15 16:05:20 -08:00
daan
edb3e70df5 Merge branch 'dev-trace' into dev-slice-trace 2021-12-15 08:49:31 -08:00
daan
d8c24ec583 merge from dev-slice 2021-12-15 08:49:27 -08:00
daan
83f8451e62 merge from dev 2021-12-15 08:48:17 -08:00
daan
60ca554413 Merge branch 'dev' into dev-slice 2021-12-15 08:47:00 -08:00
daan
b91198826c merge from dev 2021-12-15 08:37:06 -08:00
daan
f24a0b1019 merge from dev 2021-12-15 08:35:15 -08:00
Daan
d15f5fae64 merge from dev 2021-12-14 18:29:58 -08:00
Daan Leijen
ad6d182170 Merge branch 'dev-trace' into dev-slice-trace 2021-12-11 17:10:56 -08:00
Daan Leijen
9b8bb5b6d6 fix prototype 2021-12-11 17:10:00 -08:00
Daan
66c88eec06 Merge branch 'dev-trace' into dev-slice-trace 2021-12-10 17:31:37 -08:00
Daan
7a7a774257 better backtrace 2021-12-10 17:31:24 -08:00
Daan
72ca23d14f faster backtrace; show predecessor blocks on block overflow 2021-12-10 17:22:02 -08:00
Daan
5739714b8d faster backtrace; show predecessor blocks on block overflow 2021-12-10 17:16:37 -08:00
Daan
e3de22a067 merge dev-trace 2021-12-10 12:09:14 -08:00
Daan
b6e2b6e975 enable traces on apple 2021-12-10 12:08:41 -08:00
Daan Leijen
d86fc87fa1 merge from dev-trace 2021-12-10 11:54:41 -08:00
Daan Leijen
65b2cebcef improve stacktrace on linux 2021-12-10 11:42:54 -08:00
Daan Leijen
28893a6c1b improve padding and error messages 2021-12-10 11:09:19 -08:00
Daan Leijen
9c1945b1a4 fix test 2021-12-09 17:28:12 -08:00
Daan Leijen
be9ee3a4aa Merge branch 'dev-trace' into dev-slice-trace 2021-12-09 17:26:32 -08:00
Daan Leijen
ea75c745e1 add tracing on linux and freebsd 2021-12-09 17:26:13 -08:00
Daan Leijen
fdfdafebf5 merge from dev-slice 2021-12-09 16:24:17 -08:00
Daan Leijen
775c10da3b Merge branch 'dev' into dev-slice 2021-12-09 16:18:43 -08:00
Daan Leijen
e125c04081 Merge branch 'dev' into dev-trace 2021-12-09 16:18:28 -08:00
Daan Leijen
a7bb572176 test with dynamic override 2021-12-09 16:14:50 -08:00
Daan Leijen
36cf82dc71 merge from dev-trace 2021-12-09 16:06:24 -08:00
Daan Leijen
8c04558af8 improve padding extra 2021-12-09 16:04:22 -08:00
Daan Leijen
f18caf67d7 change padding extra to 128 2021-12-09 14:29:18 -08:00
Daan Leijen
7b69dc92a9 merge from dev-trace 2021-12-09 14:22:13 -08:00
Daan Leijen
a84df3795a add support for extra padding and backtraces 2021-12-09 14:19:41 -08:00
daan
67e8df6a5c Merge branch 'dev' into dev-slice 2021-11-24 12:55:07 -08:00
daan
5f6246b2cb merge from dev 2021-11-23 19:05:19 -08:00
daan
03526e5535 Merge branch 'dev' into dev-slice 2021-11-23 18:39:13 -08:00
daan
ef6ea7e718 merge from dev 2021-11-23 18:00:12 -08:00
daan
6efd78c5e0 remove O3 flag 2021-11-15 10:52:39 -08:00
daan
4a456ba054 Merge branch 'dev' into dev-slice 2021-11-15 10:52:17 -08:00
daan
9f1b25e07d Merge branch 'dev' into dev-slice 2021-11-15 10:10:58 -08:00
daan
f412df7a2b make segment size smaller on 32-bit 2021-11-14 16:52:10 -08:00
daan
5a1c3c8a4a Merge branch 'dev' into dev-slice 2021-11-14 16:48:04 -08:00
daan
7cd5b22ca7 Merge branch 'dev' into dev-slice 2021-11-14 16:41:32 -08:00
Daan
18fc788201 merge from dev 2021-11-14 15:39:05 -08:00
Daan
5a05fd446a fix compilation on macos 2021-11-14 14:38:24 -08:00
daan
e4f0a95a56 Merge branch 'dev-slice-cmask' into dev-slice 2021-11-14 14:35:46 -08:00
daan
c520901069 fix slice count comment 2021-11-14 12:10:07 -08:00
daan
70547b5f16 fix slice count 2021-11-14 12:09:20 -08:00
daan
32170897dd make decommit size equal to slice size 2021-11-14 11:45:28 -08:00
daan
c46a6f66c6 Merge branch 'dev-slice' into dev-slice-cmask 2021-11-14 11:26:47 -08:00
daan
f039774cf5 adjust decommit delay 2021-11-14 11:26:30 -08:00
daan
a4ea2205ba merge from dev 2021-11-14 11:25:51 -08:00
daan
511a8996f3 increase commit mask blocks to 2xslice size 2021-11-13 20:12:03 -08:00
daan
7e22e5ce6e Merge branch 'dev-slice' into dev-slice-cmask 2021-11-13 19:44:05 -08:00
daan
fa66db840d increase decommit hysterisis 2021-11-13 19:43:52 -08:00
daan
fb5645a30d increase decommit hysterisis 2021-11-13 19:41:41 -08:00
daan
7a3cf405d3 Merge branch 'dev-slice' into dev-slice-cmask 2021-11-13 17:12:42 -08:00
daan
cdfbd6d08f decommit when abandoned segments move to the visited list 2021-11-13 17:12:21 -08:00
daan
12bfd18ba7 fix commit mask for huge segments 2021-11-13 16:15:03 -08:00
daan
627892852c merge from dev-slice 2021-11-13 15:53:57 -08:00
daan
b72065f04b move commit mask functions to segment.c 2021-11-13 15:50:26 -08:00
daan
4f9d5f7dc6 merge from dev-slice 2021-11-13 15:33:03 -08:00
daan
f1ce9228a1 use size_t for bitmask 2021-11-13 15:29:57 -08:00
daan
88e6b52b88 fix types to size_t 2021-11-13 15:25:51 -08:00
daan
f9597ba7cb merge from dev-slice 2021-11-13 15:18:56 -08:00
daan
83ffd92b2b merge from dev 2021-11-13 15:16:23 -08:00
daan
721486c82b merge from dev 2021-11-13 14:52:11 -08:00
daan
0a86b45a91 Merge branch 'dev' into dev-slice 2021-11-13 14:13:12 -08:00
daan
9afc253726 add comments, renaming 2021-11-13 14:03:16 -08:00
daan
8bf16746e9 Merge branch 'dev-slice' into dev-slice-cmask 2021-11-13 13:31:00 -08:00
daan
97a1584bb5 Merge branch 'dev' into dev-slice 2021-11-13 13:30:17 -08:00
daan
5dc4ec48fe lower default reset delay 2021-11-12 21:15:11 -08:00
daan
53e2260ca0 merge 2021-11-12 20:14:03 -08:00
daan
a2b08664f7 merge from dev 2021-11-12 20:00:43 -08:00
daan
f58b4d923a comment 2021-11-12 19:58:49 -08:00
daan
9322123a97 start eager commit delay at N>2 2021-11-12 19:32:57 -08:00
daan
6ace2fe4e0 Merge branch 'dev-slice' into dev-slice-cmask 2021-11-12 19:04:35 -08:00
daan
5c08f75d69 merge from dev 2021-11-12 19:04:18 -08:00
daan
9e6ace6bcc Merge branch 'dev-slice' into dev-slice-cmask 2021-11-12 18:46:38 -08:00
daan
e5a3f3d7c4 merge from dev 2021-11-12 18:46:16 -08:00
daan
335d554438 merge from dev-slice 2021-11-12 18:38:14 -08:00
daan
c6b82a4b37 wip: change decommit expiration 2021-11-12 17:31:21 -08:00
daan
b1aff903f5 fix decommit bug 2021-11-11 17:45:41 -08:00
daan
998c2de633 merge from dev-slice 2021-11-10 16:49:43 -08:00
daan
ba6b4bf622 merge from dev 2021-11-10 16:33:42 -08:00
daan
49d64dbc95 save decommit_mask for segments in the segment cache 2021-11-10 16:30:21 -08:00
daan
8cc7d0c019 increase segment size to 64MiB 2021-11-10 16:29:53 -08:00
daan
49c75a3157 wip: increase commit mask resolution 2021-11-09 20:19:31 -08:00
Daan
865baa3bb1 Merge branch 'dev-slice' of https://github.com/microsoft/mimalloc into dev-slice 2021-11-06 14:19:32 -07:00
Daan
a4e7ff8608 Merge branch 'dev' into dev-slice 2021-11-06 14:19:26 -07:00
daan
c17878d1a7 Merge branch 'dev' into dev-slice 2021-11-04 19:10:31 -07:00
Daan
464cba833e Merge branch 'dev' into dev-slice 2021-11-04 18:55:34 -07:00
Daan
f3ffa663f1 merge from dev 2021-11-02 22:42:25 -07:00
Daan Leijen
9c3e6a25f6 Merge branch 'dev' into dev-slice 2021-10-27 19:06:42 -07:00
Daan
db223e4adb merge from dev 2021-10-27 18:09:16 -07:00
Daan
7756e1b5fe fix assertion 2021-10-27 10:45:19 -07:00
Daan
e477633779 fix assertion 2021-10-27 10:41:14 -07:00
Daan
1568dbb9e4 fix mi_is_valid_pointer bit index search (related to issue #478) 2021-10-27 10:35:16 -07:00
Daan
54b65a556c fix mi_cfree assertion failure for NULL pointer, issue #478 2021-10-27 10:15:12 -07:00
Daan
6d9e79a498 merge from dev 2021-10-27 10:11:51 -07:00
Daan
725fe2ac7d Merge branch 'dev' into dev-slice 2021-10-21 16:17:31 -07:00
Daan
de00de96fd merge with dev 2021-10-20 09:56:03 -07:00
Daan
b47d0802d1 Merge branch 'dev' into dev-slice 2021-10-20 09:36:08 -07:00
Daan Leijen
d4397ce16c merge from dev 2021-10-19 15:13:53 -07:00
Daan
3bf7b4313c add comment 2021-10-19 14:03:48 -07:00
Daan
2583ab73dc remove region.c which belongs in dev only 2021-10-19 13:57:36 -07:00
Daan
35b928b08f use MADV_DONTNEED instead of mmap fixedfor simplification and possibly better performance on Linux 2021-10-19 13:18:54 -07:00
Daan
aeb73b0cd4 merge from dev 2021-10-19 12:55:10 -07:00
Daan
f945dbb390 add space after _Atomic to prevent errors on msvc without /TP (see PR #452) 2021-10-19 10:18:44 -07:00
Daan
a4078df9d5 Merge branch 'dev' into dev-slice 2021-10-19 10:17:53 -07:00
Daan Leijen
8d2a21eb78 Merge branch 'dev' into dev-slice 2021-10-18 16:46:18 -07:00
Daan Leijen
54659aec9e merge from dev 2021-10-18 16:28:08 -07:00
Daan Leijen
e6b58052da add start offset to pages to reduce cache/page effects 2021-10-02 11:13:00 -07:00
Daan Leijen
262022c1d1 fix segment map for 32-bit systems (including wasm) 2021-10-01 15:10:11 -07:00
Daan Leijen
d7ac4478a8 Merge branch 'dev' into dev-slice 2021-10-01 15:05:41 -07:00
Daan Leijen
080cffe064 Merge branch 'dev' into dev-slice 2021-06-17 20:20:28 -07:00
Daan Leijen
b3b0fb5832 merge from dev 2021-06-17 20:05:40 -07:00
Daan Leijen
5869c85749 merge from dev 2021-06-17 19:18:57 -07:00
Daan Leijen
e592360d4d revert relative includes 2021-06-07 17:53:03 -07:00
Daan Leijen
6ba9387bf8 Merge branch 'dev' into dev-slice 2021-06-07 17:51:42 -07:00
Daan Leijen
d7eb0bab75 Merge branch 'dev' into dev-slice 2021-06-07 17:01:00 -07:00
Daan
8af2511e66
Merge pull request #412 from diorszeng/dev-slice
fix typo
2021-06-07 16:55:03 -07:00
Daan Leijen
9974b0ee23 Merge branch 'dev' into dev-slice 2021-06-07 16:51:14 -07:00
Daan Leijen
069b3276df merge from dev 2021-06-06 20:33:55 -07:00
Daan Leijen
7b595bd957 Merge branch 'dev' into dev-slice 2021-06-06 20:31:53 -07:00
diorszeng
f4e1563c4c
Merge pull request #1 from diorszeng/diorszeng-patch-1
Update mimalloc-types.h
2021-05-31 15:03:01 +08:00
diorszeng
0611058974
Update mimalloc-types.h
fix typo
2021-05-31 15:02:17 +08:00
Daan Leijen
54b2c3525c merge with dev 2021-05-21 15:36:30 -07:00
Daan Leijen
10ce8839fa merge from dev 2021-04-28 13:23:46 -07:00
Daan Leijen
34ba03951e merge from dev 2021-04-06 11:01:06 -07:00
Daan Leijen
c6f5092287 merge from dev 2021-04-06 11:00:28 -07:00
Daan Leijen
dc6bce256d bump version to v2.0.1 2021-04-06 10:58:12 -07:00
Daan Leijen
4e643b6d31 merge from dev 2021-02-24 15:53:26 -08:00
Daan Leijen
ad96d220f4 merge from dev 2021-02-24 15:17:35 -08:00
Daan Leijen
47050371a1 fix issue #363 and disable assertion for now 2021-02-22 15:05:47 -08:00
Daan Leijen
8f69e7095d Merge branch 'dev' into dev-slice 2021-02-22 14:28:22 -08:00
Daan Leijen
1b22da3c28 Merge branch 'dev' into dev-slice 2021-02-02 10:46:43 -08:00
Daan Leijen
ba84aa2783 Merge branch 'dev' into dev-slice 2021-02-01 15:47:37 -08:00
Daan Leijen
2762784364 Merge branch 'dev' into dev-slice 2021-01-31 14:12:51 -08:00
Daan Leijen
bd56782f26 bump version to 2.0.0 2021-01-31 14:02:06 -08:00
Daan Leijen
8bcc60edd9 Merge branch 'dev' into dev-slice 2021-01-31 13:57:35 -08:00
Daan Leijen
2aebb37fb0 merge from dev 2021-01-30 17:15:24 -08:00
Daan Leijen
36b7a3cb03 merge from dev 2021-01-30 16:37:38 -08:00
Daan Leijen
b93cba3b05 merge from dev 2021-01-29 16:53:52 -08:00
Daan Leijen
3bade4b1bd fix accounting of abandoned pages 2021-01-29 15:42:52 -08:00
Daan Leijen
542f577c81 Merge branch 'dev' into dev-slice 2021-01-29 15:23:36 -08:00
Daan Leijen
72559c5c49 merge from dev 2021-01-29 13:08:00 -08:00
Daan Leijen
f02643d9f2 Merge branch 'dev' into dev-slice 2021-01-29 12:33:52 -08:00
Daan Leijen
1e9a5c2d78 Merge branch 'dev' into dev-slice 2021-01-28 17:37:13 -08:00
Daan Leijen
e314699ee0 add debug view of arenas 2021-01-28 17:32:42 -08:00
Daan Leijen
217871cb45 fix search_idx start in managed arenas 2021-01-22 11:24:25 -08:00
Daan Leijen
da79629308 Merge branch 'dev' into dev-slice 2020-12-17 14:11:50 -08:00
Daan Leijen
3c70317393 merge from dev 2020-12-15 16:07:23 -08:00
Daan Leijen
b803095b83 merge from dev 2020-12-10 13:17:56 -08:00
unknown
ad05829195 remove shadow warning when building in static mode 2020-11-06 17:49:10 -08:00
daan
10aca1cfb9 merge from dev 2020-10-15 20:01:38 -07:00
daan
7e96634da4 merge from dev 2020-10-11 13:38:12 -07:00
daan
e1c38eef76 use allow_decommit option for both the segment cache and pages 2020-09-24 17:20:39 -07:00
daan
b149099bf3 use relaxed load for last search position in an arena 2020-09-24 16:55:00 -07:00
daan
2822e5c1f3 Merge branch 'dev' into dev-slice 2020-09-24 16:33:22 -07:00
daan
b59abce8ea Merge branch 'dev' into dev-slice 2020-09-24 10:16:54 -07:00
daan
680c9266bf Merge branch 'dev' into dev-slice 2020-09-24 09:29:43 -07:00
daan
165b64f553 Merge branch 'dev-exp' into dev-slice 2020-09-24 09:11:58 -07:00
daan
fbaa70e1eb increase default test load to 25% to increase azure pipeline test load 2020-09-14 11:01:17 -07:00
Daan Leijen
b1cc3d550c fix valid pointer detection on mac 2020-09-14 10:55:44 -07:00
daan
fba65c440c merge from dev-exp 2020-09-14 09:05:16 -07:00
daan
01307a25ff fix assertion 2020-09-11 11:00:19 -07:00
daan
1d946146cc fix all_committed 2020-09-11 10:40:22 -07:00
daan
fa01875eb2 merge from dev (with is_pinned/is_large separation) 2020-09-08 17:54:58 -07:00
daan
d87933a3b5 update comments 2020-09-08 15:50:37 -07:00
daan
037285ac09 refactor segment cache and map in a separate source file 2020-09-08 13:27:34 -07:00
daan
161f9a7751 refactor arena allocation 2020-09-08 11:12:44 -07:00
daan
97629cefaa tune performance options with longer reset delay 2020-09-08 11:12:23 -07:00
daan
a948724340 merge from dev (bitmap split) 2020-09-08 10:33:30 -07:00
daan
6b013d5f38 test for arena count early; skip test in bitmap_mask_ for perf 2020-09-07 22:55:36 -07:00
daan
371532ff02 merge from dev 2020-09-07 21:43:05 -07:00
daan
313008ecaa ensure page->retire_expire is always 1 2020-09-07 15:20:59 -07:00
daan
953bbde089 fix is_in_same_page check 2020-09-06 15:09:51 -07:00
daan
3826132240 use dynamic initial commit 2020-09-06 14:51:20 -07:00
daan
b7046934e5 Merge branch 'dev' into dev-slice 2020-09-06 13:53:30 -07:00
daan
45300ac43d merge from dev 2020-09-06 13:24:47 -07:00
daan
8c838a949f Merge branch 'dev' into dev-slice 2020-09-06 13:22:44 -07:00
daan
8e0d846b40 consistent commit order 2020-09-06 12:19:05 -07:00
daan
828613a694 use MADV_DONTNEED for commit/decommit on macOS 2020-09-06 12:06:56 -07:00
daan
5ae01fe4d9 experiment with commit strategy on macOS 2020-09-06 09:39:16 -07:00
daan
e2ae9f3125 fix pipeline script for macOS 2020-09-06 09:14:32 -07:00
daan
c821e5144a Merge branch 'dev' into dev-slice 2020-09-06 09:13:14 -07:00
daan
803e6f9e46 merge from dev 2020-09-06 09:09:55 -07:00
daan
e703bfc319 build windows pipeline in parallel 2020-09-06 09:02:15 -07:00
daan
a372847ccf verbose ctest on Linux pipeline 2020-09-06 08:57:56 -07:00
daan
4f7bc7d98e Merge branch 'dev' into dev-slice 2020-09-06 08:50:44 -07:00
daan
500a9208d5 Merge branch 'dev' into dev-slice 2020-09-05 22:55:52 -07:00
daan
f9ca7cd05a use proper file descriptor in mmap for decommit 2020-09-05 22:16:58 -07:00
daan
f7dc4847f2 keep commit_mask live in the cache for better reuse 2020-09-05 21:58:32 -07:00
daan
63a9f45ba6 add initial mi_commit_mask abstraction 2020-09-05 19:39:10 -07:00
daan
36da7e91c5 Merge branch 'dev' into dev-slice 2020-09-05 18:17:22 -07:00
daan
c1778acb93 Merge branch 'dev' into dev-slice 2020-09-05 15:03:54 -07:00
daan
8834fe02da again try to fix verbose ctest on mac pipeline 2020-09-05 12:31:28 -07:00
daan
7a08ca4dc6 again try to fix verbose ctest on mac pipeline 2020-09-05 12:30:13 -07:00
daan
5fe80671a2 again try to fix verbose ctest on mac pipeline 2020-09-05 12:26:47 -07:00
daan
0c5f03559d fix verbose ctest on mac pipeline 2020-09-05 12:22:52 -07:00
daan
a0370f347c more verbose ctest on mac pipeline 2020-09-05 12:20:21 -07:00
daan
85a8c138fc enable verbose ctest on mac pipeline 2020-09-05 12:18:09 -07:00
daan
3d708aa7e1 fix warning in g++ 2020-09-05 12:16:46 -07:00
daan
5f31f5c2b9 Merge branch 'dev' into dev-slice 2020-09-05 12:05:00 -07:00
daan
13bbb78907 add dev-slice to azure test pipeline 2020-09-05 11:48:23 -07:00
daan
a8539f6772 Merge branch 'dev' into dev-slice 2020-09-05 11:47:48 -07:00
daan
4df01218e2 fix msvc compilation with new atomics 2020-09-05 10:03:37 -07:00
daan
644e453709 Merge branch 'dev' into dev-slice 2020-09-05 09:37:38 -07:00
daan
dc858f6d29 fix c++ compilation with new atomics for dev-slice 2020-09-05 09:23:22 -07:00
daan
7c2b79bef0 Merge branch 'dev' into dev-slice 2020-09-05 09:17:59 -07:00
daan
97f56b1e08 merge from dev 2020-09-04 14:21:33 -07:00
daan
b22401deb3 layout 2020-09-03 20:31:11 -07:00
daan
f6109765d8 update whitespace and comments 2020-09-03 15:04:40 -07:00
Daan Leijen
7058e501cb use atomic ops for the expire field; passes TSAN now 2020-09-03 13:53:56 -07:00
daan
228b5f6e9d use atomic load for segment map 2020-09-03 12:19:04 -07:00
daan
03071dec0f merge from dev-atomic with new atomic interface 2020-09-03 12:13:09 -07:00
daan
c1a834e886 add checks for when memory commit fails to return NULL 2020-08-28 10:40:46 -07:00
daan
e4ddc75069 set delayed decommit mask more precisely to only decommit currently committed blocks 2020-08-28 08:46:51 -07:00
daan
2cffc3b851 merge from dev 2020-08-27 22:43:57 -07:00
daan
38c264ccdf merge from dev 2020-06-17 19:25:03 -07:00
daan
cb05ef9f2c merge from dev 2020-05-19 10:43:46 -07:00
daan
82e29f47b3 weaken assertion, #245 2020-05-18 18:51:06 -07:00
daan
53aa46890a merge from dev 2020-05-05 10:54:59 -07:00
daan
74ea69b784 increase default arena reset delay (behaves better on 36+ core systems) 2020-05-03 16:33:29 -07:00
daan
fd0891f224 merge from dev 2020-05-03 11:44:55 -07:00
daan
cce998a835 fix assertion for huge blocks 2020-05-03 11:42:49 -07:00
daan
30799bce73 fix assertion for huge segments 2020-05-03 11:42:38 -07:00
daan
28f4f1ce04 nice cache initialization 2020-05-03 10:45:46 -07:00
daan
f8dc2a3130 Merge branch 'dev' into dev-arena 2020-05-02 22:23:11 -07:00
daan
e5b72cdfe7 reduce segment size and increase cache 2020-05-02 22:22:35 -07:00
daan
ea92fb2fe4 lower arena reset delay 2020-05-02 21:40:14 -07:00
Daan
a4b7baf6fd
Update readme with descriptions of secure and debug mode 2020-05-02 18:08:31 -07:00
daan
69158f2c76 roll back again to new arena cache: previous perf regression was caused due to accidentally making secure mode default 2020-05-02 12:04:36 -07:00
daan
18d697a1e6 roll back to old arena cache as it seems to do better on AMD 2020-05-02 11:57:33 -07:00
daan
66e5484c1c fix assertions for huge pages in secure mode 2020-05-02 11:23:25 -07:00
daan
b8846f7a27 fix unprotect of guard pages 2020-05-02 10:51:10 -07:00
daan
37b43e4cea improved arena cache 2020-05-02 10:37:33 -07:00
daan
1b158d8e80 set max retire size to MAX_MEDIUM_OBJ_SIZE 2020-05-02 10:37:07 -07:00
daan
84e1f7c92e merge from dev 2020-05-02 00:23:22 -07:00
daan
dd18852946 reduce page retire cycles 2020-05-02 00:13:40 -07:00
daan
01ad553978 set default reset delay to 250ms 2020-05-02 00:13:03 -07:00
daan
79da2728c4 reduce cache 2020-05-02 00:12:45 -07:00
daan
8bfd5ec865 improve arena cache to avoid full scans 2020-05-01 23:00:17 -07:00
daan
dcb3574cf0 fix assertions for huge segment free 2020-05-01 21:14:41 -07:00
daan
dad3be3c64 update comments 2020-04-30 17:21:36 -07:00
daan
c609248f0e do delayed decommit if not reclaiming abandoned blocks 2020-04-30 13:30:19 -07:00
daan
0d25493c39 segment size to 16MiB to improve perf on mstress and rptest 2020-04-28 16:50:03 -07:00
daan
f86519bca6 make lazy commit default; add commit check on segment allocation 2020-04-28 16:46:00 -07:00
daan
1b0de9b4cf merge from dev 2020-04-28 16:22:38 -07:00
daan
1f396e64a0 merge from dev 2020-03-16 16:41:21 -07:00
daan
d221a4b904 merge from dev-exp 2020-01-27 23:36:53 -08:00
daan
54e206a0a1 increase retire page size 2020-01-27 22:41:24 -08:00
daan
09b98e0f7f merge from dev-exp; resolve conflicts 2020-01-27 22:14:10 -08:00
daan
b50bec463d merge from dev-exp; better abandoned reclamation 2020-01-27 22:12:23 -08:00
daan
a46d20a681 merge with new atomic macros 2020-01-22 20:53:44 -08:00
daan
e226ebcc97 Merge branch 'dev' into dev-arena 2020-01-22 20:39:33 -08:00
Daan Leijen
caa5e51a67 align size of page_t, increase slices per segment 2020-01-22 11:29:32 -08:00
daan
0028272cf4 small fixes, reduced segment size, fix merge conflicts 2020-01-20 22:33:29 -08:00
daan
394a7a92ab merge from dev 2020-01-20 19:06:08 -08:00
daan
88b141cf1f ensure proper padding for the page structure 2020-01-13 20:48:37 -08:00
daan
94bff89347 ensure page reset flag is always reset 2020-01-13 20:48:18 -08:00
daan
2808c9f4c8 default to non-eager commit 2020-01-13 18:01:52 -08:00
daan
4a27ea1643 merge from dev 2020-01-13 18:01:34 -08:00
daan
b5fbdb7180 merge from dev 2019-11-25 11:16:39 -08:00
daan
41af533a34 define commit unit in terms of segment size 2019-11-24 19:17:56 -08:00
daan
ec0005b919 more fine grained commit tracking per MiB 2019-11-24 19:09:15 -08:00
daan
128cdd1dfb merge from dev 2019-11-24 18:51:09 -08:00
daan
f45ec667a3 Merge branch 'dev' into dev-arena 2019-11-22 09:29:00 -08:00
daan
7da00c1220 wip: full decommit delay, for arena cache as well 2019-11-21 20:57:32 -08:00
daan
321e18777e wip: delayed decommit on segments 2019-11-21 19:53:43 -08:00
daan
1066be1594 merge from dev-exp 2019-11-21 17:03:30 -08:00
daan
aa61e6381d Merge branch 'dev-arena' of https://github.com/microsoft/mimalloc into dev-arena 2019-11-10 10:47:55 -08:00
Daan Leijen
b04206a9d3 add os cache to arena 2019-11-10 10:10:10 -08:00
Daan Leijen
268698b9ef fix vs2019 project 2019-11-10 08:00:51 -08:00
Daan Leijen
fed0068dac merge from dev-exp; bitmap based arena 2019-11-10 07:56:40 -08:00
daan
c3ef23e4f6 Merge branch 'dev-exp' into dev-arena 2019-11-04 09:40:25 -08:00
daan
62df2e2df9 merge from dev-exp 2019-11-04 08:56:42 -08:00
daan
2b005addd3 merge from dev-exp 2019-11-03 13:37:03 -08:00
daan
1a6d150687 merge from dev-exp 2019-11-03 12:21:22 -08:00
daan
5bdcda30b0 merge from dev-exp 2019-11-02 20:12:22 -07:00
daan
e0b8ec7f54 merge with dev-exp 2019-11-02 11:56:19 -07:00
daan
ae092e05a2 Merge branch 'dev-exp' into dev-arena 2019-11-02 10:39:27 -07:00
daan
b0182b2376 Merge branch 'dev-exp' into dev-arena 2019-11-02 10:30:33 -07:00
daan
08c4726043 merge from dev-exp 2019-11-01 22:04:20 -07:00
daan
6916e6590f Merge branch 'dev-exp' into dev-arena 2019-11-01 20:30:32 -07:00
daan
4be5b14869 merge from dev-exp 2019-11-01 20:19:32 -07:00
daan
6b26f0cb17 merge from dev-exp (numa support) 2019-11-01 20:08:56 -07:00
daan
eed42445e8 merge from dev-exp 2019-10-31 20:40:02 -07:00
daan
a74e072a9a set test-stress scale to 20 again 2019-10-31 19:00:26 -07:00
daan
62984c0a24 merge from dev-exp 2019-10-31 18:44:48 -07:00
daan
bbca1cd8d9 allow decommit by default 2019-10-31 12:42:23 -07:00
daan
6695f8ae91 add allow_decommit option 2019-10-31 10:59:50 -07:00
daan
ed4f60fc7e respect large pages for arena allocation 2019-10-31 10:59:40 -07:00
daan
28cb19148c fixed memory arena allocation for huge pages 2019-10-31 09:10:58 -07:00
daan
f7d2c45af3 initial experiment with fixed memory arena and sliced segments 2019-10-31 00:40:41 -07:00
daan
c7ec30ae25 fix secure mode 2019-10-30 15:36:13 -07:00
daan
93ae3e26b1 Merge branch 'dev' into dev-win-exp 2019-10-30 15:22:56 -07:00
daan
b73beede34 merge from dev 2019-10-30 15:19:34 -07:00
daan
9d4f57abf3 merge from dev-win 2019-10-28 12:33:01 -07:00
daan
4b15e2ed97 merge from dev 2019-10-17 18:24:35 -07:00
daan
25dca38ef9 merge from dev-win 2019-08-26 12:47:16 -07:00
daan
b0e38d5697 merge from dev-win 2019-08-25 13:12:57 -07:00
daan
80a36f1d7c reduce page retire words to 32 2019-08-24 17:02:32 -07:00
daan
19f473e49a merge from dev; free huge objects directly and keep them abandoned 2019-08-24 16:16:09 -07:00
daan
6f5492cef8 enable initial lazy commit and optional decommit to reduce commit charge with many threads 2019-08-24 15:00:55 -07:00
daan
612b2cc9b7 clean up segment slice handling 2019-08-24 12:20:32 -07:00
daan
cce38bc147 more conservative setting to avoid internal fragmentation 2019-08-24 07:32:23 -07:00
daan
082f012a91 merge from dev-win 2019-08-23 21:56:28 -07:00
daan
3e01eac105 Merge branch 'dev-win' into dev-win-exp 2019-08-21 14:38:58 -07:00
daan
5c912f16d4 merge from remote 2019-08-21 11:35:09 -07:00
daan
a3c4b1c95b merge from dev-win 2019-08-21 11:18:05 -07:00
daan
cd52d0a6d9 merge dev-win 2019-08-20 17:31:46 -07:00
Daan Leijen
fb12f298ca merge from dev-win, fix small object size check 2019-08-16 19:14:08 -07:00
Daan Leijen
91497e8d2d whitespace and warning fix 2019-08-16 17:49:49 -07:00
daan
a0b4ac2f66 new segment allocation; good results with Qas service 2019-08-15 23:19:52 -07:00
daan
f2ba95bc64 first working version of new segment allocation 2019-08-15 22:00:42 -07:00
daan
6ee248b012 wip: fixing bugs in new segment allocation 2019-08-15 14:40:15 -07:00
daan
f2bafbc57f wip: new segment allocation 2019-08-15 11:49:56 -07:00
daan
bbd81bbbd1 wip: new segment allocation with flexible large objects 2019-08-15 00:46:45 -07:00
38 changed files with 2539 additions and 1240 deletions

View File

@ -21,6 +21,7 @@ option(MI_BUILD_OBJECT "Build object library" ON)
option(MI_BUILD_TESTS "Build test executables" ON)
option(MI_DEBUG_TSAN "Build with thread sanitizer (needs clang)" OFF)
option(MI_DEBUG_UBSAN "Build with undefined-behavior sanitizer (needs clang++)" OFF)
option(MI_DEBUG_TRACE "Store allocation stack trace in each heap block to debug heap block overflows or corruption" OFF)
option(MI_SKIP_COLLECT_ON_EXIT, "Skip collecting memory on program exit" OFF)
# deprecated options
@ -28,6 +29,9 @@ option(MI_CHECK_FULL "Use full internal invariant checking in DEBUG mode
option(MI_INSTALL_TOPLEVEL "Install directly into $CMAKE_INSTALL_PREFIX instead of PREFIX/lib/mimalloc-version (deprecated)" OFF)
option(MI_USE_LIBATOMIC "Explicitly link with -latomic (on older systems) (deprecated and detected automatically)" OFF)
set(MI_PADDING_EXTRA 0 CACHE STRING "Specify extra bytes for padding in each heap block (to debug heap block overflows)")
include(GNUInstallDirs)
include("cmake/mimalloc-config-version.cmake")
@ -37,7 +41,7 @@ set(mi_sources
src/os.c
src/bitmap.c
src/arena.c
src/region.c
src/segment-cache.c
src/segment.c
src/page.c
src/alloc.c
@ -128,6 +132,17 @@ if(MI_DEBUG_FULL)
list(APPEND mi_defines MI_DEBUG=3) # full invariant checking
endif()
if(MI_DEBUG_TRACE)
message(STATUS "Enable allocation trace in each heap block (MI_DEBUG_TRACE=ON)")
list(APPEND mi_defines MI_DEBUG_TRACE=1)
set(CMAKE_ENABLE_EXPORTS TRUE)
endif()
if(MI_PADDING_EXTRA)
message(STATUS "Add extra debug padding to each heap block (MI_PADDING_EXTRA=${MI_PADDING_EXTRA})")
list(APPEND mi_defines MI_PADDING_EXTRA=${MI_PADDING_EXTRA})
endif()
if(NOT MI_PADDING)
message(STATUS "Disable padding of heap blocks in debug mode (MI_PADDING=OFF)")
list(APPEND mi_defines MI_PADDING=0)
@ -230,6 +245,12 @@ else()
if (MI_LIBATOMIC OR MI_USE_LIBATOMIC)
list(APPEND mi_libraries atomic)
endif()
if(MI_DEBUG_TRACE)
find_library(MI_LIBEXECINFO execinfo)
if (MI_LIBEXECINFO)
list(APPEND mi_libraries ${MI_LIBEXECINFO})
endif()
endif()
endif()
# -----------------------------------------------------------------------------

View File

@ -134,9 +134,16 @@ jobs:
cmakeArgs: .. $(cmakeExtraArgs)
- script: make -j$(sysctl -n hw.ncpu) -C $(BuildType)
displayName: Make
# - script: MIMALLOC_VERBOSE=1 ./mimalloc-test-api
# workingDirectory: $(BuildType)
# displayName: TestAPI
# - script: MIMALLOC_VERBOSE=1 ./mimalloc-test-stress
# workingDirectory: $(BuildType)
# displayName: TestStress
- script: ctest --verbose --timeout 120
workingDirectory: $(BuildType)
displayName: CTest
# - upload: $(Build.SourcesDirectory)/$(BuildType)
# artifact: mimalloc-macos-$(BuildType)

View File

@ -1,5 +1,5 @@
set(mi_version_major 1)
set(mi_version_minor 7)
set(mi_version_major 2)
set(mi_version_minor 0)
set(mi_version_patch 6)
set(mi_version ${mi_version_major}.${mi_version_minor})

View File

@ -1080,7 +1080,7 @@ or via environment variables.
- `MIMALLOC_PAGE_RESET=0`: by default, mimalloc will reset (or purge) OS pages when not in use to signal to the OS
that the underlying physical memory can be reused. This can reduce memory fragmentation in long running (server)
programs. By setting it to `0` no such page resets will be done which can improve performance for programs that are not long
running. As an alternative, the `MIMALLOC_RESET_DELAY=`<msecs> can be set higher (100ms by default) to make the page
running. As an alternative, the `MIMALLOC_DECOMMIT_DELAY=`<msecs> can be set higher (100ms by default) to make the page
reset occur less frequently instead of turning it off completely.
- `MIMALLOC_LARGE_OS_PAGES=1`: use large OS pages (2MiB) when available; for some workloads this can significantly
improve performance. Use `MIMALLOC_VERBOSE` to check if the large OS pages are enabled -- usually one needs

View File

@ -236,7 +236,6 @@
<ClCompile Include="..\..\src\bitmap.c" />
<ClCompile Include="..\..\src\heap.c" />
<ClCompile Include="..\..\src\init.c" />
<ClCompile Include="..\..\src\region.c" />
<ClCompile Include="..\..\src\options.c" />
<ClCompile Include="..\..\src\os.c" />
<ClCompile Include="..\..\src\page-queue.c">
@ -247,6 +246,7 @@
</ClCompile>
<ClCompile Include="..\..\src\page.c" />
<ClCompile Include="..\..\src\random.c" />
<ClCompile Include="..\..\src\segment-cache.c" />
<ClCompile Include="..\..\src\segment.c" />
<ClCompile Include="..\..\src\stats.c" />
</ItemGroup>

View File

@ -64,9 +64,6 @@
<ClCompile Include="..\..\src\init.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\region.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\alloc-override.c">
<Filter>Source Files</Filter>
</ClCompile>
@ -82,5 +79,8 @@
<ClCompile Include="..\..\src\bitmap.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\segment-cache.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -233,7 +233,6 @@
<ClCompile Include="..\..\src\bitmap.c" />
<ClCompile Include="..\..\src\heap.c" />
<ClCompile Include="..\..\src\init.c" />
<ClCompile Include="..\..\src\region.c" />
<ClCompile Include="..\..\src\options.c" />
<ClCompile Include="..\..\src\page-queue.c">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@ -243,6 +242,7 @@
</ClCompile>
<ClCompile Include="..\..\src\page.c" />
<ClCompile Include="..\..\src\random.c" />
<ClCompile Include="..\..\src\segment-cache.c" />
<ClCompile Include="..\..\src\segment.c" />
<ClCompile Include="..\..\src\os.c" />
<ClCompile Include="..\..\src\stats.c" />

View File

@ -47,10 +47,10 @@
<ClCompile Include="..\..\src\init.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\region.c">
<ClCompile Include="..\..\src\alloc-posix.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\alloc-posix.c">
<ClCompile Include="..\..\src\arena.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\arena.c">
@ -62,6 +62,9 @@
<ClCompile Include="..\..\src\bitmap.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\segment-cache.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(ProjectDir)..\..\include\mimalloc.h">

View File

@ -115,6 +115,8 @@
<ExceptionHandling>Sync</ExceptionHandling>
<CompileAs>Default</CompileAs>
<SupportJustMyCode>false</SupportJustMyCode>
<PreprocessorDefinitions>
</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>

View File

@ -236,7 +236,6 @@
<ClCompile Include="..\..\src\bitmap.c" />
<ClCompile Include="..\..\src\heap.c" />
<ClCompile Include="..\..\src\init.c" />
<ClCompile Include="..\..\src\region.c" />
<ClCompile Include="..\..\src\options.c" />
<ClCompile Include="..\..\src\os.c" />
<ClCompile Include="..\..\src\page-queue.c">
@ -247,6 +246,7 @@
</ClCompile>
<ClCompile Include="..\..\src\page.c" />
<ClCompile Include="..\..\src\random.c" />
<ClCompile Include="..\..\src\segment-cache.c" />
<ClCompile Include="..\..\src\segment.c" />
<ClCompile Include="..\..\src\stats.c" />
</ItemGroup>

View File

@ -19,9 +19,6 @@
<ClCompile Include="..\..\src\init.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\region.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\os.c">
<Filter>Source Files</Filter>
</ClCompile>
@ -49,6 +46,9 @@
<ClCompile Include="..\..\src\bitmap.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\segment-cache.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(ProjectDir)..\..\include\mimalloc.h">
@ -70,7 +70,7 @@
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\bitmap.h">
<Filter>Source Files</Filter>
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>

View File

@ -116,7 +116,7 @@
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>
<PreprocessorDefinitions>MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions>
<PreprocessorDefinitions>MI_DEBUG_TRACE=1;MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions>
<CompileAs>CompileAsCpp</CompileAs>
<SupportJustMyCode>false</SupportJustMyCode>
<LanguageStandard>Default</LanguageStandard>
@ -225,7 +225,6 @@
</ClCompile>
<ClCompile Include="..\..\src\heap.c" />
<ClCompile Include="..\..\src\init.c" />
<ClCompile Include="..\..\src\region.c" />
<ClCompile Include="..\..\src\options.c" />
<ClCompile Include="..\..\src\page-queue.c">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@ -235,6 +234,7 @@
</ClCompile>
<ClCompile Include="..\..\src\page.c" />
<ClCompile Include="..\..\src\random.c" />
<ClCompile Include="..\..\src\segment-cache.c" />
<ClCompile Include="..\..\src\segment.c" />
<ClCompile Include="..\..\src\os.c" />
<ClCompile Include="..\..\src\stats.c" />

View File

@ -22,9 +22,6 @@
<ClCompile Include="..\..\src\init.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\region.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\options.c">
<Filter>Source Files</Filter>
</ClCompile>
@ -52,6 +49,9 @@
<ClCompile Include="..\..\src\bitmap.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\segment-cache.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(ProjectDir)..\..\include\mimalloc.h">

View File

@ -236,7 +236,6 @@
<ClCompile Include="..\..\src\bitmap.c" />
<ClCompile Include="..\..\src\heap.c" />
<ClCompile Include="..\..\src\init.c" />
<ClCompile Include="..\..\src\region.c" />
<ClCompile Include="..\..\src\options.c" />
<ClCompile Include="..\..\src\os.c" />
<ClCompile Include="..\..\src\page-queue.c">
@ -247,6 +246,7 @@
</ClCompile>
<ClCompile Include="..\..\src\page.c" />
<ClCompile Include="..\..\src\random.c" />
<ClCompile Include="..\..\src\segment-cache.c" />
<ClCompile Include="..\..\src\segment.c" />
<ClCompile Include="..\..\src\stats.c" />
</ItemGroup>

View File

@ -116,7 +116,7 @@
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>
<PreprocessorDefinitions>MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions>
<PreprocessorDefinitions>MI_DEBUG_TRACE=1;MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions>
<CompileAs>CompileAsCpp</CompileAs>
<SupportJustMyCode>false</SupportJustMyCode>
<LanguageStandard>stdcpp20</LanguageStandard>
@ -225,7 +225,6 @@
</ClCompile>
<ClCompile Include="..\..\src\heap.c" />
<ClCompile Include="..\..\src\init.c" />
<ClCompile Include="..\..\src\region.c" />
<ClCompile Include="..\..\src\options.c" />
<ClCompile Include="..\..\src\page-queue.c">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@ -235,6 +234,7 @@
</ClCompile>
<ClCompile Include="..\..\src\page.c" />
<ClCompile Include="..\..\src\random.c" />
<ClCompile Include="..\..\src\segment-cache.c" />
<ClCompile Include="..\..\src\segment.c" />
<ClCompile Include="..\..\src\os.c" />
<ClCompile Include="..\..\src\stats.c" />

View File

@ -16,6 +16,7 @@ terms of the MIT license. A copy of the license can be found in the file
#define mi_trace_message(...)
#endif
#define MI_CACHE_LINE 64
#if defined(_MSC_VER)
#pragma warning(disable:4127) // suppress constant conditional warning (due to MI_SECURE paths)
@ -57,6 +58,11 @@ void _mi_trace_message(const char* fmt, ...);
void _mi_options_init(void);
void _mi_error_message(int err, const char* fmt, ...);
#if MI_DEBUG_TRACE > 0
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip);
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail);
#endif
// random.c
void _mi_random_init(mi_random_ctx_t* ctx);
void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* new_ctx);
@ -77,31 +83,40 @@ size_t _mi_os_page_size(void);
void _mi_os_init(void); // called from process init
void* _mi_os_alloc(size_t size, mi_stats_t* stats); // to allocate thread local data
void _mi_os_free(void* p, size_t size, mi_stats_t* stats); // to free thread local data
bool _mi_os_protect(void* addr, size_t size);
bool _mi_os_unprotect(void* addr, size_t size);
bool _mi_os_commit(void* addr, size_t size, bool* is_zero, mi_stats_t* stats);
bool _mi_os_decommit(void* p, size_t size, mi_stats_t* stats);
bool _mi_os_reset(void* p, size_t size, mi_stats_t* stats);
// bool _mi_os_unreset(void* p, size_t size, bool* is_zero, mi_stats_t* stats);
size_t _mi_os_good_alloc_size(size_t size);
bool _mi_os_has_overcommit(void);
// memory.c
void* _mi_mem_alloc_aligned(size_t size, size_t alignment, bool* commit, bool* large, bool* is_pinned, bool* is_zero, size_t* id, mi_os_tld_t* tld);
void _mi_mem_free(void* p, size_t size, size_t id, bool fully_committed, bool any_reset, mi_os_tld_t* tld);
// arena.c
void* _mi_arena_alloc_aligned(size_t size, size_t alignment, bool* commit, bool* large, bool* is_pinned, bool* is_zero, size_t* memid, mi_os_tld_t* tld);
void* _mi_arena_alloc(size_t size, bool* commit, bool* large, bool* is_pinned, bool* is_zero, size_t* memid, mi_os_tld_t* tld);
void _mi_arena_free(void* p, size_t size, size_t memid, bool is_committed, mi_os_tld_t* tld);
bool _mi_mem_reset(void* p, size_t size, mi_os_tld_t* tld);
bool _mi_mem_unreset(void* p, size_t size, bool* is_zero, mi_os_tld_t* tld);
bool _mi_mem_commit(void* p, size_t size, bool* is_zero, mi_os_tld_t* tld);
bool _mi_mem_protect(void* addr, size_t size);
bool _mi_mem_unprotect(void* addr, size_t size);
void _mi_mem_collect(mi_os_tld_t* tld);
// "segment-cache.c"
void* _mi_segment_cache_pop(size_t size, mi_commit_mask_t* commit_mask, mi_commit_mask_t* decommit_mask, bool* large, bool* is_pinned, bool* is_zero, size_t* memid, mi_os_tld_t* tld);
bool _mi_segment_cache_push(void* start, size_t size, size_t memid, const mi_commit_mask_t* commit_mask, const mi_commit_mask_t* decommit_mask, bool is_large, bool is_pinned, mi_os_tld_t* tld);
void _mi_segment_cache_collect(bool force, mi_os_tld_t* tld);
void _mi_segment_map_allocated_at(const mi_segment_t* segment);
void _mi_segment_map_freed_at(const mi_segment_t* segment);
// "segment.c"
mi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_wsize, mi_segments_tld_t* tld, mi_os_tld_t* os_tld);
void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld);
void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld);
uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t block_size, size_t* page_size, size_t* pre_size); // page start for any page
bool _mi_segment_try_reclaim_abandoned( mi_heap_t* heap, bool try_all, mi_segments_tld_t* tld);
void _mi_segment_thread_collect(mi_segments_tld_t* tld);
void _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block);
void _mi_segment_thread_collect(mi_segments_tld_t* tld);
uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size); // page start for any page
void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld);
void _mi_abandoned_await_readers(void);
void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld);
@ -143,6 +158,7 @@ void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_att
void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero) mi_attr_noexcept;
mi_block_t* _mi_page_ptr_unalign(const mi_segment_t* segment, const mi_page_t* page, const void* p);
bool _mi_free_delayed_block(mi_block_t* block);
void _mi_show_block_trace_with_predecessor(const mi_page_t* page, const mi_block_t* block, const char* msg);
#if MI_DEBUG>1
bool _mi_page_is_valid(mi_page_t* page);
@ -235,6 +251,18 @@ static inline uintptr_t _mi_align_up(uintptr_t sz, size_t alignment) {
}
}
// Align downwards
static inline uintptr_t _mi_align_down(uintptr_t sz, size_t alignment) {
mi_assert_internal(alignment != 0);
uintptr_t mask = alignment - 1;
if ((alignment & mask) == 0) { // power of two?
return (sz & ~mask);
}
else {
return ((sz / alignment) * alignment);
}
}
// Divide upwards: `s <= _mi_divide_up(s,d)*d < s+d`.
static inline uintptr_t _mi_divide_up(uintptr_t size, size_t divider) {
mi_assert_internal(divider != 0);
@ -249,6 +277,7 @@ static inline bool mi_mem_is_zero(void* p, size_t size) {
return true;
}
// Align a byte size to a size in _machine words_,
// i.e. byte size == `wsize*sizeof(void*)`.
static inline size_t _mi_wsize_from_size(size_t size) {
@ -405,7 +434,7 @@ static inline uintptr_t _mi_ptr_cookie(const void* p) {
----------------------------------------------------------- */
static inline mi_page_t* _mi_heap_get_free_small_page(mi_heap_t* heap, size_t size) {
mi_assert_internal(size <= (MI_SMALL_SIZE_MAX + MI_PADDING_SIZE));
mi_assert_internal(size <= (MI_SMALL_SIZE_MAX + MI_PADDING_MINSIZE));
const size_t idx = _mi_wsize_from_size(size);
mi_assert_internal(idx < MI_PAGES_DIRECT);
return heap->pages_free_direct[idx];
@ -422,35 +451,47 @@ static inline mi_segment_t* _mi_ptr_segment(const void* p) {
return (mi_segment_t*)((uintptr_t)p & ~MI_SEGMENT_MASK);
}
static inline mi_page_t* mi_slice_to_page(mi_slice_t* s) {
mi_assert_internal(s->slice_offset== 0 && s->slice_count > 0);
return (mi_page_t*)(s);
}
static inline mi_slice_t* mi_page_to_slice(mi_page_t* p) {
mi_assert_internal(p->slice_offset== 0 && p->slice_count > 0);
return (mi_slice_t*)(p);
}
// Segment belonging to a page
static inline mi_segment_t* _mi_page_segment(const mi_page_t* page) {
mi_segment_t* segment = _mi_ptr_segment(page);
mi_assert_internal(segment == NULL || page == &segment->pages[page->segment_idx]);
mi_segment_t* segment = _mi_ptr_segment(page);
mi_assert_internal(segment == NULL || ((mi_slice_t*)page >= segment->slices && (mi_slice_t*)page < segment->slices + segment->slice_entries));
return segment;
}
// used internally
static inline size_t _mi_segment_page_idx_of(const mi_segment_t* segment, const void* p) {
// if (segment->page_size > MI_SEGMENT_SIZE) return &segment->pages[0]; // huge pages
ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment;
mi_assert_internal(diff >= 0 && (size_t)diff < MI_SEGMENT_SIZE);
size_t idx = (size_t)diff >> segment->page_shift;
mi_assert_internal(idx < segment->capacity);
mi_assert_internal(segment->page_kind <= MI_PAGE_MEDIUM || idx == 0);
return idx;
static inline mi_slice_t* mi_slice_first(const mi_slice_t* slice) {
mi_slice_t* start = (mi_slice_t*)((uint8_t*)slice - slice->slice_offset);
mi_assert_internal(start >= _mi_ptr_segment(slice)->slices);
mi_assert_internal(start->slice_offset == 0);
mi_assert_internal(start + start->slice_count > slice);
return start;
}
// Get the page containing the pointer
static inline mi_page_t* _mi_segment_page_of(const mi_segment_t* segment, const void* p) {
size_t idx = _mi_segment_page_idx_of(segment, p);
return &((mi_segment_t*)segment)->pages[idx];
ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment;
mi_assert_internal(diff >= 0 && diff < (ptrdiff_t)MI_SEGMENT_SIZE);
size_t idx = (size_t)diff >> MI_SEGMENT_SLICE_SHIFT;
mi_assert_internal(idx < segment->slice_entries);
mi_slice_t* slice0 = (mi_slice_t*)&segment->slices[idx];
mi_slice_t* slice = mi_slice_first(slice0); // adjust to the block that holds the page data
mi_assert_internal(slice->slice_offset == 0);
mi_assert_internal(slice >= segment->slices && slice < segment->slices + segment->slice_entries);
return mi_slice_to_page(slice);
}
// Quick page start for initialized pages
static inline uint8_t* _mi_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size) {
const size_t bsize = page->xblock_size;
mi_assert_internal(bsize > 0 && (bsize%sizeof(void*)) == 0);
return _mi_segment_page_start(segment, page, bsize, page_size, NULL);
return _mi_segment_page_start(segment, page, page_size);
}
// Get the page containing the pointer
@ -467,7 +508,7 @@ static inline size_t mi_page_block_size(const mi_page_t* page) {
}
else {
size_t psize;
_mi_segment_page_start(_mi_page_segment(page), page, bsize, &psize, NULL);
_mi_segment_page_start(_mi_page_segment(page), page, &psize);
return psize;
}
}
@ -478,6 +519,14 @@ static inline size_t mi_page_usable_block_size(const mi_page_t* page) {
return mi_page_block_size(page) - MI_PADDING_SIZE;
}
// size of a segment
static inline size_t mi_segment_size(mi_segment_t* segment) {
return segment->segment_slices * MI_SEGMENT_SLICE_SIZE;
}
static inline uint8_t* mi_segment_end(mi_segment_t* segment) {
return (uint8_t*)segment + mi_segment_size(segment);
}
// Thread free access
static inline mi_block_t* mi_page_thread_free(const mi_page_t* page) {
@ -597,12 +646,13 @@ static inline bool mi_is_in_same_segment(const void* p, const void* q) {
}
static inline bool mi_is_in_same_page(const void* p, const void* q) {
mi_segment_t* segmentp = _mi_ptr_segment(p);
mi_segment_t* segmentq = _mi_ptr_segment(q);
if (segmentp != segmentq) return false;
size_t idxp = _mi_segment_page_idx_of(segmentp, p);
size_t idxq = _mi_segment_page_idx_of(segmentq, q);
return (idxp == idxq);
mi_segment_t* segment = _mi_ptr_segment(p);
if (_mi_ptr_segment(q) != segment) return false;
// assume q may be invalid // return (_mi_segment_page_of(segment, p) == _mi_segment_page_of(segment, q));
mi_page_t* page = _mi_segment_page_of(segment, p);
size_t psize;
uint8_t* start = _mi_segment_page_start(segment, page, &psize);
return (start <= (uint8_t*)q && (uint8_t*)q < start + psize);
}
static inline uintptr_t mi_rotl(uintptr_t x, uintptr_t shift) {
@ -648,7 +698,8 @@ static inline mi_block_t* mi_block_next(const mi_page_t* page, const mi_block_t*
// check for free list corruption: is `next` at least in the same page?
// TODO: check if `next` is `page->block_size` aligned?
if mi_unlikely(next!=NULL && !mi_is_in_same_page(block, next)) {
_mi_error_message(EFAULT, "corrupted free list entry of size %zub at %p: value 0x%zx\n", mi_page_block_size(page), block, (uintptr_t)next);
_mi_show_block_trace_with_predecessor(page, block, "free block");
_mi_error_message(EFAULT, "corrupted free list entry of size %zu at %p: value 0x%zx\n", mi_page_block_size(page), block, (uintptr_t)next);
next = NULL;
}
return next;
@ -667,6 +718,52 @@ static inline void mi_block_set_next(const mi_page_t* page, mi_block_t* block, c
#endif
}
// -------------------------------------------------------------------
// commit mask
// -------------------------------------------------------------------
static inline void mi_commit_mask_create_empty(mi_commit_mask_t* cm) {
for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
cm->mask[i] = 0;
}
}
static inline void mi_commit_mask_create_full(mi_commit_mask_t* cm) {
for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
cm->mask[i] = ~((size_t)0);
}
}
static inline bool mi_commit_mask_is_empty(const mi_commit_mask_t* cm) {
for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
if (cm->mask[i] != 0) return false;
}
return true;
}
static inline bool mi_commit_mask_is_full(const mi_commit_mask_t* cm) {
for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
if (cm->mask[i] != ~((size_t)0)) return false;
}
return true;
}
// defined in `segment.c`:
size_t _mi_commit_mask_committed_size(const mi_commit_mask_t* cm, size_t total);
size_t _mi_commit_mask_next_run(const mi_commit_mask_t* cm, size_t* idx);
#define mi_commit_mask_foreach(cm,idx,count) \
idx = 0; \
while ((count = _mi_commit_mask_next_run(cm,&idx)) > 0) {
#define mi_commit_mask_foreach_end() \
idx += count; \
}
// -------------------------------------------------------------------
// Fast "random" shuffle
// -------------------------------------------------------------------

View File

@ -60,9 +60,26 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_PADDING 1
#endif
#if !defined(MI_DEBUG_TRACE) // store stack trace at each allocation
#define MI_DEBUG_TRACE (0)
#endif
#if !defined(MI_DEBUG_TRACE_LEN)
#define MI_DEBUG_TRACE_LEN (8) // store up to N frames if tracing is enabled
#endif
#if !defined(MI_PADDING_EXTRA) // use extra padding bytes? (so a stack trace can be preserved or next block corruption prevented)
#if MI_DEBUG_TRACE > 0
#define MI_PADDING_EXTRA (64)
#else
#define MI_PADDING_EXTRA (0)
#endif
#endif
// Encoded free lists allow detection of corrupted free lists
// and can detect buffer overflows, modify after free, and double `free`s.
// (It must be enabled if MI_PADDING is enabled as the same mechanism is used to encode the canary.)
#if (MI_SECURE>=3 || MI_DEBUG>=1 || MI_PADDING > 0)
#define MI_ENCODE_FREELIST 1
#endif
@ -128,44 +145,55 @@ typedef int32_t mi_ssize_t;
// ------------------------------------------------------
// Main tuning parameters for segment and page sizes
// Sizes for 64-bit, divide by two for 32-bit
#define MI_SMALL_PAGE_SHIFT (13 + MI_INTPTR_SHIFT) // 64KiB
#define MI_MEDIUM_PAGE_SHIFT ( 3 + MI_SMALL_PAGE_SHIFT) // 512KiB
#define MI_LARGE_PAGE_SHIFT ( 3 + MI_MEDIUM_PAGE_SHIFT) // 4MiB
#define MI_SEGMENT_SHIFT ( MI_LARGE_PAGE_SHIFT) // 4MiB
// Sizes for 64-bit (usually divide by two for 32-bit)
#define MI_SEGMENT_SLICE_SHIFT (13 + MI_INTPTR_SHIFT) // 64KiB (32KiB on 32-bit)
#if MI_INTPTR_SIZE > 4
#define MI_SEGMENT_SHIFT (10 + MI_SEGMENT_SLICE_SHIFT) // 64MiB
#else
#define MI_SEGMENT_SHIFT ( 7 + MI_SEGMENT_SLICE_SHIFT) // 4MiB on 32-bit
#endif
#define MI_SMALL_PAGE_SHIFT (MI_SEGMENT_SLICE_SHIFT) // 64KiB
#define MI_MEDIUM_PAGE_SHIFT ( 3 + MI_SMALL_PAGE_SHIFT) // 512KiB
// Derived constants
#define MI_SEGMENT_SIZE (MI_ZU(1)<<MI_SEGMENT_SHIFT)
#define MI_SEGMENT_ALIGN MI_SEGMENT_SIZE
#define MI_SEGMENT_MASK (MI_SEGMENT_SIZE - 1)
#define MI_SEGMENT_SLICE_SIZE (MI_ZU(1)<< MI_SEGMENT_SLICE_SHIFT)
#define MI_SLICES_PER_SEGMENT (MI_SEGMENT_SIZE / MI_SEGMENT_SLICE_SIZE) // 1024
#define MI_SMALL_PAGE_SIZE (MI_ZU(1)<<MI_SMALL_PAGE_SHIFT)
#define MI_MEDIUM_PAGE_SIZE (MI_ZU(1)<<MI_MEDIUM_PAGE_SHIFT)
#define MI_LARGE_PAGE_SIZE (MI_ZU(1)<<MI_LARGE_PAGE_SHIFT)
#define MI_SMALL_PAGES_PER_SEGMENT (MI_SEGMENT_SIZE/MI_SMALL_PAGE_SIZE)
#define MI_MEDIUM_PAGES_PER_SEGMENT (MI_SEGMENT_SIZE/MI_MEDIUM_PAGE_SIZE)
#define MI_LARGE_PAGES_PER_SEGMENT (MI_SEGMENT_SIZE/MI_LARGE_PAGE_SIZE)
// The max object size are checked to not waste more than 12.5% internally over the page sizes.
// (Except for large pages since huge objects are allocated in 4MiB chunks)
#define MI_SMALL_OBJ_SIZE_MAX (MI_SMALL_PAGE_SIZE/4) // 16KiB
#define MI_MEDIUM_OBJ_SIZE_MAX (MI_MEDIUM_PAGE_SIZE/4) // 128KiB
#define MI_LARGE_OBJ_SIZE_MAX (MI_LARGE_PAGE_SIZE/2) // 2MiB
#define MI_SMALL_OBJ_SIZE_MAX (MI_SMALL_PAGE_SIZE/4) // 8KiB on 64-bit
#define MI_MEDIUM_OBJ_SIZE_MAX (MI_MEDIUM_PAGE_SIZE/4) // 128KiB on 64-bit
#define MI_MEDIUM_OBJ_WSIZE_MAX (MI_MEDIUM_OBJ_SIZE_MAX/MI_INTPTR_SIZE)
#define MI_LARGE_OBJ_SIZE_MAX (MI_SEGMENT_SIZE/2) // 32MiB on 64-bit
#define MI_LARGE_OBJ_WSIZE_MAX (MI_LARGE_OBJ_SIZE_MAX/MI_INTPTR_SIZE)
#define MI_HUGE_OBJ_SIZE_MAX (2*MI_INTPTR_SIZE*MI_SEGMENT_SIZE) // (must match MI_REGION_MAX_ALLOC_SIZE in memory.c)
// Maximum number of size classes. (spaced exponentially in 12.5% increments)
#define MI_BIN_HUGE (73U)
#if (MI_LARGE_OBJ_WSIZE_MAX >= 655360)
#if (MI_MEDIUM_OBJ_WSIZE_MAX >= 655360)
#error "mimalloc internal: define more bins"
#endif
#if (MI_ALIGNMENT_MAX > MI_SEGMENT_SIZE/2)
#error "mimalloc internal: the max aligned boundary is too large for the segment size"
#if (MI_ALIGNED_MAX % MI_SEGMENT_SLICE_SIZE != 0)
#error "mimalloc internal: the max aligned boundary must be an integral multiple of the segment slice size"
#endif
// Maximum slice offset (15)
#define MI_MAX_SLICE_OFFSET ((MI_ALIGNMENT_MAX / MI_SEGMENT_SLICE_SIZE) - 1)
// Used as a special value to encode block sizes in 32 bits.
#define MI_HUGE_BLOCK_SIZE ((uint32_t)MI_HUGE_OBJ_SIZE_MAX)
#define MI_HUGE_BLOCK_SIZE ((uint32_t)(2*MI_GiB))
// blocks up to this size are always allocated aligned
#define MI_MAX_ALIGN_GUARANTEE (8*MI_MAX_ALIGN_SIZE)
// ------------------------------------------------------
@ -253,18 +281,18 @@ typedef uintptr_t mi_thread_free_t;
// will be freed correctly even if only other threads free blocks.
typedef struct mi_page_s {
// "owned" by the segment
uint8_t segment_idx; // index in the segment `pages` array, `page == &segment->pages[page->segment_idx]`
uint8_t segment_in_use:1; // `true` if the segment allocated this page
uint8_t is_reset:1; // `true` if the page memory was reset
uint8_t is_committed:1; // `true` if the page virtual memory is committed
uint8_t is_zero_init:1; // `true` if the page was zero initialized
uint32_t slice_count; // slices in this page (0 if not a page)
uint32_t slice_offset; // distance from the actual page data slice (0 if a page)
uint8_t is_reset : 1; // `true` if the page memory was reset
uint8_t is_committed : 1; // `true` if the page virtual memory is committed
uint8_t is_zero_init : 1; // `true` if the page was zero initialized
// layout like this to optimize access in `mi_malloc` and `mi_free`
uint16_t capacity; // number of blocks committed, must be the first field, see `segment.c:page_clear`
uint16_t reserved; // number of blocks reserved in memory
mi_page_flags_t flags; // `in_full` and `has_aligned` flags (8 bits)
uint8_t is_zero:1; // `true` if the blocks in the free list are zero initialized
uint8_t retire_expire:7; // expiration count for retired blocks
uint8_t is_zero : 1; // `true` if the blocks in the free list are zero initialized
uint8_t retire_expire : 7; // expiration count for retired blocks
mi_block_t* free; // list of available free blocks (`malloc` allocates from this list)
#ifdef MI_ENCODE_FREELIST
@ -273,51 +301,95 @@ typedef struct mi_page_s {
uint32_t used; // number of blocks in use (including blocks in `local_free` and `thread_free`)
uint32_t xblock_size; // size available in each block (always `>0`)
mi_block_t* local_free; // list of deferred free blocks by this thread (migrates to `free`)
mi_block_t* local_free; // list of deferred free blocks by this thread (migrates to `free`)
_Atomic(mi_thread_free_t) xthread_free; // list of deferred free blocks freed by other threads
_Atomic(uintptr_t) xheap;
struct mi_page_s* next; // next page owned by this thread with the same `block_size`
struct mi_page_s* prev; // previous page owned by this thread with the same `block_size`
struct mi_page_s* next; // next page owned by this thread with the same `block_size`
struct mi_page_s* prev; // previous page owned by this thread with the same `block_size`
// 64-bit 9 words, 32-bit 12 words, (+2 for secure)
#if MI_INTPTR_SIZE==8
uintptr_t padding[1];
#endif
} mi_page_t;
typedef enum mi_page_kind_e {
MI_PAGE_SMALL, // small blocks go into 64KiB pages inside a segment
MI_PAGE_MEDIUM, // medium blocks go into 512KiB pages inside a segment
MI_PAGE_LARGE, // larger blocks go into a single page spanning a whole segment
MI_PAGE_HUGE // huge blocks (>512KiB) are put into a single page in a segment of the exact size (but still 2MiB aligned)
MI_PAGE_MEDIUM, // medium blocks go into medium pages inside a segment
MI_PAGE_LARGE, // larger blocks go into a page of just one block
MI_PAGE_HUGE, // huge blocks (> 16 MiB) are put into a single page in a single segment.
} mi_page_kind_t;
// Segments are large allocated memory blocks (2MiB on 64 bit) from
typedef enum mi_segment_kind_e {
MI_SEGMENT_NORMAL, // MI_SEGMENT_SIZE size with pages inside.
MI_SEGMENT_HUGE, // > MI_LARGE_SIZE_MAX segment with just one huge page inside.
} mi_segment_kind_t;
// ------------------------------------------------------
// A segment holds a commit mask where a bit is set if
// the corresponding MI_COMMIT_SIZE area is committed.
// The MI_COMMIT_SIZE must be a multiple of the slice
// size. If it is equal we have the most fine grained
// decommit (but setting it higher can be more efficient).
// The MI_MINIMAL_COMMIT_SIZE is the minimal amount that will
// be committed in one go which can be set higher than
// MI_COMMIT_SIZE for efficiency (while the decommit mask
// is still tracked in fine-grained MI_COMMIT_SIZE chunks)
// ------------------------------------------------------
#define MI_MINIMAL_COMMIT_SIZE (2*MI_MiB)
#define MI_COMMIT_SIZE (MI_SEGMENT_SLICE_SIZE) // 64KiB
#define MI_COMMIT_MASK_BITS (MI_SEGMENT_SIZE / MI_COMMIT_SIZE)
#define MI_COMMIT_MASK_FIELD_BITS MI_SIZE_BITS
#define MI_COMMIT_MASK_FIELD_COUNT (MI_COMMIT_MASK_BITS / MI_COMMIT_MASK_FIELD_BITS)
#if (MI_COMMIT_MASK_BITS != (MI_COMMIT_MASK_FIELD_COUNT * MI_COMMIT_MASK_FIELD_BITS))
#error "the segment size must be exactly divisible by the (commit size * size_t bits)"
#endif
typedef struct mi_commit_mask_s {
size_t mask[MI_COMMIT_MASK_FIELD_COUNT];
} mi_commit_mask_t;
typedef mi_page_t mi_slice_t;
typedef int64_t mi_msecs_t;
// Segments are large allocated memory blocks (8mb on 64 bit) from
// the OS. Inside segments we allocated fixed size _pages_ that
// contain blocks.
typedef struct mi_segment_s {
// memory fields
size_t memid; // id for the os-level memory manager
bool mem_is_pinned; // `true` if we cannot decommit/reset/protect in this memory (i.e. when allocated using large OS pages)
bool mem_is_committed; // `true` if the whole segment is eagerly committed
size_t memid; // memory id for arena allocation
bool mem_is_pinned; // `true` if we cannot decommit/reset/protect in this memory (i.e. when allocated using large OS pages)
bool mem_is_large; // in large/huge os pages?
bool mem_is_committed; // `true` if the whole segment is eagerly committed
bool allow_decommit;
mi_msecs_t decommit_expire;
mi_commit_mask_t decommit_mask;
mi_commit_mask_t commit_mask;
// segment fields
_Atomic(struct mi_segment_s*) abandoned_next;
struct mi_segment_s* next; // must be the first segment field after abandoned_next -- see `segment.c:segment_init`
struct mi_segment_s* prev;
size_t abandoned; // abandoned pages (i.e. the original owning thread stopped) (`abandoned <= used`)
size_t abandoned_visits; // count how often this segment is visited in the abandoned list (to force reclaim if it is too long)
// from here is zero initialized
struct mi_segment_s* next; // the list of freed segments in the cache (must be first field, see `segment.c:mi_segment_init`)
size_t abandoned; // abandoned pages (i.e. the original owning thread stopped) (`abandoned <= used`)
size_t abandoned_visits; // count how often this segment is visited in the abandoned list (to force reclaim it it is too long)
size_t used; // count of pages in use
uintptr_t cookie; // verify addresses in debug mode: `mi_ptr_cookie(segment) == segment->cookie`
size_t used; // count of pages in use (`used <= capacity`)
size_t capacity; // count of available pages (`#free + used`)
size_t segment_size; // for huge pages this may be different from `MI_SEGMENT_SIZE`
size_t segment_info_size;// space we are using from the first page for segment meta-data and possible guard pages.
uintptr_t cookie; // verify addresses in secure mode: `_mi_ptr_cookie(segment) == segment->cookie`
size_t segment_slices; // for huge segments this may be different from `MI_SLICES_PER_SEGMENT`
size_t segment_info_slices; // initial slices we are using segment info and possible guard pages.
// layout like this to optimize access in `mi_free`
size_t page_shift; // `1 << page_shift` == the page sizes == `page->block_size * page->reserved` (unless the first page, then `-segment_info_size`).
mi_segment_kind_t kind;
_Atomic(mi_threadid_t) thread_id; // unique id of the thread owning this segment
mi_page_kind_t page_kind; // kind of pages: small, medium, large, or huge
mi_page_t pages[1]; // up to `MI_SMALL_PAGES_PER_SEGMENT` pages
size_t slice_entries; // entries in the `slices` array, at most `MI_SLICES_PER_SEGMENT`
mi_slice_t slices[MI_SLICES_PER_SEGMENT];
} mi_segment_t;
@ -354,20 +426,40 @@ typedef struct mi_random_cxt_s {
} mi_random_ctx_t;
// In debug mode there is a padding structure at the end of the blocks to check for buffer overflows
// If MI_PADDING is enabled, there is a padding structure at the end of the blocks to check for buffer overflows
// The full layout is of a block becomes:
//
// |--- data ---------|--- fill ----------|--- struct padding_s -----------------------------------------|
// |.. actual data .. | .. delta bytes .. | canary_lo | .. extra .. | canary | delta | .. stack trace .. |
//
// where the delta bytes are used to align the padding structure and to detect byte precise overflow.
// The `canary` is used to see if `delta` and `strace` are not corrupted, while `canary_lo` can
// detect overflow into the `extra` padding (where the stack trace could remain valid)
#if (MI_PADDING)
typedef struct mi_padding_s {
uint32_t canary; // encoded block value to check validity of the padding (in case of overflow)
uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes)
#if MI_PADDING_EXTRA > 0
uint32_t canary_lo; // extra canary to detect initial overflow
uint8_t extra[MI_PADDING_EXTRA];
#endif
uint32_t canary; // encoded block value to check validity of the delat (in case of overflow)
uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes)
#if (MI_DEBUG_TRACE > 0)
void* strace[MI_DEBUG_TRACE_LEN]; // stack trace at allocation time
#endif
} mi_padding_t;
#define MI_PADDING_SIZE (sizeof(mi_padding_t))
#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE)
#define MI_PADDING_MINSIZE (8) // 2*sizeof(uint32_t)
#define MI_PADDING_SIZE (sizeof(mi_padding_t))
#else
#define MI_PADDING_SIZE 0
#define MI_PADDING_WSIZE 0
#define MI_PADDING_MINSIZE (0)
#define MI_PADDING_SIZE (0)
#endif
#define MI_PAGES_DIRECT (MI_SMALL_WSIZE_MAX + MI_PADDING_WSIZE + 1)
// add 2 more for minimal padding (MI_PADDING && !MI_DEBUG_TRACE && MI_PADDING_EXTRA==0)
// since this is used in secure mode, we optimize this case by allowing
// `heap_malloc_small` to also work with `MI_WSMALL_SIZE_MAX + MI_PADDING_MINSIZE` sizes.
// see `init.c` where all are initialized with an empty page and the check at `heap_malloc_small`.
#define MI_PAGES_DIRECT (MI_SMALL_WSIZE_MAX + 1 + 2)
// A heap owns a set of pages.
@ -459,7 +551,7 @@ typedef struct mi_stats_s {
mi_stat_count_t threads;
mi_stat_count_t normal;
mi_stat_count_t huge;
mi_stat_count_t giant;
mi_stat_count_t large;
mi_stat_count_t malloc;
mi_stat_count_t segments_cache;
mi_stat_counter_t pages_extended;
@ -469,7 +561,7 @@ typedef struct mi_stats_s {
mi_stat_counter_t searches;
mi_stat_counter_t normal_count;
mi_stat_counter_t huge_count;
mi_stat_counter_t giant_count;
mi_stat_counter_t large_count;
#if MI_STAT>1
mi_stat_count_t normal_bins[MI_BIN_HUGE+1];
#endif
@ -498,13 +590,15 @@ void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);
// Thread Local data
// ------------------------------------------------------
typedef int64_t mi_msecs_t;
// A "span" is is an available range of slices. The span queues keep
// track of slice spans of at most the given `slice_count` (but more than the previous size class).
typedef struct mi_span_queue_s {
mi_slice_t* first;
mi_slice_t* last;
size_t slice_count;
} mi_span_queue_t;
// Queue of segments
typedef struct mi_segment_queue_s {
mi_segment_t* first;
mi_segment_t* last;
} mi_segment_queue_t;
#define MI_SEGMENT_BIN_MAX (35) // 35 == mi_segment_bin(MI_SLICES_PER_SEGMENT)
// OS thread local data
typedef struct mi_os_tld_s {
@ -512,11 +606,10 @@ typedef struct mi_os_tld_s {
mi_stats_t* stats; // points to tld stats
} mi_os_tld_t;
// Segments thread local data
typedef struct mi_segments_tld_s {
mi_segment_queue_t small_free; // queue of segments with free small pages
mi_segment_queue_t medium_free; // queue of segments with free medium pages
mi_page_queue_t pages_reset; // queue of freed pages that can be reset
mi_span_queue_t spans[MI_SEGMENT_BIN_MAX+1]; // free slice spans inside segments
size_t count; // current number of segments;
size_t peak_count; // peak number of segments
size_t current_size; // current size of all segments

View File

@ -8,7 +8,7 @@ terms of the MIT license. A copy of the license can be found in the file
#ifndef MIMALLOC_H
#define MIMALLOC_H
#define MI_MALLOC_VERSION 176 // major + 2 digits minor
#define MI_MALLOC_VERSION 206 // major + 2 digits minor
// ------------------------------------------------------
// Compiler specific attributes
@ -166,7 +166,7 @@ mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, s
// Note that `alignment` always follows `size` for consistency with unaligned
// allocation, but unfortunately this differs from `posix_memalign` and `aligned_alloc`.
// -------------------------------------------------------------------------------------
#define MI_ALIGNMENT_MAX (1024*1024UL) // maximum supported alignment is 1MiB
#define MI_ALIGNMENT_MAX (16*1024*1024UL) // maximum supported alignment is 16MiB
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
@ -273,6 +273,8 @@ mi_decl_export int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size
mi_decl_export int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept;
mi_decl_export bool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept;
mi_decl_export void mi_debug_show_arenas(void) mi_attr_noexcept;
// deprecated
mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept;
@ -302,29 +304,33 @@ mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size
typedef enum mi_option_e {
// stable options
mi_option_show_errors, // print error messages
mi_option_show_stats, // print statistics on termination
mi_option_verbose, // print verbose messages
// the following options are experimental (see src/options.h)
mi_option_eager_commit,
mi_option_eager_region_commit,
mi_option_reset_decommits,
mi_option_show_errors,
mi_option_show_stats,
mi_option_verbose,
// some of the following options are experimental
// (deprecated options are kept for binary backward compatibility with v1.x versions)
mi_option_eager_commit,
mi_option_deprecated_eager_region_commit,
mi_option_deprecated_reset_decommits,
mi_option_large_os_pages, // use large (2MiB) OS pages, implies eager commit
mi_option_reserve_huge_os_pages, // reserve N huge OS pages (1GiB) at startup
mi_option_reserve_huge_os_pages_at, // reserve huge OS pages at a specific NUMA node
mi_option_reserve_os_memory, // reserve specified amount of OS memory at startup
mi_option_deprecated_segment_cache,
mi_option_page_reset,
mi_option_abandoned_page_reset,
mi_option_segment_reset,
mi_option_deprecated_segment_cache,
mi_option_page_reset,
mi_option_abandoned_page_decommit,
mi_option_deprecated_segment_reset,
mi_option_eager_commit_delay,
mi_option_reset_delay,
mi_option_decommit_delay,
mi_option_use_numa_nodes, // 0 = use available numa nodes, otherwise use at most N nodes.
mi_option_limit_os_alloc, // 1 = do not use OS memory for allocation (but only reserved arenas)
mi_option_os_tag,
mi_option_max_errors,
mi_option_max_warnings,
mi_option_max_segment_reclaim,
mi_option_allow_decommit,
mi_option_segment_decommit_delay,
mi_option_decommit_extend_delay,
_mi_option_last
} mi_option_t;

View File

@ -24,7 +24,7 @@ static mi_decl_noinline void* mi_heap_malloc_zero_aligned_at_fallback(mi_heap_t*
const size_t padsize = size + MI_PADDING_SIZE;
// use regular allocation if it is guaranteed to fit the alignment constraints
if (offset==0 && alignment<=padsize && padsize<=MI_MEDIUM_OBJ_SIZE_MAX && (padsize&align_mask)==0) {
if (offset==0 && alignment<=padsize && padsize<=MI_MAX_ALIGN_GUARANTEE && (padsize&align_mask)==0) {
void* p = _mi_heap_malloc_zero(heap, size, zero);
mi_assert_internal(p == NULL || ((uintptr_t)p % alignment) == 0);
return p;

View File

@ -52,7 +52,7 @@ extern inline void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t siz
#if (MI_STAT>0)
const size_t bsize = mi_page_usable_block_size(page);
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) {
mi_heap_stat_increase(heap, normal, bsize);
mi_heap_stat_counter_increase(heap, normal_count, 1);
#if (MI_STAT>1)
@ -68,6 +68,13 @@ extern inline void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t siz
mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta));
padding->canary = (uint32_t)(mi_ptr_encode(page,block,page->keys));
padding->delta = (uint32_t)(delta);
#if MI_PADDING_EXTRA > 0
padding->canary_lo = padding->canary;
memset(padding->extra, 0, sizeof(padding->extra));
#endif
#if (MI_DEBUG_TRACE)
_mi_stack_trace_capture(padding->strace, MI_DEBUG_TRACE_LEN, 2 /*frames to skip*/);
#endif
uint8_t* fill = (uint8_t*)padding - delta;
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes
for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; }
@ -80,15 +87,25 @@ static inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap,
mi_assert(heap != NULL);
mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local
mi_assert(size <= MI_SMALL_SIZE_MAX);
#if (MI_PADDING)
void* p;
#if (MI_PADDING)
if (size == 0) {
size = sizeof(void*);
}
#endif
mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE);
void* p = _mi_page_malloc(heap, page, size + MI_PADDING_SIZE, zero);
mi_assert_internal(p == NULL || mi_usable_size(p) >= size);
#if MI_STAT>1
#endif
#if (MI_PADDING_EXTRA > 0 || MI_DEBUG_TRACE > 0)
// with extra padding it is not guaranteed the size + MI_PADDING_SIZE <= MI_SMALL_SIZE_MAX + MI_PADDING_MINSIZE, so we need an extra check
if (size + MI_PADDING_SIZE > MI_SMALL_SIZE_MAX) {
p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero);
}
else
#endif
{
mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE);
p = _mi_page_malloc(heap, page, size + MI_PADDING_SIZE, zero);
}
mi_assert_internal(p==NULL || mi_usable_size(p) >= size);
#if MI_STAT>1
if (p != NULL) {
if (!mi_heap_is_initialized(heap)) { heap = mi_get_default_heap(); }
mi_heap_stat_increase(heap, malloc, mi_usable_size(p));
@ -108,10 +125,11 @@ mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc_small(size_t si
// The main allocation function
extern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept {
if mi_likely(size <= MI_SMALL_SIZE_MAX) {
if mi_likely(size + MI_PADDING_SIZE <= MI_SMALL_SIZE_MAX + MI_PADDING_MINSIZE) {
return mi_heap_malloc_small_zero(heap, size, zero);
}
else {
else
{
mi_assert(heap!=NULL);
mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local
void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero); // note: size can overflow but it is detected in malloc_generic
@ -148,6 +166,144 @@ mi_decl_nodiscard mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept
}
// ---------------------------------------------------------------------------
// Check for heap block overflow by setting up padding at the end of the block
// ---------------------------------------------------------------------------
#if (MI_PADDING>0) && defined(MI_ENCODE_FREELIST)
static mi_padding_t* mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) {
if (page->capacity == 0) return NULL; // page may have been freed in double free check
*bsize = mi_page_usable_block_size(page);
mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);
*delta = padding->delta;
if ((uint32_t)mi_ptr_encode(page, block, page->keys) == padding->canary && *delta <= *bsize) {
return padding;
}
else {
return NULL;
}
}
#if MI_DEBUG_TRACE > 0
static void _mi_show_block_trace(const mi_page_t* page, const mi_block_t* block, const char* msg) {
size_t bsize;
size_t delta;
mi_padding_t* padding = mi_page_decode_padding(page, block, &delta, &bsize);
if (padding != NULL) {
_mi_stack_trace_print(msg, &padding->strace[0], MI_DEBUG_TRACE_LEN, block, bsize, bsize - delta);
}
}
#else
static void _mi_show_block_trace(const mi_page_t* page, const mi_block_t* block, const char* msg) {
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(msg);
}
#endif
// Return the exact usable size of a block. (whereas `mi_page_usable_block_size` returns the total available size without padding)
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
size_t bsize;
size_t delta;
bool ok = (mi_page_decode_padding(page, block, &delta, &bsize) != NULL);
mi_assert_internal(ok); mi_assert_internal(delta <= bsize);
return (ok ? bsize - delta : 0);
}
static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) {
size_t bsize;
size_t delta;
const mi_padding_t* padding = mi_page_decode_padding(page, block, &delta, &bsize);
*size = *wrong = bsize;
if (padding == NULL) return false;
mi_assert_internal(bsize >= delta);
*size = bsize - delta;
uint8_t* fill = (uint8_t*)block + bsize - delta;
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes
for (size_t i = 0; i < maxpad; i++) {
if (fill[i] != MI_DEBUG_PADDING) {
*wrong = bsize - delta + i;
return false;
}
}
#if MI_PADDING_EXTRA > 0
if (padding->canary_lo != padding->canary) {
*wrong = bsize;
return false;
}
#endif
return true;
}
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
size_t size;
size_t wrong;
if mi_unlikely(!mi_verify_padding(page,block,&size,&wrong)) {
_mi_show_block_trace_with_predecessor(page, block, NULL);
_mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong );
}
}
// When a non-thread-local block is freed, it becomes part of the thread delayed free
// list that is freed later by the owning heap. If the exact usable size is too small to
// contain the pointer for the delayed list, then shrink the padding (by decreasing delta)
// so it will later not trigger an overflow error in `mi_free_block`.
// Returns the originally allocated byte size.
static size_t mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
size_t bsize;
size_t delta;
mi_padding_t* padding = mi_page_decode_padding(page, block, &delta, &bsize);
mi_assert_internal(padding!=NULL);
if (padding == NULL) return 0;
mi_assert_internal(bsize > delta);
if (bsize <= delta) return 0;
const size_t avail = bsize - delta;
if (avail >= min_size) return avail; // usually already enough space
mi_assert_internal(bsize >= min_size);
if (bsize < min_size) return avail; // should never happen
size_t new_delta = (bsize - min_size);
mi_assert_internal(new_delta < bsize);
padding->delta = (uint32_t)new_delta;
return avail;
}
#else
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(page); MI_UNUSED(block);
}
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(block);
return mi_page_usable_block_size(page);
}
static size_t mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
MI_UNUSED(block); MI_UNUSED(min_size);
return mi_page_usable_block_size(page);
}
static void _mi_show_block_trace(const mi_page_t* page, const mi_block_t* block, const char* msg) {
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(msg);
}
#endif
static const mi_block_t* mi_block_predecessor(const mi_page_t* page, const mi_block_t* block) {
const size_t bsize = page->xblock_size;
mi_assert_internal(bsize > 0 || page->used == 0);
if (bsize == 0 /* if page is freed */|| bsize >= MI_HUGE_BLOCK_SIZE) return NULL;
const mi_block_t* prev = (const mi_block_t*)((uint8_t*)block - bsize);
uint8_t* pstart = _mi_segment_page_start(_mi_page_segment(page), page, NULL);
if (pstart > (uint8_t*)prev) return NULL;
return prev;
}
// Used if a free list is corrupted which is usually caused by the previous block(s)
void _mi_show_block_trace_with_predecessor(const mi_page_t* page, const mi_block_t* block, const char* msg) {
const mi_block_t* prev = mi_block_predecessor(page,block);
if (prev != NULL) {
_mi_show_block_trace(page, prev, "predecessor block");
}
_mi_show_block_trace(page, block, msg);
}
// ------------------------------------------------------
// Check for double free in secure and debug mode
// This is somewhat expensive so only enabled for secure mode 4
@ -170,7 +326,8 @@ static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, con
mi_list_contains(page, page->local_free, block) ||
mi_list_contains(page, mi_page_thread_free(page), block))
{
_mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_block_size(page));
_mi_show_block_trace(page, block, NULL);
_mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_usable_size_of(page,block));
return true;
}
return false;
@ -179,7 +336,7 @@ static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, con
static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {
mi_block_t* n = mi_block_nextx(page, block, page->keys); // pretend it is freed, and get the decoded first field
if (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer?
(n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL?
(n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL?
{
// Suspicous: decoded value a in block is in the same page (or NULL) -- maybe a double free?
// (continue in separate function to improve code generation)
@ -195,106 +352,30 @@ static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block
}
#endif
// ---------------------------------------------------------------------------
// Check for heap block overflow by setting up padding at the end of the block
// ---------------------------------------------------------------------------
#if (MI_PADDING>0) && defined(MI_ENCODE_FREELIST)
static bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) {
*bsize = mi_page_usable_block_size(page);
const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);
*delta = padding->delta;
return ((uint32_t)mi_ptr_encode(page,block,page->keys) == padding->canary && *delta <= *bsize);
}
// Return the exact usable size of a block.
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
mi_assert_internal(ok); mi_assert_internal(delta <= bsize);
return (ok ? bsize - delta : 0);
}
static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
*size = *wrong = bsize;
if (!ok) return false;
mi_assert_internal(bsize >= delta);
*size = bsize - delta;
uint8_t* fill = (uint8_t*)block + bsize - delta;
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes
for (size_t i = 0; i < maxpad; i++) {
if (fill[i] != MI_DEBUG_PADDING) {
*wrong = bsize - delta + i;
return false;
}
}
return true;
}
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
size_t size;
size_t wrong;
if (!mi_verify_padding(page,block,&size,&wrong)) {
_mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong );
}
}
// When a non-thread-local block is freed, it becomes part of the thread delayed free
// list that is freed later by the owning heap. If the exact usable size is too small to
// contain the pointer for the delayed list, then shrink the padding (by decreasing delta)
// so it will later not trigger an overflow error in `mi_free_block`.
static void mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
mi_assert_internal(ok);
if (!ok || (bsize - delta) >= min_size) return; // usually already enough space
mi_assert_internal(bsize >= min_size);
if (bsize < min_size) return; // should never happen
size_t new_delta = (bsize - min_size);
mi_assert_internal(new_delta < bsize);
mi_padding_t* padding = (mi_padding_t*)((uint8_t*)block + bsize);
padding->delta = (uint32_t)new_delta;
}
#else
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(page);
MI_UNUSED(block);
}
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(block);
return mi_page_usable_block_size(page);
}
static void mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
MI_UNUSED(page);
MI_UNUSED(block);
MI_UNUSED(min_size);
}
#endif
// only maintain stats for smaller objects if requested
#if (MI_STAT>0)
static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {
#if (MI_STAT < 2)
#if (MI_STAT < 2)
MI_UNUSED(block);
#endif
#endif
mi_heap_t* const heap = mi_heap_get_default();
const size_t bsize = mi_page_usable_block_size(page);
#if (MI_STAT>1)
const size_t bsize = mi_page_usable_block_size(page);
#if (MI_STAT>1)
const size_t usize = mi_page_usable_size_of(page, block);
mi_heap_stat_decrease(heap, malloc, usize);
#endif
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
#endif
if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) {
mi_heap_stat_decrease(heap, normal, bsize);
#if (MI_STAT > 1)
#if (MI_STAT > 1)
mi_heap_stat_decrease(heap, normal_bins[_mi_bin(bsize)], 1);
#endif
#endif
}
else if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
mi_heap_stat_decrease(heap, large, bsize);
}
else {
mi_heap_stat_decrease(heap, huge, bsize);
}
}
#else
@ -308,11 +389,11 @@ static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {
static void mi_stat_huge_free(const mi_page_t* page) {
mi_heap_t* const heap = mi_heap_get_default();
const size_t bsize = mi_page_block_size(page); // to match stats in `page.c:mi_page_huge_alloc`
if (bsize <= MI_HUGE_OBJ_SIZE_MAX) {
mi_heap_stat_decrease(heap, huge, bsize);
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
mi_heap_stat_decrease(heap, large, bsize);
}
else {
mi_heap_stat_decrease(heap, giant, bsize);
mi_heap_stat_decrease(heap, huge, bsize);
}
}
#else
@ -331,14 +412,16 @@ static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* bloc
// The padding check may access the non-thread-owned page for the key values.
// that is safe as these are constant and the page won't be freed (as the block is not freed yet).
mi_check_padding(page, block);
mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection
const size_t avail = mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection
#if (MI_DEBUG!=0)
memset(block, MI_DEBUG_FREED, mi_usable_size(block));
memset(block, MI_DEBUG_FREED, avail);
#else
MI_UNUSED(avail);
#endif
// huge page segments are always abandoned and can be freed immediately
mi_segment_t* const segment = _mi_page_segment(page);
if (segment->page_kind==MI_PAGE_HUGE) {
mi_segment_t* segment = _mi_page_segment(page);
if (segment->kind==MI_SEGMENT_HUGE) {
mi_stat_huge_free(page);
_mi_segment_huge_page_free(segment, page, block);
return;
@ -392,7 +475,7 @@ static inline void _mi_free_block(mi_page_t* page, bool local, mi_block_t* block
if mi_unlikely(mi_check_is_double_free(page, block)) return;
mi_check_padding(page, block);
#if (MI_DEBUG!=0)
memset(block, MI_DEBUG_FREED, mi_page_block_size(page));
memset(block, MI_DEBUG_FREED, mi_page_usable_block_size(page));
#endif
mi_block_set_next(page, block, page->local_free);
page->local_free = block;
@ -468,15 +551,15 @@ void mi_free(void* p) mi_attr_noexcept
mi_threadid_t tid = _mi_thread_id();
mi_page_t* const page = _mi_segment_page_of(segment, p);
mi_block_t* const block = (mi_block_t*)p;
if mi_likely(tid == mi_atomic_load_relaxed(&segment->thread_id) && page->flags.full_aligned == 0) { // the thread id matches and it is not a full page, nor has aligned blocks
// local, and not full or aligned
if mi_unlikely(mi_check_is_double_free(page,block)) return;
mi_block_t* block = (mi_block_t*)(p);
if mi_unlikely(mi_check_is_double_free(page, block)) return;
mi_check_padding(page, block);
mi_stat_free(page, block);
#if (MI_DEBUG!=0)
memset(block, MI_DEBUG_FREED, mi_page_block_size(page));
memset(block, MI_DEBUG_FREED, mi_page_usable_block_size(page));
#endif
mi_block_set_next(page, block, page->local_free);
page->local_free = block;

View File

@ -7,23 +7,18 @@ terms of the MIT license. A copy of the license can be found in the file
/* ----------------------------------------------------------------------------
"Arenas" are fixed area's of OS memory from which we can allocate
large blocks (>= MI_ARENA_BLOCK_SIZE, 32MiB).
large blocks (>= MI_ARENA_MIN_BLOCK_SIZE, 4MiB).
In contrast to the rest of mimalloc, the arenas are shared between
threads and need to be accessed using atomic operations.
Currently arenas are only used to for huge OS page (1GiB) reservations,
otherwise it delegates to direct allocation from the OS.
or direct OS memory reservations -- otherwise it delegates to direct allocation from the OS.
In the future, we can expose an API to manually add more kinds of arenas
which is sometimes needed for embedded devices or shared memory for example.
(We can also employ this with WASI or `sbrk` systems to reserve large arenas
on demand and be able to reuse them efficiently).
The arena allocation needs to be thread safe and we use an atomic
bitmap to allocate. The current implementation of the bitmap can
only do this within a field (`size_t`) so we can allocate at most
blocks of 2GiB (64*32MiB) and no object can cross the boundary. This
can lead to fragmentation but fortunately most objects will be regions
of 256MiB in practice.
The arena allocation needs to be thread safe and we use an atomic bitmap to allocate.
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
#include "mimalloc-internal.h"
@ -38,7 +33,6 @@ of 256MiB in practice.
// os.c
void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool* large, mi_stats_t* stats);
void _mi_os_free_ex(void* p, size_t size, bool was_committed, mi_stats_t* stats);
void _mi_os_free(void* p, size_t size, mi_stats_t* stats);
void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_secs, size_t* pages_reserved, size_t* psize);
void _mi_os_free_huge_pages(void* p, size_t size, mi_stats_t* stats);
@ -46,13 +40,17 @@ void _mi_os_free_huge_pages(void* p, size_t size, mi_stats_t* stats);
bool _mi_os_commit(void* p, size_t size, bool* is_zero, mi_stats_t* stats);
bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* stats);
/* -----------------------------------------------------------
Arena allocation
----------------------------------------------------------- */
#define MI_SEGMENT_ALIGN MI_SEGMENT_SIZE
#define MI_ARENA_BLOCK_SIZE (4*MI_SEGMENT_ALIGN) // 32MiB
#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2) // 16MiB
// Block info: bit 0 contains the `in_use` bit, the upper bits the
// size in count of arena blocks.
typedef uintptr_t mi_block_info_t;
#define MI_ARENA_BLOCK_SIZE (MI_SEGMENT_SIZE) // 8MiB (must be at least MI_SEGMENT_ALIGN)
#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2) // 4MiB
#define MI_MAX_ARENAS (64) // not more than 256 (since we use 8 bits in the memid)
// A memory arena descriptor
@ -105,9 +103,9 @@ static size_t mi_block_count_of_size(size_t size) {
----------------------------------------------------------- */
static bool mi_arena_alloc(mi_arena_t* arena, size_t blocks, mi_bitmap_index_t* bitmap_idx)
{
size_t idx = mi_atomic_load_acquire(&arena->search_idx); // start from last search
size_t idx = 0; // mi_atomic_load_relaxed(&arena->search_idx); // start from last search; ok to be relaxed as the exact start does not matter
if (_mi_bitmap_try_find_from_claim_across(arena->blocks_inuse, arena->field_count, idx, blocks, bitmap_idx)) {
mi_atomic_store_release(&arena->search_idx, idx); // start search from here next time
mi_atomic_store_relaxed(&arena->search_idx, mi_bitmap_index_field(*bitmap_idx)); // start search from found location next time around
return true;
};
return false;
@ -118,8 +116,8 @@ static bool mi_arena_alloc(mi_arena_t* arena, size_t blocks, mi_bitmap_index_t*
Arena Allocation
----------------------------------------------------------- */
static void* mi_arena_alloc_from(mi_arena_t* arena, size_t arena_index, size_t needed_bcount,
bool* commit, bool* large, bool* is_pinned, bool* is_zero, size_t* memid, mi_os_tld_t* tld)
static mi_decl_noinline void* mi_arena_alloc_from(mi_arena_t* arena, size_t arena_index, size_t needed_bcount,
bool* commit, bool* large, bool* is_pinned, bool* is_zero, size_t* memid, mi_os_tld_t* tld)
{
mi_bitmap_index_t bitmap_index;
if (!mi_arena_alloc(arena, needed_bcount, &bitmap_index)) return NULL;
@ -151,6 +149,48 @@ static void* mi_arena_alloc_from(mi_arena_t* arena, size_t arena_index, size_t n
return p;
}
static mi_decl_noinline void* mi_arena_allocate(int numa_node, size_t size, size_t alignment, bool* commit, bool* large, bool* is_pinned, bool* is_zero, size_t* memid, mi_os_tld_t* tld)
{
MI_UNUSED_RELEASE(alignment);
mi_assert_internal(alignment <= MI_SEGMENT_ALIGN);
const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);
const size_t bcount = mi_block_count_of_size(size);
if mi_likely(max_arena == 0) return NULL;
mi_assert_internal(size <= bcount*MI_ARENA_BLOCK_SIZE);
// try numa affine allocation
for (size_t i = 0; i < max_arena; i++) {
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
if (arena==NULL) break; // end reached
if ((arena->numa_node<0 || arena->numa_node==numa_node) && // numa local?
(*large || !arena->is_large)) // large OS pages allowed, or arena is not large OS pages
{
void* p = mi_arena_alloc_from(arena, i, bcount, commit, large, is_pinned, is_zero, memid, tld);
mi_assert_internal((uintptr_t)p % alignment == 0);
if (p != NULL) {
return p;
}
}
}
// try from another numa node instead..
for (size_t i = 0; i < max_arena; i++) {
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
if (arena==NULL) break; // end reached
if ((arena->numa_node>=0 && arena->numa_node!=numa_node) && // not numa local!
(*large || !arena->is_large)) // large OS pages allowed, or arena is not large OS pages
{
void* p = mi_arena_alloc_from(arena, i, bcount, commit, large, is_pinned, is_zero, memid, tld);
mi_assert_internal((uintptr_t)p % alignment == 0);
if (p != NULL) {
return p;
}
}
}
return NULL;
}
void* _mi_arena_alloc_aligned(size_t size, size_t alignment, bool* commit, bool* large, bool* is_pinned, bool* is_zero,
size_t* memid, mi_os_tld_t* tld)
{
@ -160,40 +200,14 @@ void* _mi_arena_alloc_aligned(size_t size, size_t alignment, bool* commit, bool*
*is_zero = false;
*is_pinned = false;
// try to allocate in an arena if the alignment is small enough
// and the object is not too large or too small.
if (alignment <= MI_SEGMENT_ALIGN &&
size >= MI_ARENA_MIN_OBJ_SIZE &&
mi_atomic_load_relaxed(&mi_arena_count) > 0)
{
const size_t bcount = mi_block_count_of_size(size);
const int numa_node = _mi_os_numa_node(tld); // current numa node
bool default_large = false;
if (large==NULL) large = &default_large; // ensure `large != NULL`
const int numa_node = _mi_os_numa_node(tld); // current numa node
mi_assert_internal(size <= bcount*MI_ARENA_BLOCK_SIZE);
// try numa affine allocation
for (size_t i = 0; i < MI_MAX_ARENAS; i++) {
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
if (arena==NULL) break; // end reached
if ((arena->numa_node<0 || arena->numa_node==numa_node) && // numa local?
(*large || !arena->is_large)) // large OS pages allowed, or arena is not large OS pages
{
void* p = mi_arena_alloc_from(arena, i, bcount, commit, large, is_pinned, is_zero, memid, tld);
mi_assert_internal((uintptr_t)p % alignment == 0);
if (p != NULL) return p;
}
}
// try from another numa node instead..
for (size_t i = 0; i < MI_MAX_ARENAS; i++) {
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
if (arena==NULL) break; // end reached
if ((arena->numa_node>=0 && arena->numa_node!=numa_node) && // not numa local!
(*large || !arena->is_large)) // large OS pages allowed, or arena is not large OS pages
{
void* p = mi_arena_alloc_from(arena, i, bcount, commit, large, is_pinned, is_zero, memid, tld);
mi_assert_internal((uintptr_t)p % alignment == 0);
if (p != NULL) return p;
}
}
// try to allocate in an arena if the alignment is small enough and the object is not too small (as for heap meta data)
if (size >= MI_ARENA_MIN_OBJ_SIZE && alignment <= MI_SEGMENT_ALIGN) {
void* p = mi_arena_allocate(numa_node, size, alignment, commit, large, is_pinned, is_zero, memid, tld);
if (p != NULL) return p;
}
// finally, fall back to the OS
@ -217,13 +231,14 @@ void* _mi_arena_alloc(size_t size, bool* commit, bool* large, bool* is_pinned, b
Arena free
----------------------------------------------------------- */
void _mi_arena_free(void* p, size_t size, size_t memid, bool all_committed, mi_stats_t* stats) {
mi_assert_internal(size > 0 && stats != NULL);
void _mi_arena_free(void* p, size_t size, size_t memid, bool all_committed, mi_os_tld_t* tld) {
mi_assert_internal(size > 0 && tld->stats != NULL);
if (p==NULL) return;
if (size==0) return;
if (memid == MI_MEMID_OS) {
// was a direct OS allocation, pass through
_mi_os_free_ex(p, size, all_committed, stats);
_mi_os_free_ex(p, size, all_committed, tld->stats);
}
else {
// allocated in an arena
@ -250,8 +265,7 @@ void _mi_arena_free(void* p, size_t size, size_t memid, bool all_committed, mi_s
}
else {
mi_assert_internal(arena->blocks_committed != NULL);
_mi_os_decommit(p, blocks * MI_ARENA_BLOCK_SIZE, stats); // ok if this fails
// todo: use reset instead of decommit on windows?
_mi_os_decommit(p, blocks * MI_ARENA_BLOCK_SIZE, tld->stats); // ok if this fails
_mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx);
}
// and make it available to others again
@ -341,6 +355,33 @@ int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noe
return 0;
}
static size_t mi_debug_show_bitmap(const char* prefix, mi_bitmap_field_t* fields, size_t field_count ) {
size_t inuse_count = 0;
for (size_t i = 0; i < field_count; i++) {
char buf[MI_BITMAP_FIELD_BITS + 1];
uintptr_t field = mi_atomic_load_relaxed(&fields[i]);
for (size_t bit = 0; bit < MI_BITMAP_FIELD_BITS; bit++) {
bool inuse = ((((uintptr_t)1 << bit) & field) != 0);
if (inuse) inuse_count++;
buf[MI_BITMAP_FIELD_BITS - 1 - bit] = (inuse ? 'x' : '.');
}
buf[MI_BITMAP_FIELD_BITS] = 0;
_mi_verbose_message("%s%s\n", prefix, buf);
}
return inuse_count;
}
void mi_debug_show_arenas(void) mi_attr_noexcept {
size_t max_arenas = mi_atomic_load_relaxed(&mi_arena_count);
for (size_t i = 0; i < max_arenas; i++) {
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
if (arena == NULL) break;
size_t inuse_count = 0;
_mi_verbose_message("arena %zu: %zu blocks with %zu fields\n", i, arena->block_count, arena->field_count);
inuse_count += mi_debug_show_bitmap(" ", arena->blocks_inuse, arena->field_count);
_mi_verbose_message(" blocks in use ('x'): %zu\n", inuse_count);
}
}
/* -----------------------------------------------------------
Reserve a huge page arena.

View File

@ -35,17 +35,17 @@ static inline size_t mi_bitmap_mask_(size_t count, size_t bitidx) {
}
/* -----------------------------------------------------------
Claim a bit sequence atomically
----------------------------------------------------------- */
// Try to atomically claim a sequence of `count` bits in a single
// field at `idx` in `bitmap`. Returns `true` on success.
bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx)
inline bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx)
{
mi_assert_internal(bitmap_idx != NULL);
mi_assert_internal(count <= MI_BITMAP_FIELD_BITS);
mi_assert_internal(count > 0);
mi_bitmap_field_t* field = &bitmap[idx];
size_t map = mi_atomic_load_relaxed(field);
if (map==MI_BITMAP_FIELD_FULL) return false; // short cut
@ -94,9 +94,9 @@ bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_
return false;
}
// Find `count` bits of 0 and set them to 1 atomically; returns `true` on success.
// Starts at idx, and wraps around to search in all `bitmap_fields` fields.
// For now, `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields.
// `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields.
bool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx) {
size_t idx = start_field_idx;
for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) {
@ -118,7 +118,7 @@ bool _mi_bitmap_try_find_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, c
// Set `count` bits at `bitmap_idx` to 0 atomically
// Returns `true` if all `count` bits were 1 previously.
bool mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {
bool _mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {
const size_t idx = mi_bitmap_index_field(bitmap_idx);
const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);
const size_t mask = mi_bitmap_mask_(count, bitidx);
@ -215,7 +215,7 @@ static bool mi_bitmap_try_find_claim_field_across(mi_bitmap_t bitmap, size_t bit
// intermediate fields
while (++field < final_field) {
newmap = mi_bitmap_mask_(MI_BITMAP_FIELD_BITS, 0);
newmap = MI_BITMAP_FIELD_FULL;
map = 0;
if (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)) { goto rollback; }
}
@ -236,7 +236,7 @@ rollback:
// roll back intermediate fields
while (--field > initial_field) {
newmap = 0;
map = mi_bitmap_mask_(MI_BITMAP_FIELD_BITS, 0);
map = MI_BITMAP_FIELD_FULL;
mi_assert_internal(mi_atomic_load_relaxed(field) == map);
mi_atomic_store_release(field, newmap);
}

View File

@ -40,6 +40,11 @@ static inline mi_bitmap_index_t mi_bitmap_index_create(size_t idx, size_t bitidx
return (idx*MI_BITMAP_FIELD_BITS) + bitidx;
}
// Create a bit index.
static inline mi_bitmap_index_t mi_bitmap_index_create_from_bit(size_t full_bitidx) {
return mi_bitmap_index_create(full_bitidx / MI_BITMAP_FIELD_BITS, full_bitidx % MI_BITMAP_FIELD_BITS);
}
// Get the field index from a bit index.
static inline size_t mi_bitmap_index_field(mi_bitmap_index_t bitmap_idx) {
return (bitmap_idx / MI_BITMAP_FIELD_BITS);
@ -69,7 +74,7 @@ bool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fiel
// Set `count` bits at `bitmap_idx` to 0 atomically
// Returns `true` if all `count` bits were 1 previously.
bool mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);
bool _mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);
// Set `count` bits at `bitmap_idx` to 1 atomically
// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.

View File

@ -115,17 +115,20 @@ static bool mi_heap_page_never_delayed_free(mi_heap_t* heap, mi_page_queue_t* pq
static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)
{
if (heap==NULL || !mi_heap_is_initialized(heap)) return;
_mi_deferred_free(heap, collect >= MI_FORCE);
const bool force = collect >= MI_FORCE;
_mi_deferred_free(heap, force);
// note: never reclaim on collect but leave it to threads that need storage to reclaim
if (
#ifdef NDEBUG
const bool force_main =
#ifdef NDEBUG
collect == MI_FORCE
#else
#else
collect >= MI_FORCE
#endif
&& _mi_is_main_thread() && mi_heap_is_backing(heap) && !heap->no_reclaim)
{
#endif
&& _mi_is_main_thread() && mi_heap_is_backing(heap) && !heap->no_reclaim;
if (force_main) {
// the main thread is abandoned (end-of-program), try to reclaim all abandoned segments.
// if all memory is freed by now, all segments should be freed.
_mi_abandoned_reclaim_all(heap, &heap->tld->segments);
@ -141,20 +144,28 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)
_mi_heap_delayed_free(heap);
// collect retired pages
_mi_heap_collect_retired(heap, collect >= MI_FORCE);
_mi_heap_collect_retired(heap, force);
// collect all pages owned by this thread
mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL);
mi_assert_internal( collect != MI_ABANDON || mi_atomic_load_ptr_acquire(mi_block_t,&heap->thread_delayed_free) == NULL );
// collect segment caches
if (collect >= MI_FORCE) {
// collect abandoned segments (in particular, decommit expired parts of segments in the abandoned segment list)
// note: forced decommit can be quite expensive if many threads are created/destroyed so we do not force on abandonment
_mi_abandoned_collect(heap, collect == MI_FORCE /* force? */, &heap->tld->segments);
// collect segment local caches
if (force) {
_mi_segment_thread_collect(&heap->tld->segments);
}
// decommit in global segment caches
// note: forced decommit can be quite expensive if many threads are created/destroyed so we do not force on abandonment
_mi_segment_cache_collect( collect == MI_FORCE, &heap->tld->os);
// collect regions on program-exit (or shared library unload)
if (collect >= MI_FORCE && _mi_is_main_thread() && mi_heap_is_backing(heap)) {
_mi_mem_collect(&heap->tld->os);
if (force && _mi_is_main_thread() && mi_heap_is_backing(heap)) {
//_mi_mem_collect(&heap->tld->os);
}
}
@ -272,9 +283,9 @@ static bool _mi_heap_page_destroy(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_
// stats
const size_t bsize = mi_page_block_size(page);
if (bsize > MI_LARGE_OBJ_SIZE_MAX) {
if (bsize > MI_HUGE_OBJ_SIZE_MAX) {
mi_heap_stat_decrease(heap, giant, bsize);
if (bsize > MI_MEDIUM_OBJ_SIZE_MAX) {
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
mi_heap_stat_decrease(heap, large, bsize);
}
else {
mi_heap_stat_decrease(heap, huge, bsize);

View File

@ -28,17 +28,13 @@ const mi_page_t _mi_page_empty = {
MI_ATOMIC_VAR_INIT(0), // xthread_free
MI_ATOMIC_VAR_INIT(0), // xheap
NULL, NULL
#if MI_INTPTR_SIZE==8
, { 0 } // padding
#endif
};
#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty)
#if (MI_PADDING>0) && (MI_INTPTR_SIZE >= 8)
#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() }
#elif (MI_PADDING>0)
#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty)
#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() }
#else
#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY() }
#endif
// Empty page queues for every bin
@ -54,8 +50,8 @@ const mi_page_t _mi_page_empty = {
QNULL( 10240), QNULL( 12288), QNULL( 14336), QNULL( 16384), QNULL( 20480), QNULL( 24576), QNULL( 28672), QNULL( 32768), /* 56 */ \
QNULL( 40960), QNULL( 49152), QNULL( 57344), QNULL( 65536), QNULL( 81920), QNULL( 98304), QNULL(114688), QNULL(131072), /* 64 */ \
QNULL(163840), QNULL(196608), QNULL(229376), QNULL(262144), QNULL(327680), QNULL(393216), QNULL(458752), QNULL(524288), /* 72 */ \
QNULL(MI_LARGE_OBJ_WSIZE_MAX + 1 /* 655360, Huge queue */), \
QNULL(MI_LARGE_OBJ_WSIZE_MAX + 2) /* Full queue */ }
QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 1 /* 655360, Huge queue */), \
QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 2) /* Full queue */ }
#define MI_STAT_COUNT_NULL() {0,0,0,0}
@ -78,6 +74,18 @@ const mi_page_t _mi_page_empty = {
{ 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } \
MI_STAT_COUNT_END_NULL()
// Empty slice span queues for every bin
#define SQNULL(sz) { NULL, NULL, sz }
#define MI_SEGMENT_SPAN_QUEUES_EMPTY \
{ SQNULL(1), \
SQNULL( 1), SQNULL( 2), SQNULL( 3), SQNULL( 4), SQNULL( 5), SQNULL( 6), SQNULL( 7), SQNULL( 10), /* 8 */ \
SQNULL( 12), SQNULL( 14), SQNULL( 16), SQNULL( 20), SQNULL( 24), SQNULL( 28), SQNULL( 32), SQNULL( 40), /* 16 */ \
SQNULL( 48), SQNULL( 56), SQNULL( 64), SQNULL( 80), SQNULL( 96), SQNULL( 112), SQNULL( 128), SQNULL( 160), /* 24 */ \
SQNULL( 192), SQNULL( 224), SQNULL( 256), SQNULL( 320), SQNULL( 384), SQNULL( 448), SQNULL( 512), SQNULL( 640), /* 32 */ \
SQNULL( 768), SQNULL( 896), SQNULL( 1024) /* 35 */ }
// --------------------------------------------------------
// Statically allocate an empty heap as the initial
// thread local value for the default heap,
@ -102,6 +110,17 @@ mi_decl_cache_align const mi_heap_t _mi_heap_empty = {
false
};
#define tld_empty_stats ((mi_stats_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,stats)))
#define tld_empty_os ((mi_os_tld_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,os)))
mi_decl_cache_align static const mi_tld_t tld_empty = {
0,
false,
NULL, NULL,
{ MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, tld_empty_stats, tld_empty_os }, // segments
{ 0, tld_empty_stats }, // os
{ MI_STATS_NULL } // stats
};
// the thread-local default heap for allocation
mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty;
@ -110,11 +129,8 @@ extern mi_heap_t _mi_heap_main;
static mi_tld_t tld_main = {
0, false,
&_mi_heap_main, &_mi_heap_main,
{ { NULL, NULL }, {NULL ,NULL}, {NULL ,NULL, 0},
0, 0, 0, 0,
&tld_main.stats, &tld_main.os
}, // segments
&_mi_heap_main, & _mi_heap_main,
{ MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, &tld_main.stats, &tld_main.os }, // segments
{ 0, &tld_main.stats }, // os
{ MI_STATS_NULL } // stats
};
@ -245,6 +261,7 @@ static bool _mi_heap_init(void) {
// OS allocated so already zero initialized
mi_tld_t* tld = &td->tld;
mi_heap_t* heap = &td->heap;
_mi_memcpy_aligned(tld, &tld_empty, sizeof(*tld));
_mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(*heap));
heap->thread_id = _mi_thread_id();
_mi_random_init(&heap->random);
@ -296,7 +313,10 @@ static bool _mi_heap_done(mi_heap_t* heap) {
// free if not the main thread
if (heap != &_mi_heap_main) {
mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id());
// the following assertion does not always hold for huge segments as those are always treated
// as abondened: one may allocate it in one thread, but deallocate in another in which case
// the count can be too large or negative. todo: perhaps not count huge segments? see issue #363
// mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id());
mi_thread_data_free((mi_thread_data_t*)heap);
}
else {
@ -497,7 +517,7 @@ static void mi_allocator_done(void) {
// Called once by the process loader
static void mi_process_load(void) {
mi_heap_main_init();
#if defined(MI_TLS_RECURSE_GUARD)
#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true;
MI_UNUSED(dummy);
#endif
@ -534,6 +554,25 @@ static void mi_detect_cpu_features(void) {
}
#endif
#if defined(_WIN32) && (MI_DEBUG_TRACE > 0)
#include <dbghelp.h>
static void mi_debug_init(void) {
if (SymInitialize(GetCurrentProcess(), NULL, TRUE) != TRUE) { // initialize here as it is single threaded.
_mi_warning_message("unable to initialize debug symbol information (error 0x%x)", GetLastError());
}
}
static void mi_debug_done(void) {
SymCleanup(GetCurrentProcess());
}
#else
static void mi_debug_init(void) {
// nothing
}
static void mi_debug_done(void) {
// nothing
}
#endif
// Initialize the process; called by thread_init or the process loader
void mi_process_init(void) mi_attr_noexcept {
// ensure we are called once
@ -550,6 +589,7 @@ void mi_process_init(void) mi_attr_noexcept {
_mi_verbose_message("debug level : %d\n", MI_DEBUG);
#endif
_mi_verbose_message("secure level: %d\n", MI_SECURE);
mi_debug_init();
mi_thread_init();
#if defined(_WIN32) && !defined(MI_SHARED_LIB)
@ -558,7 +598,7 @@ void mi_process_init(void) mi_attr_noexcept {
// will not call _mi_thread_done on the (still executing) main thread. See issue #508.
FlsSetValue(mi_fls_key, NULL);
#endif
mi_stats_reset(); // only call stat reset *after* thread init (or the heap tld == NULL)
if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) {
@ -573,7 +613,7 @@ void mi_process_init(void) mi_attr_noexcept {
if (mi_option_is_enabled(mi_option_reserve_os_memory)) {
long ksize = mi_option_get(mi_option_reserve_os_memory);
if (ksize > 0) {
mi_reserve_os_memory((size_t)ksize*MI_KiB, true, true);
mi_reserve_os_memory((size_t)ksize*MI_KiB, true /* commit? */, true /* allow large pages? */);
}
}
}
@ -604,6 +644,7 @@ static void mi_process_done(void) {
mi_stats_print(NULL);
}
mi_allocator_done();
mi_debug_done();
_mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id);
os_preloading = true; // don't call the C runtime anymore
}

View File

@ -49,51 +49,52 @@ typedef struct mi_option_desc_s {
mi_init_t init; // is it initialized yet? (from the environment)
mi_option_t option; // for debugging: the option index should match the option
const char* name; // option name without `mimalloc_` prefix
const char* legacy_name; // potential legacy v1.x option name
} mi_option_desc_t;
#define MI_OPTION(opt) mi_option_##opt, #opt
#define MI_OPTION_DESC(opt) {0, UNINIT, MI_OPTION(opt) }
#define MI_OPTION(opt) mi_option_##opt, #opt, NULL
#define MI_OPTION_LEGACY(opt,legacy) mi_option_##opt, #opt, #legacy
static mi_option_desc_t options[_mi_option_last] =
{
// stable options
#if MI_DEBUG || defined(MI_SHOW_ERRORS)
#if MI_DEBUG || defined(MI_SHOW_ERRORS)
{ 1, UNINIT, MI_OPTION(show_errors) },
#else
#else
{ 0, UNINIT, MI_OPTION(show_errors) },
#endif
#endif
{ 0, UNINIT, MI_OPTION(show_stats) },
{ 0, UNINIT, MI_OPTION(verbose) },
// the following options are experimental and not all combinations make sense.
{ 1, UNINIT, MI_OPTION(eager_commit) }, // commit per segment directly (4MiB) (but see also `eager_commit_delay`)
#if defined(_WIN32) || (MI_INTPTR_SIZE <= 4) // and other OS's without overcommit?
{ 0, UNINIT, MI_OPTION(eager_region_commit) },
{ 1, UNINIT, MI_OPTION(reset_decommits) }, // reset decommits memory
#else
{ 1, UNINIT, MI_OPTION(eager_region_commit) },
{ 0, UNINIT, MI_OPTION(reset_decommits) }, // reset uses MADV_FREE/MADV_DONTNEED
#endif
// Some of the following options are experimental and not all combinations are valid. Use with care.
{ 1, UNINIT, MI_OPTION(eager_commit) }, // commit per segment directly (8MiB) (but see also `eager_commit_delay`)
{ 0, UNINIT, MI_OPTION(deprecated_eager_region_commit) },
{ 0, UNINIT, MI_OPTION(deprecated_reset_decommits) },
{ 0, UNINIT, MI_OPTION(large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's
{ 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages
{ -1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) }, // reserve huge pages at node N
{ 0, UNINIT, MI_OPTION(reserve_os_memory) },
{ 0, UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread
{ 1, UNINIT, MI_OPTION(page_reset) }, // reset page memory on free
{ 0, UNINIT, MI_OPTION(abandoned_page_reset) },// reset free page memory when a thread terminates
{ 0, UNINIT, MI_OPTION(segment_reset) }, // reset segment memory on free (needs eager commit)
#if defined(__NetBSD__)
{ 0, UNINIT, MI_OPTION(page_reset) }, // reset page memory on free
{ 0, UNINIT, MI_OPTION_LEGACY(abandoned_page_decommit, abandoned_page_reset) },// decommit free page memory when a thread terminates
{ 0, UNINIT, MI_OPTION(deprecated_segment_reset) },
#if defined(__NetBSD__)
{ 0, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed
#else
#elif defined(_WIN32)
{ 4, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed (but per page in the segment on demand)
#else
{ 1, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed (but per page in the segment on demand)
#endif
{ 100, UNINIT, MI_OPTION(reset_delay) }, // reset delay in milli-seconds
{ 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.
{ 0, UNINIT, MI_OPTION(limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
{ 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
{ 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
{ 8, UNINIT, MI_OPTION(max_segment_reclaim)} // max. number of segment reclaims from the abandoned segments per try.
#endif
{ 25, UNINIT, MI_OPTION_LEGACY(decommit_delay, reset_delay) }, // page decommit delay in milli-seconds
{ 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.
{ 0, UNINIT, MI_OPTION(limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
{ 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
{ 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
{ 8, UNINIT, MI_OPTION(max_segment_reclaim)},// max. number of segment reclaims from the abandoned segments per try.
{ 1, UNINIT, MI_OPTION(allow_decommit) }, // decommit slices when no longer used (after decommit_delay milli-seconds)
{ 500, UNINIT, MI_OPTION(segment_decommit_delay) }, // decommit delay in milli-seconds for freed segments
{ 2, UNINIT, MI_OPTION(decommit_extend_delay) }
};
static void mi_option_init(mi_option_desc_t* desc);
@ -396,6 +397,93 @@ void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, co
}
#endif
// --------------------------------------------------------
// Stack traces
// --------------------------------------------------------
#if (MI_DEBUG_TRACE > 0) && defined(_WIN32)
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip) {
CaptureStackBackTrace((DWORD)skip + 1, (DWORD)len, strace, NULL);
}
#include <dbghelp.h>
#pragma comment(lib,"dbghelp")
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail) {
_mi_fprintf(NULL, NULL, "trace %s at %p of size %zu (%zub usable), allocated at:\n",
(msg==NULL ? "block" : msg), block, avail, bsize);
uintptr_t uninit = 0;
for( size_t i = 0; i < MI_INTPTR_SIZE; i++ ) {
uninit = (uninit << 8) | MI_DEBUG_UNINIT;
}
if (strace == NULL || uninit == (uintptr_t)strace[0]) {
_mi_fprintf(NULL, NULL, " (uninitialized trace)\n");
}
else {
PSYMBOL_INFO info = (PSYMBOL_INFO)_malloca(sizeof(SYMBOL_INFO) + 256 * sizeof(TCHAR));
if (info==NULL) return;
memset(info, 0, sizeof(info));
info->MaxNameLen = 255;
info->SizeOfStruct = sizeof(SYMBOL_INFO);
HANDLE current_process = GetCurrentProcess();
for (size_t i = 0; i < len && strace[i] != NULL; i++) {
if (SymFromAddr(current_process, (DWORD64)(strace[i]), 0, info)) {
_mi_fprintf(NULL, NULL, " %2zu: %8p: %s\n", i, strace[i], info->Name);
}
else {
_mi_fprintf(NULL, NULL, " %2zu: %8p: <unknown address: error: 0x%04x>\n", i, strace[i], GetLastError());
}
}
}
}
#elif (MI_DEBUG_TRACE > 0) && (defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__))
#include <execinfo.h>
#define MI_MAX_TRACE_LEN (64)
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip) {
if (_mi_preloading()) return;
if (!mi_recurse_enter()) return; // needed for pthreads
void* trace[MI_MAX_TRACE_LEN];
size_t trace_len = skip + len;
if (trace_len > len) { trace_len = MI_MAX_TRACE_LEN; }
memset(trace,0,trace_len);
trace_len = backtrace(trace, trace_len);
for (size_t i = 0; i < len; i++) {
void* p = (i + skip < trace_len ? trace[i+skip] : NULL);
strace[i] = p;
}
mi_recurse_exit();
}
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail) {
_mi_fprintf(NULL, NULL, "trace %s at %p of size %zu (%zub usable), allocated at:\n",
(msg==NULL ? "block" : msg), block, avail, bsize);
uintptr_t uninit = 0;
for( size_t i = 0; i < MI_INTPTR_SIZE; i++ ) {
uninit = (uninit << 8) | MI_DEBUG_UNINIT;
}
if (strace == NULL || uninit == (uintptr_t)strace[0]) {
_mi_fprintf(NULL, NULL, " (uninitialized trace)\n");
}
else {
while( len > 0 && strace[len-1] == NULL) { len--; }
if (len == 0) return;
char** names = backtrace_symbols(strace, len);
for (size_t i = 0; i < len && strace[i] != NULL; i++) {
_mi_fprintf(NULL, NULL, " %2zu: %8p: %s\n", i, strace[i], (names == NULL || names[i] == NULL ? "<unknown>" : names[i]));
}
// free(names); // avoid potential recursion and leak the trace
}
}
#else
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip) {
MI_UNUSED(strace); MI_UNUSED(len); MI_UNUSED(skip);
}
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail) {
MI_UNUSED(strace); MI_UNUSED(len); MI_UNUSED(block);
MI_UNUSED(bsize); MI_UNUSED(avail); MI_UNUSED(msg);
}
#endif
// --------------------------------------------------------
// Errors
// --------------------------------------------------------
@ -558,11 +646,21 @@ static bool mi_getenv(const char* name, char* result, size_t result_size) {
static void mi_option_init(mi_option_desc_t* desc) {
// Read option value from the environment
char s[64+1];
char buf[64+1];
mi_strlcpy(buf, "mimalloc_", sizeof(buf));
mi_strlcat(buf, desc->name, sizeof(buf));
char s[64+1];
if (mi_getenv(buf, s, sizeof(s))) {
bool found = mi_getenv(buf,s,sizeof(s));
if (!found && desc->legacy_name != NULL) {
mi_strlcpy(buf, "mimalloc_", sizeof(buf));
mi_strlcat(buf, desc->legacy_name, sizeof(buf));
found = mi_getenv(buf,s,sizeof(s));
if (found) {
_mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name );
}
}
if (found) {
size_t len = strlen(s);
if (len >= sizeof(buf)) len = sizeof(buf) - 1;
for (size_t i = 0; i < len; i++) {

View File

@ -74,17 +74,6 @@ static void* mi_align_up_ptr(void* p, size_t alignment) {
return (void*)_mi_align_up((uintptr_t)p, alignment);
}
static inline uintptr_t _mi_align_down(uintptr_t sz, size_t alignment) {
mi_assert_internal(alignment != 0);
uintptr_t mask = alignment - 1;
if ((alignment & mask) == 0) { // power of two?
return (sz & ~mask);
}
else {
return ((sz / alignment) * alignment);
}
}
static void* mi_align_down_ptr(void* p, size_t alignment) {
return (void*)_mi_align_down((uintptr_t)p, alignment);
}
@ -403,6 +392,9 @@ static bool mi_os_mem_free(void* addr, size_t size, bool was_committed, mi_stats
-------------------------------------------------------------- */
#ifdef _WIN32
#define MEM_COMMIT_RESERVE (MEM_COMMIT|MEM_RESERVE)
static void* mi_win_virtual_allocx(void* addr, size_t size, size_t try_alignment, DWORD flags) {
#if (MI_INTPTR_SIZE >= 8)
// on 64-bit systems, try to use the virtual address area after 2TiB for 4MiB aligned allocations
@ -587,6 +579,17 @@ static void* mi_unix_mmapx(void* addr, size_t size, size_t try_alignment, int pr
return NULL;
}
static int mi_unix_mmap_fd(void) {
#if defined(VM_MAKE_TAG)
// macOS: tracking anonymous page with a specific ID. (All up to 98 are taken officially but LLVM sanitizers had taken 99)
int os_tag = (int)mi_option_get(mi_option_os_tag);
if (os_tag < 100 || os_tag > 255) os_tag = 100;
return VM_MAKE_TAG(os_tag);
#else
return -1;
#endif
}
static void* mi_unix_mmap(void* addr, size_t size, size_t try_alignment, int protect_flags, bool large_only, bool allow_large, bool* is_large) {
void* p = NULL;
#if !defined(MAP_ANONYMOUS)
@ -595,20 +598,14 @@ static void* mi_unix_mmap(void* addr, size_t size, size_t try_alignment, int pro
#if !defined(MAP_NORESERVE)
#define MAP_NORESERVE 0
#endif
const int fd = mi_unix_mmap_fd();
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
int fd = -1;
if (_mi_os_has_overcommit()) {
flags |= MAP_NORESERVE;
}
#if defined(PROT_MAX)
protect_flags |= PROT_MAX(PROT_READ | PROT_WRITE); // BSD
#endif
#if defined(VM_MAKE_TAG)
// macOS: tracking anonymous page with a specific ID. (All up to 98 are taken officially but LLVM sanitizers had taken 99)
int os_tag = (int)mi_option_get(mi_option_os_tag);
if (os_tag < 100 || os_tag > 255) { os_tag = 100; }
fd = VM_MAKE_TAG(os_tag);
#endif
#endif
// huge page allocation
if ((large_only || use_large_os_page(size, try_alignment)) && allow_large) {
static _Atomic(size_t) large_page_try_ok; // = 0;
@ -969,9 +966,12 @@ bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* tld_stats) {
return mi_os_commitx(addr, size, false, true /* conservative */, &is_zero, stats);
}
/*
static bool mi_os_commit_unreset(void* addr, size_t size, bool* is_zero, mi_stats_t* stats) {
return mi_os_commitx(addr, size, true, true /* conservative */, is_zero, stats);
return mi_os_commitx(addr, size, true, true // conservative
, is_zero, stats);
}
*/
// Signal to the OS that the address range is no longer in use
// but may be used later again. This will release physical memory
@ -1034,14 +1034,10 @@ static bool mi_os_resetx(void* addr, size_t size, bool reset, mi_stats_t* stats)
bool _mi_os_reset(void* addr, size_t size, mi_stats_t* tld_stats) {
MI_UNUSED(tld_stats);
mi_stats_t* stats = &_mi_stats_main;
if (mi_option_is_enabled(mi_option_reset_decommits)) {
return _mi_os_decommit(addr, size, stats);
}
else {
return mi_os_resetx(addr, size, true, stats);
}
return mi_os_resetx(addr, size, true, stats);
}
/*
bool _mi_os_unreset(void* addr, size_t size, bool* is_zero, mi_stats_t* tld_stats) {
MI_UNUSED(tld_stats);
mi_stats_t* stats = &_mi_stats_main;
@ -1053,7 +1049,7 @@ bool _mi_os_unreset(void* addr, size_t size, bool* is_zero, mi_stats_t* tld_stat
return mi_os_resetx(addr, size, false, stats);
}
}
*/
// Protect a region in memory to be not accessible.
static bool mi_os_protectx(void* addr, size_t size, bool protect) {

View File

@ -34,15 +34,15 @@ terms of the MIT license. A copy of the license can be found in the file
static inline bool mi_page_queue_is_huge(const mi_page_queue_t* pq) {
return (pq->block_size == (MI_LARGE_OBJ_SIZE_MAX+sizeof(uintptr_t)));
return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+sizeof(uintptr_t)));
}
static inline bool mi_page_queue_is_full(const mi_page_queue_t* pq) {
return (pq->block_size == (MI_LARGE_OBJ_SIZE_MAX+(2*sizeof(uintptr_t))));
return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+(2*sizeof(uintptr_t))));
}
static inline bool mi_page_queue_is_special(const mi_page_queue_t* pq) {
return (pq->block_size > MI_LARGE_OBJ_SIZE_MAX);
return (pq->block_size > MI_MEDIUM_OBJ_SIZE_MAX);
}
/* -----------------------------------------------------------
@ -72,11 +72,11 @@ static inline uint8_t mi_bin(size_t size) {
bin = (uint8_t)wsize;
}
#endif
else if (wsize > MI_LARGE_OBJ_WSIZE_MAX) {
else if (wsize > MI_MEDIUM_OBJ_WSIZE_MAX) {
bin = MI_BIN_HUGE;
}
else {
#if defined(MI_ALIGN4W)
#if defined(MI_ALIGN4W)
if (wsize <= 16) { wsize = (wsize+3)&~3; } // round to 4x word sizes
#endif
wsize--;
@ -108,7 +108,7 @@ size_t _mi_bin_size(uint8_t bin) {
// Good size for allocation
size_t mi_good_size(size_t size) mi_attr_noexcept {
if (size <= MI_LARGE_OBJ_SIZE_MAX) {
if (size <= MI_MEDIUM_OBJ_SIZE_MAX) {
return _mi_bin_size(mi_bin(size));
}
else {
@ -206,8 +206,9 @@ static bool mi_page_queue_is_empty(mi_page_queue_t* queue) {
static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) {
mi_assert_internal(page != NULL);
mi_assert_expensive(mi_page_queue_contains(queue, page));
mi_assert_internal(page->xblock_size == queue->block_size || (page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_huge(queue)) || (mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
mi_assert_internal(page->xblock_size == queue->block_size || (page->xblock_size > MI_MEDIUM_OBJ_SIZE_MAX && mi_page_queue_is_huge(queue)) || (mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
mi_heap_t* heap = mi_page_heap(page);
if (page->prev != NULL) page->prev->next = page->next;
if (page->next != NULL) page->next->prev = page->prev;
if (page == queue->last) queue->last = page->prev;
@ -228,9 +229,10 @@ static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) {
static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
mi_assert_internal(mi_page_heap(page) == heap);
mi_assert_internal(!mi_page_queue_contains(queue, page));
mi_assert_internal(_mi_page_segment(page)->page_kind != MI_PAGE_HUGE);
mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
mi_assert_internal(page->xblock_size == queue->block_size ||
(page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_huge(queue)) ||
(page->xblock_size > MI_MEDIUM_OBJ_SIZE_MAX) ||
(mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
mi_page_set_in_full(page, mi_page_queue_is_full(queue));
@ -256,6 +258,7 @@ static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* fro
mi_assert_internal(page != NULL);
mi_assert_expensive(mi_page_queue_contains(from, page));
mi_assert_expensive(!mi_page_queue_contains(to, page));
mi_assert_internal((page->xblock_size == to->block_size && page->xblock_size == from->block_size) ||
(page->xblock_size == to->block_size && mi_page_queue_is_full(from)) ||
(page->xblock_size == from->block_size && mi_page_queue_is_full(to)) ||

View File

@ -74,10 +74,10 @@ static bool mi_page_is_valid_init(mi_page_t* page) {
mi_assert_internal(page->used <= page->capacity);
mi_assert_internal(page->capacity <= page->reserved);
const size_t bsize = mi_page_block_size(page);
mi_segment_t* segment = _mi_page_segment(page);
uint8_t* start = _mi_page_start(segment,page,NULL);
mi_assert_internal(start == _mi_segment_page_start(segment,page,bsize,NULL,NULL));
mi_assert_internal(start == _mi_segment_page_start(segment,page,NULL));
//const size_t bsize = mi_page_block_size(page);
//mi_assert_internal(start + page->capacity*page->block_size == page->top);
mi_assert_internal(mi_page_list_is_valid(page,page->free));
@ -110,11 +110,12 @@ bool _mi_page_is_valid(mi_page_t* page) {
#endif
if (mi_page_heap(page)!=NULL) {
mi_segment_t* segment = _mi_page_segment(page);
mi_assert_internal(!_mi_process_is_initialized || segment->thread_id == mi_page_heap(page)->thread_id || segment->thread_id==0);
if (segment->page_kind != MI_PAGE_HUGE) {
mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id);
if (segment->kind != MI_SEGMENT_HUGE) {
mi_page_queue_t* pq = mi_page_queue_of(page);
mi_assert_internal(mi_page_queue_contains(pq, page));
mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_LARGE_OBJ_SIZE_MAX || mi_page_is_in_full(page));
mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_in_full(page));
mi_assert_internal(mi_heap_contains_queue(mi_page_heap(page),pq));
}
}
@ -230,9 +231,10 @@ void _mi_page_free_collect(mi_page_t* page, bool force) {
// called from segments when reclaiming abandoned pages
void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) {
mi_assert_expensive(mi_page_is_valid_init(page));
mi_assert_internal(mi_page_heap(page) == heap);
mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE);
mi_assert_internal(_mi_page_segment(page)->page_kind != MI_PAGE_HUGE);
mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
mi_assert_internal(!page->is_reset);
// TODO: push on full queue immediately if it is full?
mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page));
@ -243,14 +245,12 @@ void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) {
// allocate a fresh page from a segment
static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size_t block_size) {
mi_assert_internal(pq==NULL||mi_heap_contains_queue(heap, pq));
mi_assert_internal(pq==NULL||block_size == pq->block_size);
mi_page_t* page = _mi_segment_page_alloc(heap, block_size, &heap->tld->segments, &heap->tld->os);
if (page == NULL) {
// this may be out-of-memory, or an abandoned page was reclaimed (and in our queue)
return NULL;
}
// a fresh page was found, initialize it
mi_assert_internal(pq==NULL || _mi_page_segment(page)->page_kind != MI_PAGE_HUGE);
mi_assert_internal(pq==NULL || _mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
mi_page_init(heap, page, block_size, heap->tld);
mi_heap_stat_increase(heap, pages, 1);
if (pq!=NULL) mi_page_queue_push(heap, pq, page); // huge pages use pq==NULL
@ -367,9 +367,11 @@ void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
// no more aligned blocks in here
mi_page_set_has_aligned(page, false);
mi_heap_t* heap = mi_page_heap(page);
// remove from the page list
// (no need to do _mi_heap_delayed_free first as all blocks are already free)
mi_segments_tld_t* segments_tld = &mi_page_heap(page)->tld->segments;
mi_segments_tld_t* segments_tld = &heap->tld->segments;
mi_page_queue_remove(pq, page);
// and free it
@ -377,7 +379,8 @@ void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
_mi_segment_page_free(page, force, segments_tld);
}
#define MI_MAX_RETIRE_SIZE MI_LARGE_OBJ_SIZE_MAX
// Retire parameters
#define MI_MAX_RETIRE_SIZE MI_MEDIUM_OBJ_SIZE_MAX
#define MI_RETIRE_CYCLES (8)
// Retire a page with no more used blocks
@ -390,7 +393,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
mi_assert_internal(page != NULL);
mi_assert_expensive(_mi_page_is_valid(page));
mi_assert_internal(mi_page_all_free(page));
mi_page_set_has_aligned(page, false);
// don't retire too often..
@ -403,7 +406,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
if mi_likely(page->xblock_size <= MI_MAX_RETIRE_SIZE && !mi_page_is_in_full(page)) {
if (pq->last==page && pq->first==page) { // the only page in the queue?
mi_stat_counter_increase(_mi_stats_main.page_no_retire,1);
page->retire_expire = (page->xblock_size <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);
page->retire_expire = 1 + (page->xblock_size <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);
mi_heap_t* heap = mi_page_heap(page);
mi_assert_internal(pq >= heap->pages);
const size_t index = pq - heap->pages;
@ -414,7 +417,6 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
return; // dont't free after all
}
}
_mi_page_free(page, pq, false);
}
@ -558,6 +560,7 @@ static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, co
// allocations but this did not speed up any benchmark (due to an
// extra test in malloc? or cache effects?)
static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) {
MI_UNUSED(tld);
mi_assert_expensive(mi_page_is_valid_init(page));
#if (MI_SECURE<=2)
mi_assert(page->free == NULL);
@ -567,7 +570,6 @@ static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld)
if (page->capacity >= page->reserved) return;
size_t page_size;
//uint8_t* page_start =
_mi_page_start(_mi_page_segment(page), page, &page_size);
mi_stat_counter_increase(tld->stats.pages_extended, 1);
@ -615,9 +617,11 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
mi_assert_internal(block_size > 0);
// set fields
mi_page_set_heap(page, heap);
page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE); // initialize before _mi_segment_page_start
size_t page_size;
_mi_segment_page_start(segment, page, block_size, &page_size, NULL);
page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE);
_mi_segment_page_start(segment, page, &page_size);
mi_assert_internal(mi_page_block_size(page) <= page_size);
mi_assert_internal(page_size <= page->slice_count*MI_SEGMENT_SLICE_SIZE);
mi_assert_internal(page_size / block_size < (1L<<16));
page->reserved = (uint16_t)(page_size / block_size);
#ifdef MI_ENCODE_FREELIST
@ -630,6 +634,8 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
page->is_zero = page->is_zero_init;
#endif
mi_assert_internal(page->is_committed);
mi_assert_internal(!page->is_reset);
mi_assert_internal(page->capacity == 0);
mi_assert_internal(page->free == NULL);
mi_assert_internal(page->used == 0);
@ -691,7 +697,7 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
mi_heap_stat_counter_increase(heap, searches, count);
if (page == NULL) {
_mi_heap_collect_retired(heap, false); // perhaps make a page available
_mi_heap_collect_retired(heap, false); // perhaps make a page available?
page = mi_page_fresh(heap, pq);
if (page == NULL && first_try) {
// out-of-memory _or_ an abandoned page with free blocks was reclaimed, try once again
@ -762,26 +768,35 @@ void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noex
General allocation
----------------------------------------------------------- */
// A huge page is allocated directly without being in a queue.
// Large and huge page allocation.
// Huge pages are allocated directly without being in a queue.
// Because huge pages contain just one block, and the segment contains
// just that page, we always treat them as abandoned and any thread
// that frees the block can free the whole page and segment directly.
static mi_page_t* mi_huge_page_alloc(mi_heap_t* heap, size_t size) {
static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size) {
size_t block_size = _mi_os_good_alloc_size(size);
mi_assert_internal(mi_bin(block_size) == MI_BIN_HUGE);
mi_page_t* page = mi_page_fresh_alloc(heap,NULL,block_size);
bool is_huge = (block_size > MI_LARGE_OBJ_SIZE_MAX);
mi_page_queue_t* pq = (is_huge ? NULL : mi_page_queue(heap, block_size));
mi_page_t* page = mi_page_fresh_alloc(heap, pq, block_size);
if (page != NULL) {
const size_t bsize = mi_page_block_size(page); // note: not `mi_page_usable_block_size` as `size` includes padding already
mi_assert_internal(bsize >= size);
mi_assert_internal(mi_page_immediate_available(page));
mi_assert_internal(_mi_page_segment(page)->page_kind==MI_PAGE_HUGE);
mi_assert_internal(_mi_page_segment(page)->used==1);
mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue
mi_page_set_heap(page, NULL);
if (bsize > MI_HUGE_OBJ_SIZE_MAX) {
mi_heap_stat_increase(heap, giant, bsize);
mi_heap_stat_counter_increase(heap, giant_count, 1);
if (pq == NULL) {
// huge pages are directly abandoned
mi_assert_internal(_mi_page_segment(page)->kind == MI_SEGMENT_HUGE);
mi_assert_internal(_mi_page_segment(page)->used==1);
mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue
mi_page_set_heap(page, NULL);
}
else {
mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
}
const size_t bsize = mi_page_usable_block_size(page); // note: not `mi_page_block_size` to account for padding
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
mi_heap_stat_increase(heap, large, bsize);
mi_heap_stat_counter_increase(heap, large_count, 1);
}
else {
mi_heap_stat_increase(heap, huge, bsize);
@ -797,13 +812,13 @@ static mi_page_t* mi_huge_page_alloc(mi_heap_t* heap, size_t size) {
static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size) mi_attr_noexcept {
// huge allocation?
const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size`
if mi_unlikely(req_size > (MI_LARGE_OBJ_SIZE_MAX - MI_PADDING_SIZE) ) {
if mi_unlikely(req_size > (MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE)) {
if mi_unlikely(req_size > PTRDIFF_MAX) { // we don't allocate more than PTRDIFF_MAX (see <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)
_mi_error_message(EOVERFLOW, "allocation request is too large (%zu bytes)\n", req_size);
return NULL;
}
else {
return mi_huge_page_alloc(heap,size);
return mi_large_huge_page_alloc(heap,size);
}
}
else {

360
src/segment-cache.c Normal file
View File

@ -0,0 +1,360 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2020, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
/* ----------------------------------------------------------------------------
Implements a cache of segments to avoid expensive OS calls and to reuse
the commit_mask to optimize the commit/decommit calls.
The full memory map of all segments is also implemented here.
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
#include "mimalloc-internal.h"
#include "mimalloc-atomic.h"
#include "bitmap.h" // atomic bitmap
//#define MI_CACHE_DISABLE 1 // define to completely disable the segment cache
#define MI_CACHE_FIELDS (16)
#define MI_CACHE_MAX (MI_BITMAP_FIELD_BITS*MI_CACHE_FIELDS) // 1024 on 64-bit
#define BITS_SET() MI_ATOMIC_VAR_INIT(UINTPTR_MAX)
#define MI_CACHE_BITS_SET MI_INIT16(BITS_SET) // note: update if MI_CACHE_FIELDS changes
typedef struct mi_cache_slot_s {
void* p;
size_t memid;
bool is_pinned;
mi_commit_mask_t commit_mask;
mi_commit_mask_t decommit_mask;
_Atomic(mi_msecs_t) expire;
} mi_cache_slot_t;
static mi_decl_cache_align mi_cache_slot_t cache[MI_CACHE_MAX]; // = 0
static mi_decl_cache_align mi_bitmap_field_t cache_available[MI_CACHE_FIELDS] = { MI_CACHE_BITS_SET }; // zero bit = available!
static mi_decl_cache_align mi_bitmap_field_t cache_available_large[MI_CACHE_FIELDS] = { MI_CACHE_BITS_SET };
static mi_decl_cache_align mi_bitmap_field_t cache_inuse[MI_CACHE_FIELDS]; // zero bit = free
mi_decl_noinline void* _mi_segment_cache_pop(size_t size, mi_commit_mask_t* commit_mask, mi_commit_mask_t* decommit_mask, bool* large, bool* is_pinned, bool* is_zero, size_t* memid, mi_os_tld_t* tld)
{
#ifdef MI_CACHE_DISABLE
return NULL;
#else
// only segment blocks
if (size != MI_SEGMENT_SIZE) return NULL;
// numa node determines start field
const int numa_node = _mi_os_numa_node(tld);
size_t start_field = 0;
if (numa_node > 0) {
start_field = (MI_CACHE_FIELDS / _mi_os_numa_node_count())*numa_node;
if (start_field >= MI_CACHE_FIELDS) start_field = 0;
}
// find an available slot
mi_bitmap_index_t bitidx = 0;
bool claimed = false;
if (*large) { // large allowed?
claimed = _mi_bitmap_try_find_from_claim(cache_available_large, MI_CACHE_FIELDS, start_field, 1, &bitidx);
if (claimed) *large = true;
}
if (!claimed) {
claimed = _mi_bitmap_try_find_from_claim(cache_available, MI_CACHE_FIELDS, start_field, 1, &bitidx);
if (claimed) *large = false;
}
if (!claimed) return NULL;
// found a slot
mi_cache_slot_t* slot = &cache[mi_bitmap_index_bit(bitidx)];
void* p = slot->p;
*memid = slot->memid;
*is_pinned = slot->is_pinned;
*is_zero = false;
*commit_mask = slot->commit_mask;
*decommit_mask = slot->decommit_mask;
slot->p = NULL;
mi_atomic_storei64_release(&slot->expire,(mi_msecs_t)0);
// mark the slot as free again
mi_assert_internal(_mi_bitmap_is_claimed(cache_inuse, MI_CACHE_FIELDS, 1, bitidx));
_mi_bitmap_unclaim(cache_inuse, MI_CACHE_FIELDS, 1, bitidx);
return p;
#endif
}
static mi_decl_noinline void mi_commit_mask_decommit(mi_commit_mask_t* cmask, void* p, size_t total, mi_stats_t* stats)
{
if (mi_commit_mask_is_empty(cmask)) {
// nothing
}
else if (mi_commit_mask_is_full(cmask)) {
_mi_os_decommit(p, total, stats);
}
else {
// todo: one call to decommit the whole at once?
mi_assert_internal((total%MI_COMMIT_MASK_BITS)==0);
size_t part = total/MI_COMMIT_MASK_BITS;
size_t idx;
size_t count;
mi_commit_mask_foreach(cmask, idx, count) {
void* start = (uint8_t*)p + (idx*part);
size_t size = count*part;
_mi_os_decommit(start, size, stats);
}
mi_commit_mask_foreach_end()
}
mi_commit_mask_create_empty(cmask);
}
#define MI_MAX_PURGE_PER_PUSH (4)
static mi_decl_noinline void mi_segment_cache_purge(bool force, mi_os_tld_t* tld)
{
MI_UNUSED(tld);
if (!mi_option_is_enabled(mi_option_allow_decommit)) return;
mi_msecs_t now = _mi_clock_now();
size_t purged = 0;
const size_t max_visits = (force ? MI_CACHE_MAX /* visit all */ : MI_CACHE_FIELDS /* probe at most N (=16) slots */);
size_t idx = (force ? 0 : _mi_random_shuffle((uintptr_t)now) % MI_CACHE_MAX /* random start */ );
for (size_t visited = 0; visited < max_visits; visited++,idx++) { // visit N slots
if (idx >= MI_CACHE_MAX) idx = 0; // wrap
mi_cache_slot_t* slot = &cache[idx];
mi_msecs_t expire = mi_atomic_loadi64_relaxed(&slot->expire);
if (expire != 0 && (force || now >= expire)) { // racy read
// seems expired, first claim it from available
purged++;
mi_bitmap_index_t bitidx = mi_bitmap_index_create_from_bit(idx);
if (_mi_bitmap_claim(cache_available, MI_CACHE_FIELDS, 1, bitidx, NULL)) {
// was available, we claimed it
expire = mi_atomic_loadi64_acquire(&slot->expire);
if (expire != 0 && (force || now >= expire)) { // safe read
// still expired, decommit it
mi_atomic_storei64_relaxed(&slot->expire,(mi_msecs_t)0);
mi_assert_internal(!mi_commit_mask_is_empty(&slot->commit_mask) && _mi_bitmap_is_claimed(cache_available_large, MI_CACHE_FIELDS, 1, bitidx));
_mi_abandoned_await_readers(); // wait until safe to decommit
// decommit committed parts
// TODO: instead of decommit, we could also free to the OS?
mi_commit_mask_decommit(&slot->commit_mask, slot->p, MI_SEGMENT_SIZE, tld->stats);
mi_commit_mask_create_empty(&slot->decommit_mask);
}
_mi_bitmap_unclaim(cache_available, MI_CACHE_FIELDS, 1, bitidx); // make it available again for a pop
}
if (!force && purged > MI_MAX_PURGE_PER_PUSH) break; // bound to no more than N purge tries per push
}
}
}
void _mi_segment_cache_collect(bool force, mi_os_tld_t* tld) {
mi_segment_cache_purge(force, tld );
}
mi_decl_noinline bool _mi_segment_cache_push(void* start, size_t size, size_t memid, const mi_commit_mask_t* commit_mask, const mi_commit_mask_t* decommit_mask, bool is_large, bool is_pinned, mi_os_tld_t* tld)
{
#ifdef MI_CACHE_DISABLE
return false;
#else
// only for normal segment blocks
if (size != MI_SEGMENT_SIZE || ((uintptr_t)start % MI_SEGMENT_ALIGN) != 0) return false;
// numa node determines start field
int numa_node = _mi_os_numa_node(NULL);
size_t start_field = 0;
if (numa_node > 0) {
start_field = (MI_CACHE_FIELDS / _mi_os_numa_node_count())*numa_node;
if (start_field >= MI_CACHE_FIELDS) start_field = 0;
}
// purge expired entries
mi_segment_cache_purge(false /* force? */, tld);
// find an available slot
mi_bitmap_index_t bitidx;
bool claimed = _mi_bitmap_try_find_from_claim(cache_inuse, MI_CACHE_FIELDS, start_field, 1, &bitidx);
if (!claimed) return false;
mi_assert_internal(_mi_bitmap_is_claimed(cache_available, MI_CACHE_FIELDS, 1, bitidx));
mi_assert_internal(_mi_bitmap_is_claimed(cache_available_large, MI_CACHE_FIELDS, 1, bitidx));
#if MI_DEBUG>1
if (is_pinned || is_large) {
mi_assert_internal(mi_commit_mask_is_full(commit_mask));
}
#endif
// set the slot
mi_cache_slot_t* slot = &cache[mi_bitmap_index_bit(bitidx)];
slot->p = start;
slot->memid = memid;
slot->is_pinned = is_pinned;
mi_atomic_storei64_relaxed(&slot->expire,(mi_msecs_t)0);
slot->commit_mask = *commit_mask;
slot->decommit_mask = *decommit_mask;
if (!mi_commit_mask_is_empty(commit_mask) && !is_large && !is_pinned && mi_option_is_enabled(mi_option_allow_decommit)) {
long delay = mi_option_get(mi_option_segment_decommit_delay);
if (delay == 0) {
_mi_abandoned_await_readers(); // wait until safe to decommit
mi_commit_mask_decommit(&slot->commit_mask, start, MI_SEGMENT_SIZE, tld->stats);
mi_commit_mask_create_empty(&slot->decommit_mask);
}
else {
mi_atomic_storei64_release(&slot->expire, _mi_clock_now() + delay);
}
}
// make it available
_mi_bitmap_unclaim((is_large ? cache_available_large : cache_available), MI_CACHE_FIELDS, 1, bitidx);
return true;
#endif
}
/* -----------------------------------------------------------
The following functions are to reliably find the segment or
block that encompasses any pointer p (or NULL if it is not
in any of our segments).
We maintain a bitmap of all memory with 1 bit per MI_SEGMENT_SIZE (64MiB)
set to 1 if it contains the segment meta data.
----------------------------------------------------------- */
#if (MI_INTPTR_SIZE==8)
#define MI_MAX_ADDRESS ((size_t)20 << 40) // 20TB
#else
#define MI_MAX_ADDRESS ((size_t)2 << 30) // 2Gb
#endif
#define MI_SEGMENT_MAP_BITS (MI_MAX_ADDRESS / MI_SEGMENT_SIZE)
#define MI_SEGMENT_MAP_SIZE (MI_SEGMENT_MAP_BITS / 8)
#define MI_SEGMENT_MAP_WSIZE (MI_SEGMENT_MAP_SIZE / MI_INTPTR_SIZE)
static _Atomic(uintptr_t) mi_segment_map[MI_SEGMENT_MAP_WSIZE + 1]; // 2KiB per TB with 64MiB segments
static size_t mi_segment_map_index_of(const mi_segment_t* segment, size_t* bitidx) {
mi_assert_internal(_mi_ptr_segment(segment) == segment); // is it aligned on MI_SEGMENT_SIZE?
if ((uintptr_t)segment >= MI_MAX_ADDRESS) {
*bitidx = 0;
return MI_SEGMENT_MAP_WSIZE;
}
else {
const uintptr_t segindex = ((uintptr_t)segment) / MI_SEGMENT_SIZE;
*bitidx = segindex % MI_INTPTR_BITS;
const size_t mapindex = segindex / MI_INTPTR_BITS;
mi_assert_internal(mapindex < MI_SEGMENT_MAP_WSIZE);
return mapindex;
}
}
void _mi_segment_map_allocated_at(const mi_segment_t* segment) {
size_t bitidx;
size_t index = mi_segment_map_index_of(segment, &bitidx);
mi_assert_internal(index <= MI_SEGMENT_MAP_WSIZE);
if (index==MI_SEGMENT_MAP_WSIZE) return;
uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]);
uintptr_t newmask;
do {
newmask = (mask | ((uintptr_t)1 << bitidx));
} while (!mi_atomic_cas_weak_release(&mi_segment_map[index], &mask, newmask));
}
void _mi_segment_map_freed_at(const mi_segment_t* segment) {
size_t bitidx;
size_t index = mi_segment_map_index_of(segment, &bitidx);
mi_assert_internal(index <= MI_SEGMENT_MAP_WSIZE);
if (index == MI_SEGMENT_MAP_WSIZE) return;
uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]);
uintptr_t newmask;
do {
newmask = (mask & ~((uintptr_t)1 << bitidx));
} while (!mi_atomic_cas_weak_release(&mi_segment_map[index], &mask, newmask));
}
// Determine the segment belonging to a pointer or NULL if it is not in a valid segment.
static mi_segment_t* _mi_segment_of(const void* p) {
mi_segment_t* segment = _mi_ptr_segment(p);
if (segment == NULL) return NULL;
size_t bitidx;
size_t index = mi_segment_map_index_of(segment, &bitidx);
// fast path: for any pointer to valid small/medium/large object or first MI_SEGMENT_SIZE in huge
const uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]);
if mi_likely((mask & ((uintptr_t)1 << bitidx)) != 0) {
return segment; // yes, allocated by us
}
if (index==MI_SEGMENT_MAP_WSIZE) return NULL;
// TODO: maintain max/min allocated range for efficiency for more efficient rejection of invalid pointers?
// search downwards for the first segment in case it is an interior pointer
// could be slow but searches in MI_INTPTR_SIZE * MI_SEGMENT_SIZE (512MiB) steps trough
// valid huge objects
// note: we could maintain a lowest index to speed up the path for invalid pointers?
size_t lobitidx;
size_t loindex;
uintptr_t lobits = mask & (((uintptr_t)1 << bitidx) - 1);
if (lobits != 0) {
loindex = index;
lobitidx = mi_bsr(lobits); // lobits != 0
}
else if (index == 0) {
return NULL;
}
else {
mi_assert_internal(index > 0);
uintptr_t lomask = mask;
loindex = index;
do {
loindex--;
lomask = mi_atomic_load_relaxed(&mi_segment_map[loindex]);
} while (lomask != 0 && loindex > 0);
if (lomask == 0) return NULL;
lobitidx = mi_bsr(lomask); // lomask != 0
}
mi_assert_internal(loindex < MI_SEGMENT_MAP_WSIZE);
// take difference as the addresses could be larger than the MAX_ADDRESS space.
size_t diff = (((index - loindex) * (8*MI_INTPTR_SIZE)) + bitidx - lobitidx) * MI_SEGMENT_SIZE;
segment = (mi_segment_t*)((uint8_t*)segment - diff);
if (segment == NULL) return NULL;
mi_assert_internal((void*)segment < p);
bool cookie_ok = (_mi_ptr_cookie(segment) == segment->cookie);
mi_assert_internal(cookie_ok);
if mi_unlikely(!cookie_ok) return NULL;
if (((uint8_t*)segment + mi_segment_size(segment)) <= (uint8_t*)p) return NULL; // outside the range
mi_assert_internal(p >= (void*)segment && (uint8_t*)p < (uint8_t*)segment + mi_segment_size(segment));
return segment;
}
// Is this a valid pointer in our heap?
static bool mi_is_valid_pointer(const void* p) {
return (_mi_segment_of(p) != NULL);
}
mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {
return mi_is_valid_pointer(p);
}
/*
// Return the full segment range belonging to a pointer
static void* mi_segment_range_of(const void* p, size_t* size) {
mi_segment_t* segment = _mi_segment_of(p);
if (segment == NULL) {
if (size != NULL) *size = 0;
return NULL;
}
else {
if (size != NULL) *size = segment->segment_size;
return segment;
}
mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld));
mi_assert_internal(page == NULL || (mi_segment_page_size(_mi_page_segment(page)) - (MI_SECURE == 0 ? 0 : _mi_os_page_size())) >= block_size);
mi_reset_delayed(tld);
mi_assert_internal(page == NULL || mi_page_not_in_queue(page, tld));
return page;
}
*/

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ terms of the MIT license. A copy of the license can be found in the file
#include "os.c"
#include "bitmap.c"
#include "arena.c"
#include "region.c"
#include "segment-cache.c"
#include "segment.c"
#include "page.c"
#include "heap.c"

View File

@ -105,7 +105,7 @@ static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
mi_stat_add(&stats->segments_cache, &src->segments_cache, 1);
mi_stat_add(&stats->normal, &src->normal, 1);
mi_stat_add(&stats->huge, &src->huge, 1);
mi_stat_add(&stats->giant, &src->giant, 1);
mi_stat_add(&stats->large, &src->large, 1);
mi_stat_counter_add(&stats->pages_extended, &src->pages_extended, 1);
mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls, 1);
@ -115,7 +115,7 @@ static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
mi_stat_counter_add(&stats->searches, &src->searches, 1);
mi_stat_counter_add(&stats->normal_count, &src->normal_count, 1);
mi_stat_counter_add(&stats->huge_count, &src->huge_count, 1);
mi_stat_counter_add(&stats->giant_count, &src->giant_count, 1);
mi_stat_counter_add(&stats->large_count, &src->large_count, 1);
#if MI_STAT>1
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
if (src->normal_bins[i].allocated > 0 || src->normal_bins[i].freed > 0) {
@ -300,12 +300,12 @@ static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0)
#endif
#if MI_STAT
mi_stat_print(&stats->normal, "normal", (stats->normal_count.count == 0 ? 1 : -(stats->normal.allocated / stats->normal_count.count)), out, arg);
mi_stat_print(&stats->large, "large", (stats->large_count.count == 0 ? 1 : -(stats->large.allocated / stats->large_count.count)), out, arg);
mi_stat_print(&stats->huge, "huge", (stats->huge_count.count == 0 ? 1 : -(stats->huge.allocated / stats->huge_count.count)), out, arg);
mi_stat_print(&stats->giant, "giant", (stats->giant_count.count == 0 ? 1 : -(stats->giant.allocated / stats->giant_count.count)), out, arg);
mi_stat_count_t total = { 0,0,0,0 };
mi_stat_add(&total, &stats->normal, 1);
mi_stat_add(&total, &stats->large, 1);
mi_stat_add(&total, &stats->huge, 1);
mi_stat_add(&total, &stats->giant, 1);
mi_stat_print(&total, "total", 1, out, arg);
#endif
#if MI_STAT>1

View File

@ -15,8 +15,9 @@ if (NOT CMAKE_BUILD_TYPE)
endif()
endif()
# Import mimalloc (if installed)
find_package(mimalloc 1.7 REQUIRED NO_SYSTEM_ENVIRONMENT_PATH)
find_package(mimalloc 2.0 REQUIRED NO_SYSTEM_ENVIRONMENT_PATH)
message(STATUS "Found mimalloc installed at: ${MIMALLOC_LIBRARY_DIR} (${MIMALLOC_VERSION_DIR})")
# overriding with a dynamic library

View File

@ -7,10 +7,14 @@
#include <mimalloc.h>
#include <mimalloc-override.h> // redefines malloc etc.
static void double_free1();
static void double_free2();
static void corrupt_free();
static void double_free3();
static void corrupt_free1();
static void corrupt_free2();
static void block_overflow1();
static void block_overflow2();
static void invalid_free();
static void test_aslr(void);
static void test_process_info(void);
@ -19,21 +23,23 @@ static void negative_stat(void);
static void alloc_huge(void);
static void test_heap_walk(void);
int main() {
mi_version();
mi_stats_reset();
// detect double frees and heap corruption
// double_free1();
// double_free2();
// corrupt_free();
double_free3();
// corrupt_free1();
// corrupt_free2();
// block_overflow1();
// block_overflow2();
// test_aslr();
// invalid_free();
// test_reserved();
// negative_stat();
test_heap_walk();
// alloc_huge();
test_heap_walk();
void* p1 = malloc(78);
void* p2 = malloc(24);
@ -47,7 +53,7 @@ int main() {
free(p1);
free(p2);
free(s);
/* now test if override worked by allocating/freeing across the api's*/
//p1 = mi_malloc(32);
//free(p1);
@ -63,7 +69,8 @@ int main() {
static void invalid_free() {
free((void*)0xBADBEEF);
realloc((void*)0xBADBEEF,10);
void* p = realloc((void*)0xBADBEEF,10);
free(p);
}
static void block_overflow1() {
@ -72,6 +79,15 @@ static void block_overflow1() {
free(p);
}
#define OVF_SIZE 100
static void block_overflow2() {
uint8_t* p = (uint8_t*)mi_malloc(30);
memset(p+30, 0, OVF_SIZE);
free(p);
}
// The double free samples come ArcHeap [1] by Insu Yun (issue #161)
// [1]: https://arxiv.org/pdf/1903.00503.pdf
@ -109,12 +125,35 @@ static void double_free2() {
fprintf(stderr, "p1: %p-%p, p2: %p-%p\n", p[4], (uint8_t*)(p[4]) + 917504, p[1], (uint8_t*)(p[1]) + 786432);
}
static void double_free3() {
void* p1 = malloc(32);
void* p2 = malloc(32);
void* p3 = malloc(32);
free(p2);
free(p1);
free(p2);
free(p3);
}
static void corrupt_free1() {
void* p1 = malloc(32);
void* p2 = malloc(32);
void* p3 = malloc(32);
free(p2);
memset(p2, 0, 8); // corrupt free list entry
mi_collect(true);
p2 = malloc(32); // should trigger corrupted free list
free(p1);
free(p2);
free(p3);
}
// Try to corrupt the heap through buffer overflow
#define N 256
#define SZ 64
#define OVF_SZ 32
static void corrupt_free() {
static void corrupt_free2() {
void* p[N];
// allocate
for (int i = 0; i < N; i++) {
@ -128,13 +167,18 @@ static void corrupt_free() {
// try to corrupt the free list
for (int i = 0; i < N; i++) {
if (p[i] != NULL) {
memset(p[i], 0, SZ+8);
memset(p[i], 0, SZ+OVF_SZ);
}
}
// allocate more.. trying to trigger an allocation from a corrupted entry
// this may need many allocations to get there (if at all)
for (int i = 0; i < 4096; i++) {
malloc(SZ);
void* p = malloc(SZ);
}
// free the rest
for (int i = 0; i < N; i++) {
free(p[i]);
p[i] = NULL;
}
}
@ -181,7 +225,7 @@ static void test_reserved(void) {
static void negative_stat(void) {
int* p = mi_malloc(60000);
int* p = (int*)mi_malloc(60000);
mi_stats_print_out(NULL, NULL);
*p = 100;
mi_free(p);
@ -383,4 +427,3 @@ static void mi_bins(void) {
}
}
#endif

View File

@ -32,22 +32,29 @@ static void heap_late_free(); // issue #204
static void padding_shrink(); // issue #209
static void various_tests();
static void test_mt_shutdown();
static void large_alloc(void); // issue #363
static void fail_aslr(); // issue #372
static void tsan_numa_test(); // issue #414
static void strdup_test(); // issue #445
static void strdup_test(); // issue #445
static void bench_alloc_large(void); // issue #xxx
static void corrupt_free();
int main() {
mi_stats_reset(); // ignore earlier allocations
heap_thread_free_large();
heap_no_delete();
heap_late_free();
padding_shrink();
various_tests();
large_alloc();
tsan_numa_test();
strdup_test();
// corrupt_free();
test_mt_shutdown();
//fail_aslr();
bench_alloc_large();
mi_stats_print(NULL);
return 0;
}
@ -188,7 +195,7 @@ static void heap_thread_free_large_worker() {
static void heap_thread_free_large() {
for (int i = 0; i < 100; i++) {
shared_p = mi_malloc_aligned(2*1024*1024 + 1, 8);
shared_p = mi_malloc_aligned(2 * 1024 * 1024 + 1, 8);
auto t1 = std::thread(heap_thread_free_large_worker);
t1.join();
}
@ -220,6 +227,18 @@ static void test_mt_shutdown()
std::cout << "done" << std::endl;
}
// issue #363
using namespace std;
void large_alloc(void)
{
char* a = new char[1ull << 25];
thread th([&] {
delete[] a;
});
th.join();
}
// issue #372
static void fail_aslr() {
size_t sz = (4ULL << 40); // 4TiB
@ -231,11 +250,77 @@ static void fail_aslr() {
// issues #414
static void dummy_worker() {
void* p = mi_malloc(0);
mi_free(p);
mi_free(p);
}
static void tsan_numa_test() {
auto t1 = std::thread(dummy_worker);
dummy_worker();
t1.join();
}
}
// Try to corrupt the heap through buffer overflow
#define N 256
#define SZ 64
#define OVF_SZ 32
static void corrupt_free() {
void* p[N];
// allocate
for (int i = 0; i < N; i++) {
p[i] = malloc(SZ);
}
// free some
for (int i = 0; i < N; i += (N/10)) {
free(p[i]);
p[i] = NULL;
}
// try to corrupt the free list
for (int i = 0; i < N; i++) {
if (p[i] != NULL) {
memset(p[i], 0, SZ+OVF_SZ);
}
}
// allocate more.. trying to trigger an allocation from a corrupted entry
// this may need many allocations to get there (if at all)
for (int i = 0; i < 4096; i++) {
malloc(SZ);
}
// free the rest
for (int i = 0; i < N; i++) {
free(p[i]);
p[i] = NULL;
}
}
// issue #?
#include <chrono>
#include <random>
#include <iostream>
static void bench_alloc_large(void) {
static constexpr int kNumBuffers = 20;
static constexpr size_t kMinBufferSize = 5 * 1024 * 1024;
static constexpr size_t kMaxBufferSize = 25 * 1024 * 1024;
std::unique_ptr<char[]> buffers[kNumBuffers];
std::random_device rd;
std::mt19937 gen(42); //rd());
std::uniform_int_distribution<> size_distribution(kMinBufferSize, kMaxBufferSize);
std::uniform_int_distribution<> buf_number_distribution(0, kNumBuffers - 1);
static constexpr int kNumIterations = 2000;
const auto start = std::chrono::steady_clock::now();
for (int i = 0; i < kNumIterations; ++i) {
int buffer_idx = buf_number_distribution(gen);
size_t new_size = size_distribution(gen);
buffers[buffer_idx] = std::make_unique<char[]>(new_size);
}
const auto end = std::chrono::steady_clock::now();
const auto num_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
const auto us_per_allocation = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() / kNumIterations;
std::cout << kNumIterations << " allocations Done in " << num_ms << "ms." << std::endl;
std::cout << "Avg " << us_per_allocation << " us per allocation" << std::endl;
}

37
test/test-overflow.cpp Normal file
View File

@ -0,0 +1,37 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <new>
#include <vector>
#include <future>
#include <iostream>
#include <thread>
#include <assert.h>
static void block_overflow1(void) {
uint8_t* p = (uint8_t*)malloc(17);
p[18] = 0;
free(p);
uint8_t* q = (uint8_t*)malloc(17);
free(p);
free(q);
}
#define OVF_SIZE 100
static void block_overflow2(void) {
uint8_t* p = (uint8_t*)malloc(30);
memset(p+30, 0, OVF_SIZE);
free(p);
}
int main() {
printf("test overflow..\n");
block_overflow1();
block_overflow2();
printf("done..\n");
return 0;
}

View File

@ -39,12 +39,12 @@ static size_t use_one_size = 0; // use single object size of `N * s
// #define USE_STD_MALLOC
#ifdef USE_STD_MALLOC
#define custom_calloc(n,s) calloc(n,s)
#define custom_calloc(n,s) malloc(n*s)
#define custom_realloc(p,s) realloc(p,s)
#define custom_free(p) free(p)
#else
#include <mimalloc.h>
#define custom_calloc(n,s) mi_calloc(n,s)
#define custom_calloc(n,s) mi_malloc(n*s)
#define custom_realloc(p,s) mi_realloc(p,s)
#define custom_free(p) mi_free(p)
#endif
@ -103,7 +103,8 @@ static void* alloc_items(size_t items, random_t r) {
for (uintptr_t i = 0; i < items; i++) {
p[i] = (items - i) ^ cookie;
}
}
// if (pick(r)%1000 <= 1) { p[items+1] = 42; } // overflow heap block
}
return p;
}
@ -182,17 +183,20 @@ static void run_os_threads(size_t nthreads, void (*entry)(intptr_t tid));
static void test_stress(void) {
uintptr_t r = rand();
for (int n = 0; n < ITER; n++) {
run_os_threads(THREADS, &stress);
run_os_threads(THREADS, &stress);
for (int i = 0; i < TRANSFERS; i++) {
if (chance(50, &r) || n + 1 == ITER) { // free all on last run, otherwise free half of the transfers
void* p = atomic_exchange_ptr(&transfer[i], NULL);
free_items(p);
}
}
// mi_collect(false);
#if !defined(NDEBUG) || defined(MI_TSAN)
#ifndef NDEBUG
//mi_collect(false);
//mi_debug_show_arenas();
#endif
#if !defined(NDEBUG) || defined(MI_TSAN)
if ((n + 1) % 10 == 0) { printf("- iterations left: %3d\n", ITER - (n + 1)); }
#endif
#endif
}
}
@ -244,16 +248,23 @@ int main(int argc, char** argv) {
// Run ITER full iterations where half the objects in the transfer buffer survive to the next round.
srand(0x7feb352d);
// mi_stats_reset();
//mi_reserve_os_memory(512ULL << 20, true, true);
#if !defined(NDEBUG) && !defined(USE_STD_MALLOC)
mi_stats_reset();
#endif
#ifdef STRESS
test_stress();
test_stress();
#else
test_leak();
test_leak();
#endif
#ifndef USE_STD_MALLOC
#ifndef NDEBUG
mi_collect(true);
//mi_debug_show_arenas();
#endif
mi_stats_print(NULL);
#endif