diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..0883d2f
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,73 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    strategy:
+      fail-fast: false
+      # Test Linux, macOS and Windows with the oldest and newest supported OCaml
+      # and trunk.
+      matrix:
+        os:
+          - macos-latest
+          - ubuntu-latest
+          - windows-latest
+        ocaml-version:
+          - 4.06.1
+          - 4.12.0
+          - 4.12.0+options
+        exclude:
+          - os: windows-latest
+            ocaml-version: 4.12.0+options
+
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+
+      - name: Set-up OCaml ${{ matrix.ocaml-version }}
+        uses: avsm/setup-ocaml@v1
+        with:
+          ocaml-version: ${{ matrix.ocaml-version }}
+
+      - name: Switch to trunk if necessary
+        id: ocaml
+        run: |-
+          opam switch create trunk --repos=default,beta=git+https://github.com/ocaml/ocaml-beta-repository.git ocaml-variants.4.13.0+trunk
+        continue-on-error: true
+        if: ${{ contains(matrix.ocaml-version, '+options') }}
+
+      # Dune (may fail with trunk)
+      - name: Install dune, if possible
+        id: dune
+        run: opam install dune
+        if: steps.ocaml.outcome == 'success' || steps.ocaml.outcome == 'skipped'
+        continue-on-error: ${{ contains(matrix.ocaml-version, '+options') }}
+
+      # Dependencies
+      - run: opam install . --deps-only --with-doc --with-test
+        if: steps.ocaml.outcome == 'success' || steps.ocaml.outcome == 'skipped'
+      - run: opam pin add num . --no-action
+        if: steps.ocaml.outcome == 'success' || steps.ocaml.outcome == 'skipped'
+
+      # Check building with make
+      - run: opam exec -- make
+        if: steps.ocaml.outcome == 'success' || steps.ocaml.outcome == 'skipped'
+      - run: opam exec -- make test
+        if: steps.ocaml.outcome == 'success' || steps.ocaml.outcome == 'skipped'
+      - run: opam exec -- make clean
+        if: steps.ocaml.outcome == 'success' || steps.ocaml.outcome == 'skipped'
+
+      # Check building with dune
+      - run: opam exec -- dune build @install
+        if: steps.dune.outcome == 'success'
+      - run: opam exec -- dune runtest
+        if: steps.dune.outcome == 'success'
+      - run: opam exec -- dune clean
+        if: steps.dune.outcome == 'success'
+
+      # Check installing with opam
+      - run: opam install num
+        if: steps.ocaml.outcome == 'success' || steps.ocaml.outcome == 'skipped'
diff --git a/Changelog b/Changelog
index 6015f79..0f2f3d1 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,13 @@
+Release Next
+
+- Pull request #22: use `-warn-error +A` instead of `-warn-error A` to avoid
+  deprecation alert in 4.13.
+- Pull request #23: fix the testsuite dune file and allow running the
+  testsuite via opam.
+- Pull request #25: remove all uses of globally-allocated, mutable temporaries,
+  making the Num library thread-safe and domain-safe.
+- Issue #26, pull request #27: improve dune file. 
+
 Release 1.4 (2020-11-09)
 
 - Pull request #20: fix bug caused by unsafe string/bytes conversions,
