diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
 
 (and `vector` where relevant)
 
-Benchmarks below are produced under GHC 9.2.5, and can be reproduced locally with
+Benchmarks below are produced under GHC 9.4.8, and can be reproduced locally with
 
 ```shellsession
 cabal bench --benchmark-options="--csv results.csv"
@@ -21,91 +21,172 @@
 (You will need the `bench-show` tool, which is available from Hackage.)
 
 
-```
-Benchmark                                                          default(ns)
------------------------------------------------------------------ ------------
-Comparison/1000/insert/hashtables basic                               47112.15
-Comparison/1000/insert/vector-hashtables boxed                        22941.55
-Comparison/1000/insert/vector-hashtables unboxed keys                 22338.54
-Comparison/1000/insert/vector-hashtables                              19460.33
-Comparison/1000/insert/mutable vector boxed                            3216.07
-Comparison/1000/insert/mutable vector                                   659.46
-Comparison/1000/insert (resize)/hashtables basic                     158026.86
-Comparison/1000/insert (resize)/vector-hashtables boxed               42164.21
-Comparison/1000/insert (resize)/vector-hashtables unboxed keys        40334.65
-Comparison/1000/insert (resize)/vector-hashtables                     37761.08
-Comparison/1000/insert, delete/hashtables basic                       85218.96
-Comparison/1000/insert, delete/vector-hashtables                      36199.84
-Comparison/1000/find/hashtables basic                                 33857.19
-Comparison/1000/find/vector-hashtables                                14205.24
-Comparison/1000/find/vector-hashtables (frozen)                       12257.88
-Comparison/1000/lookupIndex/hashtables basic                          31842.70
-Comparison/1000/lookupIndex/vector-hashtables                         14140.17
-Comparison/1000/fromList/hashtables basic                            161048.28
-Comparison/1000/fromList/vector-hashtables                            48052.28
-Comparison/1000/toList/hashtables basic                                9569.82
-Comparison/1000/toList/vector-hashtables                              12600.58
-Comparison/10000/insert/hashtables basic                             983062.29
-Comparison/10000/insert/vector-hashtables boxed                      225218.39
-Comparison/10000/insert/vector-hashtables unboxed keys               212758.35
-Comparison/10000/insert/vector-hashtables                            186573.02
-Comparison/10000/insert/mutable vector boxed                          37415.65
-Comparison/10000/insert/mutable vector                                 6956.86
-Comparison/10000/insert (resize)/hashtables basic                   1696364.18
-Comparison/10000/insert (resize)/vector-hashtables boxed             479122.17
-Comparison/10000/insert (resize)/vector-hashtables unboxed keys      422759.56
-Comparison/10000/insert (resize)/vector-hashtables                   352441.24
-Comparison/10000/insert, delete/hashtables basic                    1426337.13
-Comparison/10000/insert, delete/vector-hashtables                    359228.91
-Comparison/10000/find/hashtables basic                               330890.67
-Comparison/10000/find/vector-hashtables                              141525.60
-Comparison/10000/find/vector-hashtables (frozen)                     122542.86
-Comparison/10000/lookupIndex/hashtables basic                        327065.33
-Comparison/10000/lookupIndex/vector-hashtables                       142678.55
-Comparison/10000/fromList/hashtables basic                          1641384.65
-Comparison/10000/fromList/vector-hashtables                          542530.31
-Comparison/10000/toList/hashtables basic                             143188.55
-Comparison/10000/toList/vector-hashtables                            274318.45
-Comparison/100000/insert/hashtables basic                          10907947.88
-Comparison/100000/insert/vector-hashtables boxed                    6932748.81
-Comparison/100000/insert/vector-hashtables unboxed keys             5307166.72
-Comparison/100000/insert/vector-hashtables                          2368482.76
-Comparison/100000/insert/mutable vector boxed                       1794351.95
-Comparison/100000/insert/mutable vector                               69989.51
-Comparison/100000/insert (resize)/hashtables basic                 22232168.04
-Comparison/100000/insert (resize)/vector-hashtables boxed          11420001.24
-Comparison/100000/insert (resize)/vector-hashtables unboxed keys    9496935.97
-Comparison/100000/insert (resize)/vector-hashtables                 5988321.89
-Comparison/100000/insert, delete/hashtables basic                  15119684.92
-Comparison/100000/insert, delete/vector-hashtables                  4058662.48
-Comparison/100000/find/hashtables basic                             3473800.70
-Comparison/100000/find/vector-hashtables                            1431873.00
-Comparison/100000/find/vector-hashtables (frozen)                   1225155.68
-Comparison/100000/lookupIndex/hashtables basic                      3235041.49
-Comparison/100000/lookupIndex/vector-hashtables                     1439338.81
-Comparison/100000/fromList/hashtables basic                        22917643.59
-Comparison/100000/fromList/vector-hashtables                        8603353.43
-Comparison/100000/toList/hashtables basic                           5336633.37
-Comparison/100000/toList/vector-hashtables                          9377042.71
-Comparison/1000000/insert/hashtables basic                        109193301.77
-Comparison/1000000/insert/vector-hashtables boxed                  77176586.83
-Comparison/1000000/insert/vector-hashtables unboxed keys           59037764.29
-Comparison/1000000/insert/vector-hashtables                        28535981.51
-Comparison/1000000/insert/mutable vector boxed                     29709444.79
-Comparison/1000000/insert/mutable vector                             866220.55
-Comparison/1000000/insert (resize)/hashtables basic               260585504.79
-Comparison/1000000/insert (resize)/vector-hashtables boxed        127608418.79
-Comparison/1000000/insert (resize)/vector-hashtables unboxed keys  96059752.01
-Comparison/1000000/insert (resize)/vector-hashtables               57038345.24
-Comparison/1000000/insert, delete/hashtables basic                153182769.34
-Comparison/1000000/insert, delete/vector-hashtables                47664402.90
-Comparison/1000000/find/hashtables basic                           35821229.20
-Comparison/1000000/find/vector-hashtables                          15711648.56
-Comparison/1000000/find/vector-hashtables (frozen)                 12885372.25
-Comparison/1000000/lookupIndex/hashtables basic                    32210529.62
-Comparison/1000000/lookupIndex/vector-hashtables                   14911308.87
-Comparison/1000000/fromList/hashtables basic                      446988819.28
-Comparison/1000000/fromList/vector-hashtables                      92793579.75
-Comparison/1000000/toList/hashtables basic                         66679253.65
-Comparison/1000000/toList/vector-hashtables                       105470198.31
-```
+| Benchmark | v.0.1.1.4 (ns) | current (ns) |
+| ----------------------------------------------------------------- | ------------ | ------------ | 
+| Comparison/1000/insert/hashtables basic                              | 36083.57 | 36907.68 |
+| Comparison/1000/insert/vector-hashtables boxed                       | 25735.13 | 17055.86 |
+| Comparison/1000/insert/vector-hashtables unboxed keys                | 23601.08 | 15333.05 |
+| Comparison/1000/insert/vector-hashtables                             | 25298.67 | 13881.72 |
+| Comparison/1000/insert/mutable vector boxed                           | 6458.14 | 3658.16 |
+| Comparison/1000/insert/mutable vector                                 | 2526.35 | 1282.13 |
+| Comparison/1000/insert (resize)/hashtables basic                    | 318207.51 | 159834.87 |
+| Comparison/1000/insert (resize)/vector-hashtables boxed             | 106490.84 | 25900.28 |
+| Comparison/1000/insert (resize)/vector-hashtables unboxed keys       | 66848.40 | 25382.56 |
+| Comparison/1000/insert (resize)/vector-hashtables                    | 65694.66 | 21393.31 |
+| Comparison/1000/insert, delete/hashtables basic                      | 96776.41 | 72615.51 |
+| Comparison/1000/insert, delete/vector-hashtables                     | 79701.44 | 20920.26 |
+| Comparison/1000/find/hashtables basic                                | 22841.85 | 23678.93 |
+| Comparison/1000/find/vector-hashtables                               | 16398.19 | 5586.93 |
+| Comparison/1000/find/vector-hashtables (frozen)                      | 11967.63 | 3128.18 |
+| Comparison/1000/lookupIndex/hashtables basic                         | 19744.27 | 19209.79 |
+| Comparison/1000/lookupIndex/vector-hashtables                        | 13624.09 | 4978.75 |
+| Comparison/1000/fromList/hashtables basic                           | 167277.66 | 162920.75 |
+| Comparison/1000/fromList/vector-hashtables                           | 48670.75 | 25283.17 |
+| Comparison/1000/toList/hashtables basic                               | 9296.91 | 9286.65 |
+| Comparison/1000/toList/vector-hashtables                              | 9753.58 | 10022.51 |
+| Comparison/10000/insert/hashtables basic                            | 384031.02 | 373885.01 |
+| Comparison/10000/insert/vector-hashtables boxed                     | 246923.90 | 175892.73 |
+| Comparison/10000/insert/vector-hashtables unboxed keys              | 229812.05 | 147689.46 |
+| Comparison/10000/insert/vector-hashtables                           | 216924.38 | 128417.71 |
+| Comparison/10000/insert/mutable vector boxed                         | 43104.20 | 42712.36 |
+| Comparison/10000/insert/mutable vector                               | 12298.81 | 12193.85 |
+| Comparison/10000/insert (resize)/hashtables basic                  | 1342541.64 | 1378595.70 |
+| Comparison/10000/insert (resize)/vector-hashtables boxed            | 487188.49 | 273187.43 |
+| Comparison/10000/insert (resize)/vector-hashtables unboxed keys     | 441635.60 | 242659.06 |
+| Comparison/10000/insert (resize)/vector-hashtables                  | 412651.12 | 188011.22 |
+| Comparison/10000/insert, delete/hashtables basic                    | 722883.16 | 736250.66 |
+| Comparison/10000/insert, delete/vector-hashtables                   | 407113.19 | 200643.92 |
+| Comparison/10000/find/hashtables basic                              | 228154.09 | 232874.24 |
+| Comparison/10000/find/vector-hashtables                             | 164343.06 | 55693.39 |
+| Comparison/10000/find/vector-hashtables (frozen)                    | 119669.00 | 31291.02 |
+| Comparison/10000/lookupIndex/hashtables basic                       | 197212.48 | 191707.57 |
+| Comparison/10000/lookupIndex/vector-hashtables                      | 136205.70 | 49762.50 |
+| Comparison/10000/fromList/hashtables basic                         | 1391968.88 | 1562065.86 |
+| Comparison/10000/fromList/vector-hashtables                         | 430590.93 | 233128.12 |
+| Comparison/10000/toList/hashtables basic                            | 112894.13 | 114543.83 |
+| Comparison/10000/toList/vector-hashtables                           | 152214.13 | 154034.89 |
+| Comparison/100000/insert/hashtables basic                          | 4492224.86 | 4546924.57 |
+| Comparison/100000/insert/vector-hashtables boxed                   | 2414664.98 | 1767261.44 |
+| Comparison/100000/insert/vector-hashtables unboxed keys            | 2243027.45 | 1476174.02 |
+| Comparison/100000/insert/vector-hashtables                         | 2144269.70 | 1272101.52 |
+| Comparison/100000/insert/mutable vector boxed                       | 667945.33 | 666673.13 |
+| Comparison/100000/insert/mutable vector                             | 121264.87 | 123138.53 |
+| Comparison/100000/insert (resize)/hashtables basic                | 18217163.16 | 18777839.68 |
+| Comparison/100000/insert (resize)/vector-hashtables boxed          | 9452674.69 | 7287443.14 |
+| Comparison/100000/insert (resize)/vector-hashtables unboxed keys   | 7721635.33 | 5698107.38 |
+| Comparison/100000/insert (resize)/vector-hashtables                | 4722092.98 | 2574932.04 |
+| Comparison/100000/insert, delete/hashtables basic                  | 8699786.66 | 8790937.73 |
+| Comparison/100000/insert, delete/vector-hashtables                 | 4073127.47 | 2010559.30 |
+| Comparison/100000/find/hashtables basic                            | 2283995.01 | 2346364.66 |
+| Comparison/100000/find/vector-hashtables                           | 1676135.49 | 588427.30 |
+| Comparison/100000/find/vector-hashtables (frozen)                  | 1201572.29 | 319639.97 |
+| Comparison/100000/lookupIndex/hashtables basic                     | 1963727.73 | 1931036.13 |
+| Comparison/100000/lookupIndex/vector-hashtables                    | 1363501.76 | 499992.98 |
+| Comparison/100000/fromList/hashtables basic                       | 20681183.31 | 30059346.94 |
+| Comparison/100000/fromList/vector-hashtables                       | 5262183.79 | 3945839.47 |
+| Comparison/100000/toList/hashtables basic                          | 2675794.96 | 2702739.12 |
+| Comparison/100000/toList/vector-hashtables                         | 5155629.15 | 5118781.70 |
+| Comparison/1000000/insert/hashtables basic                        | 86723317.72 | 85752701.43 |
+| Comparison/1000000/insert/vector-hashtables boxed                 | 68162021.23 | 75667649.90 |
+| Comparison/1000000/insert/vector-hashtables unboxed keys          | 50777620.44 | 46615543.58 |
+| Comparison/1000000/insert/vector-hashtables                       | 23334885.43 | 16025927.63 |
+| Comparison/1000000/insert/mutable vector boxed                    | 30281652.62 | 32068295.04 |
+| Comparison/1000000/insert/mutable vector                           | 1283399.43 | 1393859.51 |
+| Comparison/1000000/insert (resize)/hashtables basic              | 228726522.46 | 282346897.14 |
+| Comparison/1000000/insert (resize)/vector-hashtables boxed       | 104556190.01 | 84385042.40 |
+| Comparison/1000000/insert (resize)/vector-hashtables unboxed keys | 79183320.30 | 62413398.66 |
+| Comparison/1000000/insert (resize)/vector-hashtables              | 45925222.08 | 28777902.04 |
+| Comparison/1000000/insert, delete/hashtables basic               | 130189177.30 | 134399640.44 |
+| Comparison/1000000/insert, delete/vector-hashtables               | 42722592.04 | 23648387.28 |
+| Comparison/1000000/find/hashtables basic                          | 23094297.73 | 24583079.42 |
+| Comparison/1000000/find/vector-hashtables                         | 16709242.48 | 6178348.57 |
+| Comparison/1000000/find/vector-hashtables (frozen)                | 12176361.82 | 3425505.60 |
+| Comparison/1000000/lookupIndex/hashtables basic                   | 20222788.08 | 19753759.16 |
+| Comparison/1000000/lookupIndex/vector-hashtables                  | 14041315.59 | 5357116.98 |
+| Comparison/1000000/fromList/hashtables basic                     | 210947448.60 | 222974094.62 |
+| Comparison/1000000/fromList/vector-hashtables                     | 56875691.60 | 49212505.34 |
+| Comparison/1000000/toList/hashtables basic                        | 62256321.15 | 66351583.99 |
+| Comparison/1000000/toList/vector-hashtables                       | 95883670.57 | 98441804.39 |
+
+<details><summary>Utilities benchmark:</summary>
+
+| Benchmark | v.0.1.1.4 (ns) | current (ns) |
+| --------- | -------------- | ------------ |
+| Utilities/1000/at'                                                   | 14554.08 | 4755.41 |
+| Utilities/1000/insert                                                | 16704.55 | 6842.45 |
+| Utilities/1000/delete                                                | 11166.80 | 3959.44 |
+| Utilities/1000/lookup                                                | 14510.59 | 5161.87 |
+| Utilities/1000/lookup'                                               | 14181.87 | 4739.19 |
+| Utilities/1000/lookupIndex                                           | 17171.76 | 4196.40 |
+| Utilities/1000/null                                                      | 7.19 | 7.92 |
+| Utilities/1000/length                                                    | 7.31 | 7.41 |
+| Utilities/1000/size                                                      | 6.85 | 6.53 |
+| Utilities/1000/member                                                | 17364.48 | 4344.27 |
+| Utilities/1000/findWithDefault                                       | 15747.10 | 4802.97 |
+| Utilities/1000/upsert                                                | 32633.25 | 12708.79 |
+| Utilities/1000/alter                                                 | 31908.54 | 11109.48 |
+| Utilities/1000/alterM                                                | 32564.14 | 11110.53 |
+| Utilities/1000/union                                                 | 46432.10 | 29468.82 |
+| Utilities/1000/difference                                            | 25741.68 | 16296.53 |
+| Utilities/1000/intersection                                          | 58828.38 | 38587.57 |
+| Utilities/1000/fromList                                              | 45355.85 | 26156.93 |
+| Utilities/1000/toList                                                 | 9626.89 | 10101.05 |
+| Utilities/10000/at'                                                 | 147509.66 | 48699.88 |
+| Utilities/10000/insert                                              | 171201.56 | 68568.34 |
+| Utilities/10000/delete                                              | 111650.42 | 39392.43 |
+| Utilities/10000/lookup                                              | 149138.47 | 49800.41 |
+| Utilities/10000/lookup'                                             | 144283.31 | 46886.69 |
+| Utilities/10000/lookupIndex                                         | 172630.09 | 40088.94 |
+| Utilities/10000/null                                                     | 7.20 | 7.24 |
+| Utilities/10000/length                                                   | 7.30 | 6.77 |
+| Utilities/10000/size                                                     | 6.87 | 6.43 |
+| Utilities/10000/member                                              | 170650.68 | 43369.07 |
+| Utilities/10000/findWithDefault                                     | 157236.92 | 49471.03 |
+| Utilities/10000/upsert                                              | 329212.06 | 125290.78 |
+| Utilities/10000/alter                                               | 322814.62 | 111817.84 |
+| Utilities/10000/alterM                                              | 330094.30 | 112444.63 |
+| Utilities/10000/union                                               | 478541.46 | 329790.79 |
+| Utilities/10000/difference                                          | 295042.17 | 193790.26 |
+| Utilities/10000/intersection                                        | 644396.71 | 419483.32 |
+| Utilities/10000/fromList                                            | 494164.34 | 331449.21 |
+| Utilities/10000/toList                                              | 151375.79 | 167580.99 |
+| Utilities/100000/at'                                               | 1491045.70 | 495418.68 |
+| Utilities/100000/insert                                            | 1741058.94 | 765507.51 |
+| Utilities/100000/delete                                            | 1127146.84 | 436707.82 |
+| Utilities/100000/lookup                                            | 1601916.69 | 562205.51 |
+| Utilities/100000/lookup'                                           | 1441526.57 | 488540.28 |
+| Utilities/100000/lookupIndex                                       | 1763172.42 | 405596.28 |
+| Utilities/100000/null                                                    | 7.19 | 7.26 |
+| Utilities/100000/length                                                  | 7.38 | 7.17 |
+| Utilities/100000/size                                                    | 6.92 | 6.83 |
+| Utilities/100000/member                                            | 1740066.09 | 464281.90 |
+| Utilities/100000/findWithDefault                                   | 1577458.36 | 489790.85 |
+| Utilities/100000/upsert                                            | 3383104.75 | 1265454.18 |
+| Utilities/100000/alter                                             | 3329820.09 | 1211692.27 |
+| Utilities/100000/alterM                                            | 3356140.57 | 1220060.24 |
+| Utilities/100000/union                                             | 5563999.76 | 3705665.39 |
+| Utilities/100000/difference                                        | 6372930.19 | 5630405.29 |
+| Utilities/100000/intersection                                     | 12353680.59 | 9595098.36 |
+| Utilities/100000/fromList                                          | 5161712.37 | 3685646.90 |
+| Utilities/100000/toList                                            | 5109243.49 | 5118785.28 |
+| Utilities/1000000/at'                                             | 14831244.23 | 5055419.26 |
+| Utilities/1000000/insert                                          | 17633535.06 | 7209602.12 |
+| Utilities/1000000/delete                                          | 11251853.98 | 4072535.57 |
+| Utilities/1000000/lookup                                          | 15169518.90 | 5208497.64 |
+| Utilities/1000000/lookup'                                         | 14532451.21 | 4929673.79 |
+| Utilities/1000000/lookupIndex                                     | 17529914.96 | 4216663.23 |
+| Utilities/1000000/null                                                   | 7.20 | 7.25 |
+| Utilities/1000000/length                                                 | 7.30 | 6.81 |
+| Utilities/1000000/size                                                   | 6.86 | 6.43 |
+| Utilities/1000000/member                                          | 17461069.35 | 4604944.15 |
+| Utilities/1000000/findWithDefault                                 | 15945541.78 | 5058608.89 |
+| Utilities/1000000/upsert                                          | 34444162.79 | 12052143.01 |
+| Utilities/1000000/alter                                           | 33820504.88 | 11842773.92 |
+| Utilities/1000000/alterM                                          | 33991841.71 | 11908234.12 |
+| Utilities/1000000/union                                           | 59911378.73 | 44470700.12 |
+| Utilities/1000000/difference                                     | 117323371.53 | 107670945.26 |
+| Utilities/1000000/intersection                                   | 195009586.71 | 161847790.47 |
+| Utilities/1000000/fromList                                        | 97086662.77 | 52734408.34 |
+| Utilities/1000000/toList                                         | 166554860.99 | 99619875.06 |
+
+</details>
diff --git a/bench/Main.hs b/bench/Main.hs
--- a/bench/Main.hs
+++ b/bench/Main.hs
@@ -241,6 +241,7 @@
     hSize <- vh n
     hMember <- vh n
     hFindWithDefault <- vh n
+    hUpsert <- vh n
     hAlter <- vh n
     hAlterM <- vh n
     hUnion1 <- vh n
@@ -264,6 +265,7 @@
         , bench "size" $ nfIO (bhusize hSize)
         , bench "member" $ nfIO (bhumember n hMember)
         , bench "findWithDefault" $ nfIO (bhufindWithDefault n hFindWithDefault)
+        , bench "upsert" $ nfIO (bhuupsert n hUpsert)
         , bench "alter" $ nfIO (bhualter n hAlter)
         , bench "alterM" $ nfIO (bhualterM n hAlterM)
         , bench "union" $ nfIO (bhuunion hUnion1 hUnion2)
@@ -315,6 +317,11 @@
 
 bhufindWithDefault n ht = do
     let go !i | i <= n = VH.findWithDefault ht 0 i >> go (i + 1)
+              | otherwise = return ()
+    go 0
+
+bhuupsert n ht = do
+    let go !i | i <= n = VH.upsert ht (maybe minBound succ) i >> go (i + 1)
               | otherwise = return ()
     go 0
 
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,14 @@
+# 0.1.2.0 (2024-04-26)
+
+* Add `upsert` function to public interface (see [#21](https://github.com/klapaucius/vector-hashtables/pull/21)).
+* Simplify support of 32-bit architectures via `maxBound` (see [#22](https://github.com/klapaucius/vector-hashtables/pull/22)).
+* Improve performance via strictness annotations and bang patterns (see [#24](https://github.com/klapaucius/vector-hashtables/pull/24)).
+* Suggest using strict boxed vectors to avoid accumulation of thunks (see [#27](https://github.com/klapaucius/vector-hashtables/pull/27)).
+* Speed up division by bucket's size (see [#28](https://github.com/klapaucius/vector-hashtables/pull/28)).
+* Avoid deprecated `sizeofMutablePrimArray` (see [#29](https://github.com/klapaucius/vector-hashtables/pull/29)).
+* Introduce `findEntry_` and avoid examining `MutVar` twice in `at` (see [#32](https://github.com/klapaucius/vector-hashtables/pull/32)).
+* Bump `QuickCheck` boundary (see [#33](https://github.com/klapaucius/vector-hashtables/pull/33)).
+
 # 0.1.1.4 (2023-12-13)
 
 * Add `wasm32` support (see [#20](https://github.com/klapaucius/vector-hashtables/pull/20)).
diff --git a/gen/GenPrimes.hs b/gen/GenPrimes.hs
new file mode 100644
--- /dev/null
+++ b/gen/GenPrimes.hs
@@ -0,0 +1,45 @@
+#!/usr/bin/env cabal
+{- cabal:
+build-depends: arithmoi, base, quote-quot
+-}
+
+import Data.Bits (FiniteBits)
+import Data.Int (Int32, Int64)
+import Math.NumberTheory.Primes (nextPrime, unPrime)
+import Numeric.QuoteQuot (AST (..), assumeNonNegArg, astQuot)
+
+-- | For a given bitness, expressed as a ~ Int32 or a ~ Int64,
+-- generate a list of primes such that each prime is at least 20% larger
+-- than the previous. Additionally, for each of selected primes
+-- there exist numbers m and s such that (assuming Int64) for every n >= 0
+--
+-- n `quot` p = (n * m) `shiftR` (64 + s)
+--
+-- The function returns a list of tuples (p, m, s).
+--
+genPrimes :: (FiniteBits a, Integral a, Show a, Bounded a) => [(a, a, Int)]
+genPrimes = go 3
+  where
+    go n
+      | n < 0 = []
+      | n >= maxBound `quot` 2 = []
+      | p < n = []
+      | otherwise = case assumeNonNegArg (astQuot p) of
+        Shr (MulHi Arg mul) shft -> (p, mul, shft) : go p'
+        _ -> go (p + 1)
+      where
+        p = fromInteger (unPrime (nextPrime (toInteger n)))
+        p' = ceiling (fromIntegral p * 1.2 :: Double)
+
+main :: IO ()
+main = do
+  putStrLn "-- | This data is auto-generated by GenPrimes.hs."
+  putStrLn "-- The vector contains tuples (p, m, s) such that p is prime"
+  putStrLn "-- and (assuming 64-bit architecture) for every n >= 0"
+  putStrLn "-- it holds that n `quot` p = (n * m) `shiftR` (64 + s),"
+  putStrLn "-- enabling faster computation of remainders."
+  putStrLn "primesWithFastRem :: UI.Vector (Int, Int, Int)"
+  putStrLn "primesWithFastRem = UI.fromList $"
+  putStrLn "  if finiteBitSize (0 :: Int) == 32"
+  putStrLn $ "  then " ++ show (genPrimes :: [(Int32, Int32, Int)])
+  putStrLn $ "  else " ++ show (genPrimes :: [(Int64, Int64, Int)])
diff --git a/src-gen/Data/Vector/Hashtables/Internal/Mask.hs b/src-gen/Data/Vector/Hashtables/Internal/Mask.hs
deleted file mode 100644
--- a/src-gen/Data/Vector/Hashtables/Internal/Mask.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-{-|
-Module      : Data.Vector.Hashtables.Internal.Mask
-Description : Provides arch-dependent mask for hashtables.
-Copyright   : (c) klapaucius, swamp_agr, 2016-2021
-License     : BSD3
--}
-module Data.Vector.Hashtables.Internal.Mask where
-
--- | 'Int' mask. For 32-bit it is equal to @0x7FFFFFFF@. Otherwise, @0x7FFFFFFFFFFFFFFF@.
-mask = 0x7FFFFFFFFFFFFFFF :: Int
-{-# INLINE mask #-}
diff --git a/src-i386/Data/Vector/Hashtables/Internal/Mask.hs b/src-i386/Data/Vector/Hashtables/Internal/Mask.hs
deleted file mode 100644
--- a/src-i386/Data/Vector/Hashtables/Internal/Mask.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-{-|
-Module      : Data.Vector.Hashtables.Internal.Mask
-Description : Provides arch-dependent mask for hashtables.
-Copyright   : (c) klapaucius, swamp_agr, 2016-2021
-License     : BSD3
--}
-module Data.Vector.Hashtables.Internal.Mask where
-
--- | 'Int' mask. For 32-bit it is equal to @0x7FFFFFFF@. Otherwise, @0x7FFFFFFFFFFFFFFF@.
-mask = 0x7FFFFFFF :: Int
-{-# INLINE mask #-}
diff --git a/src/Data/Primitive/PrimArray/Utils.hs b/src/Data/Primitive/PrimArray/Utils.hs
--- a/src/Data/Primitive/PrimArray/Utils.hs
+++ b/src/Data/Primitive/PrimArray/Utils.hs
@@ -14,14 +14,17 @@
           => Int -> a -> m (MutablePrimArray (PrimState m) a)
 replicate n x = do
     xs <- newPrimArray n
-    setPrimArray xs 0 (sizeofMutablePrimArray xs) x
+    sz <- getSizeofMutablePrimArray xs
+    setPrimArray xs 0 sz x
     return xs
 
 {-# INLINE replicate #-}
 
 clone :: (PrimMonad m, Prim a) 
       => MutablePrimArray (PrimState m) a -> m (MutablePrimArray (PrimState m) a)
-clone xs = cloneMutablePrimArray xs 0 (sizeofMutablePrimArray xs)
+clone xs = do
+    sz <- getSizeofMutablePrimArray xs
+    cloneMutablePrimArray xs 0 sz
 
 {-# INLINE clone #-}
 
@@ -41,14 +44,17 @@
      => a -> MutablePrimArray (PrimState m) a -> Int -> m (MutablePrimArray (PrimState m) a)
 growWith a xs delta = do 
     r <- growNoZ xs delta
-    setPrimArray r (sizeofMutablePrimArray xs) delta a
+    sz <- getSizeofMutablePrimArray xs
+    setPrimArray r sz delta a
     return r
 
 {-# INLINE growWith #-}
 
 growNoZ :: (PrimMonad m, Prim a) 
      => MutablePrimArray (PrimState m) a -> Int -> m (MutablePrimArray (PrimState m) a)
-growNoZ xs delta = resizeMutablePrimArray xs (sizeofMutablePrimArray xs + delta)
+growNoZ xs delta = do
+    sz <- getSizeofMutablePrimArray xs
+    resizeMutablePrimArray xs (sz + delta)
 
 {-# INLINE growNoZ #-}
 
@@ -60,7 +66,7 @@
 
 {-# INLINE freeze #-}
 
-length :: Prim a => MutablePrimArray s a -> Int
-length = sizeofMutablePrimArray
+length :: (PrimMonad m, Prim a) => MutablePrimArray (PrimState m) a -> m Int
+length = getSizeofMutablePrimArray
 
 {-# INLINE length #-}
diff --git a/src/Data/Vector/Hashtables.hs b/src/Data/Vector/Hashtables.hs
--- a/src/Data/Vector/Hashtables.hs
+++ b/src/Data/Vector/Hashtables.hs
@@ -15,8 +15,6 @@
       Dictionary (..)
     , FrozenDictionary (..)
     , findElem
-    , Dictionary_ (..)
-    , findEntry
 
     -- ** Construction
     , initialize
@@ -31,8 +29,10 @@
     , lookup'
     , insert
     , delete
+    , upsert
     , alter
     , alterM
+    , findEntry
 
     -- ** Combine
 
@@ -59,6 +59,10 @@
     -- *** List
     , fromList
     , toList
+
+    -- ** Low-level interface
+    , Dictionary_ (..)
+    , findEntry_
 
     , module Control.Monad.Primitive
     ) where
diff --git a/src/Data/Vector/Hashtables/Internal.hs b/src/Data/Vector/Hashtables/Internal.hs
--- a/src/Data/Vector/Hashtables/Internal.hs
+++ b/src/Data/Vector/Hashtables/Internal.hs
@@ -5,9 +5,12 @@
 License     : BSD3
 -}
 {-# LANGUAGE BangPatterns     #-}
+{-# LANGUAGE CPP              #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE MagicHash        #-}
 {-# LANGUAGE RecordWildCards  #-}
 {-# LANGUAGE TypeFamilies     #-}
+{-# LANGUAGE UnboxedTuples    #-}
 module Data.Vector.Hashtables.Internal where
 
 import           Control.Monad
@@ -33,25 +36,26 @@
 
 import           Data.Vector.Hashtables.Internal.Mask (mask)
 
--- | Alias for 'MutablePrimArray s Int'.
+-- | Alias for 'MutablePrimArray' @s@ 'Int'.
 type IntArray s = A.MutablePrimArray s Int
 
--- | Single-element mutable array of 'Dictionary_' with primitive state token parameterized with state, keys and values types.
+-- | Single-element mutable array of 'Dictionary_' with primitive state token
+-- parameterized with state, keys and values types.
 --
--- *Example*:
+-- Different flavors of 'MVector' could be used for keys and values.
+-- It's preferable to use "Data.Vector.Unboxed.Mutable"
+-- or "Data.Vector.Storable.Mutable" if possible. Otherwise,
+-- if you must use boxed vectors, consider employing strict ones from
+-- [@strict-containers@](https://hackage.haskell.org/package/strict-containers)
+-- to eliminate potential accumulation of thunks.
 --
+-- ==== Example
+--
 -- >>> import qualified Data.Vector.Storable.Mutable as VM
 -- >>> import qualified Data.Vector.Unboxed.Mutable  as UM
 -- >>> import Data.Vector.Hashtables
 -- >>> type HashTable k v = Dictionary (PrimState IO) VM.MVector k UM.MVector v
 --
--- Different vectors could be used for keys and values:
---
--- - storable,
--- - mutable,
--- - unboxed.
---
--- In most cases unboxed vectors should be used. Nevertheless, it is up to you to decide about final form of hastable.
 newtype Dictionary s ks k vs v = DRef { getDRef :: MutVar s (Dictionary_ s ks k vs v) }
 
 -- | Represents collection of hashtable internal primitive arrays and vectors.
@@ -70,9 +74,10 @@
     hashCode,
     next,
     buckets,
-    refs :: IntArray s,
-    key :: ks s k,
-    value :: vs s v
+    refs :: !(IntArray s),
+    key :: !(ks s k),
+    value :: !(vs s v),
+    remSize :: {-# UNPACK #-} !FastRem
 }
 
 getCount, getFreeList, getFreeCount :: Int
@@ -85,10 +90,11 @@
 data FrozenDictionary ks k vs v = FrozenDictionary {
     fhashCode,
     fnext,
-    fbuckets :: A.PrimArray Int,
-    count, freeList, freeCount :: Int,
-    fkey :: ks k,
-    fvalue :: vs v
+    fbuckets :: !(A.PrimArray Int),
+    count, freeList, freeCount :: !Int,
+    fkey :: !(ks k),
+    fvalue :: !(vs v),
+    fremSize :: {-# UNPACK #-} !FastRem
 } deriving (Eq, Ord, Show)
 
 -- | /O(1)/ in the best case, /O(n)/ in the worst case.
@@ -96,7 +102,7 @@
 -- If entry not found @-1@ returned.
 findElem :: (Vector ks k, Vector vs v, Hashable k, Eq k)
          => FrozenDictionary ks k vs v -> k -> Int
-findElem FrozenDictionary{..} key' = go $ fbuckets !. (hashCode' `rem` A.sizeofPrimArray fbuckets) where
+findElem FrozenDictionary{..} key' = go $ fbuckets !. (hashCode' `fastRem` fremSize) where
     hashCode' = hash key' .&. mask
     go i | i >= 0 =
             if fhashCode !. i == hashCode' && fkey !.~ i == key'
@@ -106,15 +112,20 @@
 
 -- | Infix version of @unsafeRead@.
 (!~) :: (MVector v a, PrimMonad m) => v (PrimState m) a -> Int -> m a
-(!~) = V.unsafeRead
+(!~) xs !i = V.unsafeRead xs i
+-- Why do we need ! before i?
+-- The reason is that V.unsafeRead is essentially V.basicUnsafeRead,
+-- which is an opaque class member and, unless V.unsafeRead was
+-- already specialised to a specific v, GHC has no clue that i is most certainly
+-- to be used eagerly. Bang before i hints this vital for optimizer information.
 
 -- | Infix version of @unsafeIndex@.
 (!.~) :: (Vector v a) => v a -> Int -> a
-(!.~) = VI.unsafeIndex
+(!.~) xs !i = VI.unsafeIndex xs i
 
 -- | Infix version of @unsafeWrite@.
 (<~~) :: (MVector v a, PrimMonad m) => v (PrimState m) a -> Int -> a -> m ()
-(<~~) = V.unsafeWrite
+(<~~) xs !i x = V.unsafeWrite xs i x
 
 -- | Infix version of @readPrimArray@.
 (!) :: PrimMonad m => A.MutablePrimArray (PrimState m) Int -> Int -> m Int
@@ -134,7 +145,8 @@
     => Int
     -> m (Dictionary (PrimState m) ks k vs v)
 initialize capacity = do
-    let size = getPrime capacity
+    let !remSize = getFastRem capacity
+        size = frmPrime remSize
     hashCode <- A.replicate size 0
     next     <- A.replicate size 0
     key      <- V.new size
@@ -169,6 +181,7 @@
     -> m (FrozenDictionary ks k vs v)
 unsafeFreeze DRef {..} = do
     Dictionary {..} <- readMutVar getDRef
+    let fremSize = remSize
     fhashCode       <- A.unsafeFreeze hashCode
     fnext           <- A.unsafeFreeze next
     fbuckets        <- A.unsafeFreeze buckets
@@ -186,6 +199,7 @@
     => FrozenDictionary ks k vs v
     -> m (Dictionary (PrimState m) (Mutable ks) k (Mutable vs) v)
 unsafeThaw FrozenDictionary {..} = do
+    let remSize = fremSize
     hashCode <- A.unsafeThaw fhashCode
     next     <- A.unsafeThaw fnext
     buckets  <- A.unsafeThaw fbuckets
@@ -219,13 +233,7 @@
 -- Find value by given key in 'Dictionary'. Throws an error if value not found.
 at :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
    => Dictionary (PrimState m) ks k vs v -> k -> m v
-at d k = do
-    i <- findEntry d k
-    if i >= 0
-        then do
-            Dictionary{..} <- readMutVar . getDRef $ d
-            value !~ i
-        else error "KeyNotFoundException!"
+at d k = fromMaybe (error "KeyNotFoundException!") <$!> at' d k
 {-# INLINE at #-}
 
 -- | /O(1)/ in the best case, /O(n)/ in the worst case.
@@ -233,12 +241,11 @@
 at' :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
     => Dictionary (PrimState m) ks k vs v -> k -> m (Maybe v)
 at' d k = do
-  i <- findEntry d k
+  d_@Dictionary{..} <- readMutVar . getDRef $ d
+  i <- findEntry_ d_ k
   if i >= 0
-      then do
-          Dictionary{..} <- readMutVar . getDRef $ d
-          Just <$> value !~ i
-      else pure Nothing
+    then Just <$> value !~ i
+    else return Nothing
 {-# INLINE at' #-}
 
 atWithOrElse :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
@@ -259,7 +266,15 @@
 findEntry :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
           => Dictionary (PrimState m) ks k vs v -> k -> m Int
 findEntry d key' = do
-    Dictionary{..} <- readMutVar . getDRef $ d
+    d_ <- readMutVar . getDRef $ d
+    findEntry_ d_ key'
+{-# INLINE findEntry #-}
+
+-- | /O(1)/ in the best case, /O(n)/ in the worst case.
+-- Same as 'findEntry', but for 'Dictionary_'.
+findEntry_ :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
+          => Dictionary_ (PrimState m) ks k vs v -> k -> m Int
+findEntry_ Dictionary{..} key' = do
     let hashCode' = hash key' .&. mask
         go i | i >= 0 = do
                 hc <- hashCode ! i
@@ -271,8 +286,8 @@
                             else go =<< next ! i
                     else go =<< next ! i
              | otherwise = return $ -1
-    go =<< buckets ! (hashCode' `rem` A.length buckets)
-{-# INLINE findEntry #-}
+    go =<< buckets ! (hashCode' `fastRem` remSize)
+{-# INLINE findEntry_ #-}
 
 -- | /O(1)/ in the best case, /O(n)/ in the worst case.
 -- Insert key and value in dictionary by key's hash.
@@ -283,7 +298,7 @@
     d@Dictionary{..} <- readMutVar getDRef
     let
         hashCode' = hash key' .&. mask
-        targetBucket = hashCode' `rem` A.length buckets
+        !targetBucket = hashCode' `fastRem` remSize
 
         go i    | i >= 0 = do
                     hc <- hashCode ! i
@@ -308,7 +323,8 @@
                 else do
                     count <- refs ! getCount
                     refs <~ getCount $ count + 1
-                    if count == A.length next
+                    nextLen <- A.length next
+                    if count == nextLen
                         then do
                             nd <- resize d count hashCode' key' value'
                             writeMutVar getDRef nd
@@ -327,7 +343,20 @@
 
 insertWithIndex
   :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
-  => Int -> Int -> k -> v -> MutVar (PrimState m) (Dictionary_ (PrimState m) ks k vs v) -> Dictionary_ (PrimState m) ks k vs v -> Int -> m ()
+  => Int
+  -- ^ Target bucket, key's hash modulo table size
+  -> Int
+  -- ^ Key's hash
+  -> k
+  -- ^ Key
+  -> v
+  -- ^ Value
+  -> MutVar (PrimState m) (Dictionary_ (PrimState m) ks k vs v)
+  -- ^ MutVar with 'Dictionary_'
+  -> Dictionary_ (PrimState m) ks k vs v
+  -- ^ 'Dictionary_' itself
+  -> Int
+  -> m ()
 insertWithIndex !targetBucket !hashCode' key' value' getDRef d@Dictionary{..} = go where
   go i
     | i >= 0 = do
@@ -343,8 +372,20 @@
 {-# INLINE insertWithIndex #-}
 
 addOrResize
-  :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
-  => Int -> Int -> k -> v -> MutVar (PrimState m) (Dictionary_ (PrimState m) ks k vs v) -> Dictionary_ (PrimState m) ks k vs v -> m ()
+    :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
+    => Int
+    -- ^ Target bucket, key's hash modulo table size
+    -> Int
+    -- ^ Key's hash
+    -> k
+    -- ^ Key
+    -> v
+    -- ^ Value
+    -> MutVar (PrimState m) (Dictionary_ (PrimState m) ks k vs v)
+    -- ^ MutVar with 'Dictionary_'
+    -> Dictionary_ (PrimState m) ks k vs v
+    -- ^ 'Dictionary_' itself
+    -> m ()
 addOrResize !targetBucket !hashCode' !key' !value' dref d@Dictionary{..}  = do
     freeCount <- refs ! getFreeCount
     if freeCount > 0
@@ -357,7 +398,8 @@
         else do
             count <- refs ! getCount
             refs <~ getCount $ count + 1
-            if count == A.length next
+            nextLen <- A.length next
+            if count == nextLen
                 then do
                     nd <- resize d count hashCode' key' value'
                     writeMutVar dref nd
@@ -375,9 +417,22 @@
     buckets <~ targetBucket $ index
 {-# INLINE add #-}
 
-
+resize
+    :: (MVector ks k, MVector vs v, PrimMonad m, Hashable k, Eq k)
+    => Dictionary_ (PrimState m) ks k vs v
+    -- ^ The original 'Dictionary_'
+    -> Int
+    --
+    -> Int
+    -- ^ Key's hash
+    -> k
+    -- ^ Key
+    -> v
+    -- ^ Value
+    -> m (Dictionary_ (PrimState m) ks k vs v)
 resize Dictionary{..} index hashCode' key' value' = do
-    let newSize = getPrime (index*2)
+    let !newRemSize = getFastRem (index*2)
+        newSize = frmPrime newRemSize
         delta = newSize - index
 
     buckets <- A.replicate newSize (-1)
@@ -390,7 +445,7 @@
     let go i | i < index = do
                 hc <- hashCode ! i
                 when (hc >= 0) $ do
-                    let bucket = hc `rem` newSize
+                    let !bucket = hc `fastRem` newRemSize
                     nx <- buckets ! bucket
                     next <~ i $ nx
                     buckets <~ bucket $ i
@@ -398,13 +453,14 @@
              | otherwise = return ()
     go 0
 
-    let targetBucket = hashCode' `rem` A.length buckets
+    let !targetBucket = hashCode' `fastRem` newRemSize
     hashCode <~ index $ hashCode'
     b <- buckets ! targetBucket
     next <~ index $ b
     key <~~ index $ key'
     value <~~ index $ value'
     buckets <~ targetBucket $ index
+    let remSize = newRemSize
     return Dictionary{..}
 
 {-# INLINE resize #-}
@@ -428,7 +484,7 @@
 delete DRef{..} key' = do
     Dictionary{..} <- readMutVar getDRef
     let hashCode' = hash key' .&. mask
-        bucket = hashCode' `rem` A.length buckets
+        !bucket = hashCode' `fastRem` remSize
         go !last !i | i >= 0 = do
             hc <- hashCode ! i
             k  <- key !~ i
@@ -542,9 +598,61 @@
 {-# INLINE findWithDefault #-}
 
 -- | /O(1)/ in the best case, /O(n)/ in the worst case.
+-- The expression (@'upsert' ht f k@) updates or inserts the value @x@ at @k@.
+--
+-- It's a responsibility of 'MVector' @vs@ to force evaluation of the updated value.
+-- Unboxed / storable vectors do it automatically. If you use boxed vectors,
+-- consider employing strict ones from
+-- [@strict-containers@](https://hackage.haskell.org/package/strict-containers)
+-- to eliminate potential accumulation of thunks.
+--
+-- > let f _ = "c"
+-- > ht <- fromList [(5,"a"), (3,"b")]
+-- > upsert ht f 7
+-- > toList ht
+-- > [(3, "b"), (5, "a"), (7, "c")]
+--
+-- > ht <- fromList [(5,"a"), (3,"b")]
+-- > upsert ht f 5
+-- > toList ht
+-- > [(3, "b"), (5, "c")]
+--
+upsert
+  :: ( MVector ks k, MVector vs v
+     , PrimMonad m, Hashable k, Eq k
+     )
+  => Dictionary (PrimState m) ks k vs v -> (Maybe v -> v) -> k -> m ()
+upsert ht f k = do
+  d@Dictionary{..} <- readMutVar . getDRef $ ht
+  let
+      hashCode' = hash k .&. mask
+      !targetBucket = hashCode' `fastRem` remSize
+
+      onFound' value' dict i = insertWithIndex targetBucket hashCode' k value' (getDRef ht) dict i
+
+      onFound dict i = do
+        d'@Dictionary{..} <- readMutVar . getDRef $ dict
+        v <- value !~ i
+        onFound' (f (Just v)) d' i
+
+      onNothing dict = do
+        d' <- readMutVar . getDRef $ dict
+        onFound' (f Nothing) d' (-1)
+
+  void $ atWithOrElse ht k onFound onNothing
+
+{-# INLINE upsert #-}
+
+-- | /O(1)/ in the best case, /O(n)/ in the worst case.
 -- The expression (@'alter' ht f k@) alters the value @x@ at @k@, or absence thereof.
 -- 'alter' can be used to insert, delete, or update a value in a 'Dictionary'.
 --
+-- It's a responsibility of 'MVector' @vs@ to force evaluation of the updated value.
+-- Unboxed / storable vectors do it automatically. If you use boxed vectors,
+-- consider employing strict ones from
+-- [@strict-containers@](https://hackage.haskell.org/package/strict-containers)
+-- to eliminate potential accumulation of thunks.
+--
 -- > let f _ = Nothing
 -- > ht <- fromList [(5,"a"), (3,"b")]
 -- > alter ht f 7
@@ -576,7 +684,7 @@
   d@Dictionary{..} <- readMutVar . getDRef $ ht
   let
       hashCode' = hash k .&. mask
-      targetBucket = hashCode' `rem` A.length buckets
+      !targetBucket = hashCode' `fastRem` remSize
 
       onFound' value' dict i = insertWithIndex targetBucket hashCode' k value' (getDRef ht) dict i
       onNothing' dict i = deleteWithIndex targetBucket hashCode' d k (-1) i
@@ -610,7 +718,7 @@
   d@Dictionary{..} <- readMutVar . getDRef $ ht
   let
       hashCode' = hash k .&. mask
-      targetBucket = hashCode' `rem` A.length buckets
+      !targetBucket = hashCode' `fastRem` remSize
 
       onFound' value' dict i = insertWithIndex targetBucket hashCode' k value' (getDRef ht) dict i
       onNothing' dict i = deleteWithIndex targetBucket hashCode' d k (-1) i
@@ -692,7 +800,7 @@
         v <- value dictS !~ i
         let
            hashCode' = hash k .&. mask
-           targetBucket = hashCode' `rem` A.length (buckets dictG)
+           !targetBucket = hashCode' `fastRem` remSize dictG
 
            onFound dict i = do
              d@Dictionary{..} <- readMutVar . getDRef $ dict
@@ -856,17 +964,42 @@
 
 -- * Extras
 
-primes :: UI.Vector Int
-primes = UI.fromList [
-    3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631,
-    761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143,
-    14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363,
-    156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897,
-    1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471,
-    7199369, 8639249, 10367101, 12440537, 14928671, 17914409, 21497293, 25796759, 30956117,
-    37147349, 44576837, 53492207, 64190669, 77028803, 92434613, 110921543, 133105859, 159727031,
-    191672443, 230006941, 276008387, 331210079, 397452101, 476942527, 572331049, 686797261,
-    824156741, 988988137, 1186785773, 1424142949, 1708971541, 2050765853 ]
+-- | This data is auto-generated by GenPrimes.hs.
+-- The vector contains tuples (p, m, s) such that p is prime
+-- and (assuming 64-bit architecture) for every n >= 0
+-- it holds that n \`'quot'\` p = (n * m) \`'shiftR'\` (64 + s),
+-- enabling faster computation of remainders.
+primesWithFastRem :: UI.Vector (Int, Int, Int)
+primesWithFastRem = UI.fromList $
+  if finiteBitSize (0 :: Int) == 32
+  then [(5,1717986919,1),(11,780903145,1),(17,2021161081,3),(41,1676084799,4),(53,1296593901,4),(67,128207979,1),(83,827945503,4),(101,680390859,4),(131,1049152317,5),(163,210795941,3),(197,1395319325,6),(241,285143057,4),(311,883851791,6),(379,1450543045,7),(457,601483385,6),(557,123374285,4),(673,1633746847,8),(809,84943729,4),(977,562697865,7),(1187,1852589095,9),(1427,24078303,3),(1721,638879505,8),(2069,2125687053,10),(2503,1757110073,10),(3041,723125043,9),(3673,299349749,8),(4409,1995031305,11),(5297,103786259,7),(6367,1381512961,11),(7649,287491601,9),(9181,1916151405,12),(11047,1592485385,12),(13291,330904109,10),(15959,1102336365,12),(19157,57394771,8),(22993,382555257,11),(27611,637144111,12),(33149,1061400709,13),(39779,1768992287,14),(47741,736984397,13),(57301,1228054383,14),(68767,511646169,13),(82529,426327377,13),(99041,177625287,12),(118861,1184051021,15),(142657,1973089135,16),(171203,1644100727,16),(205477,684930617,15),(246577,285382433,14),(295901,951247129,16),(355087,792692993,16),(426131,1321072519,17),(511361,1100885585,17),(613637,917398973,17),(736369,1528988737,18),(883661,637065519,17),(1060421,2123496059,19),(1272539,1769533047,19),(1527061,184324645,16),(1832497,1228815007,19),(2199061,2047964849,20),(2638879,1706633623,20),(3166661,1422191901,20),(3800051,1185141891,20),(4560071,123452015,17),(5472109,411504927,19),(6566551,685839435,20),(7879897,285765133,19),(9455881,238137495,19),(11347079,793790125,21),(13616503,1322982745,22),(16339877,275620167,20),(19607893,229682997,20),(23529511,191402177,20),(28235483,1276011359,23),(33882593,2126684757,24),(40659149,1772235667,24),(48791009,738431071,23),(58549219,1230718279,24),(70259107,1025597921,24),(84310943,427332393,23),(101173139,712220603,24),(121407857,74189591,21),(145689433,1978389031,26),(174827333,206082175,23),(209792827,1373880987,26),(251751509,286225073,24),(302101841,1908166963,27),(362522213,1590139119,27),(435026701,662557897,26),(522032051,1104263141,27),(626438489,1840438487,28),(751726211,766849345,27),(902071483,1278082199,28)]
+  else [(5,7378697629483820647,1),(7,5270498306774157605,1),(11,3353953467947191203,1),(17,8680820740569200761,3),(29,5088756985850910791,3),(37,3988485205126389539,3),(47,6279742663390485657,4),(67,8810385229234412713,5),(83,3555998857582564167,4),(107,1379195818595106663,3),(131,281629680514649643,1),(163,7242893378634425175,6),(197,1498212716646461045,4),(241,1224680104478642431,4),(293,503665367200260795,3),(353,1672226091667721393,5),(433,681634884940768651,4),(521,9064043153300662599,8),(631,7483940543375032035,8),(761,3102737505170594753,7),(919,642324059149842929,5),(1103,4281383937325154319,8),(1327,7117357170866081709,9),(1597,739255867700320165,6),(1949,2422968949650921095,8),(2339,4037936282915472607,9),(2833,6667654758728761333,10),(3407,5544310517017487777,10),(4093,4615066193862345677,10),(4919,7680205705012637063,11),(5903,6399954576140464461,11),(7103,2659364484228999135,10),(8527,8861013688977873041,12),(10243,7376536534795892163,12),(12301,6142416366629893783,12),(14767,2558334926725615339,11),(17729,4261823212020662385,12),(21277,887788030806907969,10),(25561,1477991153043979567,11),(30677,4926026907840683471,13),(36821,4104063644437376683,13),(44201,1709415255897249461,12),(53051,5696998264003643545,14),(63667,4747066060968119963,14),(76403,7911507529904775825,15),(91691,6592390854143968191,15),(110039,5493169783506889261,15),(132047,9155269105808001505,16),(158507,3813477700084630883,15),(190243,6354640221267690137,16),(228299,5295361870243098633,16),(273967,8825338961368552963,17),(328777,3677038903617434233,16),(394549,6128140330426026551,17),(473471,5106652021410515849,17),(568171,8510999819523553119,18),(681809,3546230160102401625,17),(818173,5910367707634591583,18),(981809,4925299399841024781,18),(1178173,8208817004732779819,19),(1413827,1710146743009758867,17),(1696601,5700460247823167261,19),(2035927,4750370006840634953,19),(2443151,7917158257444614269,20),(2931793,6597605326786054403,20),(3518209,5497914738389352877,20),(4221851,9163190796564855935,21),(5066231,7635977559583866901,21),(6079481,3181655327787695495,20),(7295381,1325689029389559421,19),(8754461,8837923026367501915,22),(10505377,7364919169996114103,22),(12606463,1534356870268374785,20),(15127831,5114497409135273073,22),(18153427,8524148355606494265,23),(21784129,7103451550010217731,23),(26140973,5919538837007808943,23),(31369243,2466468586931991543,22),(37643093,8221561650668425911,24),(45171733,856412266221181587,21),(54206099,5709413064779759723,24),(65047343,4757842450557051481,24),(78056833,247804226362015825,20),(93668203,6608112463123586747,25),(112401881,1376689638411589699,23),(134882263,9177930528088635901,26),(161858731,7648274712381010049,26),(194230481,3186781067811339753,25),(233076601,663912654667005953,23),(279691949,4426083924515754563,26),(335630353,7376806228758340429,27),(402756463,48026077520285669,20),(483307787,5122781269342057073,27),(579969349,4268984357259129137,27),(695963227,7114973844934943811,28),(835155913,5929144582541584783,28),(1002187163,617619185811393333,25),(1202624651,8234922098136039455,29),(1443149623,6862434883013541971,29),(1731779563,357418480311913773,25),(2078135531,4765579610448921293,29),(2493762643,7942632665608538591,30),(2992515199,3309430247035160405,29),(3591018241,5515717075012787271,30),(4309221899,9192861770781641709,31),(5171066297,7660718115355497017,31),(6205279567,6383931751891069761,31),(7446335483,1329985781179588333,29),(8935602619,2216642958858742015,30),(10722723161,3694404925160612267,31),(12867267797,6157341540115949263,32),(15440721377,1282779485812755651,30),(18528865703,8551863215397642261,33),(22234638851,890819084640777303,30),(26681566631,2969396947711947027,32),(32017880003,309312181610871977,29),(38421456013,4124162420469296211,33),(46105747229,6873604032117775405,34),(55326896741,1432000838311112129,32),(66392276177,1193334030347810553,32),(79670731433,3977780100130856927,34),(95604877727,6629633499704948593,35),(114725853301,172646705678443951,30),(137671023989,575489018811937629,32),(165205228889,3836593456372840199,35),(198246274687,6394322426636527325,36),(237895529659,5328602021422103605,36),(285474635629,4440501683924226201,36),(342569562761,7400836139739766199,37),(411083475323,3083681724818056003,36),(493300170481,1284867385097583665,35),(591960204599,8565782567001774073,38),(710352245527,7138152139085745311,38),(852422694637,5948460115872687585,38),(1022907233639,4957050096199058351,38),(1227488680427,4130875079963290535,38),(1472986416527,3442395899935288315,38),(1767583699907,5737326499650006151,39),(2121100439917,2390552708155269385,38),(2545320527903,996063628397011449,37),(3054384633659,6640424188932079035,40),(3665261560423,5533686824061451401,40),(4398313872521,9222811373407653915,41),(5277976647059,7685676144457159429,41),(6333571976483,6404730120368629123,41),(7600286371789,2668637550150294909,40),(9120343646191,8895458500457872893,42),(10944412375433,7412882083712320257,42),(13133294850551,6177401736412164183,42),(15759953820697,5147834780331776433,42),(18911944584839,1072465579235639315,40),(22694333501813,7149770528235642145,43),(27233200202177,2979071053431364413,42),(32679840242663,1241279605594479897,41),(39215808291301,8275197370607624801,44),(47058969949679,3447998904411212491,43),(56470763939783,1436666210167059381,42),(67764916727749,1197221841805716745,42),(81317900073323,7981478945369069699,45),(97581480088031,3325616227235633285,44),(117097776105689,5542693712056936913,45),(140517331326899,4618911426711740825,45),(168620797592327,3849092855592017097,45),(202344957110837,3207577379659307247,45),(242813948533111,5345962299429831765,46),(291376738239791,8909937165714618823,47),(349652085887761,3712473819047632555,46),(419582503065331,1546864091269781275,45),(503499003678427,5156213637565632409,47),(604198804414123,8593689395942569915,48),(725038565296949,7161407829952127767,48),(870046278356531,745979982286515183,45),(1044055534027841,4973199881910083119,48),(1252866640833481,259020827182801985,44),(1503439969000181,6907222058208035475,49),(1804127962800257,2878009190919951291,48),(2164953555360361,299792624054154309,45),(2597944266432433,999308746847181107,47),(3117533119718951,1665514578078618403,48),(3741039743662841,693964407532739155,47),(4489247692395509,4626429383551491517,50),(5387097230874631,7710715639252456949,51),(6464516677049609,1606399091510915659,49),(7757420012459563,5354663638369696637,51),(9308904014951479,2231109849320706117,50),(11170684817941799,7437032831069004279,52),(13404821781530213,3098763679612072587,51),(16085786137836413,5164606132686737109,52),(19302943365403697,8607676887811227891,53),(23163532038484451,7173064073176018721,53),(27796238446181363,2988776697156672123,52),(33355486135417657,4981294495261117009,53),(40026583362501191,8302157492101861143,54),(48031900035001457,1729616144187886737,52),(57638280042001759,2882693573646477365,53),(69165936050402159,1201122322352698065,52),(82999123260482599,8007482149017986309,55),(99598947912579133,6672901790848320973,55),(119518737495095009,347546968273349907,51),(143422484994114059,4633959576977997203,55),(172106981992936889,7723265961629994521,56),(206528378391524347,6436054968024992935,56),(247834054069829309,2681689570010412721,55),(297400864883795143,8938965233368043239,57),(356881037860554209,7449137694473368585,57),(428257245432665101,6207614745394473093,57),(513908694519198103,5173012287828727761,57),(616690433423037709,8621687146381213139,58),(740028520107645211,898092411081376417,55),(888034224129174191,2993641370271254933,57),(1065641068955008969,2494701141892712585,57),(1278769282746010901,4157835236487853859,58),(1534523139295213087,216553918567075721,54),(1841427767154255641,1443692790447171523,57),(2209713320585106689,2406154650745285959,58),(2651655984702128147,8020515502484286167,60),(3181987181642553871,1670940729684226235,58),(3818384617971064327,5569802432280754581,60),(4582061541565277261,1160375506725157187,58)]
 
-getPrime :: Int -> Int
-getPrime n = fromJust $ UI.find (>= n) primes
+getFastRem :: Int -> FastRem
+getFastRem n =
+  (\(p, m, s) -> FastRem p m s) $ fromJust $
+    UI.find (\(p, _, _) -> p >= n) primesWithFastRem
+
+-- | For 64-bit architectures
+-- 'frmPrime' is a prime number such that for each @n@ >= 0
+-- it holds that @n@ \`'quot'\` 'frmPrime' = (n * '_frmMulHi') \`'shiftR'\` (64 + s).
+data FastRem = FastRem
+  { frmPrime :: !Int
+  , _frmMulHi :: !Int
+  , _frmShift :: !Int
+  } deriving (Eq, Ord, Show)
+
+fastRem :: Int -> FastRem -> Int
+#ifndef aarch64_HOST_ARCH
+fastRem !i (FastRem !p !m !s) = i - p * q
+  where
+    q = fromIntegral (mulHi (fromIntegral i) (fromIntegral m)) `unsafeShiftR` s
+    mulHi (Exts.W# x) (Exts.W# y) =
+      let (# z, _ #) = Exts.timesWord2# x y in Exts.W# z
+#else
+-- At the moment GHC NCG does not make use of UMULH instruction,
+-- so timesWord2# is painfully slow on ARM. This is being worked on
+-- at https://gitlab.haskell.org/ghc/ghc/-/merge_requests/10832,
+-- but in the meantime we should resort to a usual division.
+fastRem !i (FastRem !p _ _) = i `rem` p
+#endif
diff --git a/src/Data/Vector/Hashtables/Internal/Mask.hs b/src/Data/Vector/Hashtables/Internal/Mask.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Vector/Hashtables/Internal/Mask.hs
@@ -0,0 +1,11 @@
+{-|
+Module      : Data.Vector.Hashtables.Internal.Mask
+Description : Provides arch-dependent mask for hashtables.
+Copyright   : (c) klapaucius, swamp_agr, 2016-2021
+License     : BSD3
+-}
+module Data.Vector.Hashtables.Internal.Mask where
+
+-- | 'Int' mask. For 32-bit it is equal to @0x7FFFFFFF@. Otherwise, @0x7FFFFFFFFFFFFFFF@.
+mask = maxBound :: Int
+{-# INLINE mask #-}
diff --git a/test/Data/Vector/HashTablesSpec.hs b/test/Data/Vector/HashTablesSpec.hs
--- a/test/Data/Vector/HashTablesSpec.hs
+++ b/test/Data/Vector/HashTablesSpec.hs
@@ -117,6 +117,8 @@
 
   testAlter :: VH.Dictionary (PrimState IO) ks Int vs Int -> (Maybe Int -> Maybe Int) -> Int -> IO ()
 
+  testUpsert :: VH.Dictionary (PrimState IO) ks Int vs Int -> (Maybe Int -> Int) -> Int -> IO ()
+
   testUnion
     :: VH.Dictionary (PrimState IO) ks Int vs Int
     -> VH.Dictionary (PrimState IO) ks Int vs Int
@@ -177,6 +179,11 @@
       it "when altering is nothing - key deleted from table" $ property prop_alterDelete
 
       it "when altering is just a result - key updated with result" $ property prop_alterUpdate
+
+      it "when upserting a new key - key is set to value" $ property prop_upsertInsert
+
+      it "when upserting an existing key - key updated with result" $ property prop_upsertUpdate
+
       it "intersection + symmetric difference of two tables is equal to union of two tables" $ property prop_union
 
   where
@@ -302,6 +309,21 @@
       v <- testAt ht x
       v `shouldBe` (negate y)
 
+    prop_upsertInsert :: HashTableTest ks vs => (Int, Int) -> IO ()
+    prop_upsertInsert (x, y) = do
+      ht <- testInit (Proxy @ks) (Proxy @vs) 10
+      testUpsert ht (maybe 0 negate) x
+      v <- testAt ht x
+      v `shouldBe` 0
+
+    prop_upsertUpdate :: HashTableTest ks vs => (Int, Int) -> IO ()
+    prop_upsertUpdate (x, y) = do
+      ht <- testInit (Proxy @ks) (Proxy @vs) 10
+      testInsert ht x y
+      testUpsert ht (maybe 0 negate) x
+      v <- testAt ht x
+      v `shouldBe` (negate y)
+
     prop_union :: Positive Int -> Property
     prop_union (Positive n) = forAll (twoListsN n) $ \(xs, ys) -> do
       ht1 <- testFromList (Proxy @ks) (Proxy @vs) xs
@@ -337,6 +359,7 @@
   testNull = VH.null
   testMember = VH.member
   testAlter = VH.alter
+  testUpsert = VH.upsert
   testUnion = VH.union
   testDifference = VH.difference
   testIntersection = VH.intersection
@@ -361,6 +384,7 @@
   testNull = VH.null
   testMember = VH.member
   testAlter = VH.alter
+  testUpsert = VH.upsert
   testUnion = VH.union
   testDifference = VH.difference
   testIntersection = VH.intersection
@@ -385,6 +409,7 @@
   testNull = VH.null
   testMember = VH.member
   testAlter = VH.alter
+  testUpsert = VH.upsert
   testUnion = VH.union
   testDifference = VH.difference
   testIntersection = VH.intersection
@@ -409,6 +434,7 @@
   testNull = VH.null
   testMember = VH.member
   testAlter = VH.alter
+  testUpsert = VH.upsert
   testUnion = VH.union
   testDifference = VH.difference
   testIntersection = VH.intersection
diff --git a/vector-hashtables.cabal b/vector-hashtables.cabal
--- a/vector-hashtables.cabal
+++ b/vector-hashtables.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.0
 name:                vector-hashtables
-version:             0.1.1.4
+version:             0.1.2.0
 synopsis:            Efficient vector-based mutable hashtables implementation.
 description:
   This package provides efficient vector-based hashtable implementation similar to .NET Generic Dictionary implementation (at the time of 2015).
@@ -11,26 +11,24 @@
 license-file:        LICENSE
 author:              klapaucius
 maintainer:          klapaucius, swamp_agr, ArtemPelenitsyn
-copyright:           2016-2023 klapaucius, swamp_agr
+copyright:           2016-2024 klapaucius, swamp_agr
 category:            Data
 build-type:          Simple
 extra-doc-files:     README.md,
                      changelog.md
-extra-source-files:  src-i386/Data/Vector/Hashtables/Internal/Mask.hs,
-                     src-gen/Data/Vector/Hashtables/Internal/Mask.hs
+extra-source-files:  gen/GenPrimes.hs
 tested-with:
-    GHC == 9.4.4
-    GHC == 9.2.5
+    GHC == 9.8.1
+    GHC == 9.6.3
+    GHC == 9.4.7
+    GHC == 9.2.8
     GHC == 9.0.2
     GHC == 8.10.7
     GHC == 8.8.4
     GHC == 8.6.5
 
 library
-  if arch(i386) || arch(wasm32)
-    hs-source-dirs:    src, src-i386
-  else
-    hs-source-dirs:    src, src-gen
+  hs-source-dirs:    src
   exposed-modules:     Data.Vector.Hashtables,
                        Data.Vector.Hashtables.Internal,
                        Data.Vector.Hashtables.Internal.Mask,
@@ -73,7 +71,7 @@
   -- Additional dependencies
   build-depends:
       hspec                >= 2.6.0    && < 2.12
-    , QuickCheck           >= 2.12.6.1 && < 2.15
+    , QuickCheck           >= 2.12.6.1 && < 2.16
     , quickcheck-instances >= 0.3.19   && < 0.4
 
   build-tool-depends:
