Repository: OCamlPro/owi
Pull request #530: Add owi_range
Author: login: S41d
Created: 2025-03-03T15:42:32Z | Merged: 2025-03-05T11:24:20Z



Diff:
diff --git a/CHANGES.md b/CHANGES.md
index 766155cf7..a394b03ee 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -26,6 +26,7 @@
 - add `owi c++` subcommand
 - add `owi rust` subcommand
 - add event logger
+- add `owi_range` primitive
 
 ## 0.2 - 2024-04-24
 
diff --git a/src/cmd/cmd_replay.ml b/src/cmd/cmd_replay.ml
index 1e16228bd..804fa83ab 100644
--- a/src/cmd/cmd_replay.ml
+++ b/src/cmd/cmd_replay.ml
@@ -83,6 +83,13 @@ let run_file ~unsafe ~optimize filename model =
         Fmt.epr "Got value %a but expected a f64 value." V.pp v;
         assert false
 
+    let symbol_range _ _ =
+      match model.(next ()) with
+      | V.I32 n -> n
+      | v ->
+        Fmt.epr "Got value %a but expected a i32 value." V.pp v;
+        assert false
+
     let print_char c = Fmt.pr "%c" (char_of_int (Int32.to_int c))
 
     let in_replay_mode () = 1l
@@ -111,6 +118,9 @@ let run_file ~unsafe ~optimize filename model =
       ; ( "bool_symbol"
         , Concrete_extern_func.Extern_func (Func (UArg Res, R1 I32), symbol_bool)
         )
+      ; ( "range_symbol"
+        , Concrete_extern_func.Extern_func
+            (Func (Arg (I32, Arg (I32, Res)), R1 I32), symbol_range) )
       ; ( "assume"
         , Concrete_extern_func.Extern_func (Func (Arg (I32, Res), R0), assume)
         )
diff --git a/src/concolic/concolic_wasm_ffi.ml b/src/concolic/concolic_wasm_ffi.ml
index 816eb6fa2..6a639f456 100644
--- a/src/concolic/concolic_wasm_ffi.ml
+++ b/src/concolic/concolic_wasm_ffi.ml
@@ -85,14 +85,24 @@ module M :
       let n = Concrete_value.Bool.int32 b in
       (I32 n, Value.Bool.int32 (b, Expr.symbol sym)) )
 
+  let add_pc_wrapper e = Concolic_choice.assume e
+
   let assume (i : Value.int32) : unit Concolic_choice.t =
     let c = Value.I32.to_bool i in
-    Concolic_choice.assume c
+    add_pc_wrapper c
 
   let assert' (i : Value.int32) : unit Concolic_choice.t =
     let c = Value.I32.to_bool i in
     Concolic_choice.assertion c
 
+  let symbol_range (lo : Value.int32) (hi : Value.int32) :
+    Value.int32 Concolic_choice.t =
+    let open Concolic_choice in
+    let* x = symbol_i32 () in
+    let* () = add_pc_wrapper (Value.I32.le lo x) in
+    let+ () = add_pc_wrapper (Value.I32.gt hi x) in
+    x
+
   open Expr
 
   let abort () : unit Concolic_choice.t = Concolic_choice.abort
@@ -158,6 +168,9 @@ let symbolic_extern_module =
     ; ( "bool_symbol"
       , Concolic.Extern_func.Extern_func (Func (UArg Res, R1 I32), symbol_bool)
       )