diff --git a/debian/changelog b/debian/changelog
index 02c4f57..a5c7b97 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ocaml-num (1.4+git20220302.1.703e1f8-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 30 Mar 2022 12:26:34 -0000
+
 ocaml-num (1.4-2) unstable; urgency=medium
 
   * Fix detection of native architectures
diff --git a/debian/patches/0001-Fix-detection-of-native-architectures.patch b/debian/patches/0001-Fix-detection-of-native-architectures.patch
index 45597ee..5b67621 100644
--- a/debian/patches/0001-Fix-detection-of-native-architectures.patch
+++ b/debian/patches/0001-Fix-detection-of-native-architectures.patch
@@ -7,10 +7,10 @@ Subject: Fix detection of native architectures
  test/Makefile | 2 +-
  2 files changed, 3 insertions(+), 3 deletions(-)
 
-diff --git a/src/Makefile b/src/Makefile
-index 8ad0e2c..715fa8b 100644
---- a/src/Makefile
-+++ b/src/Makefile
+Index: ocaml-num/src/Makefile
+===================================================================
+--- ocaml-num.orig/src/Makefile
++++ ocaml-num/src/Makefile
 @@ -33,7 +33,7 @@ COBJS=bng.$(O) nat_stubs.$(O)
  
  all:: libnums.$(A) nums.cma
@@ -29,10 +29,10 @@ index 8ad0e2c..715fa8b 100644
  TOINSTALL+=nums.cmxa nums.$(A) $(CMXS)
  endif
  ifeq "$(NATDYNLINK)" "true"
-diff --git a/test/Makefile b/test/Makefile
-index 6d7e8d8..43c3db5 100644
---- a/test/Makefile
-+++ b/test/Makefile
+Index: ocaml-num/test/Makefile
+===================================================================
+--- ocaml-num.orig/test/Makefile
++++ ocaml-num/test/Makefile
 @@ -13,7 +13,7 @@ all:: test.byt
  	@echo "----- Testing in bytecode..."
  	$(OCAMLRUN) -I ../src ./test.byt
diff --git a/dune-project b/dune-project
index 42c0c16..929c696 100644
--- a/dune-project
+++ b/dune-project
@@ -1 +1 @@
-(lang dune 1.10)
+(lang dune 2.0)
diff --git a/num.opam b/num.opam
index 304d4fd..c26d87f 100644
--- a/num.opam
+++ b/num.opam
@@ -1,5 +1,5 @@
 opam-version: "2.0"
-version: "1.4"
+version: "1.5~dev"
 maintainer: "Xavier Leroy <xavier.leroy@inria.fr>"
 authors: [
   "Valérie Ménissier-Morain"
@@ -12,6 +12,7 @@ bug-reports: "https://github.com/ocaml/num/issues"
 dev-repo: "git+https://github.com/ocaml/num.git"
 build: [
   [make]
+  [make "test"] {with-test}
 ]
 install: [
   make
diff --git a/src/Makefile b/src/Makefile
index 8ad0e2c..960ad34 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -18,7 +18,7 @@ else
 BNG_ARCH=$(ARCH)
 endif
 
-CAMLCFLAGS=-w +a-4-9-41-42-44-45-48 -warn-error A -bin-annot -g \
+CAMLCFLAGS=-w +a-4-9-41-42-44-45-48 -warn-error +A -bin-annot -g \
           -safe-string -strict-sequence -strict-formats
 CAMLOPTFLAGS=$(CAMLCFLAGS)
 ifeq "$(FLAMBDA)" "true"
diff --git a/src/dune b/src/dune
index 54e202e..cde1739 100644
--- a/src/dune
+++ b/src/dune
@@ -11,11 +11,9 @@
   (public_name num.core)
   (wrapped false)
   (modules arith_flags arith_status big_int int_misc nat num ratio)
-  (c_names nat_stubs bng bng_generic)
-  (c_flags "-DBNG_ARCH_%{architecture}")
-  (flags -w +a-4-9-41-42-44-45-48 -warn-error A -bin-annot -g -safe-string -strict-sequence -strict-formats))
-
-(rule
-  (targets bng_generic.c)
-  (deps bng_digit.c bng_amd64.c bng_arm64.c bng_ia32.c bng_ppc.c)
-  (action (run touch bng_generic.c)))
+  (foreign_stubs
+   (language c)
+   (names nat_stubs bng)
+   (extra_deps (glob_files *.c))
+   (flags "-DBNG_ARCH_%{architecture}"))
+  (flags -w +a-4-9-41-42-44-45-48 -warn-error +A -g -safe-string -strict-sequence -strict-formats))
diff --git a/src/nat.ml b/src/nat.ml
index e5fd7c2..f52f73c 100644
--- a/src/nat.ml
+++ b/src/nat.ml
@@ -75,11 +75,6 @@ let make_nat len =
   if len < 0 then invalid_arg "make_nat" else
     let res = create_nat len in set_to_zero_nat res 0 len; res
 
-(* Nat temporaries *)
-let a_2 = make_nat 2
-and a_1 = make_nat 1
-and b_2 = make_nat 2
-
 let copy_nat nat off_set length =
  let res = create_nat (length) in
   blit_nat res 0 nat off_set length;
@@ -240,7 +235,8 @@ let sqrt_nat rad off len =
  if shift_cand = length_of_digit then cand else
  begin
   complement_nat cand 0 cand_len;
-  shift_right_nat cand 0 1 a_1 0 shift_cand;
+  let tmp = create_nat 1 in
+  shift_right_nat cand 0 1 tmp 0 shift_cand;
   let next_cand = create_nat rad_len in
   (* Repeat until *)
   let rec loop () =
@@ -252,7 +248,7 @@ let sqrt_nat rad off len =
               i.e. next_cand <- cand + rad / cand *)
    ignore (add_nat next_cand cand_len cand_rest cand 0 cand_len 0);
         (* next_cand <- next_cand / 2 *)
