Is this optimized by the OCaml compiler?

The code below has a superfluous () expression. Is this perhaps optimized away (i.e removed) by the OCaml compiler (ocamlopt.opt) ?

let () = 
  ();
  print_string "hello";  

This is indeed optimised away by the compiler. You can see that by using some options to dump the intermediate representations used by the compiler:

$ ocamlopt -c -dcmm tmp/unit.ml -dlambda
(let (*match*/269 = (seq 0 (apply (field 41 (global Stdlib!)) "hello")))
  (makeblock 0))

cmm:
(data)
(data global "camlUnit__gc_roots" "camlUnit__gc_roots": int 0)
(data int 768 global "camlUnit" "camlUnit":)
(data
 int 2044
 global "camlUnit__const_immstring_4"
 "camlUnit__const_immstring_4":
 string "hello"
 skip 2
 byte 2)
(function camlUnit__entry ()
 (app{tmp/unit.ml:3,2-22;stdlib.ml:487,21-43} "camlStdlib__output_string_763"
   (load_mut val "camlStdlib__Pccall_1851") "camlUnit__const_immstring_4"
   unit)
 1)

(data)

In this case you can see that in the lambda representation, there is a the seq 0 (....), which correspond to the (); in your example.
When you look at the cmm code generated for the entry point of the module (i.e. its initialisation code), you see:

(function camlUnit__entry ()
 (app{tmp/unit.ml:3,2-22;stdlib.ml:487,21-43} "camlStdlib__output_string_763"
   (load_mut val "camlStdlib__Pccall_1851") "camlUnit__const_immstring_4"
   unit)
 1)

Where the code directly calls the output_string function from the stdlib (this is because the call to print_string has been inlined, indeed, if you look at the definition of print_string in the stdlib.ml file from the compiler distribution, you see: let print_string s = output_string stdout s). In any case, you can see that there is nothing before that call, which means the seq 0 (...) from the lambda code has been optimized away.

1 Like

The lambda representation I get using the same way seems to be different:

let () =
  ();
  print_string "Hello";


The result is:

λ ~/Workspace/Demo/ ocamlopt -c -dcmm demo.ml -dlambda 
(seq
  (let
    (*match*/269 = (seq 0 (apply (field_imm 41 (global Stdlib!)) "Hello")))
    0)
  0)

cmm:
(data)
(data int 768 global "camlDemo" "camlDemo":)
(data
 global "camlDemo__gc_roots"
 "camlDemo__gc_roots":
 addr "camlDemo"
 int 0)
(data int 2044 "camlDemo__1": string "Hello" skip 2 byte 2)
(function camlDemo__entry ()
 (let
   *match*/269
     (seq []
       (app{stdlib.ml:485,21-43} "camlStdlib__output_string_248"
         (load_mut val (+a "camlStdlib" 304)) "camlDemo__1" val))
   [])
 1)

(data)

I am using ocaml5.0.0 compiler on linux

Interesting ! For the record, I used a 4.14.0 compiler for the above, I’ll look at what happens with a 5.0 compiler.

Note: it is much easier to read the intermediate representation of functions than of top-level expressions, so you should put the code you care about in a function. (If anything, you can grep the function name to find the right place.)

I suspect the difference is that one of them was compiled with flambda and not the other. The first one (from @zozozo) looks like it was compiled with flambda, while the second one (from @Muqiu-Han) looks like it was compiled without flambda.

I switched to 4.14.0 and got the same result, I think maybe it’s true as @vlaviron said, I didn’t compile with Flambda. But I use the same compile parameters as you:

λ ~/Workspace/Demo/ ocaml --version
The OCaml toplevel, version 4.14.0
λ ~/Workspace/Demo/ ocamlopt -c -dcmm demo.ml -dlambda
(seq
  (let (*match*/268 = (seq 0 (apply (field 41 (global Stdlib!)) "Hello"))) 0)
  0)

cmm:
(data)
(data int 768 global "camlDemo" "camlDemo":)
(data
 global "camlDemo__gc_roots"
 "camlDemo__gc_roots":
 addr "camlDemo"
 int 0)
(data int 2044 "camlDemo__1": string "Hello" skip 2 byte 2)
(function camlDemo__entry ()
 (let
   *match*/268
     (seq []
       (app{stdlib.ml:487,21-43} "camlStdlib__output_string_250"
         (load_mut val (+a "camlStdlib" 304)) "camlDemo__1" val))
   [])
 1)

(data)

Yes, you are right! I need to install ocaml-option-flambda to enable flambda compilation, otherwise -dlambda will not take effect, After I installed it for ocaml5.0.0, the result is the same as @zozozo!

λ ~/Workspace/Demo/ ocamlopt -c -dcmm demo.ml -dlambda
(let (*match*/270 = (seq 0 (apply (field_imm 41 (global Stdlib!)) "Hello")))
  (makeblock 0))

cmm:
(data)
(data global "camlDemo__gc_roots" "camlDemo__gc_roots": int 0)
(data int 768 global "camlDemo" "camlDemo":)
(data
 int 2044
 global "camlDemo__const_immstring_4"
 "camlDemo__const_immstring_4":
 string "Hello"
 skip 2
 byte 2)
(function camlDemo__entry ()
 (app{demo.ml:3,2-22;stdlib.ml:485,21-43} "camlStdlib__output_string_766"
   (load_mut val "camlStdlib__Pccall_1834") "camlDemo__const_immstring_4"
   unit)
 1)

(data)
λ ~/Workspace/Demo/ ocamlopt --version
5.0.0
λ ~/Workspace/Demo/ opam install ocaml-option-flambda                   
[NOTE] Package ocaml-option-flambda is already installed (current version is 1).

Note that the superfluos () is also optimized away without Flambda, but the optimization takes place after the generation of C–, so it is not visible when using -dcmm.

Cheers,
Nicolas

2 Likes

(-dlinear for example shows the () gone away.)

1 Like