Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimization epic #98

Open
1 of 36 tasks
jeaye opened this issue Oct 6, 2024 · 2 comments
Open
1 of 36 tasks

Optimization epic #98

jeaye opened this issue Oct 6, 2024 · 2 comments

Comments

@jeaye
Copy link
Member

jeaye commented Oct 6, 2024

General

  • Constant propagation (substituting constants in where possible)
  • Constant folding (compile-time eval of pure expressions with literals)
    • Requires tracking pure fns
  • Share closure contexts by having common value ordering across mutually recursive functions
  • Guaranteed named recursion TCO
  • Explicit function inlining (requires interop to be a big win)
    • Clojure uses this to inline RT.get, Numbers.multiply, etc

Function attribute tracking

Vars

  • If a var has :jank/static? true meta, deref + store it in the module's global ctor and never deref it again (superseded by direct linking)
    • Also, in each function, only load it once

Dead code elimination

  • Dead branches
  • Pure statements

Boxing

Startup

  • Group all fns into a single module to load
  • Box globals in static memory, not with the GC
  • Add CLI option to elide meta (see Clojure's elide-meta)

Form rewriting

  • Auto-transient
  • Auto-transducer
  • Zero-cost map destructuring (see here)
  • General loop unrolling
  • Specific loop unrolling (i.e. get-in)
  • Faster str usage (a la stringer)

IR-specific

  • Don't require boxing strings for reading globals #301
  • Don't put meta into globals
  • Optimize the passes we run and offer different levels
  • Put this into closure context for named recursion
    • Can also work by forcing normal functions into closures if they're named recursive

AOT-only

  • Direct linking (dynamic call)
  • Direct linking (direct call)
  • Unused var removal (includes defns)
  • Flag to elide var meta
  • Remove unused args from fns

Runtime

  • Use immer to merge maps

References

@jeaye jeaye changed the title Optimization pass epic Optimization epic Mar 10, 2025
@frenchy64
Copy link
Contributor

frenchy64 commented Mar 20, 2025

Idea: if you're likely to call an (immutable) function more than once with a known arity, unbox it until you know its calling convention. If possible, then use f->call(...) directly instead of dynamic_call. Saves a visit on each call, and the more arguments passed, the more work is saved each call (as dynamic_call is more expensive for higher arities).

Applicable to things like: (let [f ..] (loop [..] (f a b))) or (let [f ..] (reduce (fn [..] (f a b)) ..))

Not sure if you could go even further and extract the raw function pointers, and also manually pass the context of closures. I'm not sure if that would force you to use std::function or if you could call them directly. Would additionally save the virtual method call.

Similar things could be done to avoid apply. If we know the arities of a function at compile time like (let [opaque [{} []]] (apply select-keys opaque)) we could skip apply_to and compile the equivalent of:

(if (= 2 (count opaque))
  (select-keys (nth opaque 0) (nth opaque 1))
  (throw (ex-info "wrong arguments to select-keys" {}))

@jeaye
Copy link
Member Author

jeaye commented Mar 20, 2025

It's impossible to get the type of an unknown object, which could be anything, without a visit. We cannot store that type outside of the visitor. This is more of a physics problem with static type systems and type erasure.

The only way to do this optimization is to know the type ahead of time. Now, once we have seamless interop, and then we add type tracking for known values, we can actually know the full type of a local function and generate a call to ->call accordingly. This is what we had with C++ codegen and then lost when everything went to LLVM IR. We can get more of it back once we have support for interop, since the compiler and do implicit interop as part of its codegen, not just for explicit interop forms. Until we have that type info ahead of time, though, this is impossible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants