How does @inline actually work?

am i supposed to put it in .res or .ressi files, or both?

putting it in X.res makes function calls inside X.res inlined (can tell in X.res.js), but other modules using X do not seem to inline the calls. putting it in X.resi does nothing anywhere.

is this as expected?

You’re supposed to put @inline annotations in both .res and .resi files, if you use an interface file and don’t add the annotation there too, the compiler simply can’t know the implementation required @inline.

Beware though, @inline doesn’t work with functions for example.

thanks.

it actually does work with functions because my js output changes with it:

@inline let add = (a: t, b: t): t => fromMillis(a->toMillis + b->toMillis)

if i remove inline:

-  return d$1 + h$1 + m$2 + s$2 + ms$1;
+  return add(add(add(add(d$1, h$1), m$2), s$2), ms$1);

maybe i’m still not supposed to use @inline for functions?

here’s an example from stdlib

  1. used only in res, not resi
  2. used on a function

is this incorrect?

ok apparently @inline works with functions indeed!

As you can see in this example, given the annotation is only present in the .res file, it’s only inlined inside the module, not if you use in outside of it.

If you want to inline things outside of the module, you should add @inline to the .resi file too.

the curious thing is that adding @inline in the .resi does not cause it to be inlined outside the module. things might be broken here

Possibly, could you provide a repo with a minimal repro?

The compiler has an internal experimental flag -bs-cross-module-opt which is off by default.
I think it leads to some errors in some cases but you could try to add

  "bsc-flags": [
    "-bs-cross-module-opt"
  ]

in your rescript.json

npm create rescript-app@latest
> select Command Line
> select rescript12-beta3

Add src/Foo.res:

@inline let a=(x, y)=>x+y
@inline let b=(x, y, z)=>a(a(x, y), z)

Add src/Foo.resi:

@inline let a:(int, int)=>int
@inline let b:(int, int, int)=>int

Modify src/Demo.res:

Console.log("Hello, world!")

let foo = Foo.b(1, 2, 3)

Inspect .res.mjs files:

File Foo.res.mjs:

// Generated by ReScript, PLEASE EDIT WITH CARE


function a(x, y) {
  return x + y | 0;
}

function b(x, y, z) {
  return (x + y | 0) + z | 0;
}

export {
  a,
  b,
}
/* No side effect */

Notice how a has be inlined inside b correctly

File Demo.res.mjs:

// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Foo from "./Foo.res.mjs";

console.log("Hello, world!");

let foo = Foo.b(1, 2, 3);

export {
  foo,
}
/*  Not a pure module */

Notice how Foo.b has not be inlined

  1. Adding -bs-cross-module-opt does nothing
  2. Removing @inline from Foo.resi optimizes Demo.res.mjs to say let foo = 6 (so the @inline directive actually prevents optimization in this case, in my codebase it does nothing because i’m not using constants)
  3. Removing @inline from Foo.res apparently does nothing in this example, but in my codebase, it prevents a from being inlined into b

Right. The flag appears to be for something else.

From what I can recall it is not really meant to work across modules. Did you read Inlining Constants | ReScript Language Manual ? Especially the last part (Tips & Tricks)?

This is not an optimization. This is an edge-case feature for folks who absolutely need particular values inlined for a JavaScript post-processing step, like conditional compilation. Beside the difference in code that the conditional compilation might end up outputting, there’s no performance difference between inlining and not inlining simple values in the eyes of a JavaScript engine.

1 Like

yes, but that doesn’t hold for function calls