+    ; ( "range_symbol"
+      , Concolic.Extern_func.Extern_func
+          (Func (Arg (I32, Arg (I32, Res)), R1 I32), symbol_range) )
     ; ( "assume"
       , Concolic.Extern_func.Extern_func (Func (Arg (I32, Res), R0), assume) )
     ; ( "assert"
diff --git a/src/intf/wasm_ffi_intf.ml b/src/intf/wasm_ffi_intf.ml
index 140b77e24..3a9e7fc1d 100644
--- a/src/intf/wasm_ffi_intf.ml
+++ b/src/intf/wasm_ffi_intf.ml
@@ -31,6 +31,8 @@ module type S0 = sig
 
   val symbol_bool : unit -> Value.int32 t
 
+  val symbol_range : Value.int32 -> Value.int32 -> Value.int32 t
+
   val assume : Value.int32 -> unit t
 
   val assert' : Value.int32 -> unit t
diff --git a/src/libc/include/owi.h b/src/libc/include/owi.h
index aa398caa6..0f75e49da 100644
--- a/src/libc/include/owi.h
+++ b/src/libc/include/owi.h
@@ -23,6 +23,8 @@ __attribute__((import_module("symbolic"), import_name("f32_symbol"))) float
 owi_f32(void);
 __attribute__((import_module("symbolic"), import_name("f64_symbol"))) double
 owi_f64(void);
+__attribute__((import_module("symbolic"), import_name("range_symbol"))) int
+owi_range(int lo, int hi);
 __attribute__((import_module("symbolic"), import_name("print_char"))) void
 owi_print_char(int);
 __attribute__((import_module("symbolic"), import_name("bool_symbol")))
diff --git a/src/libc/src/klee.c b/src/libc/src/klee.c
index bd26fa1a4..dc845293d 100644
--- a/src/libc/src/klee.c
+++ b/src/libc/src/klee.c
@@ -36,10 +36,7 @@ void klee_make_symbolic(void *addr, size_t nbytes, const char *name) {
 }
 
 int klee_range(int begin, int end, const char *name) {
-  int n = owi_i32();
-  owi_assume(n >= begin);
-  owi_assume(n < end);
-  return n;
+  return owi_range(begin, end);
 }
 int klee_int(const char *name) { return owi_i32(); }
 
diff --git a/src/symbolic/symbolic_wasm_ffi.ml b/src/symbolic/symbolic_wasm_ffi.ml
index c0eba8db2..e34caf903 100644
--- a/src/symbolic/symbolic_wasm_ffi.ml
+++ b/src/symbolic/symbolic_wasm_ffi.ml
@@ -18,8 +18,10 @@ module M :
 
   module Value = Symbolic_value
 
+  let add_pc_wrapper e = Choice.add_pc e
+
   let assume (i : Value.int32) : unit Choice.t =
-    Choice.add_pc @@ Value.I32.to_bool i
+    add_pc_wrapper @@ Value.I32.to_bool i
 
   let assert' (i : Value.int32) : unit Choice.t =
     Choice.assertion @@ Value.I32.to_bool i
@@ -42,6 +44,13 @@ module M :
 
   let symbol_f64 () = Choice.with_new_symbol (Ty_fp 64) Expr.symbol
 
+  let symbol_range (lo : Value.int32) (hi : Value.int32) =
+    let open Choice in
+    let* x = symbol_i32 () in
+    let* () = add_pc_wrapper (Value.I32.le lo x) in
+    let+ () = add_pc_wrapper (Value.I32.gt hi x) in
+    x
+
   let abort () : unit Choice.t = Choice.stop
 
   let alloc m (base : Value.int32) (size : Value.int32) : Value.int32 Choice.t =
@@ -88,6 +97,9 @@ let symbolic_extern_module =
     ; ( "bool_symbol"
       , Symbolic.Extern_func.Extern_func (Func (UArg Res, R1 I32), symbol_bool)
       )
+    ; ( "range_symbol"
+      , Symbolic.Extern_func.Extern_func
+          (Func (Arg (I32, Arg (I32, Res)), R1 I32), symbol_range) )
     ; ( "assume"
       , Symbolic.Extern_func.Extern_func (Func (Arg (I32, Res), R0), assume) )
     ; ( "assert"
diff --git a/test/c/dune b/test/c/dune
index 5439aa65d..dd72aad70 100644
--- a/test/c/dune
+++ b/test/c/dune
@@ -10,4 +10,5 @@
   exit.c
   klee_compat.c
   main.c
-  malloc_aligned.c))
+  malloc_aligned.c
+  range.c))
diff --git a/test/c/range.c b/test/c/range.c
new file mode 100644
index 000000000..484e553b8
--- /dev/null
+++ b/test/c/range.c
@@ -0,0 +1,11 @@
+#include <owi.h>
+
+int main() {
+  int x = owi_range(10, 20);
+  owi_assert(x >= 10);
+  owi_assert(x < 20);
+
+  int y = owi_range(100, 200);
+  owi_assert(y >= 101);
+  owi_assert(y < 200);
+}
\ No newline at end of file
diff --git a/test/c/range.t b/test/c/range.t
new file mode 100644
index 000000000..bcaa97e63
--- /dev/null
+++ b/test/c/range.t
@@ -0,0 +1,9 @@
+  $ owi c ./range.c
+  Assert failure: (i32.gt symbol_1 100)
+  model {
+    symbol symbol_0 i32 10
+    symbol symbol_1 i32 100
+  }
+  Reached problem!
+  [13]
+
diff --git a/test/conc/dune b/test/conc/dune
index 2c92f0abd..433a46244 100644
--- a/test/conc/dune
+++ b/test/conc/dune
@@ -1,2 +1,2 @@
 (cram
- (deps %{bin:owi} global_sharing.wat immediately_fail.wat))
+ (deps %{bin:owi} global_sharing.wat immediately_fail.wat range.wat))
diff --git a/test/conc/range.t b/test/conc/range.t
new file mode 100644
index 000000000..42d68c023
--- /dev/null
+++ b/test/conc/range.t
@@ -0,0 +1,8 @@
+  $ owi conc ./range.wat
+  Assert failure
+  Model:
+    (model
+      (symbol_1 (i32.const 16))
+      (symbol_2 (i32.const 299)))
+  Reached problem!
+  [13]
diff --git a/test/conc/range.wat b/test/conc/range.wat
new file mode 100644
index 000000000..25e6616a7
--- /dev/null
+++ b/test/conc/range.wat
@@ -0,0 +1,18 @@
+(module
+  (import "symbolic" "range_symbol" (func $owi_range (param i32) (param i32) (result i32)))
+  (import "symbolic" "assert" (func $owi_assert (param i32)))
+
+  (start $start)
+
+  (func $start
+    (local $x i32) (local $y i32)
+
+    (local.set $x (call $owi_range (i32.const 10) (i32.const 20)))
+    (call $owi_assert (i32.ge_u (local.get $x) (i32.const 10)))
+    (call $owi_assert (i32.lt_u (local.get $x) (i32.const 20)))
+
+    (local.set $y (call $owi_range (i32.const 200) (i32.const 300)))
+    (call $owi_assert (i32.ge_u (local.get $y) (i32.const 200)))
+    (call $owi_assert (i32.lt_u (local.get $y) (i32.const 299)))
+  )
+)
diff --git a/test/replay/dune b/test/replay/dune
index 9a57afed2..ae7e72a9b 100644
--- a/test/replay/dune
+++ b/test/replay/dune
@@ -8,5 +8,7 @@
   missing_replay_file.wat
   no_bug.model
   no_bug.wat
