Skip to end of metadata
Go to start of metadata

Problem Statement

ClojureScript was released with optimizations meant to leverage the capabilities of modern JavaScript engines. Initially ClojureScript keywords and symbols were both represented as strings with unicode prefix characters. This was to allow a small map implementation optimized for JavaScript - ObjMap. However JavaScript engines like JavaScriptCore, V8, and SpiderMonkey have advanced considerably in this time and have largely made the ObjMap optimization irrelevant. In fact ObjMap has since been replaced by ArrayMap and ArrayMap has shown better performance than ObjMap on every engine other than Firefox, and Firefox the performance is competitive.

As an initial step toward the possibility of self-hosting we moved symbols into a proper deftype with metadata support. Keywords however remain a challenge. This is because keywords implement IFn. Currently we support this via monkey patching the native JavaScript String object. This has already shown to wreck havoc with JavaScript library interop. We want a real keyword type to remove this problem but we need to consider our avenues.

Possible Solutions

  • Emit keyword constructors everywhere
    • Benefits:
      • simple, and similar to how we already emit Symbols
    • Tradeoffs
      • degraded performance due to increased allocations
        • keywords are extremely common in Clojure
  • Constant optimization
    • Benefits
      • efficient, we get the benefits of the above, but we allocate a keyword only once
        • consider something as common as `:else`
    • Tradeoffs
      • more complex to implement
      • need to introduce a new equality operator `keyword-identical?` since keywords parsed from EDN will not be identical?
        • creating a lookup table with string keys is undesirable as this cannot be optimized away via Google Closure

Constant Optimization

  1. At `analyze` in analyzer.clj we can detect constants. Each one should be entered into a map, likely a dynamic var bound to an atom containing a map. This map should map each constant's original form to a unique identifier, possibly some human readable prefix like `cljs$constant$vector$` plus a short ID of some kind. Note many constants will be composed recursively of constants.
    1. alternatively this could be done in the compiler via an AST pass?
  2. In the compiler when we emit we can instead emit a reference to a lookup table that we will generate later
  3. In closure.clj we will emit this table after we emit core.cljs but before any user code
  4. Initially this should be an option in order to give time to understand the overhead of this implicit initial allocation for ClojureScript programs.