-   shift_right_nat next_cand cand_len cand_rest a_1 0 1;
+   shift_right_nat next_cand cand_len cand_rest tmp 0 1;
    if lt_nat next_cand cand_len cand_rest cand 0 cand_len then
     begin  (* cand <- next_cand *)
      blit_nat cand 0 next_cand cand_len cand_len; loop ()
@@ -280,40 +276,8 @@ let pmax =
   | _ -> assert false
 ;;
 
-let max_superscript_10_power_in_int =
-  match length_of_digit with
-  | 64 -> 18
-  | 32 -> 9
-  | _ -> assert false
-;;
-let max_power_10_power_in_int =
-  match length_of_digit with
-  | 64 -> nat_of_int (Int64.to_int 1000000000000000000L)
-  | 32 -> nat_of_int 1000000000
-  | _ -> assert false
-;;
-
 let raw_string_of_digit nat off =
-  if is_nat_int nat off 1
-     then begin string_of_int (nth_digit_nat nat off) end
-  else begin
-       blit_nat b_2 0 nat off 1;
-       div_digit_nat a_2 0 a_1 0 b_2 0 2 max_power_10_power_in_int 0;
-       let leading_digits = nth_digit_nat a_2 0
-       and s1 = string_of_int (nth_digit_nat a_1 0) in
-       let len = String.length s1 in
-       if leading_digits < 10 then begin
-            let result = Bytes.make (max_superscript_10_power_in_int+1) '0' in
-            Bytes.set result 0 (Char.chr (48 + leading_digits));
-            String.blit s1 0 result (Bytes.length result - len) len;
-            Bytes.to_string result
-       end else begin
-            let result = Bytes.make (max_superscript_10_power_in_int+2) '0' in
-            String.blit (string_of_int leading_digits) 0 result 0 2;
-            String.blit s1 0 result (Bytes.length result - len) len;
-            Bytes.to_string result
-       end
-  end
+  Printf.sprintf "%nu" (nth_digit_nat_native nat off)
 
 (* XL: suppression de string_of_digit et de sys_string_of_digit.
    La copie est de toute facon faite dans string_of_nat, qui est le
diff --git a/test/dune b/test/dune
index 9ff3623..8d99b4a 100644
--- a/test/dune
+++ b/test/dune
@@ -1,7 +1,21 @@
-(alias
-  (name runtest)
-  (action (progn (run %{dep:test.bc}) (run %{dep:test.exe}))))
+(rule
+ (alias runtest)
+ (action (run ./driver.exe)))
 
-(executable
-  (name test)
+(rule
+ (alias runtest)
+ (action (run ./driver.bc)))
+
+(library
+  (name test_lib)
+  (modules test test_nats test_big_ints test_ratios test_nums test_io end_test)
   (libraries num))
+
+(executable
+  (name driver)
+  (modules driver)
+  (flags -linkall)
+  (modes (byte exe))
+  (libraries test_lib))
+
+(rule (with-stdout-to driver.ml (echo "")))
diff --git a/toplevel/Makefile b/toplevel/Makefile
index 4af7905..a4824e9 100644
--- a/toplevel/Makefile
+++ b/toplevel/Makefile
@@ -3,7 +3,7 @@ OCAMLDEP=ocamldep
 OCAMLFIND=ocamlfind
 
 CAMLCFLAGS=-I ../src -I +compiler-libs \
-           -w +a-4-9-41-42-44-45-48 -warn-error A \
+           -w +a-4-9-41-42-44-45-48 -warn-error +A \
            -safe-string -strict-sequence -strict-formats
 
 CMOS=num_top_printers.cmo num_top.cmo
diff --git a/toplevel/dune b/toplevel/dune
index 78c6e80..745f0f0 100644
--- a/toplevel/dune
+++ b/toplevel/dune
@@ -4,7 +4,7 @@
   (libraries num compiler-libs)
   (wrapped false)
   (modes byte)
-  (flags -w +a-4-9-41-42-44-45-48 -warn-error A -safe-string -strict-sequence -strict-formats))
+  (flags -w +a-4-9-41-42-44-45-48 -warn-error +A -safe-string -strict-sequence -strict-formats))
 
 (rule
   (with-stdout-to META.shim