+  range.model
+  range.wat
   simple.model
   simple.wat))
diff --git a/test/replay/range.model b/test/replay/range.model
new file mode 100644
index 000000000..6a3c01583
--- /dev/null
+++ b/test/replay/range.model
@@ -0,0 +1,4 @@
+model {
+  symbol symbol_0 i32 16
+  symbol symbol_1 i32 299
+}
diff --git a/test/replay/range.t b/test/replay/range.t
new file mode 100644
index 000000000..1d1d900e9
--- /dev/null
+++ b/test/replay/range.t
@@ -0,0 +1,38 @@
+  $ owi sym range.wat
+  Assert failure: (i32.lt_u symbol_1 299)
+  model {
+    symbol symbol_0 i32 16
+    symbol symbol_1 i32 299
+  }
+  Reached problem!
+  [13]
+  $ owi replay --replay-file range.model range.wat --debug
+  parsing      ...
+  checking     ...
+  grouping     ...
+  assigning    ...
+  rewriting    ...
+  typechecking ...
+  typechecking ...
+  linking      ...
+  interpreting ...
+  stack        : [  ]
+  running instr: call 2
+  calling func : func start
+  stack        : [  ]
+  running instr: i32.const 10
+  stack        : [ i32.const 10 ]
+  running instr: i32.const 20
+  stack        : [ i32.const 20 ; i32.const 10 ]
+  running instr: call 0
+  stack        : [ i32.const 16 ]
+  running instr: local.set 0
+  stack        : [  ]
+  running instr: local.get 0
+  stack        : [ i32.const 16 ]
+  running instr: i32.const 10
+  stack        : [ i32.const 10 ; i32.const 16 ]
+  running instr: i32.ge_u
+  stack        : [ i32.const 1 ]
+  running instr: call 1
+  Assertion failure was correctly reached
diff --git a/test/replay/range.wat b/test/replay/range.wat
new file mode 100644
index 000000000..8dcbeb54a
--- /dev/null
+++ b/test/replay/range.wat
@@ -0,0 +1,18 @@
+(module
+  (import "symbolic" "range_symbol" (func $owi_range (param i32) (param i32) (result i32)))
+  (import "symbolic" "assert" (func $owi_assert (param i32)))
+
+  (start $start)
+  (func $start
+    (local $x i32) (local $y i32)
+
+    (local.set $x (call $owi_range (i32.const 10) (i32.const 20)))
+    (call $owi_assert (i32.ge_u (local.get $x) (i32.const 10)))
+    (call $owi_assert (i32.lt_u (local.get $x) (i32.const 20)))
+
+    (local.set $y (call $owi_range (i32.const 200) (i32.const 300)))
+    (call $owi_assert (i32.ge_u (local.get $y) (i32.const 200)))
+    (call $owi_assert (i32.lt_u (local.get $y) (i32.const 299)))
+  )
+)
+
diff --git a/test/sym/dune b/test/sym/dune
index e9d598b2a..8afb6e8e0 100644
--- a/test/sym/dune
+++ b/test/sym/dune
@@ -22,6 +22,7 @@
   mul_i64.wat
   mul_f32.wat
   mul_f64.wat
