Skip to content

Commit 87cdfbf

Browse files
committed
OxCaml playground
1 parent c5eb9ae commit 87cdfbf

File tree

13 files changed

+1528
-0
lines changed

13 files changed

+1528
-0
lines changed

.devcontainer/Dockerfile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
FROM mcr.microsoft.com/devcontainers/universal
2+
3+
RUN cd /root && \
4+
curl -L -o autoconf.tar.gz https://ftp.gnu.org/gnu/autoconf/autoconf-2.71.tar.gz && \
5+
tar fxz autoconf.tar.gz && \
6+
cd autoconf-2.71 && \
7+
./configure --prefix=/usr && \
8+
make && \
9+
make install && \
10+
cd .. && \
11+
rm -rf autoconf-2.71 autoconf.tar.gz
12+
13+
ARG USERNAME=codespace
14+
ARG USER_UID=1000
15+
ARG USER_GID=$USER_UID
16+
17+
RUN curl -L -o /usr/bin/opam https://github.com/ocaml/opam/releases/download/2.3.0/opam-2.3.0-i686-linux && \
18+
chmod +x /usr/bin/opam && \
19+
apt-get update && \
20+
apt-get install -y imagemagick && \
21+
apt-get clean && \
22+
rm -rf /var/lib/apt/lists/*
23+
24+
USER ${USERNAME}
25+
26+
RUN opam init -a --disable-sandboxing --yes && \
27+
opam switch create 5.2.0+flambda2 --yes \
28+
--repos "with-extensions=git+https://github.com/janestreet/opam-repository.git#with-extensions,default" && \
29+
eval $(opam env --switch 5.2.0+flambda2) && \
30+
opam install --yes ocamlformat merlin ocaml-lsp-server utop && \
31+
opam install --yes parallel core_unix
32+
33+
USER root

.devcontainer/devcontainer.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"build": {
3+
"dockerfile": "Dockerfile"
4+
},
5+
"name": "OxCaml Playground",
6+
"customizations": {
7+
"vscode": {
8+
"extensions": [
9+
"ocamllabs.ocaml-platform",
10+
"ms-vscode.cpptools",
11+
"eamodio.gitlens"
12+
]
13+
}
14+
}
15+
}

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# A playground for OxCaml
2+
3+
**(Disclaimer: currently in alpha)**
4+
5+
To make a playground, press the green "Code" button, then select "+" next to "Codespaces". A new Codespace will open. It currently takes maybe 20 or 30 minutes to initialize; please be patient. We'll work on improving this startup time shortly.
6+
You can click the link in the status popup in the bottom-right of the window to see current progress (although there are no spinners).
7+
8+
Once initialized you should have a full OPAM environment with the OxCaml compiler and dune on the path. VSCode will have the OCaml Platform plugin together with the LSP server and merlin, the editor assistant.
9+
10+
## Building your first OxCaml project
11+
12+
```shell
13+
$ cd parallel-example/filter
14+
$ dune build filter.exe
15+
$ ../_build/default/filter/filter.exe
16+
$ mogrify -format jpg filtered-ox.pgm
17+
```
18+
19+
Then you can open `filtered-ox.pgm` directly from the sidebar on the left. Behold the *filtered ox*.

parallel-example/capsules.ml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
open! Base
2+
module Capsule = Portable.Capsule.Expert
3+
4+
[@@@disable_unused_warnings]
5+
6+
let fork_join parallel =
7+
let ref = Capsule.Data.create (fun () -> ref 0) in
8+
Parallel.fork_join2
9+
parallel
10+
(fun _ -> (ref : _ @ uncontended))
11+
(fun _ -> (ref : _ @ uncontended))
12+
;;
13+
14+
let increment ~(access : 'k Capsule.Access.t) ref =
15+
let ref = Capsule.Data.unwrap ~access ref in
16+
ref := !ref + 1
17+
;;
18+
19+
let increment ~(password : 'k Capsule.Password.t) ref =
20+
Capsule.access ~password ~f:(fun access ->
21+
let ref = Capsule.Data.unwrap ~access ref in
22+
ref := !ref + 1)
23+
;;
24+
25+
let () =
26+
let (Capsule.Key.P (key : _ Capsule.Key.t)) = Capsule.create () in
27+
()
28+
;;
29+
30+
let parallel_key parallel =
31+
let (P key) = Capsule.create () in
32+
let ref = Capsule.Data.create (fun () -> ref 0) in
33+
Parallel.fork_join2
34+
parallel
35+
(fun _ ->
36+
Capsule.Key.with_password key ~f:(fun password -> increment ~password ref)
37+
|> (ignore : _ -> _))
38+
(fun _ -> ())
39+
;;
40+
41+
let merge_fresh () =
42+
let (P key) = Capsule.create () in
43+
let ref = Capsule.Data.create (fun () -> ref 0) in
44+
let access = Capsule.Key.destroy key in
45+
let ref = Capsule.Data.unwrap ~access ref in
46+
ref := !ref + 1
47+
;;
48+
49+
let parallel_mutexes parallel =
50+
let (P key) = Capsule.create () in
51+
let mutex = Capsule.Mutex.create key in
52+
let ref = Capsule.Data.create (fun () -> ref 0) in
53+
Parallel.fork_join2
54+
parallel
55+
(fun _ -> Capsule.Mutex.with_lock mutex ~f:(fun password -> increment ~password ref))
56+
(fun _ -> Capsule.Mutex.with_lock mutex ~f:(fun password -> increment ~password ref))
57+
;;

parallel-example/dune

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(library
2+
(name parallel_example)
3+
(public_name parallel_example)
4+
(libraries base parallel parallel_scheduler_work_stealing portable stdio)
5+
(preprocess
6+
(pps ppx_jane)))

parallel-example/dune-project

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(lang dune 3.19)
2+
3+
(package
4+
(name parallel_example))

parallel-example/filter/camel.pgm

Lines changed: 259 additions & 0 deletions
Large diffs are not rendered by default.

parallel-example/filter/dune

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(executables
2+
(modes byte exe)
3+
(names filter)
4+
(libraries core_unix.command_unix core parallel
5+
parallel.scheduler.work_stealing portable core_unix.time_stamp_counter)
6+
(preprocess
7+
(pps ppx_jane)))
8+

parallel-example/filter/filter.ml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
open! Core
2+
module Capsule = Portable.Capsule.Expert
3+
module Parallel_array = Parallel.Arrays.Array
4+
5+
let blur_at image ~x ~y =
6+
let width = Image.width image in
7+
let height = Image.height image in
8+
let acc = ref 0. in
9+
for i = -4 to 4 do
10+
for j = -4 to 4 do
11+
let x = Int.clamp_exn (x + i) ~min:0 ~max:(width - 1) in
12+
let y = Int.clamp_exn (y + j) ~min:0 ~max:(height - 1) in
13+
acc := !acc +. Image.get image ~x ~y
14+
done
15+
done;
16+
!acc /. 81.
17+
;;
18+
19+
let filter ~scheduler ~key image =
20+
let monitor = Parallel.Monitor.create_root () in
21+
Parallel_scheduler_work_stealing.schedule scheduler ~monitor ~f:(fun parallel ->
22+
let width = Image.width (Capsule.Data.project image) in
23+
let height = Image.height (Capsule.Data.project image) in
24+
let data =
25+
Parallel_array.init parallel (width * height) ~f:(fun i ->
26+
let x = i % width in
27+
let y = i / width in
28+
(Capsule.Key.access_shared key ~f:(fun access ->
29+
{ aliased = blur_at (Capsule.Data.unwrap_shared image ~access) ~x ~y }))
30+
.aliased)
31+
in
32+
Image.of_array (Parallel_array.to_array data) ~width ~height)
33+
;;
34+
35+
let command =
36+
Command.basic
37+
~summary:"filter an image"
38+
[%map_open.Command
39+
let file = anon (maybe_with_default "ox.pgm" ("FILE" %: string))
40+
and domains = flag "domains" (optional int) ~doc:"INT number of domains" in
41+
fun () ->
42+
let scheduler =
43+
(Parallel_scheduler_work_stealing.create [@alert "-experimental"]) ?domains ()
44+
in
45+
let (P key) = Capsule.create () in
46+
let image = Capsule.Data.create (fun () -> Image.load file) in
47+
let start = Time_stamp_counter.now () in
48+
let result = filter ~scheduler ~key image in
49+
let finish = Time_stamp_counter.now () in
50+
printf
51+
"Completed in %dms.\n"
52+
(Time_stamp_counter.Span.to_int_exn (Time_stamp_counter.diff finish start)
53+
/ 1_000_000);
54+
Image.save result ("filtered-" ^ Filename.basename file)]
55+
;;
56+
57+
let () = Command_unix.run command

parallel-example/filter/image.ml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
open! Base
2+
module In = Stdlib.In_channel
3+
module Out = Stdlib.Out_channel
4+
module Scanf = Stdlib.Scanf.Scanning
5+
6+
type t =
7+
{ width : int
8+
; height : int
9+
; data : float array
10+
}
11+
12+
let save { width; height; data } file =
13+
Out.with_open_text file (fun out ->
14+
let header = Stdlib.Printf.sprintf "P2\n%d %d\n255\n" width height in
15+
let data =
16+
Array.(fold_right [@mode shared]) data ~init:[] ~f:(fun x acc ->
17+
(x
18+
|> Float.clamp_exn ~min:0.0 ~max:1.0
19+
|> Float.( * ) 255.0
20+
|> Float.round_nearest
21+
|> Float.to_int
22+
|> Int.to_string)
23+
:: acc)
24+
|> List.chunks_of ~length:width
25+
|> List.map ~f:(String.concat ~sep:" ")
26+
|> String.concat ~sep:"\n"
27+
in
28+
Out.output_string out header;
29+
Out.output_string out data;
30+
Out.output_string out "\n")
31+
;;
32+
33+
let load file =
34+
In.with_open_text file (fun in_ ->
35+
let in_ = Scanf.from_channel in_ in
36+
let width, height, max =
37+
Stdlib.Scanf.bscanf in_ "P2\n%d %d\n%d\n" (fun width height max ->
38+
width, height, max)
39+
in
40+
let max = Float.of_int max in
41+
let data =
42+
Array.init (width * height) ~f:(fun _ ->
43+
Stdlib.Scanf.bscanf in_ " %f " (fun f -> f /. max))
44+
in
45+
Scanf.close_in in_;
46+
{ width; height; data })
47+
;;
48+
49+
let of_array data ~width ~height =
50+
if Array.length data <> width * height then invalid_arg "mismatched array size";
51+
{ width; height; data }
52+
;;
53+
54+
let width { width; _ } = width
55+
let height { height; _ } = height
56+
57+
let get { data; width; _ } ~x ~y =
58+
let idx = x + (y * width) in
59+
(Array.get [@mode shared]) data idx
60+
;;
61+
62+
let set { data; width; _ } ~x ~y p =
63+
let idx = x + (y * width) in
64+
Array.set data idx p
65+
;;

0 commit comments

Comments
 (0)