+  range.wat
   store.wat
   sub_i32.wat
   sub_i64.wat
diff --git a/test/sym/range.t b/test/sym/range.t
new file mode 100644
index 000000000..fc9eafa89
--- /dev/null
+++ b/test/sym/range.t
@@ -0,0 +1,8 @@
+  $ owi sym ./range.wat
+  Assert failure: (i32.lt_u symbol_1 299)
+  model {
+    symbol symbol_0 i32 16
+    symbol symbol_1 i32 299
+  }
+  Reached problem!
+  [13]
diff --git a/test/sym/range.wat b/test/sym/range.wat
new file mode 100644
index 000000000..8df61ce41
--- /dev/null
+++ b/test/sym/range.wat
@@ -0,0 +1,18 @@
+(module
+  (import "symbolic" "range_symbol" (func $owi_range (param i32) (param i32) (result i32)))
+  (import "symbolic" "assert" (func $owi_assert (param i32)))
+
+  (start $start)
+
+  (func $start
+    (local $x i32) (local $y i32)
+
+    (local.set $x (call $owi_range (i32.const 10) (i32.const 20)))
+    (call $owi_assert (i32.ge_u (local.get $x) (i32.const 10)))
+    (call $owi_assert (i32.lt_u (local.get $x) (i32.const 20)))
+
+    (local.set $y (call $owi_range (i32.const 200) (i32.const 300)))
+    (call $owi_assert (i32.ge_u (local.get $y) (i32.const 200)))
+    (call $owi_assert (i32.lt_u (local.get $y) (i32.const 299)))
+  )
+)
\ No newline at end of file


Reviews:
user: login: redianthus
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T15:58:07Z
user: login: redianthus
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T15:59:33Z
user: login: redianthus
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T16:01:46Z
user: login: redianthus
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T16:02:34Z
user: login: redianthus
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T16:03:46Z
user: login: redianthus
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T16:03:52Z

Review comments:
diff_hunk: @@ -4,6 +4,8 @@
 
 module Expr = Smtml.Expr
 
+let (let*) = Concolic_choice.(let*)
path: src/concolic/concolic_wasm_ffi.ml
user: login: redianthus
body: Can you remove this line? I wrote another way to do it below.
created_at: 2025-03-03T15:58:07Z
author_association: MEMBER
diff_hunk: @@ -93,6 +95,12 @@ module M :
     let c = Value.I32.to_bool i in
     Concolic_choice.assertion c
 
+  let symbol_range (lo : Value.int32) (hi : Value.int32) : Value.int32 Concolic_choice.t =
+    let* x = symbol_i32 () in
+    let* () = assume (Value.Bool.int32 (Value.I32.le lo x)) in
+    let* () = assume (Value.Bool.int32 (Value.I32.gt hi x)) in
+    Concolic_choice.return x
path: src/concolic/concolic_wasm_ffi.ml
user: login: redianthus
body: You can use `let open Concolic_choice in` in the beginning to get access to `let*`.

Also, when you have the following pattern:

```ocaml
let* ...
let* ...
let* ...
return x
```

It can be replaced by:

```ocaml
let* ...
let* ...
let+ ... (* <- we use a let+ here now *)
x
```
created_at: 2025-03-03T15:59:33Z
author_association: MEMBER
diff_hunk: @@ -93,6 +95,12 @@ module M :
     let c = Value.I32.to_bool i in
     Concolic_choice.assertion c
 
+  let symbol_range (lo : Value.int32) (hi : Value.int32) : Value.int32 Concolic_choice.t =
+    let* x = symbol_i32 () in
+    let* () = assume (Value.Bool.int32 (Value.I32.le lo x)) in
+    let* () = assume (Value.Bool.int32 (Value.I32.gt hi x)) in
+    Concolic_choice.return x
+
path: src/concolic/concolic_wasm_ffi.ml
user: login: redianthus
body: I can't comment there, but you also have to add the newly added function to the list below: https://github.com/OCamlPro/owi/pull/530/files#diff-655f5bc7ec306dcbfee9902f293c2ebee139cdcfa8e1f901a0c8126b96ca35cfR150-R181
created_at: 2025-03-03T16:01:46Z
author_association: MEMBER
diff_hunk: @@ -79,6 +79,13 @@ let run_file ~unsafe ~optimize filename model =
         Fmt.epr "Got value %a but expected a f64 value." V.pp v;
         assert false
 
+    let symbol_range _ _ =
+      match model.(next ()) with
+      | V.I32 n -> n
+      | v ->
+        Fmt.epr "Got value %a but expected a i32 value." V.pp v;
+        assert false
+
path: src/cmd/cmd_replay.ml
user: login: redianthus
body: You also have to add the function below to the list of functions: https://github.com/OCamlPro/owi/pull/530/files#diff-fc3fa460100074e98bfc853fb3a78fd09509cc21162389c6f9374fc4c5eb8242R93-R130
created_at: 2025-03-03T16:02:34Z
author_association: MEMBER
diff_hunk: @@ -42,6 +44,12 @@ module M :
 
   let symbol_f64 () = Choice.with_new_symbol (Ty_fp 64) Expr.symbol
 
+  let symbol_range (lo : Value.int32) (hi : Value.int32) =
+    let* x = symbol_i32 () in
+    let* () = assume (Value.I32.le lo x) in
+    let* () = assume (Value.I32.gt hi x) in
+    Choice.return x
path: src/symbolic/symbolic_wasm_ffi.ml
user: login: redianthus
body: Here too you can use `let open Choice in` and remove the `return` by using `let+` on the last let-binding.
created_at: 2025-03-03T16:03:46Z
author_association: MEMBER
diff_hunk: @@ -6,6 +6,8 @@ module Expr = Smtml.Expr
 module Choice = Symbolic_choice_with_memory
 module Memory = Symbolic.Memory
 
+let (let*) = Choice.(let*)
path: src/symbolic/symbolic_wasm_ffi.ml
user: login: redianthus
body: This can be removed
created_at: 2025-03-03T16:03:51Z
author_association: MEMBER

PR comments:
user: login: redianthus
created_at: 2025-03-03T16:10:29Z
body: Could you add a short line in `CHANGES.md` to simply say we added this new primitive?
author_association: MEMBER
user: login: redianthus
created_at: 2025-03-05T11:24:25Z
body: Thanks!
author_association: MEMBER