<< Back to previous view

[CLJS-1439] Add type annotations to goog-define defined vars Created: 01/Sep/15  Updated: 01/Sep/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Martin Klepsch Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Currently it's still required to annotate goog-define constants with ^boolean to allow the Closure compiler to safely remove dead branches. The macro could emit the var with ^boolean metadata to make this unnecessary.

In general it would be nice to have similar annotations for the already defined constants like goog.DEBUG although I'm not sure how/if that's possible.






[CLJS-1437] Recompile dependents broken when files are not inside one directory Created: 30/Aug/15  Updated: 30/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Juho Teperi Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: build


 Description   

When a file is changed files dependent on that file are marked for recompile. This doesn't seem to work when the changed file is not in the directory which is passed to build and the directory where dependent namespace is.

Looks like this caused by how build and compile-file work:

  • Build calls -compile -> compile-dir -> compile-root which sorts the files in dependency order and calls -compile for the files in dependency order
    • core ns is compiled
  • Build calls add-depedencies -> add-dependencies -> get-compiled-cljs -> -compile -> compile-file
    • test ns is compiled, core ns is marked as dependent on test ns but as core ns is already compiled this doesn't matter

I believe this effectively breaks :recompile-dependents in cases all cases but one where all the sources are inside one directory. In multiple directory case order of directories passed to cljs.build.api/inputs affects the order on which compile is called so it's possible that some dependent namespaces are compiled before namespaces they depend on are compiled.

Original problem was that added tests were not picked up by run-tests but I have since reproduced this without cljs.test.

The minimal way to reproduce this needs two namespaces, where one depends on another. To display that the dependent namespace is not recompiled, it will print some compile time metadata from the another namespace.

src/hello/core.cljs

(ns hello.core
  (:require hello-world.test))

(enable-console-print!)

(println (:foo (meta #'hello.test/a)))

src2/hello/test.cljs

(ns hello.test)

(def ^{:foo :bar} a "foobar")

To build:

(require '[cljs.build.api :refer [build]])

(def opts {:main "hello.core"
           :output-to "out/main.js"
           :output-dir "out"
           :optimizations :none
           :verbose true})

(defn broken []
  (build "src" opts))

(defn working []
  " Note: ordering of the dirs matters
  (build (inputs "src2" "src" opts)))

To trigger the problem, the metadata needs to be changed after running the build once. Changing test ns doesn't cause core ns to recompiled and changing the metadata doesn't have any effect to println on core ns.






[CLJS-1436] self-host: Doesn't load dep ns when loading own cache Created: 29/Aug/15  Updated: 30/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Mike Fikes Assignee: Mike Fikes
Resolution: Unresolved Votes: 0
Labels: bootstrap


 Description   

Background: Both *load-fn* and *eval-fn* support the concept of passing JavaScript and analysis cache info. Such data passed to *eval-fn* should be able to be stored and subsequently passed to *load-fn* upon subsequent runs in order to effect analysis/compilation caching.

If one namespace depends on another then transitive loading occurs if no caching is involved. But if cached information it returned to *load-fn* when the top-level namespace is loaded, then transitive loading does not occur.

My speculation is that this is the case because, while the analysis cache is incorporated here

https://github.com/clojure/clojurescript/blob/r1.7.122/src/main/cljs/cljs/js.cljs#L206-L208

nothing further is done to load dependent namespaces as is done when ClojureScript source is passed in the callback.

Here is a concrete example. Let foo.core depend on bar.core:

(ns foo.core
  (:require bar.core))

(prn bar.core/x)
(ns bar.core)

(def x 2)

If you require foo.core (via an ns form passed to eval-str), then *load-fn* is called with

{:name foo.core, :macros nil, :path "foo/core"}

and it is called back upon with the loaded ClojureScript source:

{:lang :clj, :source "(ns foo.core\n  (:require bar.core))\n\n(prn bar.core/x)\n"}

and the embedded :require then causes *load-fn* to be invoked for bar.core:

{:name bar.core, :macros nil, :path "bar/core"}

with its source being passed to the callback:

{:lang :clj, :source "(ns bar.core)\n\n(def x 2)\n"}

Then the evaluation phase begins with two evaluations (done properly in reverse dependency order):

{:lang :clj, :name bar.core, :path "bar/core", :source "goog.provide(\"bar.core\");\nbar.core.x = 2;\n", :cache {:use-macros nil, :excludes #{}, :name bar.core, :imports nil, :requires nil, :uses nil, :defs {x {:name bar.core/x, :file nil, :line 3, :column 1, :end-line 3, :end-column 7, :meta {:file bar.core, :line 3, :column 6, :end-line 3, :end-column 7}}}, :require-macros nil, :doc nil}}

and

{:lang :clj, :name foo.core, :path "foo/core", :source "goog.provide(\"foo.core\");\ncljs.core.prn.call(null,bar.core.x);\n", :cache {:name foo.core, :doc nil, :excludes #{}, :use-macros nil, :require-macros nil, :uses nil, :requires {bar.core bar.core}, :imports nil}}

which causes 2 to be printed.

Then if you start fresh, but go through the same sequence, consuming cached information, you start off the same with *load-fn* being called the same way:

{:name foo.core, :macros nil, :path "foo/core"}

and with the callback now being passed cached information back in return:

{:lang :js, :source "goog.provide(\"foo.core\");\ncljs.core.prn.call(null,bar.core.x);\n", :cache {:name foo.core, :doc nil, :excludes #{}, :use-macros nil, :require-macros nil, :uses nil, :requires {bar.core bar.core}, :imports nil}}

This JavaScript is evaluated by cljs.js here

https://github.com/clojure/clojurescript/blob/r1.7.122/src/main/cljs/cljs/js.cljs#L205

and this causes this to be printed

Can't find variable: bar

Perhaps this isn't a defect if instead it is expected that clients of cljs.js handle the loading of dependent namespaces when caching is involved.



 Comments   
Comment by David Nolen [ 30/Aug/15 10:34 AM ]

A patch for this is welcome. Analysis caches for a ns include all the information needed to load lib and macro dependencies.





[CLJS-1427] Add a cljs.core/ICloseable protocol definition Created: 16/Aug/15  Updated: 16/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Add protocol definition that abstracts over the concept of entities that can be closed.

Rationale: It naturally goes with IWriter and the proposed IReader (CLJS-1426), making it easy to define closable writers or readers.

(defprotocol IClosable
  "Protocol for closable entities."
  (-close [this] "Closes this entity."))





[CLJS-1426] Add a cljs.core/IReader protocol definition Created: 16/Aug/15  Updated: 16/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

There is a cljs.core/IWriter

https://github.com/clojure/clojurescript/blob/a741cd4a3d0683f9dcfd08822343abcfa477d951/src/main/cljs/cljs/core.cljs#L618

The addition of cljs.core/IReader would be useful to projects that wish abstract over the concept of reading.

Perhaps a definition along the following lines:

(defprotocol IReader
  "Protocol for reading."
  (-read [this] "Returns available characters as a string or nil if EOF."))





[CLJS-1425] self-host: cljs.js/eval cb argument inconsistent with docstring Created: 14/Aug/15  Updated: 14/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: bootstrap


 Description   

The docstring for cljs.js/eval indicates a map (containing either an :error or :value key).

This is the case for :error: Here is an example:

https://github.com/clojure/clojurescript/blob/r1.7.48/src/main/cljs/cljs/js.cljs#L489

But for :value the value is returned without being wrapped in a map; instead the result of applying *eval-fn* is directly returned (and *eval-fn* is supposed to return the value). Here is an example:

https://github.com/clojure/clojurescript/blob/r1.7.48/src/main/cljs/cljs/js.cljs#L499






[CLJS-1423] self-host: Requiring analyzer/compiler breaks unchecked Boolean Created: 14/Aug/15  Updated: 14/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: bootstrap
Environment:

Quick Start



 Description   

Grab the Quick Start cljs.jar, and make a src/foo/core.cljs file with

(ns foo.core)

(if 0 
  (prn 1) 
  (prn 2))

Build it using java -cp cljs.jar:src clojure.main build.clj where build.clj contains:

(require 'cljs.build.api)

(cljs.build.api/build "src" 
  {})

Inspect out/foo/core.js:

// Compiled by ClojureScript 1.7.48 {}
goog.provide('foo.core');
goog.require('cljs.core');
if(cljs.core.truth_((0))){
cljs.core.prn.call(null,(1));
} else {
cljs.core.prn.call(null,(2));
}

//# sourceMappingURL=core.js.map

Now revise src/foo/core.cljs to require the analyzer namespace:

(ns foo.core
  (:require cljs.analyzer))

(if 0 
  (prn 1) 
  (prn 2))

Build again and inspect out/foo/core.js:

// Compiled by ClojureScript 1.7.48 {}
goog.provide('foo.core');
goog.require('cljs.core');
goog.require('cljs.analyzer');
if((0)){
cljs.core.prn.call(null,(1));
} else {
cljs.core.prn.call(null,(2));
}

//# sourceMappingURL=core.js.map

Note the lack of cljs.core.truth_



 Comments   
Comment by Mike Fikes [ 14/Aug/15 3:41 PM ]

Speculation: The intent of this line

https://github.com/clojure/clojurescript/blob/r1.7.48/src/main/clojure/cljs/analyzer.cljc#L1565

is to provide *unchecked-if* capability in bootstrapped environments. But, perhaps it additionally acts as a meta-circular gateway allowing cljs.analyzer/*unchecked-if* to be set in new scenarios while compiling ClojureScript using the JVM-based compiler. (That line was not there previously.)

Fact: If you remove this line, it works. Additionally, if you revise it to set a literal false, things work. Neither are satisfying as they would defeat the original intent of that bit of code.

I have a much more complicated downstream usage of bootstrap that exhibits similar behavior with respect to the minimal repro above (https://github.com/mfikes/planck/issues/84).





[CLJS-1422] cljs.js/eval-str fails for ns form on node.js with simple optimizations Created: 14/Aug/15  Updated: 27/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Michael Ballantyne Assignee: Michael Ballantyne
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File CLJS_1422.patch     Text File CLJS_1422-v2.patch    
Patch: Code and Test
Approval: Accepted

 Description   

find-ns-obj has a special case for node.js under simple optimizations that uses eval to find the namespace object. If the object does not already exist this produces an exception; on other platforms find-ns-obj returns nil for a non-existant namespace.

As a result eval-str produces an error when evaluating a ns form for a new namespace.

There's a commented out test for this already in the source at https://github.com/clojure/clojurescript/blob/master/src/test/self/self_host/test.cljs#L121



 Comments   
Comment by Michael Ballantyne [ 14/Aug/15 1:17 PM ]

Added a patch with a fix and uncomment the existing test.

The special case of find-ns-obj for node.js under optimizations now checks each path segment with an eval of typeof to see if it exists, returning nil if the namespace object is absent.

Comment by David Nolen [ 26/Aug/15 10:47 PM ]

The overall idea is sound but the patch needs work. Just use goog.isObject instead of the needless js/eval call.

Comment by Michael Ballantyne [ 27/Aug/15 12:36 AM ]

I believe we need at least one `js/eval` of `typeof` in order to establish whether the object for the initial namespace segment exists, as there is no parent object to examine and referencing an undefined variable throws `ReferenceError`. `goog.isObject(thing)` where `thing` is a reference to an undefined variable throws `ReferenceError`; `typeof thing` returns `"undefined"`.

However, after obtaining the initial namespace segment we can use the normal `find-ns-obj*`. I've attached an updated patch using this strategy.

Comment by David Nolen [ 27/Aug/15 6:33 AM ]

Just a small code review thing to clarify the intent, why not just remove the multiple js/evals and lean on try/catch? The only possible error is attempting to use an undefined var which in that case we just want to bail anyway.





[CLJS-1415] Handling JSDoc param name [x] optional syntax Created: 10/Aug/15  Updated: 11/Aug/15

Status: In Progress
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: type-check





[CLJS-1412] Add JSDoc type information to individual IFn methods Created: 10/Aug/15  Updated: 11/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: type-check


 Description   

Propagate user supplied docstring type information to the various fn arities so that more code may be checked.






[CLJS-1410] Support source maps in deps.cljs Created: 09/Aug/15  Updated: 09/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Martin Klepsch Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

There should be support to package source maps with a foreign-lib using deps.cljs






[CLJS-1407] Exposing output file dependency graph in API Created: 09/Aug/15  Updated: 28/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Juho Teperi Assignee: Juho Teperi
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Use case for boot-cljs and boot-reload:

After compilation boot-reload reloads the changed JS files. So that the files can be reloaded in correct order, boot-cljs uses dependency graph to sort the files. Currently boot-cljs accesses compiler state directly and uses data from :js-dependency-index to build the graph: https://github.com/adzerk-oss/boot-cljs/blob/0.0-3308/src/adzerk/boot_cljs/impl.clj#L17-L36

Simple solution:

If dependencies (requires) of namespace are exposed through API it is easy to build graph of cljs namespace dependencies: https://github.com/adzerk-oss/boot-cljs/blob/d479f10935be321232e2363e2ae3e9cc515a81af/src/adzerk/boot_cljs/impl.clj#L12-L32

Problem with this solution is that all-ns, ns-dependencies or target-file-for-cljs-ns do not work with foreign-deps. While foreign-dep files don't usually change and thus aren't reloaded, it's possible that user has local JS files in the project using foreign-deps and those can change.

Questions, notes and issues

  • Should cljs-dependency-graph be exposed in the API or is it enough to provide ns-dependencies and such which user can use to create dependency graph?
  • cljs.build.api/parse-js-ns can also be used to read provides and requires from compiled JS files, but this doesn't work with foreign-deps either
  • Perhaps there is some way in Closure library to reload files in correct order?
  • Supporting foreign-deps is not perhaps necessary, but if there is good way it would be nice to have.


 Comments   
Comment by Juho Teperi [ 11/Aug/15 3:18 AM ]

I would add the call to cljs.compiler.api and it could be called output-dependency-graph.

Creating the graph requires list of all the nodes and dependencies for each node. For Cljs namespaces
these are accessible through all-ns and ns analysis map :requires. Data about foreign-deps
and closure libs is available in the compiler state under :js-dependency-index key. To create the
graph we need to:

1. Get list of all nodes
2. Get dependencies for given node
3. Get output file for given node

Because steps 2 and 3 depend on the type of node, it would probably be easiest to collect those
values in step 1. So step 1 would do something like this:

{{(get-nodes ...) => {:provides "goog.net" :file "out/goog/net.js" :dependencies #{"goog.foo"}} {:provides "frontend.core" :file "out/frontend/core.js" :dependencies #{"cljs.core"}}}}

That could be implemented by concatenating data from cljs namespaces retrieved from all-ns etc. with
data from :js-dependency-index. The next and last step would be to construct the graph using reduce.

Using this implementation there would be just one new API call: output-dependency-graph.

I was thinking alternative approach with all-ns, find-ns etc. versions which would work also with foreign-deps and closure libs, but I don't think it's very easy (or efficient) e.g. to retrieve data for foreign-dep with just a name as they are indexed by file paths.





[CLJS-1402] Source Mapping Closure Error Logger Created: 08/Aug/15  Updated: 11/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: type-check


 Description   

Current error reports generated by Google Closure point back to the generated JavaScript sources. For JavaScript source that originated from ClojureScript we should generated source mapped reports.






[CLJS-1373] Generalize CLJS-1324, check invokes of all IFn implementors Created: 28/Jul/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

We currently track all IFn implementors but in order to do arity checking of statically analyzeable invokes of keywords, vector, etc. we need to do a bit more. extend-type should update the type in the compiler state with :method-params :max-fixed-arity and :variadic. Then we can just reuse the existing checks in cljs.analyzer/parse-invoke.






[CLJS-1350] Compiler support for browser REPL Created: 19/Jul/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None


 Description   

Currently the browser REPL experience could be considerably enhanced just by eliminating manual configuration in source. Instead REPL configuration could happen via a compiler option. This would make REPL support considerably more robust in the face of user errors while developing.






[CLJS-1346] Support require outside of ns Created: 18/Jul/15  Updated: 18/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Jonathan Boston Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Makes http://dev.clojure.org/jira/browse/CLJS-1277 useful.






[CLJS-1344] port Clojure tuples commit Created: 17/Jul/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Task Priority: Major
Reporter: David Nolen Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File 0001-CLJS-1344-port-Clojure-tuples-commit-of-16-July-2015.patch     Text File 0002-CLJS-1344-port-Clojure-tuples-commit-of-16-July-2015.patch     Text File 0003-CLJS-1344-port-Clojure-tuples-commit-of-16-July-2015.patch    

 Description   

See https://github.com/clojure/clojure/commit/36d665793b43f62cfd22354aced4c6892088abd6



 Comments   
Comment by Michał Marczyk [ 18/Jul/15 11:38 AM ]

Patch based on current master.

Comment by Michał Marczyk [ 18/Jul/15 11:50 AM ]

In absence of abstract bases macros seemed like the most straightforward way to keep things DRY. Anything involving transients or metadata still uses PV, as in Clojure.

Comment by Michał Marczyk [ 18/Jul/15 12:04 PM ]

The 0002 patch is the same, except it does NOT change PV's -equiv to check satisfies? IVector rather than instance? PersistentVector. (The 0001 patch does make this change.)

Haven't made up my mind as to whether it's better to switch or not, so I thought I'd prepare both versions.

Comment by Michał Marczyk [ 18/Jul/15 12:25 PM ]

Some benchmark results, for now obtained using SpiderMonkey (I've just realized that I don't have a working V8 setup on this box – which is not the one I normally use for CLJS dev – I'll have to look into fixing that).

On the subject of -equiv, script benchmark says 0002 patch is very slightly faster than 0001 patch, which itself is noticeably faster than master: 633 ms vs 660 ms vs 781 ms in the vector equality benchmark.

The reason both patches are faster than master is undoubtedly their choice to call -count rather than count on the "other thing"; that is 100% justified in the 0002 patch (with instance?) and slightly less justified in the 0001 patch (who could implement IVector without ICounted though?).

Full script/benchmark results:

master
======

Benchmarking with SpiderMonkey
[x 1], (identity x), 1000000 runs, 0 msecs
;; symbol construction
[], (symbol (quote foo)), 1000000 runs, 585 msecs

;; array-reduce & ci-reduce
[coll (seq arr)], (ci-reduce coll + 0), 1 runs, 137 msecs
[coll (seq arr)], (ci-reduce coll sum 0), 1 runs, 735 msecs
[coll arr], (array-reduce coll + 0), 1 runs, 3 msecs
[coll arr], (array-reduce coll sum 0), 1 runs, 297 msecs
;;; instance?
[coll []], (instance? PersistentVector coll), 1000000 runs, 23 msecs
;;; satisfies?
[coll (list 1 2 3)], (satisfies? ISeq coll), 1000000 runs, 21 msecs
[coll [1 2 3]], (satisfies? ISeq coll), 1000000 runs, 32 msecs

;;; array & string ops
[coll (array 1 2 3)], (seq coll), 1000000 runs, 524 msecs
[coll "foobar"], (seq coll), 1000000 runs, 1879 msecs
[coll (array 1 2 3)], (first coll), 1000000 runs, 789 msecs
[coll "foobar"], (first coll), 1000000 runs, 1762 msecs
[coll (array 1 2 3)], (nth coll 2), 1000000 runs, 150 msecs
[coll "foobar"], (nth coll 2), 1000000 runs, 1297 msecs

;;; cloning & specify
[coll [1 2 3]], (clone coll), 1000000 runs, 587 msecs
[coll [1 2 3]], (specify coll IFoo (foo [_] :bar)), 1000000 runs, 1486 msecs
[coll (specify [1 2 3] IFoo (foo [_] :bar))], (foo coll), 1000000 runs, 105 msecs

;;; list ops
[coll (list 1 2 3)], (first coll), 1000000 runs, 214 msecs
[coll (list 1 2 3)], (-first coll), 1000000 runs, 114 msecs
[coll (list 1 2 3)], (rest coll), 1000000 runs, 111 msecs
[coll (list 1 2 3)], (-rest coll), 1000000 runs, 88 msecs
[], (list), 1000000 runs, 13 msecs
[], (list 1 2 3), 1000000 runs, 1691 msecs

;;; vector ops
[], [], 1000000 runs, 9 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count [a b c]), 1000000 runs, 715 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vec #object[cljs.tagged_literals.JSValue 0xd0fabc8 "cljs.tagged_literals.JSValue@d0fabc8"])), 1000000 runs, 972 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vector a b c)), 1000000 runs, 638 msecs
[coll [1 2 3]], (transient coll), 100000 runs, 161 msecs
[coll [1 2 3]], (nth coll 0), 1000000 runs, 323 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 361 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 238 msecs
[coll [1 2 3]], (coll 0), 1000000 runs, 211 msecs
[coll [1 2 3]], (conj coll 4), 1000000 runs, 1284 msecs
[coll [1 2 3]], (-conj coll 4), 1000000 runs, 1071 msecs
[coll []], (-conj coll 1), 1000000 runs, 1067 msecs
[coll [1]], (-conj coll 2), 1000000 runs, 1133 msecs
[coll [1 2]], (-conj coll 3), 1000000 runs, 835 msecs
[coll [1 2 3]], (seq coll), 1000000 runs, 472 msecs
[coll [1 2 3]], (-seq coll), 1000000 runs, 557 msecs
[coll (seq [1 2 3])], (first coll), 1000000 runs, 91 msecs
[coll (seq [1 2 3])], (-first coll), 1000000 runs, 104 msecs
[coll (seq [1 2 3])], (rest coll), 1000000 runs, 690 msecs
[coll (seq [1 2 3])], (-rest coll), 1000000 runs, 435 msecs
[coll (seq [1 2 3])], (next coll), 1000000 runs, 425 msecs

;;; large vector ops
[], (reduce conj [] (range 40000)), 10 runs, 251 msecs
[coll (reduce conj [] (range (+ 32768 32)))], (conj coll :foo), 100000 runs, 160 msecs
[coll (reduce conj [] (range 40000))], (assoc coll 123 :foo), 100000 runs, 213 msecs
[coll (reduce conj [] (range (+ 32768 33)))], (pop coll), 100000 runs, 184 msecs

;;; chunked seqs
[], (-first v), 1000000 runs, 207 msecs
[], (-next v), 1000000 runs, 768 msecs
[], (-rest v), 1000000 runs, 326 msecs

;;; transients
transient vector, conj! 1000000 items
"Elapsed time: 679 msecs"


;;; vector equality
[a (into [] (range 1000000)) b (into [] (range 1000000))], (= a b), 1 runs, 781 msecs

;;; keyword compare
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed)))))], (.sort arr compare), 100 runs, 672 msecs
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed) (rand-nth seed)))))], (.sort arr compare), 100 runs, 965 msecs

;;; reduce lazy-seqs, vectors, ranges
[coll (take 100000 (iterate inc 0))], (reduce + 0 coll), 1 runs, 403 msecs
[coll (range 1000000)], (reduce + 0 coll), 1 runs, 20 msecs
[coll (into [] (range 1000000))], (reduce + 0 coll), 1 runs, 267 msecs

;; apply
[coll (into [] (range 1000000))], (apply + coll), 1 runs, 675 msecs
[], (list 1 2 3 4 5), 1000000 runs, 639 msecs
[xs (array-seq (array 1 2 3 4 5))], (apply list xs), 1000000 runs, 2422 msecs
[xs (list 1 2 3 4 5)], (apply list xs), 1000000 runs, 1725 msecs
[xs [1 2 3 4 5]], (apply list xs), 1000000 runs, 1620 msecs
[f (fn [a b & more])], (apply f (range 32)), 1000000 runs, 3240 msecs
[f (fn [a b c d e f g h i j & more])], (apply f (range 32)), 1000000 runs, 2525 msecs

;; update-in
[coll {:foo 1} ks [:foo]], (update-in coll ks inc), 1000000 runs, 2980 msecs
[coll (array-map :foo 1) ks [:foo]], (update-in coll ks inc), 1000000 runs, 453 msecs

;;; obj-map
[coll (obj-map)], (assoc coll :foo :bar), 1000000 runs, 549 msecs
[coll (obj-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 265 msecs
[coll (obj-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 1695 msecs
[coll (obj-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 276 msecs
[coll (obj-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 252 msecs

;;; array-map
[], {[1] true, [2] true, [3] true}, 1000000 runs, 2831 msecs
[coll (array-map)], (assoc coll :foo :bar), 1000000 runs, 373 msecs
[coll (array-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 276 msecs
[coll (array-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 515 msecs
[coll (array-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 320 msecs
[coll (array-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 355 msecs

;;; array-map w/ symbols
[coll (array-map)], (assoc coll a b), 1000000 runs, 322 msecs
[coll (array-map a b)], (-lookup coll a), 1000000 runs, 297 msecs
[coll (array-map a b)], (assoc coll c d), 1000000 runs, 416 msecs
[coll (array-map a b c d)], (-lookup coll c), 1000000 runs, 360 msecs
[coll (array-map a b c d e f)], (-lookup coll e), 1000000 runs, 331 msecs

;;; array-map w/ inline symbols
[coll (array-map)], (assoc coll (quote foo) (quote bar)), 1000000 runs, 894 msecs
[coll (array-map (quote foo) (quote bar))], (-lookup coll (quote foo)), 1000000 runs, 732 msecs
[coll (array-map (quote foo) (quote bar))], (assoc coll (quote baz) (quote woz)), 1000000 runs, 1027 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz))], (-lookup coll (quote baz)), 1000000 runs, 699 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz) (quote lol) (quote rofl))], (-lookup coll (quote lol)), 1000000 runs, 589 msecs

;;; map / record ops
[coll {:foo 1, :bar 2}], (get coll :foo), 1000000 runs, 330 msecs
[coll {(quote foo) 1, (quote bar) 2}], (get coll (quote foo)), 1000000 runs, 523 msecs
[coll {:foo 1, :bar 2}], (-lookup coll :foo nil), 1000000 runs, 307 msecs
[coll {(quote foo) 1, (quote bar) 2}], (-lookup coll (quote foo) nil), 1000000 runs, 530 msecs
[coll {:foo 1, :bar 2}], (:foo coll), 1000000 runs, 295 msecs
[coll {(quote foo) 1, (quote bar) 2}], ((quote foo) coll), 1000000 runs, 574 msecs
[coll {:foo 1, :bar 2}], (kw coll), 1000000 runs, 291 msecs
[coll {(quote foo) 1, (quote bar) 2}], (sym coll), 1000000 runs, 280 msecs
[coll {:foo 1, :bar 2}], (loop [i 0 m coll] (if (< i 100000) (recur (inc i) (assoc m :foo 2)) m)), 1 runs, 9 msecs
[coll (Foo. 1 2)], (:bar coll), 1000000 runs, 251 msecs
[coll (Foo. 1 2)], (-lookup coll :bar), 1000000 runs, 253 msecs
[coll (Foo. 1 2)], (assoc coll :bar 2), 1000000 runs, 322 msecs
[coll (Foo. 1 2)], (assoc coll :baz 3), 1000000 runs, 336 msecs
[coll (Foo. 1 2)], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :bar 2)) m)), 1 runs, 373 msecs

;;; zipmap
[m {:a 1, :b 2, :c 3}], (zipmap (keys m) (map inc (vals m))), 100000 runs, 626 msecs

;;; persistent hash maps
[key :f0], (hash key), 1000000 runs, 266 msecs
[key "f0"], (m3-hash-unencoded-chars key), 1000000 runs, 244 msecs
[key :unsynchronized-mutable], (hash key), 1000000 runs, 267 msecs
[coll hash-coll-test], (hash-coll coll), 100 runs, 340 msecs
[coll hash-coll-test], (hash-ordered-coll coll), 100 runs, 128 msecs
[coll hash-imap-test], (hash-imap coll), 100 runs, 111 msecs
[coll hash-imap-test], (hash-unordered-coll coll), 100 runs, 361 msecs
[coll pmap], (:f0 coll), 1000000 runs, 385 msecs
[coll pmap], (get coll :f0), 1000000 runs, 370 msecs
[coll pmap], (-lookup coll :f0 nil), 1000000 runs, 324 msecs
[coll pmap], (-lookup hash-imap-test :foo500 nil), 1000000 runs, 364 msecs
[coll pmap], (-lookup hash-imap-int-test 500 nil), 1000000 runs, 3598 msecs
[coll pmap], (assoc coll :g0 32), 1000000 runs, 657 msecs
[coll pmap], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :a 1)) m)), 1 runs, 565 msecs
[coll cljs.core.PersistentHashMap.EMPTY], (assoc coll :f0 1), 1000000 runs, 421 msecs

transient map, conj! 100000 items
"Elapsed time: 539 msecs"


;;; set ops
[], #{}, 1000000 runs, 217 msecs
[], #{1 3 2}, 1000000 runs, 0 msecs
[v [1 2 3]], (set v), 1000000 runs, 587 msecs
[], (hash-set 1 2 3), 1000000 runs, 0 msecs
[coll #{1 3 2}], (conj coll 4), 1000000 runs, 477 msecs
[coll #{1 3 2}], (get coll 2), 1000000 runs, 304 msecs
[coll #{1 3 2}], (contains? coll 2), 1000000 runs, 267 msecs
[coll #{1 3 2}], (coll 2), 1000000 runs, 289 msecs

;;; seq ops
[coll (range 500000)], (reduce + coll), 1 runs, 21 msecs

;;; reader
[s "{:foo [1 2 3]}"], (reader/read-string s), 1000 runs, 85 msecs
[s big-str-data], (reader/read-string s), 1000 runs, 1060 msecs

;;; range
[r (range 1000000)], (last r), 1 runs, 77 msecs

;;; lazy-seq
;;; first run
[r r], (last r), 1 runs, 369 msecs
;;; second run
[r r], (last r), 1 runs, 94 msecs

;;; comprehensions
[xs (range 512)], (last (for [x xs y xs] (+ x y))), 1 runs, 233 msecs
[xs (vec (range 512))], (last (for [x xs y xs] (+ x y))), 4 runs, 473 msecs
[a (Box. 0) xs (range 512)], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 1233 msecs
[a (Box. 0) xs (vec (range 512))], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 688 msecs

;; reducers
[xs (into [] (range 1000000))], (r/reduce + (r/map inc (r/map inc (r/map inc xs)))), 1 runs, 53 msecs
;; transducers
[xs (into [] (range 1000000))], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 52 msecs
;; primitive array reduce 1000000 many ops
[xs (into-array (range 1000000))], (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)), 1 runs, 177 msecs
;; reduce range 1000000 many ops
[xs (range 1000000)], (reduce + 0 (map inc (map inc (map inc xs)))), 1 runs, 1298 msecs
;; transduce range 1000000 many ops 
[xs (range 1000000)], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 57 msecs


;; multimethods
[], (simple-multi :foo), 1000000 runs, 569 msecs


;; higher-order variadic function calls
[f array], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 67 msecs
[f vector], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 82 msecs
[], (= 1 1 1 1 1 1 1 1 1 0), 100000 runs, 134 msecs

0001 patch
==========

Benchmarking with SpiderMonkey
[x 1], (identity x), 1000000 runs, 0 msecs
;; symbol construction
[], (symbol (quote foo)), 1000000 runs, 585 msecs

;; array-reduce & ci-reduce
[coll (seq arr)], (ci-reduce coll + 0), 1 runs, 137 msecs
[coll (seq arr)], (ci-reduce coll sum 0), 1 runs, 735 msecs
[coll arr], (array-reduce coll + 0), 1 runs, 3 msecs
[coll arr], (array-reduce coll sum 0), 1 runs, 297 msecs
;;; instance?
[coll []], (instance? PersistentVector coll), 1000000 runs, 23 msecs
;;; satisfies?
[coll (list 1 2 3)], (satisfies? ISeq coll), 1000000 runs, 21 msecs
[coll [1 2 3]], (satisfies? ISeq coll), 1000000 runs, 32 msecs

;;; array & string ops
[coll (array 1 2 3)], (seq coll), 1000000 runs, 524 msecs
[coll "foobar"], (seq coll), 1000000 runs, 1879 msecs
[coll (array 1 2 3)], (first coll), 1000000 runs, 789 msecs
[coll "foobar"], (first coll), 1000000 runs, 1762 msecs
[coll (array 1 2 3)], (nth coll 2), 1000000 runs, 150 msecs
[coll "foobar"], (nth coll 2), 1000000 runs, 1297 msecs

;;; cloning & specify
[coll [1 2 3]], (clone coll), 1000000 runs, 587 msecs
[coll [1 2 3]], (specify coll IFoo (foo [_] :bar)), 1000000 runs, 1486 msecs
[coll (specify [1 2 3] IFoo (foo [_] :bar))], (foo coll), 1000000 runs, 105 msecs

;;; list ops
[coll (list 1 2 3)], (first coll), 1000000 runs, 214 msecs
[coll (list 1 2 3)], (-first coll), 1000000 runs, 114 msecs
[coll (list 1 2 3)], (rest coll), 1000000 runs, 111 msecs
[coll (list 1 2 3)], (-rest coll), 1000000 runs, 88 msecs
[], (list), 1000000 runs, 13 msecs
[], (list 1 2 3), 1000000 runs, 1691 msecs

;;; vector ops
[], [], 1000000 runs, 9 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count [a b c]), 1000000 runs, 715 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vec #object[cljs.tagged_literals.JSValue 0xd0fabc8 "cljs.tagged_literals.JSValue@d0fabc8"])), 1000000 runs, 972 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vector a b c)), 1000000 runs, 638 msecs
[coll [1 2 3]], (transient coll), 100000 runs, 161 msecs
[coll [1 2 3]], (nth coll 0), 1000000 runs, 323 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 361 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 238 msecs
[coll [1 2 3]], (coll 0), 1000000 runs, 211 msecs
[coll [1 2 3]], (conj coll 4), 1000000 runs, 1284 msecs
[coll [1 2 3]], (-conj coll 4), 1000000 runs, 1071 msecs
[coll []], (-conj coll 1), 1000000 runs, 1067 msecs
[coll [1]], (-conj coll 2), 1000000 runs, 1133 msecs
[coll [1 2]], (-conj coll 3), 1000000 runs, 835 msecs
[coll [1 2 3]], (seq coll), 1000000 runs, 472 msecs
[coll [1 2 3]], (-seq coll), 1000000 runs, 557 msecs
[coll (seq [1 2 3])], (first coll), 1000000 runs, 91 msecs
[coll (seq [1 2 3])], (-first coll), 1000000 runs, 104 msecs
[coll (seq [1 2 3])], (rest coll), 1000000 runs, 690 msecs
[coll (seq [1 2 3])], (-rest coll), 1000000 runs, 435 msecs
[coll (seq [1 2 3])], (next coll), 1000000 runs, 425 msecs

;;; large vector ops
[], (reduce conj [] (range 40000)), 10 runs, 251 msecs
[coll (reduce conj [] (range (+ 32768 32)))], (conj coll :foo), 100000 runs, 160 msecs
[coll (reduce conj [] (range 40000))], (assoc coll 123 :foo), 100000 runs, 213 msecs
[coll (reduce conj [] (range (+ 32768 33)))], (pop coll), 100000 runs, 184 msecs

;;; chunked seqs
[], (-first v), 1000000 runs, 207 msecs
[], (-next v), 1000000 runs, 768 msecs
[], (-rest v), 1000000 runs, 326 msecs

;;; transients
transient vector, conj! 1000000 items
"Elapsed time: 679 msecs"


;;; vector equality
[a (into [] (range 1000000)) b (into [] (range 1000000))], (= a b), 1 runs, 781 msecs

;;; keyword compare
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed)))))], (.sort arr compare), 100 runs, 672 msecs
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed) (rand-nth seed)))))], (.sort arr compare), 100 runs, 965 msecs

;;; reduce lazy-seqs, vectors, ranges
[coll (take 100000 (iterate inc 0))], (reduce + 0 coll), 1 runs, 403 msecs
[coll (range 1000000)], (reduce + 0 coll), 1 runs, 20 msecs
[coll (into [] (range 1000000))], (reduce + 0 coll), 1 runs, 267 msecs

;; apply
[coll (into [] (range 1000000))], (apply + coll), 1 runs, 675 msecs
[], (list 1 2 3 4 5), 1000000 runs, 639 msecs
[xs (array-seq (array 1 2 3 4 5))], (apply list xs), 1000000 runs, 2422 msecs
[xs (list 1 2 3 4 5)], (apply list xs), 1000000 runs, 1725 msecs
[xs [1 2 3 4 5]], (apply list xs), 1000000 runs, 1620 msecs
[f (fn [a b & more])], (apply f (range 32)), 1000000 runs, 3240 msecs
[f (fn [a b c d e f g h i j & more])], (apply f (range 32)), 1000000 runs, 2525 msecs

;; update-in
[coll {:foo 1} ks [:foo]], (update-in coll ks inc), 1000000 runs, 2980 msecs
[coll (array-map :foo 1) ks [:foo]], (update-in coll ks inc), 1000000 runs, 453 msecs

;;; obj-map
[coll (obj-map)], (assoc coll :foo :bar), 1000000 runs, 549 msecs
[coll (obj-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 265 msecs
[coll (obj-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 1695 msecs
[coll (obj-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 276 msecs
[coll (obj-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 252 msecs

;;; array-map
[], {[1] true, [2] true, [3] true}, 1000000 runs, 2831 msecs
[coll (array-map)], (assoc coll :foo :bar), 1000000 runs, 373 msecs
[coll (array-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 276 msecs
[coll (array-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 515 msecs
[coll (array-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 320 msecs
[coll (array-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 355 msecs

;;; array-map w/ symbols
[coll (array-map)], (assoc coll a b), 1000000 runs, 322 msecs
[coll (array-map a b)], (-lookup coll a), 1000000 runs, 297 msecs
[coll (array-map a b)], (assoc coll c d), 1000000 runs, 416 msecs
[coll (array-map a b c d)], (-lookup coll c), 1000000 runs, 360 msecs
[coll (array-map a b c d e f)], (-lookup coll e), 1000000 runs, 331 msecs

;;; array-map w/ inline symbols
[coll (array-map)], (assoc coll (quote foo) (quote bar)), 1000000 runs, 894 msecs
[coll (array-map (quote foo) (quote bar))], (-lookup coll (quote foo)), 1000000 runs, 732 msecs
[coll (array-map (quote foo) (quote bar))], (assoc coll (quote baz) (quote woz)), 1000000 runs, 1027 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz))], (-lookup coll (quote baz)), 1000000 runs, 699 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz) (quote lol) (quote rofl))], (-lookup coll (quote lol)), 1000000 runs, 589 msecs

;;; map / record ops
[coll {:foo 1, :bar 2}], (get coll :foo), 1000000 runs, 330 msecs
[coll {(quote foo) 1, (quote bar) 2}], (get coll (quote foo)), 1000000 runs, 523 msecs
[coll {:foo 1, :bar 2}], (-lookup coll :foo nil), 1000000 runs, 307 msecs
[coll {(quote foo) 1, (quote bar) 2}], (-lookup coll (quote foo) nil), 1000000 runs, 530 msecs
[coll {:foo 1, :bar 2}], (:foo coll), 1000000 runs, 295 msecs
[coll {(quote foo) 1, (quote bar) 2}], ((quote foo) coll), 1000000 runs, 574 msecs
[coll {:foo 1, :bar 2}], (kw coll), 1000000 runs, 291 msecs
[coll {(quote foo) 1, (quote bar) 2}], (sym coll), 1000000 runs, 280 msecs
[coll {:foo 1, :bar 2}], (loop [i 0 m coll] (if (< i 100000) (recur (inc i) (assoc m :foo 2)) m)), 1 runs, 9 msecs
[coll (Foo. 1 2)], (:bar coll), 1000000 runs, 251 msecs
[coll (Foo. 1 2)], (-lookup coll :bar), 1000000 runs, 253 msecs
[coll (Foo. 1 2)], (assoc coll :bar 2), 1000000 runs, 322 msecs
[coll (Foo. 1 2)], (assoc coll :baz 3), 1000000 runs, 336 msecs
[coll (Foo. 1 2)], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :bar 2)) m)), 1 runs, 373 msecs

;;; zipmap
[m {:a 1, :b 2, :c 3}], (zipmap (keys m) (map inc (vals m))), 100000 runs, 626 msecs

;;; persistent hash maps
[key :f0], (hash key), 1000000 runs, 266 msecs
[key "f0"], (m3-hash-unencoded-chars key), 1000000 runs, 244 msecs
[key :unsynchronized-mutable], (hash key), 1000000 runs, 267 msecs
[coll hash-coll-test], (hash-coll coll), 100 runs, 340 msecs
[coll hash-coll-test], (hash-ordered-coll coll), 100 runs, 128 msecs
[coll hash-imap-test], (hash-imap coll), 100 runs, 111 msecs
[coll hash-imap-test], (hash-unordered-coll coll), 100 runs, 361 msecs
[coll pmap], (:f0 coll), 1000000 runs, 385 msecs
[coll pmap], (get coll :f0), 1000000 runs, 370 msecs
[coll pmap], (-lookup coll :f0 nil), 1000000 runs, 324 msecs
[coll pmap], (-lookup hash-imap-test :foo500 nil), 1000000 runs, 364 msecs
[coll pmap], (-lookup hash-imap-int-test 500 nil), 1000000 runs, 3598 msecs
[coll pmap], (assoc coll :g0 32), 1000000 runs, 657 msecs
[coll pmap], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :a 1)) m)), 1 runs, 565 msecs
[coll cljs.core.PersistentHashMap.EMPTY], (assoc coll :f0 1), 1000000 runs, 421 msecs

transient map, conj! 100000 items
"Elapsed time: 539 msecs"


;;; set ops
[], #{}, 1000000 runs, 217 msecs
[], #{1 3 2}, 1000000 runs, 0 msecs
[v [1 2 3]], (set v), 1000000 runs, 587 msecs
[], (hash-set 1 2 3), 1000000 runs, 0 msecs
[coll #{1 3 2}], (conj coll 4), 1000000 runs, 477 msecs
[coll #{1 3 2}], (get coll 2), 1000000 runs, 304 msecs
[coll #{1 3 2}], (contains? coll 2), 1000000 runs, 267 msecs
[coll #{1 3 2}], (coll 2), 1000000 runs, 289 msecs

;;; seq ops
[coll (range 500000)], (reduce + coll), 1 runs, 21 msecs

;;; reader
[s "{:foo [1 2 3]}"], (reader/read-string s), 1000 runs, 85 msecs
[s big-str-data], (reader/read-string s), 1000 runs, 1060 msecs

;;; range
[r (range 1000000)], (last r), 1 runs, 77 msecs

;;; lazy-seq
;;; first run
[r r], (last r), 1 runs, 369 msecs
;;; second run
[r r], (last r), 1 runs, 94 msecs

;;; comprehensions
[xs (range 512)], (last (for [x xs y xs] (+ x y))), 1 runs, 233 msecs
[xs (vec (range 512))], (last (for [x xs y xs] (+ x y))), 4 runs, 473 msecs
[a (Box. 0) xs (range 512)], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 1233 msecs
[a (Box. 0) xs (vec (range 512))], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 688 msecs

;; reducers
[xs (into [] (range 1000000))], (r/reduce + (r/map inc (r/map inc (r/map inc xs)))), 1 runs, 53 msecs
;; transducers
[xs (into [] (range 1000000))], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 52 msecs
;; primitive array reduce 1000000 many ops
[xs (into-array (range 1000000))], (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)), 1 runs, 177 msecs
;; reduce range 1000000 many ops
[xs (range 1000000)], (reduce + 0 (map inc (map inc (map inc xs)))), 1 runs, 1298 msecs
;; transduce range 1000000 many ops 
[xs (range 1000000)], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 57 msecs


;; multimethods
[], (simple-multi :foo), 1000000 runs, 569 msecs


;; higher-order variadic function calls
[f array], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 67 msecs
[f vector], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 82 msecs
[], (= 1 1 1 1 1 1 1 1 1 0), 100000 runs, 134 msecs

0002 patch
==========

Benchmarking with SpiderMonkey
[x 1], (identity x), 1000000 runs, 0 msecs
;; symbol construction
[], (symbol (quote foo)), 1000000 runs, 645 msecs

;; array-reduce & ci-reduce
[coll (seq arr)], (ci-reduce coll + 0), 1 runs, 95 msecs
[coll (seq arr)], (ci-reduce coll sum 0), 1 runs, 557 msecs
[coll arr], (array-reduce coll + 0), 1 runs, 2 msecs
[coll arr], (array-reduce coll sum 0), 1 runs, 460 msecs
;;; instance?
[coll []], (instance? PersistentVector coll), 1000000 runs, 18 msecs
;;; satisfies?
[coll (list 1 2 3)], (satisfies? ISeq coll), 1000000 runs, 32 msecs
[coll [1 2 3]], (satisfies? ISeq coll), 1000000 runs, 36 msecs

;;; array & string ops
[coll (array 1 2 3)], (seq coll), 1000000 runs, 613 msecs
[coll "foobar"], (seq coll), 1000000 runs, 1658 msecs
[coll (array 1 2 3)], (first coll), 1000000 runs, 834 msecs
[coll "foobar"], (first coll), 1000000 runs, 1934 msecs
[coll (array 1 2 3)], (nth coll 2), 1000000 runs, 219 msecs
[coll "foobar"], (nth coll 2), 1000000 runs, 1371 msecs

;;; cloning & specify
[coll [1 2 3]], (clone coll), 1000000 runs, 444 msecs
[coll [1 2 3]], (specify coll IFoo (foo [_] :bar)), 1000000 runs, 351 msecs
[coll (specify [1 2 3] IFoo (foo [_] :bar))], (foo coll), 1000000 runs, 125 msecs

;;; list ops
[coll (list 1 2 3)], (first coll), 1000000 runs, 138 msecs
[coll (list 1 2 3)], (-first coll), 1000000 runs, 97 msecs
[coll (list 1 2 3)], (rest coll), 1000000 runs, 109 msecs
[coll (list 1 2 3)], (-rest coll), 1000000 runs, 80 msecs
[], (list), 1000000 runs, 10 msecs
[], (list 1 2 3), 1000000 runs, 1387 msecs

;;; vector ops
[], [], 1000000 runs, 10 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count [a b c]), 1000000 runs, 316 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vec #object[cljs.tagged_literals.JSValue 0x57b33c29 "cljs.tagged_literals.JSValue@57b33c29"])), 1000000 runs, 732 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vector a b c)), 1000000 runs, 281 msecs
[coll [1 2 3]], (transient coll), 100000 runs, 484 msecs
[coll [1 2 3]], (nth coll 0), 1000000 runs, 112 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 162 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 194 msecs
[coll [1 2 3]], (coll 0), 1000000 runs, 125 msecs
[coll [1 2 3]], (conj coll 4), 1000000 runs, 756 msecs
[coll [1 2 3]], (-conj coll 4), 1000000 runs, 606 msecs
[coll []], (-conj coll 1), 1000000 runs, 515 msecs
[coll [1]], (-conj coll 2), 1000000 runs, 648 msecs
[coll [1 2]], (-conj coll 3), 1000000 runs, 422 msecs
[coll [1 2 3]], (seq coll), 1000000 runs, 566 msecs
[coll [1 2 3]], (-seq coll), 1000000 runs, 89 msecs
[coll (seq [1 2 3])], (first coll), 1000000 runs, 154 msecs
[coll (seq [1 2 3])], (-first coll), 1000000 runs, 89 msecs
[coll (seq [1 2 3])], (rest coll), 1000000 runs, 631 msecs
[coll (seq [1 2 3])], (-rest coll), 1000000 runs, 450 msecs
[coll (seq [1 2 3])], (next coll), 1000000 runs, 547 msecs

;;; large vector ops
[], (reduce conj [] (range 40000)), 10 runs, 589 msecs
[coll (reduce conj [] (range (+ 32768 32)))], (conj coll :foo), 100000 runs, 204 msecs
[coll (reduce conj [] (range 40000))], (assoc coll 123 :foo), 100000 runs, 177 msecs
[coll (reduce conj [] (range (+ 32768 33)))], (pop coll), 100000 runs, 143 msecs

;;; chunked seqs
[], (-first v), 1000000 runs, 141 msecs
[], (-next v), 1000000 runs, 529 msecs
[], (-rest v), 1000000 runs, 236 msecs

;;; transients
transient vector, conj! 1000000 items
"Elapsed time: 924 msecs"


;;; vector equality
[a (into [] (range 1000000)) b (into [] (range 1000000))], (= a b), 1 runs, 633 msecs

;;; keyword compare
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed)))))], (.sort arr compare), 100 runs, 610 msecs
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed) (rand-nth seed)))))], (.sort arr compare), 100 runs, 1138 msecs

;;; reduce lazy-seqs, vectors, ranges
[coll (take 100000 (iterate inc 0))], (reduce + 0 coll), 1 runs, 545 msecs
[coll (range 1000000)], (reduce + 0 coll), 1 runs, 121 msecs
[coll (into [] (range 1000000))], (reduce + 0 coll), 1 runs, 281 msecs

;; apply
[coll (into [] (range 1000000))], (apply + coll), 1 runs, 597 msecs
[], (list 1 2 3 4 5), 1000000 runs, 560 msecs
[xs (array-seq (array 1 2 3 4 5))], (apply list xs), 1000000 runs, 2573 msecs
[xs (list 1 2 3 4 5)], (apply list xs), 1000000 runs, 1927 msecs
[xs [1 2 3 4 5]], (apply list xs), 1000000 runs, 6163 msecs
[f (fn [a b & more])], (apply f (range 32)), 1000000 runs, 3149 msecs
[f (fn [a b c d e f g h i j & more])], (apply f (range 32)), 1000000 runs, 1883 msecs

;; update-in
[coll {:foo 1} ks [:foo]], (update-in coll ks inc), 1000000 runs, 659 msecs
[coll (array-map :foo 1) ks [:foo]], (update-in coll ks inc), 1000000 runs, 611 msecs

;;; obj-map
[coll (obj-map)], (assoc coll :foo :bar), 1000000 runs, 556 msecs
[coll (obj-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 368 msecs
[coll (obj-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 1707 msecs
[coll (obj-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 280 msecs
[coll (obj-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 266 msecs

;;; array-map
[], {[1] true, [2] true, [3] true}, 1000000 runs, 2862 msecs
[coll (array-map)], (assoc coll :foo :bar), 1000000 runs, 356 msecs
[coll (array-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 391 msecs
[coll (array-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 439 msecs
[coll (array-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 363 msecs
[coll (array-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 321 msecs

;;; array-map w/ symbols
[coll (array-map)], (assoc coll a b), 1000000 runs, 405 msecs
[coll (array-map a b)], (-lookup coll a), 1000000 runs, 328 msecs
[coll (array-map a b)], (assoc coll c d), 1000000 runs, 444 msecs
[coll (array-map a b c d)], (-lookup coll c), 1000000 runs, 330 msecs
[coll (array-map a b c d e f)], (-lookup coll e), 1000000 runs, 353 msecs

;;; array-map w/ inline symbols
[coll (array-map)], (assoc coll (quote foo) (quote bar)), 1000000 runs, 1427 msecs
[coll (array-map (quote foo) (quote bar))], (-lookup coll (quote foo)), 1000000 runs, 589 msecs
[coll (array-map (quote foo) (quote bar))], (assoc coll (quote baz) (quote woz)), 1000000 runs, 1087 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz))], (-lookup coll (quote baz)), 1000000 runs, 674 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz) (quote lol) (quote rofl))], (-lookup coll (quote lol)), 1000000 runs, 719 msecs

;;; map / record ops
[coll {:foo 1, :bar 2}], (get coll :foo), 1000000 runs, 287 msecs
[coll {(quote foo) 1, (quote bar) 2}], (get coll (quote foo)), 1000000 runs, 841 msecs
[coll {:foo 1, :bar 2}], (-lookup coll :foo nil), 1000000 runs, 327 msecs
[coll {(quote foo) 1, (quote bar) 2}], (-lookup coll (quote foo) nil), 1000000 runs, 625 msecs
[coll {:foo 1, :bar 2}], (:foo coll), 1000000 runs, 294 msecs
[coll {(quote foo) 1, (quote bar) 2}], ((quote foo) coll), 1000000 runs, 630 msecs
[coll {:foo 1, :bar 2}], (kw coll), 1000000 runs, 314 msecs
[coll {(quote foo) 1, (quote bar) 2}], (sym coll), 1000000 runs, 312 msecs
[coll {:foo 1, :bar 2}], (loop [i 0 m coll] (if (< i 100000) (recur (inc i) (assoc m :foo 2)) m)), 1 runs, 10 msecs
[coll (Foo. 1 2)], (:bar coll), 1000000 runs, 269 msecs
[coll (Foo. 1 2)], (-lookup coll :bar), 1000000 runs, 268 msecs
[coll (Foo. 1 2)], (assoc coll :bar 2), 1000000 runs, 309 msecs
[coll (Foo. 1 2)], (assoc coll :baz 3), 1000000 runs, 440 msecs
[coll (Foo. 1 2)], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :bar 2)) m)), 1 runs, 404 msecs

;;; zipmap
[m {:a 1, :b 2, :c 3}], (zipmap (keys m) (map inc (vals m))), 100000 runs, 756 msecs

;;; persistent hash maps
[key :f0], (hash key), 1000000 runs, 290 msecs
[key "f0"], (m3-hash-unencoded-chars key), 1000000 runs, 326 msecs
[key :unsynchronized-mutable], (hash key), 1000000 runs, 279 msecs
[coll hash-coll-test], (hash-coll coll), 100 runs, 338 msecs
[coll hash-coll-test], (hash-ordered-coll coll), 100 runs, 138 msecs
[coll hash-imap-test], (hash-imap coll), 100 runs, 177 msecs
[coll hash-imap-test], (hash-unordered-coll coll), 100 runs, 385 msecs
[coll pmap], (:f0 coll), 1000000 runs, 411 msecs
[coll pmap], (get coll :f0), 1000000 runs, 439 msecs
[coll pmap], (-lookup coll :f0 nil), 1000000 runs, 336 msecs
[coll pmap], (-lookup hash-imap-test :foo500 nil), 1000000 runs, 457 msecs
[coll pmap], (-lookup hash-imap-int-test 500 nil), 1000000 runs, 4330 msecs
[coll pmap], (assoc coll :g0 32), 1000000 runs, 831 msecs
[coll pmap], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :a 1)) m)), 1 runs, 490 msecs
[coll cljs.core.PersistentHashMap.EMPTY], (assoc coll :f0 1), 1000000 runs, 474 msecs

transient map, conj! 100000 items
"Elapsed time: 565 msecs"


;;; set ops
[], #{}, 1000000 runs, 225 msecs
[], #{1 3 2}, 1000000 runs, 0 msecs
[v [1 2 3]], (set v), 1000000 runs, 711 msecs
[], (hash-set 1 2 3), 1000000 runs, 0 msecs
[coll #{1 3 2}], (conj coll 4), 1000000 runs, 608 msecs
[coll #{1 3 2}], (get coll 2), 1000000 runs, 353 msecs
[coll #{1 3 2}], (contains? coll 2), 1000000 runs, 322 msecs
[coll #{1 3 2}], (coll 2), 1000000 runs, 335 msecs

;;; seq ops
[coll (range 500000)], (reduce + coll), 1 runs, 22 msecs

;;; reader
[s "{:foo [1 2 3]}"], (reader/read-string s), 1000 runs, 99 msecs
[s big-str-data], (reader/read-string s), 1000 runs, 1538 msecs

;;; range
[r (range 1000000)], (last r), 1 runs, 37 msecs

;;; lazy-seq
;;; first run
[r r], (last r), 1 runs, 348 msecs
;;; second run
[r r], (last r), 1 runs, 71 msecs

;;; comprehensions
[xs (range 512)], (last (for [x xs y xs] (+ x y))), 1 runs, 433 msecs
[xs (vec (range 512))], (last (for [x xs y xs] (+ x y))), 4 runs, 287 msecs
[a (Box. 0) xs (range 512)], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 1191 msecs
[a (Box. 0) xs (vec (range 512))], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 831 msecs

;; reducers
[xs (into [] (range 1000000))], (r/reduce + (r/map inc (r/map inc (r/map inc xs)))), 1 runs, 52 msecs
;; transducers
[xs (into [] (range 1000000))], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 81 msecs
;; primitive array reduce 1000000 many ops
[xs (into-array (range 1000000))], (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)), 1 runs, 207 msecs
;; reduce range 1000000 many ops
[xs (range 1000000)], (reduce + 0 (map inc (map inc (map inc xs)))), 1 runs, 1375 msecs
;; transduce range 1000000 many ops 
[xs (range 1000000)], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 73 msecs


;; multimethods
[], (simple-multi :foo), 1000000 runs, 429 msecs


;; higher-order variadic function calls
[f array], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 51 msecs
[f vector], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 73 msecs
[], (= 1 1 1 1 1 1 1 1 1 0), 100000 runs, 133 msecs
Comment by David Nolen [ 19/Jul/15 11:06 AM ]

Thanks will review.

Comment by David Nolen [ 20/Jul/15 2:33 PM ]

This ticket should probably be updated with the latest equiv changes in Clojure master no?

Comment by Michał Marczyk [ 20/Jul/15 2:49 PM ]

Right, will do (plus a rebase on top of current master while I'm at it).

Comment by Michał Marczyk [ 20/Jul/15 6:43 PM ]

New 0003 patch superseding the previous ones attached.

See some new benchmark results below. There are some apparent substantial speedups where I would expect them, there's the somewhat expected slowdown for transient with small vectors.

Freak result: most large vector ops stay around their original spots as expected, except for reduce conj [] over a long range, which becomes weirdly slow. This I find hard to explain, particularly since ranges are used in other benchmarks as well, and those behave sensibly.

I tried compiling the benchmark suite with :optimizations :simple to see if the freak result was something obvious maybe. Oddly enough, all/most timings are significantly better under :simple. Am I missing something obvious here…?

1. master:

Benchmarking with SpiderMonkey
[x 1], (identity x), 1000000 runs, 0 msecs
;; symbol construction
[], (symbol (quote foo)), 1000000 runs, 506 msecs

;; array-reduce & ci-reduce
[coll (seq arr)], (ci-reduce coll + 0), 1 runs, 70 msecs
[coll (seq arr)], (ci-reduce coll sum 0), 1 runs, 554 msecs
[coll arr], (array-reduce coll + 0), 1 runs, 3 msecs
[coll arr], (array-reduce coll sum 0), 1 runs, 313 msecs
;;; instance?
[coll []], (instance? PersistentVector coll), 1000000 runs, 15 msecs
;;; satisfies?
[coll (list 1 2 3)], (satisfies? ISeq coll), 1000000 runs, 19 msecs
[coll [1 2 3]], (satisfies? ISeq coll), 1000000 runs, 33 msecs

;;; array & string ops
[coll (array 1 2 3)], (seq coll), 1000000 runs, 401 msecs
[coll "foobar"], (seq coll), 1000000 runs, 1202 msecs
[coll (array 1 2 3)], (first coll), 1000000 runs, 563 msecs
[coll "foobar"], (first coll), 1000000 runs, 1307 msecs
[coll (array 1 2 3)], (nth coll 2), 1000000 runs, 161 msecs
[coll "foobar"], (nth coll 2), 1000000 runs, 949 msecs

;;; cloning & specify
[coll [1 2 3]], (clone coll), 1000000 runs, 379 msecs
[coll [1 2 3]], (specify coll IFoo (foo [_] :bar)), 1000000 runs, 1025 msecs
[coll (specify [1 2 3] IFoo (foo [_] :bar))], (foo coll), 1000000 runs, 59 msecs

;;; list ops
[coll (list 1 2 3)], (first coll), 1000000 runs, 88 msecs
[coll (list 1 2 3)], (-first coll), 1000000 runs, 67 msecs
[coll (list 1 2 3)], (rest coll), 1000000 runs, 80 msecs
[coll (list 1 2 3)], (-rest coll), 1000000 runs, 62 msecs
[], (list), 1000000 runs, 10 msecs
[], (list 1 2 3), 1000000 runs, 1132 msecs

;;; vector ops
[], [], 1000000 runs, 10 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count [a b c]), 1000000 runs, 495 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vec #object[cljs.tagged_literals.JSValue 0x56b66a26 "cljs.tagged_literals.JSValue@56b66a26"])), 1000000 runs, 547 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vector a b c)), 1000000 runs, 435 msecs
[coll [1 2 3]], (transient coll), 100000 runs, 120 msecs
[coll [1 2 3]], (nth coll 0), 1000000 runs, 197 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 95 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 199 msecs
[coll [1 2 3]], (coll 0), 1000000 runs, 209 msecs
[coll [1 2 3]], (conj coll 4), 1000000 runs, 893 msecs
[coll [1 2 3]], (-conj coll 4), 1000000 runs, 842 msecs
[coll []], (-conj coll 1), 1000000 runs, 765 msecs
[coll [1]], (-conj coll 2), 1000000 runs, 854 msecs
[coll [1 2]], (-conj coll 3), 1000000 runs, 631 msecs
[coll [1 2 3]], (seq coll), 1000000 runs, 413 msecs
[coll [1 2 3]], (-seq coll), 1000000 runs, 668 msecs
[coll (seq [1 2 3])], (first coll), 1000000 runs, 163 msecs
[coll (seq [1 2 3])], (-first coll), 1000000 runs, 89 msecs
[coll (seq [1 2 3])], (rest coll), 1000000 runs, 497 msecs
[coll (seq [1 2 3])], (-rest coll), 1000000 runs, 319 msecs
[coll (seq [1 2 3])], (next coll), 1000000 runs, 316 msecs

;;; large vector ops
[], (reduce conj [] (range 40000)), 10 runs, 172 msecs
[coll (reduce conj [] (range (+ 32768 32)))], (conj coll :foo), 100000 runs, 240 msecs
[coll (reduce conj [] (range 40000))], (assoc coll 123 :foo), 100000 runs, 128 msecs
[coll (reduce conj [] (range (+ 32768 33)))], (pop coll), 100000 runs, 80 msecs

;;; chunked seqs
[], (-first v), 1000000 runs, 105 msecs
[], (-next v), 1000000 runs, 460 msecs
[], (-rest v), 1000000 runs, 166 msecs

;;; transients
transient vector, conj! 1000000 items
"Elapsed time: 746 msecs"


;;; vector equality
[a (into [] (range 1000000)) b (into [] (range 1000000))], (= a b), 1 runs, 557 msecs

;;; keyword compare
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed)))))], (.sort arr compare), 100 runs, 437 msecs
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed) (rand-nth seed)))))], (.sort arr compare), 100 runs, 820 msecs

;;; reduce lazy-seqs, vectors, ranges
[coll (take 100000 (iterate inc 0))], (reduce + 0 coll), 1 runs, 308 msecs
[coll (range 1000000)], (reduce + 0 coll), 1 runs, 20 msecs
[coll (into [] (range 1000000))], (reduce + 0 coll), 1 runs, 250 msecs

;; apply
[coll (into [] (range 1000000))], (apply + coll), 1 runs, 471 msecs
[], (list 1 2 3 4 5), 1000000 runs, 410 msecs
[xs (array-seq (array 1 2 3 4 5))], (apply list xs), 1000000 runs, 1898 msecs
[xs (list 1 2 3 4 5)], (apply list xs), 1000000 runs, 1506 msecs
[xs [1 2 3 4 5]], (apply list xs), 1000000 runs, 954 msecs
[f (fn [a b & more])], (apply f (range 32)), 1000000 runs, 2495 msecs
[f (fn [a b c d e f g h i j & more])], (apply f (range 32)), 1000000 runs, 1864 msecs

;; update-in
[coll {:foo 1} ks [:foo]], (update-in coll ks inc), 1000000 runs, 2799 msecs
[coll (array-map :foo 1) ks [:foo]], (update-in coll ks inc), 1000000 runs, 2367 msecs

;;; obj-map
[coll (obj-map)], (assoc coll :foo :bar), 1000000 runs, 465 msecs
[coll (obj-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 268 msecs
[coll (obj-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 1228 msecs
[coll (obj-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 251 msecs
[coll (obj-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 270 msecs

;;; array-map
[], {[1] true, [2] true, [3] true}, 1000000 runs, 3502 msecs
[coll (array-map)], (assoc coll :foo :bar), 1000000 runs, 330 msecs
[coll (array-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 294 msecs
[coll (array-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 528 msecs
[coll (array-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 282 msecs
[coll (array-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 333 msecs

;;; array-map w/ symbols
[coll (array-map)], (assoc coll a b), 1000000 runs, 318 msecs
[coll (array-map a b)], (-lookup coll a), 1000000 runs, 286 msecs
[coll (array-map a b)], (assoc coll c d), 1000000 runs, 409 msecs
[coll (array-map a b c d)], (-lookup coll c), 1000000 runs, 341 msecs
[coll (array-map a b c d e f)], (-lookup coll e), 1000000 runs, 353 msecs

;;; array-map w/ inline symbols
[coll (array-map)], (assoc coll (quote foo) (quote bar)), 1000000 runs, 878 msecs
[coll (array-map (quote foo) (quote bar))], (-lookup coll (quote foo)), 1000000 runs, 589 msecs
[coll (array-map (quote foo) (quote bar))], (assoc coll (quote baz) (quote woz)), 1000000 runs, 972 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz))], (-lookup coll (quote baz)), 1000000 runs, 582 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz) (quote lol) (quote rofl))], (-lookup coll (quote lol)), 1000000 runs, 850 msecs

;;; map / record ops
[coll {:foo 1, :bar 2}], (get coll :foo), 1000000 runs, 269 msecs
[coll {(quote foo) 1, (quote bar) 2}], (get coll (quote foo)), 1000000 runs, 793 msecs
[coll {:foo 1, :bar 2}], (-lookup coll :foo nil), 1000000 runs, 269 msecs
[coll {(quote foo) 1, (quote bar) 2}], (-lookup coll (quote foo) nil), 1000000 runs, 596 msecs
[coll {:foo 1, :bar 2}], (:foo coll), 1000000 runs, 311 msecs
[coll {(quote foo) 1, (quote bar) 2}], ((quote foo) coll), 1000000 runs, 586 msecs
[coll {:foo 1, :bar 2}], (kw coll), 1000000 runs, 321 msecs
[coll {(quote foo) 1, (quote bar) 2}], (sym coll), 1000000 runs, 280 msecs
[coll {:foo 1, :bar 2}], (loop [i 0 m coll] (if (< i 100000) (recur (inc i) (assoc m :foo 2)) m)), 1 runs, 10 msecs
[coll (Foo. 1 2)], (:bar coll), 1000000 runs, 254 msecs
[coll (Foo. 1 2)], (-lookup coll :bar), 1000000 runs, 250 msecs
[coll (Foo. 1 2)], (assoc coll :bar 2), 1000000 runs, 288 msecs
[coll (Foo. 1 2)], (assoc coll :baz 3), 1000000 runs, 322 msecs
[coll (Foo. 1 2)], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :bar 2)) m)), 1 runs, 305 msecs

;;; zipmap
[m {:a 1, :b 2, :c 3}], (zipmap (keys m) (map inc (vals m))), 100000 runs, 537 msecs

;;; persistent hash maps
[key :f0], (hash key), 1000000 runs, 257 msecs
[key "f0"], (m3-hash-unencoded-chars key), 1000000 runs, 250 msecs
[key :unsynchronized-mutable], (hash key), 1000000 runs, 238 msecs
[coll hash-coll-test], (hash-coll coll), 100 runs, 338 msecs
[coll hash-coll-test], (hash-ordered-coll coll), 100 runs, 123 msecs
[coll hash-imap-test], (hash-imap coll), 100 runs, 114 msecs
[coll hash-imap-test], (hash-unordered-coll coll), 100 runs, 342 msecs
[coll pmap], (:f0 coll), 1000000 runs, 368 msecs
[coll pmap], (get coll :f0), 1000000 runs, 356 msecs
[coll pmap], (-lookup coll :f0 nil), 1000000 runs, 274 msecs
[coll pmap], (-lookup hash-imap-test :foo500 nil), 1000000 runs, 290 msecs
[coll pmap], (-lookup hash-imap-int-test 500 nil), 1000000 runs, 3028 msecs
[coll pmap], (assoc coll :g0 32), 1000000 runs, 641 msecs
[coll pmap], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :a 1)) m)), 1 runs, 412 msecs
[coll cljs.core.PersistentHashMap.EMPTY], (assoc coll :f0 1), 1000000 runs, 411 msecs

transient map, conj! 100000 items
"Elapsed time: 505 msecs"


;;; set ops
[], #{}, 1000000 runs, 215 msecs
[], #{1 3 2}, 1000000 runs, 0 msecs
[v [1 2 3]], (set v), 1000000 runs, 460 msecs
[], (hash-set 1 2 3), 1000000 runs, 0 msecs
[coll #{1 3 2}], (conj coll 4), 1000000 runs, 516 msecs
[coll #{1 3 2}], (get coll 2), 1000000 runs, 293 msecs
[coll #{1 3 2}], (contains? coll 2), 1000000 runs, 269 msecs
[coll #{1 3 2}], (coll 2), 1000000 runs, 290 msecs

;;; seq ops
[coll (range 500000)], (reduce + coll), 1 runs, 18 msecs

;;; reader
[s "{:foo [1 2 3]}"], (reader/read-string s), 1000 runs, 77 msecs
[s big-str-data], (reader/read-string s), 1000 runs, 957 msecs

;;; range
[r (range 1000000)], (last r), 1 runs, 42 msecs

;;; lazy-seq
;;; first run
[r r], (last r), 1 runs, 310 msecs
;;; second run
[r r], (last r), 1 runs, 71 msecs

;;; comprehensions
[xs (range 512)], (last (for [x xs y xs] (+ x y))), 1 runs, 234 msecs
[xs (vec (range 512))], (last (for [x xs y xs] (+ x y))), 4 runs, 416 msecs
[a (Box. 0) xs (range 512)], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 981 msecs
[a (Box. 0) xs (vec (range 512))], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 699 msecs

;; reducers
[xs (into [] (range 1000000))], (r/reduce + (r/map inc (r/map inc (r/map inc xs)))), 1 runs, 50 msecs
;; transducers
[xs (into [] (range 1000000))], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 52 msecs
;; primitive array reduce 1000000 many ops
[xs (into-array (range 1000000))], (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)), 1 runs, 184 msecs
;; reduce range 1000000 many ops
[xs (range 1000000)], (reduce + 0 (map inc (map inc (map inc xs)))), 1 runs, 1278 msecs
;; transduce range 1000000 many ops 
[xs (range 1000000)], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 46 msecs


;; multimethods
[], (simple-multi :foo), 1000000 runs, 333 msecs


;; higher-order variadic function calls
[f array], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 216 msecs
[f vector], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 71 msecs
[], (= 1 1 1 1 1 1 1 1 1 0), 100000 runs, 142 msecs

2. 0003 patch:

Benchmarking with SpiderMonkey
[x 1], (identity x), 1000000 runs, 0 msecs
;; symbol construction
[], (symbol (quote foo)), 1000000 runs, 480 msecs

;; array-reduce & ci-reduce
[coll (seq arr)], (ci-reduce coll + 0), 1 runs, 71 msecs
[coll (seq arr)], (ci-reduce coll sum 0), 1 runs, 476 msecs
[coll arr], (array-reduce coll + 0), 1 runs, 3 msecs
[coll arr], (array-reduce coll sum 0), 1 runs, 621 msecs
;;; instance?
[coll []], (instance? PersistentVector coll), 1000000 runs, 28 msecs
;;; satisfies?
[coll (list 1 2 3)], (satisfies? ISeq coll), 1000000 runs, 30 msecs
[coll [1 2 3]], (satisfies? ISeq coll), 1000000 runs, 54 msecs

;;; array & string ops
[coll (array 1 2 3)], (seq coll), 1000000 runs, 542 msecs
[coll "foobar"], (seq coll), 1000000 runs, 1209 msecs
[coll (array 1 2 3)], (first coll), 1000000 runs, 564 msecs
[coll "foobar"], (first coll), 1000000 runs, 1257 msecs
[coll (array 1 2 3)], (nth coll 2), 1000000 runs, 140 msecs
[coll "foobar"], (nth coll 2), 1000000 runs, 913 msecs

;;; cloning & specify
[coll [1 2 3]], (clone coll), 1000000 runs, 424 msecs
[coll [1 2 3]], (specify coll IFoo (foo [_] :bar)), 1000000 runs, 170 msecs
[coll (specify [1 2 3] IFoo (foo [_] :bar))], (foo coll), 1000000 runs, 58 msecs

;;; list ops
[coll (list 1 2 3)], (first coll), 1000000 runs, 89 msecs
[coll (list 1 2 3)], (-first coll), 1000000 runs, 69 msecs
[coll (list 1 2 3)], (rest coll), 1000000 runs, 80 msecs
[coll (list 1 2 3)], (-rest coll), 1000000 runs, 61 msecs
[], (list), 1000000 runs, 10 msecs
[], (list 1 2 3), 1000000 runs, 1142 msecs

;;; vector ops
[], [], 1000000 runs, 10 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count [a b c]), 1000000 runs, 272 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vec #object[cljs.tagged_literals.JSValue 0x3ff26c9 "cljs.tagged_literals.JSValue@3ff26c9"])), 1000000 runs, 585 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vector a b c)), 1000000 runs, 240 msecs
[coll [1 2 3]], (transient coll), 100000 runs, 273 msecs
[coll [1 2 3]], (nth coll 0), 1000000 runs, 101 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 102 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 102 msecs
[coll [1 2 3]], (coll 0), 1000000 runs, 38 msecs
[coll [1 2 3]], (conj coll 4), 1000000 runs, 429 msecs
[coll [1 2 3]], (-conj coll 4), 1000000 runs, 392 msecs
[coll []], (-conj coll 1), 1000000 runs, 368 msecs
[coll [1]], (-conj coll 2), 1000000 runs, 395 msecs
[coll [1 2]], (-conj coll 3), 1000000 runs, 364 msecs
[coll [1 2 3]], (seq coll), 1000000 runs, 383 msecs
[coll [1 2 3]], (-seq coll), 1000000 runs, 75 msecs
[coll (seq [1 2 3])], (first coll), 1000000 runs, 142 msecs
[coll (seq [1 2 3])], (-first coll), 1000000 runs, 79 msecs
[coll (seq [1 2 3])], (rest coll), 1000000 runs, 395 msecs
[coll (seq [1 2 3])], (-rest coll), 1000000 runs, 408 msecs
[coll (seq [1 2 3])], (next coll), 1000000 runs, 383 msecs

;;; large vector ops
[], (reduce conj [] (range 40000)), 10 runs, 406 msecs
[coll (reduce conj [] (range (+ 32768 32)))], (conj coll :foo), 100000 runs, 269 msecs
[coll (reduce conj [] (range 40000))], (assoc coll 123 :foo), 100000 runs, 131 msecs
[coll (reduce conj [] (range (+ 32768 33)))], (pop coll), 100000 runs, 80 msecs

;;; chunked seqs
[], (-first v), 1000000 runs, 104 msecs
[], (-next v), 1000000 runs, 461 msecs
[], (-rest v), 1000000 runs, 171 msecs

;;; transients
transient vector, conj! 1000000 items
"Elapsed time: 592 msecs"


;;; vector equality
[a (into [] (range 1000000)) b (into [] (range 1000000))], (= a b), 1 runs, 562 msecs

;;; keyword compare
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed)))))], (.sort arr compare), 100 runs, 467 msecs
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed) (rand-nth seed)))))], (.sort arr compare), 100 runs, 830 msecs

;;; reduce lazy-seqs, vectors, ranges
[coll (take 100000 (iterate inc 0))], (reduce + 0 coll), 1 runs, 426 msecs
[coll (range 1000000)], (reduce + 0 coll), 1 runs, 15 msecs
[coll (into [] (range 1000000))], (reduce + 0 coll), 1 runs, 265 msecs

;; apply
[coll (into [] (range 1000000))], (apply + coll), 1 runs, 580 msecs
[], (list 1 2 3 4 5), 1000000 runs, 386 msecs
[xs (array-seq (array 1 2 3 4 5))], (apply list xs), 1000000 runs, 1885 msecs
[xs (list 1 2 3 4 5)], (apply list xs), 1000000 runs, 1362 msecs
[xs [1 2 3 4 5]], (apply list xs), 1000000 runs, 4564 msecs
[f (fn [a b & more])], (apply f (range 32)), 1000000 runs, 2536 msecs
[f (fn [a b c d e f g h i j & more])], (apply f (range 32)), 1000000 runs, 1940 msecs

;; update-in
[coll {:foo 1} ks [:foo]], (update-in coll ks inc), 1000000 runs, 1948 msecs
[coll (array-map :foo 1) ks [:foo]], (update-in coll ks inc), 1000000 runs, 452 msecs

;;; obj-map
[coll (obj-map)], (assoc coll :foo :bar), 1000000 runs, 484 msecs
[coll (obj-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 264 msecs
[coll (obj-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 1540 msecs
[coll (obj-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 294 msecs
[coll (obj-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 251 msecs

;;; array-map
[], {[1] true, [2] true, [3] true}, 1000000 runs, 3150 msecs
[coll (array-map)], (assoc coll :foo :bar), 1000000 runs, 378 msecs
[coll (array-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 278 msecs
[coll (array-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 507 msecs
[coll (array-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 288 msecs
[coll (array-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 339 msecs

;;; array-map w/ symbols
[coll (array-map)], (assoc coll a b), 1000000 runs, 324 msecs
[coll (array-map a b)], (-lookup coll a), 1000000 runs, 301 msecs
[coll (array-map a b)], (assoc coll c d), 1000000 runs, 533 msecs
[coll (array-map a b c d)], (-lookup coll c), 1000000 runs, 355 msecs
[coll (array-map a b c d e f)], (-lookup coll e), 1000000 runs, 309 msecs

;;; array-map w/ inline symbols
[coll (array-map)], (assoc coll (quote foo) (quote bar)), 1000000 runs, 757 msecs
[coll (array-map (quote foo) (quote bar))], (-lookup coll (quote foo)), 1000000 runs, 514 msecs
[coll (array-map (quote foo) (quote bar))], (assoc coll (quote baz) (quote woz)), 1000000 runs, 844 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz))], (-lookup coll (quote baz)), 1000000 runs, 622 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz) (quote lol) (quote rofl))], (-lookup coll (quote lol)), 1000000 runs, 765 msecs

;;; map / record ops
[coll {:foo 1, :bar 2}], (get coll :foo), 1000000 runs, 271 msecs
[coll {(quote foo) 1, (quote bar) 2}], (get coll (quote foo)), 1000000 runs, 521 msecs
[coll {:foo 1, :bar 2}], (-lookup coll :foo nil), 1000000 runs, 276 msecs
[coll {(quote foo) 1, (quote bar) 2}], (-lookup coll (quote foo) nil), 1000000 runs, 515 msecs
[coll {:foo 1, :bar 2}], (:foo coll), 1000000 runs, 314 msecs
[coll {(quote foo) 1, (quote bar) 2}], ((quote foo) coll), 1000000 runs, 534 msecs
[coll {:foo 1, :bar 2}], (kw coll), 1000000 runs, 324 msecs
[coll {(quote foo) 1, (quote bar) 2}], (sym coll), 1000000 runs, 327 msecs
[coll {:foo 1, :bar 2}], (loop [i 0 m coll] (if (< i 100000) (recur (inc i) (assoc m :foo 2)) m)), 1 runs, 10 msecs
[coll (Foo. 1 2)], (:bar coll), 1000000 runs, 290 msecs
[coll (Foo. 1 2)], (-lookup coll :bar), 1000000 runs, 254 msecs
[coll (Foo. 1 2)], (assoc coll :bar 2), 1000000 runs, 301 msecs
[coll (Foo. 1 2)], (assoc coll :baz 3), 1000000 runs, 347 msecs
[coll (Foo. 1 2)], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :bar 2)) m)), 1 runs, 365 msecs

;;; zipmap
[m {:a 1, :b 2, :c 3}], (zipmap (keys m) (map inc (vals m))), 100000 runs, 496 msecs

;;; persistent hash maps
[key :f0], (hash key), 1000000 runs, 242 msecs
[key "f0"], (m3-hash-unencoded-chars key), 1000000 runs, 254 msecs
[key :unsynchronized-mutable], (hash key), 1000000 runs, 262 msecs
[coll hash-coll-test], (hash-coll coll), 100 runs, 319 msecs
[coll hash-coll-test], (hash-ordered-coll coll), 100 runs, 128 msecs
[coll hash-imap-test], (hash-imap coll), 100 runs, 124 msecs
[coll hash-imap-test], (hash-unordered-coll coll), 100 runs, 368 msecs
[coll pmap], (:f0 coll), 1000000 runs, 446 msecs
[coll pmap], (get coll :f0), 1000000 runs, 511 msecs
[coll pmap], (-lookup coll :f0 nil), 1000000 runs, 328 msecs
[coll pmap], (-lookup hash-imap-test :foo500 nil), 1000000 runs, 319 msecs
[coll pmap], (-lookup hash-imap-int-test 500 nil), 1000000 runs, 4954 msecs
[coll pmap], (assoc coll :g0 32), 1000000 runs, 963 msecs
[coll pmap], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :a 1)) m)), 1 runs, 425 msecs
[coll cljs.core.PersistentHashMap.EMPTY], (assoc coll :f0 1), 1000000 runs, 421 msecs

transient map, conj! 100000 items
"Elapsed time: 531 msecs"


;;; set ops
[], #{}, 1000000 runs, 230 msecs
[], #{1 3 2}, 1000000 runs, 0 msecs
[v [1 2 3]], (set v), 1000000 runs, 679 msecs
[], (hash-set 1 2 3), 1000000 runs, 0 msecs
[coll #{1 3 2}], (conj coll 4), 1000000 runs, 605 msecs
[coll #{1 3 2}], (get coll 2), 1000000 runs, 295 msecs
[coll #{1 3 2}], (contains? coll 2), 1000000 runs, 273 msecs
[coll #{1 3 2}], (coll 2), 1000000 runs, 321 msecs

;;; seq ops
[coll (range 500000)], (reduce + coll), 1 runs, 22 msecs

;;; reader
[s "{:foo [1 2 3]}"], (reader/read-string s), 1000 runs, 73 msecs
[s big-str-data], (reader/read-string s), 1000 runs, 934 msecs

;;; range
[r (range 1000000)], (last r), 1 runs, 33 msecs

;;; lazy-seq
;;; first run
[r r], (last r), 1 runs, 310 msecs
;;; second run
[r r], (last r), 1 runs, 60 msecs

;;; comprehensions
[xs (range 512)], (last (for [x xs y xs] (+ x y))), 1 runs, 230 msecs
[xs (vec (range 512))], (last (for [x xs y xs] (+ x y))), 4 runs, 400 msecs
[a (Box. 0) xs (range 512)], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 865 msecs
[a (Box. 0) xs (vec (range 512))], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 627 msecs

;; reducers
[xs (into [] (range 1000000))], (r/reduce + (r/map inc (r/map inc (r/map inc xs)))), 1 runs, 49 msecs
;; transducers
[xs (into [] (range 1000000))], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 55 msecs
;; primitive array reduce 1000000 many ops
[xs (into-array (range 1000000))], (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)), 1 runs, 197 msecs
;; reduce range 1000000 many ops
[xs (range 1000000)], (reduce + 0 (map inc (map inc (map inc xs)))), 1 runs, 1296 msecs
;; transduce range 1000000 many ops 
[xs (range 1000000)], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 48 msecs


;; multimethods
[], (simple-multi :foo), 1000000 runs, 502 msecs


;; higher-order variadic function calls
[f array], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 50 msecs
[f vector], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 434 msecs
[], (= 1 1 1 1 1 1 1 1 1 0), 100000 runs, 132 msecs
Comment by David Nolen [ 20/Jul/15 10:35 PM ]

Michal it might be a GC thing? Not sure. These tests need to be run on more engines, kinda wish we had something a bit more visual by now

Comment by Michał Marczyk [ 20/Jul/15 11:32 PM ]

Indeed… I got the V8 situation sorted in the meantime and benchmarked master vs 0003 with a fresh build; I thought the results were pretty encouraging, particularly the 2x speedup for "small vector conj".

1. master:

Benchmarking with V8
[x 1], (identity x), 1000000 runs, 0 msecs
;; symbol construction
[], (symbol (quote foo)), 1000000 runs, 63 msecs

;; array-reduce & ci-reduce
[coll (seq arr)], (ci-reduce coll + 0), 1 runs, 24 msecs
[coll (seq arr)], (ci-reduce coll sum 0), 1 runs, 21 msecs
[coll arr], (array-reduce coll + 0), 1 runs, 14 msecs
[coll arr], (array-reduce coll sum 0), 1 runs, 14 msecs
;;; instance?
[coll []], (instance? PersistentVector coll), 1000000 runs, 6 msecs
;;; satisfies?
[coll (list 1 2 3)], (satisfies? ISeq coll), 1000000 runs, 12 msecs
[coll [1 2 3]], (satisfies? ISeq coll), 1000000 runs, 21 msecs

;;; array & string ops
[coll (array 1 2 3)], (seq coll), 1000000 runs, 26 msecs
[coll "foobar"], (seq coll), 1000000 runs, 31 msecs
[coll (array 1 2 3)], (first coll), 1000000 runs, 39 msecs
[coll "foobar"], (first coll), 1000000 runs, 59 msecs
[coll (array 1 2 3)], (nth coll 2), 1000000 runs, 37 msecs
[coll "foobar"], (nth coll 2), 1000000 runs, 139 msecs

;;; cloning & specify
[coll [1 2 3]], (clone coll), 1000000 runs, 26 msecs
[coll [1 2 3]], (specify coll IFoo (foo [_] :bar)), 1000000 runs, 61 msecs
[coll (specify [1 2 3] IFoo (foo [_] :bar))], (foo coll), 1000000 runs, 20 msecs

;;; list ops
[coll (list 1 2 3)], (first coll), 1000000 runs, 16 msecs
[coll (list 1 2 3)], (-first coll), 1000000 runs, 13 msecs
[coll (list 1 2 3)], (rest coll), 1000000 runs, 10 msecs
[coll (list 1 2 3)], (-rest coll), 1000000 runs, 11 msecs
[], (list), 1000000 runs, 5 msecs
[], (list 1 2 3), 1000000 runs, 67 msecs

;;; vector ops
[], [], 1000000 runs, 4 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count [a b c]), 1000000 runs, 71 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vec #object[cljs.tagged_literals.JSValue 0x158e6fc2 "cljs.tagged_literals.JSValue@158e6fc2"])), 1000000 runs, 56 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vector a b c)), 1000000 runs, 83 msecs
[coll [1 2 3]], (transient coll), 100000 runs, 28 msecs
[coll [1 2 3]], (nth coll 0), 1000000 runs, 23 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 25 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 22 msecs
[coll [1 2 3]], (coll 0), 1000000 runs, 27 msecs
[coll [1 2 3]], (conj coll 4), 1000000 runs, 64 msecs
[coll [1 2 3]], (-conj coll 4), 1000000 runs, 60 msecs
[coll []], (-conj coll 1), 1000000 runs, 53 msecs
[coll [1]], (-conj coll 2), 1000000 runs, 54 msecs
[coll [1 2]], (-conj coll 3), 1000000 runs, 53 msecs
[coll [1 2 3]], (seq coll), 1000000 runs, 26 msecs
[coll [1 2 3]], (-seq coll), 1000000 runs, 25 msecs
[coll (seq [1 2 3])], (first coll), 1000000 runs, 19 msecs
[coll (seq [1 2 3])], (-first coll), 1000000 runs, 16 msecs
[coll (seq [1 2 3])], (rest coll), 1000000 runs, 24 msecs
[coll (seq [1 2 3])], (-rest coll), 1000000 runs, 22 msecs
[coll (seq [1 2 3])], (next coll), 1000000 runs, 26 msecs

;;; large vector ops
[], (reduce conj [] (range 40000)), 10 runs, 56 msecs
[coll (reduce conj [] (range (+ 32768 32)))], (conj coll :foo), 100000 runs, 27 msecs
[coll (reduce conj [] (range 40000))], (assoc coll 123 :foo), 100000 runs, 64 msecs
[coll (reduce conj [] (range (+ 32768 33)))], (pop coll), 100000 runs, 23 msecs

;;; chunked seqs
[], (-first v), 1000000 runs, 14 msecs
[], (-next v), 1000000 runs, 32 msecs
[], (-rest v), 1000000 runs, 33 msecs

;;; transients
transient vector, conj! 1000000 items
"Elapsed time: 41 msecs"


;;; vector equality
[a (into [] (range 1000000)) b (into [] (range 1000000))], (= a b), 1 runs, 36 msecs

;;; keyword compare
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed)))))], (.sort arr compare), 100 runs, 327 msecs
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed) (rand-nth seed)))))], (.sort arr compare), 100 runs, 974 msecs

;;; reduce lazy-seqs, vectors, ranges
[coll (take 100000 (iterate inc 0))], (reduce + 0 coll), 1 runs, 74 msecs
[coll (range 1000000)], (reduce + 0 coll), 1 runs, 16 msecs
[coll (into [] (range 1000000))], (reduce + 0 coll), 1 runs, 28 msecs

;; apply
[coll (into [] (range 1000000))], (apply + coll), 1 runs, 61 msecs
[], (list 1 2 3 4 5), 1000000 runs, 146 msecs
[xs (array-seq (array 1 2 3 4 5))], (apply list xs), 1000000 runs, 121 msecs
[xs (list 1 2 3 4 5)], (apply list xs), 1000000 runs, 809 msecs
[xs [1 2 3 4 5]], (apply list xs), 1000000 runs, 141 msecs
[f (fn [a b & more])], (apply f (range 32)), 1000000 runs, 201 msecs
[f (fn [a b c d e f g h i j & more])], (apply f (range 32)), 1000000 runs, 483 msecs

;; update-in
[coll {:foo 1} ks [:foo]], (update-in coll ks inc), 1000000 runs, 290 msecs
[coll (array-map :foo 1) ks [:foo]], (update-in coll ks inc), 1000000 runs, 295 msecs

;;; obj-map
[coll (obj-map)], (assoc coll :foo :bar), 1000000 runs, 271 msecs
[coll (obj-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 27 msecs
[coll (obj-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 711 msecs
[coll (obj-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 23 msecs
[coll (obj-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 24 msecs

;;; array-map
[], {[1] true, [2] true, [3] true}, 1000000 runs, 1284 msecs
[coll (array-map)], (assoc coll :foo :bar), 1000000 runs, 66 msecs
[coll (array-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 43 msecs
[coll (array-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 72 msecs
[coll (array-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 52 msecs
[coll (array-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 56 msecs

;;; array-map w/ symbols
[coll (array-map)], (assoc coll a b), 1000000 runs, 79 msecs
[coll (array-map a b)], (-lookup coll a), 1000000 runs, 57 msecs
[coll (array-map a b)], (assoc coll c d), 1000000 runs, 87 msecs
[coll (array-map a b c d)], (-lookup coll c), 1000000 runs, 61 msecs
[coll (array-map a b c d e f)], (-lookup coll e), 1000000 runs, 70 msecs

;;; array-map w/ inline symbols
[coll (array-map)], (assoc coll (quote foo) (quote bar)), 1000000 runs, 142 msecs
[coll (array-map (quote foo) (quote bar))], (-lookup coll (quote foo)), 1000000 runs, 91 msecs
[coll (array-map (quote foo) (quote bar))], (assoc coll (quote baz) (quote woz)), 1000000 runs, 171 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz))], (-lookup coll (quote baz)), 1000000 runs, 99 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz) (quote lol) (quote rofl))], (-lookup coll (quote lol)), 1000000 runs, 106 msecs

;;; map / record ops
[coll {:foo 1, :bar 2}], (get coll :foo), 1000000 runs, 34 msecs
[coll {(quote foo) 1, (quote bar) 2}], (get coll (quote foo)), 1000000 runs, 84 msecs
[coll {:foo 1, :bar 2}], (-lookup coll :foo nil), 1000000 runs, 32 msecs
[coll {(quote foo) 1, (quote bar) 2}], (-lookup coll (quote foo) nil), 1000000 runs, 82 msecs
[coll {:foo 1, :bar 2}], (:foo coll), 1000000 runs, 40 msecs
[coll {(quote foo) 1, (quote bar) 2}], ((quote foo) coll), 1000000 runs, 119 msecs
[coll {:foo 1, :bar 2}], (kw coll), 1000000 runs, 47 msecs
[coll {(quote foo) 1, (quote bar) 2}], (sym coll), 1000000 runs, 70 msecs
[coll {:foo 1, :bar 2}], (loop [i 0 m coll] (if (< i 100000) (recur (inc i) (assoc m :foo 2)) m)), 1 runs, 9 msecs
[coll (Foo. 1 2)], (:bar coll), 1000000 runs, 36 msecs
[coll (Foo. 1 2)], (-lookup coll :bar), 1000000 runs, 29 msecs
[coll (Foo. 1 2)], (assoc coll :bar 2), 1000000 runs, 60 msecs
[coll (Foo. 1 2)], (assoc coll :baz 3), 1000000 runs, 69 msecs
[coll (Foo. 1 2)], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :bar 2)) m)), 1 runs, 47 msecs

;;; zipmap
[m {:a 1, :b 2, :c 3}], (zipmap (keys m) (map inc (vals m))), 100000 runs, 257 msecs

;;; persistent hash maps
[key :f0], (hash key), 1000000 runs, 13 msecs
[key "f0"], (m3-hash-unencoded-chars key), 1000000 runs, 24 msecs
[key :unsynchronized-mutable], (hash key), 1000000 runs, 13 msecs
[coll hash-coll-test], (hash-coll coll), 100 runs, 80 msecs
[coll hash-coll-test], (hash-ordered-coll coll), 100 runs, 75 msecs
[coll hash-imap-test], (hash-imap coll), 100 runs, 91 msecs
[coll hash-imap-test], (hash-unordered-coll coll), 100 runs, 83 msecs
[coll pmap], (:f0 coll), 1000000 runs, 64 msecs
[coll pmap], (get coll :f0), 1000000 runs, 56 msecs
[coll pmap], (-lookup coll :f0 nil), 1000000 runs, 45 msecs
[coll pmap], (-lookup hash-imap-test :foo500 nil), 1000000 runs, 78 msecs
[coll pmap], (-lookup hash-imap-int-test 500 nil), 1000000 runs, 224 msecs
[coll pmap], (assoc coll :g0 32), 1000000 runs, 405 msecs
[coll pmap], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :a 1)) m)), 1 runs, 83 msecs
[coll cljs.core.PersistentHashMap.EMPTY], (assoc coll :f0 1), 1000000 runs, 155 msecs

transient map, conj! 100000 items
"Elapsed time: 48 msecs"


;;; set ops
[], #{}, 1000000 runs, 5 msecs
[], #{1 3 2}, 1000000 runs, 0 msecs
[v [1 2 3]], (set v), 1000000 runs, 355 msecs
[], (hash-set 1 2 3), 1000000 runs, 0 msecs
[coll #{1 3 2}], (conj coll 4), 1000000 runs, 243 msecs
[coll #{1 3 2}], (get coll 2), 1000000 runs, 48 msecs
[coll #{1 3 2}], (contains? coll 2), 1000000 runs, 38 msecs
[coll #{1 3 2}], (coll 2), 1000000 runs, 54 msecs

;;; seq ops
[coll (range 500000)], (reduce + coll), 1 runs, 24 msecs

;;; reader
[s "{:foo [1 2 3]}"], (reader/read-string s), 1000 runs, 23 msecs
[s big-str-data], (reader/read-string s), 1000 runs, 654 msecs

;;; range
[r (range 1000000)], (last r), 1 runs, 42 msecs

;;; lazy-seq
;;; first run
[r r], (last r), 1 runs, 319 msecs
;;; second run
[r r], (last r), 1 runs, 87 msecs

;;; comprehensions
[xs (range 512)], (last (for [x xs y xs] (+ x y))), 1 runs, 246 msecs
[xs (vec (range 512))], (last (for [x xs y xs] (+ x y))), 4 runs, 163 msecs
[a (Box. 0) xs (range 512)], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 134 msecs
[a (Box. 0) xs (vec (range 512))], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 22 msecs

;; reducers
[xs (into [] (range 1000000))], (r/reduce + (r/map inc (r/map inc (r/map inc xs)))), 1 runs, 85 msecs
;; transducers
[xs (into [] (range 1000000))], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 105 msecs
;; primitive array reduce 1000000 many ops
[xs (into-array (range 1000000))], (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)), 1 runs, 359 msecs
;; reduce range 1000000 many ops
[xs (range 1000000)], (reduce + 0 (map inc (map inc (map inc xs)))), 1 runs, 1413 msecs
;; transduce range 1000000 many ops 
[xs (range 1000000)], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 80 msecs


;; multimethods
[], (simple-multi :foo), 1000000 runs, 134 msecs


;; higher-order variadic function calls
[f array], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 8 msecs
[f vector], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 30 msecs
[], (= 1 1 1 1 1 1 1 1 1 0), 100000 runs, 95 msecs

2. 0003:

Benchmarking with V8
[x 1], (identity x), 1000000 runs, 0 msecs
;; symbol construction
[], (symbol (quote foo)), 1000000 runs, 51 msecs

;; array-reduce & ci-reduce
[coll (seq arr)], (ci-reduce coll + 0), 1 runs, 23 msecs
[coll (seq arr)], (ci-reduce coll sum 0), 1 runs, 19 msecs
[coll arr], (array-reduce coll + 0), 1 runs, 13 msecs
[coll arr], (array-reduce coll sum 0), 1 runs, 11 msecs
;;; instance?
[coll []], (instance? PersistentVector coll), 1000000 runs, 6 msecs
;;; satisfies?
[coll (list 1 2 3)], (satisfies? ISeq coll), 1000000 runs, 12 msecs
[coll [1 2 3]], (satisfies? ISeq coll), 1000000 runs, 22 msecs

;;; array & string ops
[coll (array 1 2 3)], (seq coll), 1000000 runs, 27 msecs
[coll "foobar"], (seq coll), 1000000 runs, 39 msecs
[coll (array 1 2 3)], (first coll), 1000000 runs, 34 msecs
[coll "foobar"], (first coll), 1000000 runs, 44 msecs
[coll (array 1 2 3)], (nth coll 2), 1000000 runs, 35 msecs
[coll "foobar"], (nth coll 2), 1000000 runs, 137 msecs

;;; cloning & specify
[coll [1 2 3]], (clone coll), 1000000 runs, 23 msecs
[coll [1 2 3]], (specify coll IFoo (foo [_] :bar)), 1000000 runs, 69 msecs
[coll (specify [1 2 3] IFoo (foo [_] :bar))], (foo coll), 1000000 runs, 20 msecs

;;; list ops
[coll (list 1 2 3)], (first coll), 1000000 runs, 16 msecs
[coll (list 1 2 3)], (-first coll), 1000000 runs, 13 msecs
[coll (list 1 2 3)], (rest coll), 1000000 runs, 10 msecs
[coll (list 1 2 3)], (-rest coll), 1000000 runs, 10 msecs
[], (list), 1000000 runs, 4 msecs
[], (list 1 2 3), 1000000 runs, 67 msecs

;;; vector ops
[], [], 1000000 runs, 4 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count [a b c]), 1000000 runs, 40 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vec #object[cljs.tagged_literals.JSValue 0x3e5ebdfe "cljs.tagged_literals.JSValue@3e5ebdfe"])), 1000000 runs, 55 msecs
[[a b c] (take 3 (repeatedly (fn* [] (rand-int 10))))], (-count (vector a b c)), 1000000 runs, 46 msecs
[coll [1 2 3]], (transient coll), 100000 runs, 31 msecs
[coll [1 2 3]], (nth coll 0), 1000000 runs, 16 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 16 msecs
[coll [1 2 3]], (-nth coll 0), 1000000 runs, 16 msecs
[coll [1 2 3]], (coll 0), 1000000 runs, 24 msecs
[coll [1 2 3]], (conj coll 4), 1000000 runs, 31 msecs
[coll [1 2 3]], (-conj coll 4), 1000000 runs, 29 msecs
[coll []], (-conj coll 1), 1000000 runs, 25 msecs
[coll [1]], (-conj coll 2), 1000000 runs, 22 msecs
[coll [1 2]], (-conj coll 3), 1000000 runs, 26 msecs
[coll [1 2 3]], (seq coll), 1000000 runs, 26 msecs
[coll [1 2 3]], (-seq coll), 1000000 runs, 26 msecs
[coll (seq [1 2 3])], (first coll), 1000000 runs, 28 msecs
[coll (seq [1 2 3])], (-first coll), 1000000 runs, 24 msecs
[coll (seq [1 2 3])], (rest coll), 1000000 runs, 30 msecs
[coll (seq [1 2 3])], (-rest coll), 1000000 runs, 26 msecs
[coll (seq [1 2 3])], (next coll), 1000000 runs, 29 msecs

;;; large vector ops
[], (reduce conj [] (range 40000)), 10 runs, 60 msecs
[coll (reduce conj [] (range (+ 32768 32)))], (conj coll :foo), 100000 runs, 32 msecs
[coll (reduce conj [] (range 40000))], (assoc coll 123 :foo), 100000 runs, 63 msecs
[coll (reduce conj [] (range (+ 32768 33)))], (pop coll), 100000 runs, 22 msecs

;;; chunked seqs
[], (-first v), 1000000 runs, 14 msecs
[], (-next v), 1000000 runs, 36 msecs
[], (-rest v), 1000000 runs, 33 msecs

;;; transients
transient vector, conj! 1000000 items
"Elapsed time: 44 msecs"


;;; vector equality
[a (into [] (range 1000000)) b (into [] (range 1000000))], (= a b), 1 runs, 37 msecs

;;; keyword compare
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed)))))], (.sort arr compare), 100 runs, 281 msecs
[arr (into-array (repeatedly 10000 (fn* [] (keyword (rand-nth seed) (rand-nth seed)))))], (.sort arr compare), 100 runs, 1025 msecs

;;; reduce lazy-seqs, vectors, ranges
[coll (take 100000 (iterate inc 0))], (reduce + 0 coll), 1 runs, 75 msecs
[coll (range 1000000)], (reduce + 0 coll), 1 runs, 17 msecs
[coll (into [] (range 1000000))], (reduce + 0 coll), 1 runs, 25 msecs

;; apply
[coll (into [] (range 1000000))], (apply + coll), 1 runs, 69 msecs
[], (list 1 2 3 4 5), 1000000 runs, 206 msecs
[xs (array-seq (array 1 2 3 4 5))], (apply list xs), 1000000 runs, 121 msecs
[xs (list 1 2 3 4 5)], (apply list xs), 1000000 runs, 757 msecs
[xs [1 2 3 4 5]], (apply list xs), 1000000 runs, 924 msecs
[f (fn [a b & more])], (apply f (range 32)), 1000000 runs, 184 msecs
[f (fn [a b c d e f g h i j & more])], (apply f (range 32)), 1000000 runs, 453 msecs

;; update-in
[coll {:foo 1} ks [:foo]], (update-in coll ks inc), 1000000 runs, 320 msecs
[coll (array-map :foo 1) ks [:foo]], (update-in coll ks inc), 1000000 runs, 301 msecs

;;; obj-map
[coll (obj-map)], (assoc coll :foo :bar), 1000000 runs, 267 msecs
[coll (obj-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 27 msecs
[coll (obj-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 727 msecs
[coll (obj-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 24 msecs
[coll (obj-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 25 msecs

;;; array-map
[], {[1] true, [2] true, [3] true}, 1000000 runs, 762 msecs
[coll (array-map)], (assoc coll :foo :bar), 1000000 runs, 66 msecs
[coll (array-map :foo :bar)], (-lookup coll :foo), 1000000 runs, 50 msecs
[coll (array-map :foo :bar)], (assoc coll :baz :woz), 1000000 runs, 74 msecs
[coll (array-map :foo :bar :baz :woz)], (-lookup coll :baz), 1000000 runs, 56 msecs
[coll (array-map :foo :bar :baz :woz :lol :rofl)], (-lookup coll :lol), 1000000 runs, 62 msecs

;;; array-map w/ symbols
[coll (array-map)], (assoc coll a b), 1000000 runs, 82 msecs
[coll (array-map a b)], (-lookup coll a), 1000000 runs, 61 msecs
[coll (array-map a b)], (assoc coll c d), 1000000 runs, 90 msecs
[coll (array-map a b c d)], (-lookup coll c), 1000000 runs, 65 msecs
[coll (array-map a b c d e f)], (-lookup coll e), 1000000 runs, 72 msecs

;;; array-map w/ inline symbols
[coll (array-map)], (assoc coll (quote foo) (quote bar)), 1000000 runs, 142 msecs
[coll (array-map (quote foo) (quote bar))], (-lookup coll (quote foo)), 1000000 runs, 92 msecs
[coll (array-map (quote foo) (quote bar))], (assoc coll (quote baz) (quote woz)), 1000000 runs, 163 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz))], (-lookup coll (quote baz)), 1000000 runs, 102 msecs
[coll (array-map (quote foo) (quote bar) (quote baz) (quote woz) (quote lol) (quote rofl))], (-lookup coll (quote lol)), 1000000 runs, 105 msecs

;;; map / record ops
[coll {:foo 1, :bar 2}], (get coll :foo), 1000000 runs, 35 msecs
[coll {(quote foo) 1, (quote bar) 2}], (get coll (quote foo)), 1000000 runs, 86 msecs
[coll {:foo 1, :bar 2}], (-lookup coll :foo nil), 1000000 runs, 31 msecs
[coll {(quote foo) 1, (quote bar) 2}], (-lookup coll (quote foo) nil), 1000000 runs, 80 msecs
[coll {:foo 1, :bar 2}], (:foo coll), 1000000 runs, 42 msecs
[coll {(quote foo) 1, (quote bar) 2}], ((quote foo) coll), 1000000 runs, 116 msecs
[coll {:foo 1, :bar 2}], (kw coll), 1000000 runs, 47 msecs
[coll {(quote foo) 1, (quote bar) 2}], (sym coll), 1000000 runs, 67 msecs
[coll {:foo 1, :bar 2}], (loop [i 0 m coll] (if (< i 100000) (recur (inc i) (assoc m :foo 2)) m)), 1 runs, 5 msecs
[coll (Foo. 1 2)], (:bar coll), 1000000 runs, 35 msecs
[coll (Foo. 1 2)], (-lookup coll :bar), 1000000 runs, 30 msecs
[coll (Foo. 1 2)], (assoc coll :bar 2), 1000000 runs, 52 msecs
[coll (Foo. 1 2)], (assoc coll :baz 3), 1000000 runs, 61 msecs
[coll (Foo. 1 2)], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :bar 2)) m)), 1 runs, 44 msecs

;;; zipmap
[m {:a 1, :b 2, :c 3}], (zipmap (keys m) (map inc (vals m))), 100000 runs, 230 msecs

;;; persistent hash maps
[key :f0], (hash key), 1000000 runs, 13 msecs
[key "f0"], (m3-hash-unencoded-chars key), 1000000 runs, 22 msecs
[key :unsynchronized-mutable], (hash key), 1000000 runs, 11 msecs
[coll hash-coll-test], (hash-coll coll), 100 runs, 70 msecs
[coll hash-coll-test], (hash-ordered-coll coll), 100 runs, 65 msecs
[coll hash-imap-test], (hash-imap coll), 100 runs, 73 msecs
[coll hash-imap-test], (hash-unordered-coll coll), 100 runs, 96 msecs
[coll pmap], (:f0 coll), 1000000 runs, 64 msecs
[coll pmap], (get coll :f0), 1000000 runs, 52 msecs
[coll pmap], (-lookup coll :f0 nil), 1000000 runs, 47 msecs
[coll pmap], (-lookup hash-imap-test :foo500 nil), 1000000 runs, 79 msecs
[coll pmap], (-lookup hash-imap-int-test 500 nil), 1000000 runs, 216 msecs
[coll pmap], (assoc coll :g0 32), 1000000 runs, 405 msecs
[coll pmap], (loop [i 0 m coll] (if (< i 1000000) (recur (inc i) (assoc m :a 1)) m)), 1 runs, 80 msecs
[coll cljs.core.PersistentHashMap.EMPTY], (assoc coll :f0 1), 1000000 runs, 156 msecs

transient map, conj! 100000 items
"Elapsed time: 53 msecs"


;;; set ops
[], #{}, 1000000 runs, 5 msecs
[], #{1 3 2}, 1000000 runs, 0 msecs
[v [1 2 3]], (set v), 1000000 runs, 515 msecs
[], (hash-set 1 2 3), 1000000 runs, 0 msecs
[coll #{1 3 2}], (conj coll 4), 1000000 runs, 266 msecs
[coll #{1 3 2}], (get coll 2), 1000000 runs, 50 msecs
[coll #{1 3 2}], (contains? coll 2), 1000000 runs, 38 msecs
[coll #{1 3 2}], (coll 2), 1000000 runs, 62 msecs

;;; seq ops
[coll (range 500000)], (reduce + coll), 1 runs, 23 msecs

;;; reader
[s "{:foo [1 2 3]}"], (reader/read-string s), 1000 runs, 30 msecs
[s big-str-data], (reader/read-string s), 1000 runs, 636 msecs

;;; range
[r (range 1000000)], (last r), 1 runs, 44 msecs

;;; lazy-seq
;;; first run
[r r], (last r), 1 runs, 322 msecs
;;; second run
[r r], (last r), 1 runs, 90 msecs

;;; comprehensions
[xs (range 512)], (last (for [x xs y xs] (+ x y))), 1 runs, 229 msecs
[xs (vec (range 512))], (last (for [x xs y xs] (+ x y))), 4 runs, 196 msecs
[a (Box. 0) xs (range 512)], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 133 msecs
[a (Box. 0) xs (vec (range 512))], (doseq [x xs y xs] (set! a -val (+ (.-val a) x))), 4 runs, 23 msecs

;; reducers
[xs (into [] (range 1000000))], (r/reduce + (r/map inc (r/map inc (r/map inc xs)))), 1 runs, 85 msecs
;; transducers
[xs (into [] (range 1000000))], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 108 msecs
;; primitive array reduce 1000000 many ops
[xs (into-array (range 1000000))], (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)), 1 runs, 349 msecs
;; reduce range 1000000 many ops
[xs (range 1000000)], (reduce + 0 (map inc (map inc (map inc xs)))), 1 runs, 1387 msecs
;; transduce range 1000000 many ops 
[xs (range 1000000)], (transduce (comp (map inc) (map inc) (map inc)) + 0 xs), 1 runs, 79 msecs


;; multimethods
[], (simple-multi :foo), 1000000 runs, 122 msecs


;; higher-order variadic function calls
[f array], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 8 msecs
[f vector], (f 1 2 3 4 5 6 7 8 9 0), 100000 runs, 154 msecs
[], (= 1 1 1 1 1 1 1 1 1 0), 100000 runs, 94 msecs
Comment by Michał Marczyk [ 20/Jul/15 11:35 PM ]

(No rebase this time, as 0003 still applies cleanly and all tests pass.)





[CLJS-1343] Investigate performance impact of using records over maps during compilation Created: 17/Jul/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Task Priority: Major
Reporter: David Nolen Assignee: Sebastian Bensusan
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Profiles in Chrome DevTools reveal that a considerable amount of time is spent in PersistentArrayMap and PersistentHashMap. It would be worth considering coverting all maps into records with a protocol to extract the pure data form of the AST.






[CLJS-1335] resolve-macro-var: information missing for macros Created: 12/Jul/15  Updated: 12/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: bootstrap
Environment:

https://github.com/swannodette/cljs-bootstrap



 Description   

In bootstrapped ClojureScript, if you resolve-var on a function, you get lots of information, but resolve-macro-var doesn't work for macros. (The only reason I have any expectation for this to work is that it appears to do so in ClojureScript JVM).

cljs-bootstrap.core=> (with-compiler-env cenv (ana/resolve-macro-var (ana/empty-env) 'or)))
nil

But:

cljs-bootstrap.core=> (with-compiler-env cenv (ana/resolve-var (ana/empty-env) 'map)))
{:protocol-inline nil, :meta {:file "cljs/core.cljs", :end-column 10, :top-fn {:variadic true, :method-params ([f] [f coll] [f c1 c2] [f c1 c2 c3]), :arglists-meta (nil nil nil nil nil), :max-fixed-arity 4, :arglists ([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])}, :column 7, :line 4128, :end-line 4128, :arglists (quote ([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])), :doc "Returns a lazy sequence consisting of the result of applying f to\n  the set of first items of each coll, followed by applying f to the\n  set of second items in each coll, until any one of the colls is\n  exhausted.  Any remaining items in other colls are ignored. Function\n  f should accept number-of-colls arguments. Returns a transducer when\n  no collection is provided."}, :ns cljs.core, :name cljs.core/map, :variadic true, :file "cljs/core.cljs", :end-column 10, :top-fn {:variadic true, :method-params ([f] [f coll] [f c1 c2] [f c1 c2 c3]), :arglists-meta (nil nil nil nil nil), :max-fixed-arity 4, :arglists ([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])}, :method-params ([f] [f coll] [f c1 c2] [f c1 c2 c3]), :protocol-impl nil, :arglists-meta (nil nil nil nil nil), :column 1, :line 4128, :end-line 4128, :max-fixed-arity 4, :fn-var true, :arglists ([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls]), :doc "Returns a lazy sequence consisting of the result of applying f to\n  the set of first items of each coll, followed by applying f to the\n  set of second items in each coll, until any one of the colls is\n  exhausted.  Any remaining items in other colls are ignored. Function\n  f should accept number-of-colls arguments. Returns a transducer when\n  no collection is provided."}

As an aside:

cljs-bootstrap.core=> (with-compiler-env cenv (ana/resolve-var (ana/empty-env) 'or)))
{:name cljs.core/or, :ns cljs.core}





[CLJS-1332] investigate inlining version of cljs.core/get Created: 10/Jul/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None





[CLJS-1330] self-host: .toString on int needs parens Created: 08/Jul/15  Updated: 18/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: bootstrap
Environment:

https://github.com/swannodette/cljs-bootstrap



 Description   
cljs-bootstrap.core=> (with-out-str
      (c/emit
        (ensure
          (ana/analyze
            (assoc (ana/empty-env) :context :expr)
            '(.toString 1)))))) 
"1.toString$()"
  • Note the lack of parens CLJS-715
  • Note the dollar sign after fn name is covered by separate ticket CLJS-1430





[CLJS-1328] Support defrecord reader tags Created: 04/Jul/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: Herwig Hochleitner Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: reader, readertags


 Description   

Currently, defrecord instances print similar to how they do in clojure

> (pr-str (garden.units/px 5))
#garden.types.CSSUnit{:unit :px, :magnitude 5}

This representation cannot be read by the compiler, nor at runtime by cljs.reader/read-string

> #garden.types.CSSUnit{:unit :px, :magnitude 5}
clojure.lang.ExceptionInfo: garden.types.CSSUnit {:type :reader-exception, :line 1, :column 22, :file "NO_SOURCE_FILE"}
...
> (cljs.reader/read-string "#garden.types.CSSUnit{:unit :px, :magnitude 5}")
#<Error: Could not find tag parser for garden.types.CSSUnit in ("inst" "uuid" "queue" "js")>
...

Analysis

The two requirements - using record literals in cljs source code and supporting runtime reading - can be addressed by using the analyzer to find defrecords and registering them with the two respective reader libraries.

Record literals

Since clojurescript reads and compiles a file at a time, clojure's behavior for literals is hard to exactly mimic. That is, to be able to use the literal in the same file where the record is defined.
A reasonable compromise might be to update the record tag table after each file has been analyzed. Thus the literal form of a record could be used only in requiring files.

EDIT: Record literals can also go into the constant pool

cljs.reader

To play well with minification, the ^:export annotation could be reused on defrecords, to publish the corresponding reader tag to cljs.reader.

Related Tickets



 Comments   
Comment by David Nolen [ 08/Jul/15 12:00 PM ]

It's preferred that we avoid exporting. Instead we can adopt the same approach as the constant literal optimization for keywords under advanced optimizations. We can make a lookup table (which won't pollute the global namespace like exporting does) which maps a string to its type.

I'm all for this enhancement.





[CLJS-1322] Investigate why Babel cannot load into JDK 8 Nashorn Created: 01/Jul/15  Updated: 01/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: GSoC
Fix Version/s: None

Type: Task Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

It appears neither the developer browser.js file nor the browser.min.js file can be loaded into Nashorn due to a method size limit exception. The code appears to originate around the lodash include. This may be a red herring but this is a good starting point for investigation.






[CLJS-1315] Warning on Google Closure enum property access with / Created: 18/Jun/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Edge case in / usage, EventType/CLICK does not trigger a warning. Foo/bar always means that Foo is a namespace, it cannot be used for the static field access pattern common in Java as there's no reflection information in JavaScript to determine this.






[CLJS-1313] REPL support for libs compiler opts Created: 17/Jun/15  Updated: 17/Jun/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3308
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File CLJS-1313-v1.patch    

 Description   

Today, module processing and the lib dependency index setup occurs only when cljs.closure/build is invoked. This ticket asks for the same setup to occur if either :libs or :foreign-libs options are passed when a REPL is launched (without an explicit build step occurring first, as is done in the Quick Start examples involving cljs.repl/repl).

An example:

(cljs.repl/repl (cljs.repl.node/repl-env)
  :foreign-libs [{:file "libs/greeting.js"
                  :provides ["greeting"]
                  :module-type :commonjs}
                 {:file "libs/german.js"
                  :provides ["german"]
                  :module-type :commonjs}])

The above would be sufficient to cause, for example, CommonJS module processing to occur, and the results to be available within the REPL.

Additionally, the implementation should defer processing to after REPL -setup has been called, in case the REPL establishes an :output-dir for :merge-opts during -setup, thereby ensuring that any module processing output goes to the correct :output-dir.



 Comments   
Comment by Mike Fikes [ 17/Jun/15 6:51 PM ]

The attached CLJS-1313-v1.patch is working fine for Ambly, but it fails for the Node REPL. Interestingly, Maria Neise was finding that CommonJS processing was not quite working properly under Node, even with an explicit build step, when compared to Nashorn. Apart from general discussion that could be had about suitability of such a patch, there is a specific concern with Node that needs to be sorted.

Comment by Mike Fikes [ 17/Jun/15 8:45 PM ]

So, with further investigation, it looks like the issue with using this patch with Node is actually a separate problem with Node: CLJS-1314

Additionally, I can confirm that the patch functions properly for Nashorn.





[CLJS-1302] (js/goog.base (js* "this")) no longer works Created: 09/Jun/15  Updated: 05/Aug/15

Status: In Progress
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: None


 Description   
(defn Foo
  []
  (goog/base (js* "this")))
(goog/inherits Foo js/Object)

(defn Foo
  []
  (this-as this
    (goog/base this)))
(goog/inherits Foo js/Object)


 Comments   
Comment by David Nolen [ 01/Aug/15 6:12 AM ]

90% sure this is because of the SSA bit for fn invokes under advanced optimizations.





[CLJS-1300] REPLs do no write out updated deps.js when compiling files Created: 05/Jun/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

For example a user may edit a file including a new dependency. This will work at the REPL but if a browser refresh is made the emitted goog.require will fail due to the initial deps.js file being stale.






[CLJS-1298] source-on-disk conditional should include :source-url Created: 05/Jun/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Bruce Hauman Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

All



 Description   

cljs.closure/source-on-disk is conditional on having the IJavaScript having a :source-url. Currently this conditional is exercised after the call to util/ext. But util/ext has a precondition that will fail on a nil :source-url.

Solution: protect util/ext by checking for :source-url before calling util/ext.

(defn source-on-disk
  "Ensure that the given IJavaScript exists on disk in the output directory.
  Return updated IJavaScript with the new location if necessary."
  [opts js]
  (if (write-js? js)
    (write-javascript opts js)
    ;; always copy original ClojureScript sources to the output directory
    ;; when source maps enabled
    (let [out-file (when-let [ns (and (:source-map opts)
                                      (:source-url js) ;;<--- ADD THIS HERE
                                      (first (:provides js)))]
                     (io/file (io/file (util/output-directory opts))
                       (util/ns->relpath ns (util/ext (:source-url js)))))
          source-url (:source-url js)]
      (when (and out-file source-url
                 (or (not (.exists ^File out-file))
                     (> (.lastModified (io/file source-url))
                        (.lastModified out-file))))
        (spit out-file (slurp source-url)))
      js)))

Or some refactoring of the above.



 Comments   
Comment by David Nolen [ 05/Jun/15 2:20 PM ]

In what cases will an IJavaScript not have a :source-url? Just trying to get some context.

Comment by Bruce Hauman [ 05/Jun/15 2:42 PM ]

Good question. When the sources are already output javascript and they have an output dir relative :url but not a :source-url, this doesn't seem like it should happen.

I'm getting a situation like this

:url #object[java.net.URL 0x5f921081 "file:/Users/brucehauman/workspace/temp/hello-world/resources/goog/uri/utils.js"]
:source-url nil

Comment by David Nolen [ 05/Jun/15 2:58 PM ]

Ok that seems like the real underlying issue. Do you have a small reproducer by chance?

Comment by Bruce Hauman [ 05/Jun/15 3:08 PM ]

It seems like its when the :asset-path is "". And :output-dir is at the root of a resource path like :output-dir "resources".





[CLJS-1297] defrecord does not emit IKVReduce protocol Created: 03/Jun/15  Updated: 11/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Daniel Skarda Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: newbie

Attachments: Text File CLJS-1297-19-July-2015.patch    

 Description   

Records are maps and in Clojure they support reduce-kv (IKVReduce protocol).
This is not true in ClojureScript:

(defrecord Foobar [x y])
 (reduce-kv assoc {} (Foobar. 1 2))

Fails wit Error: No protocol method IKVReduce.-kv-reduce defined for type : [object Object]



 Comments   
Comment by David Nolen [ 03/Jun/15 7:25 PM ]

Just seems like an oversight. Patch welcome, this one is a relatively easy one.

Comment by Daniel Skarda [ 04/Jun/15 2:53 AM ]

OK

I checked Clojure implementation. Records do not implement any reduce protocol on their own. For IKVReduce records use default implementation using reduce and destructuring. Is this approach OK?

Recently Alex Miller implemented many optimizations of reduce protocols in Clojure. Eg range returns an object which implements IReduce protocol so reduce (and transducers in general) can take advantage of it. Any plans for such optimizations in ClojureScript?

;;clojure/src/clj/clojure/core.clj:6523
;;slow path default
clojure.lang.IPersistentMap
(kv-reduce 
  [amap f init]
  (reduce (fn [ret [k v]] (f ret k v)) init amap))
Comment by David Nolen [ 04/Jun/15 9:05 AM ]

Going with the Clojure implementation is fine. Yes all of the optimizations in 1.7.0 are on the table for ClojureScript but these are separate issues from this one.

Comment by Samuel Miller [ 16/Jul/15 10:39 PM ]

Mind if I take this as my first cljs bug? Poking around quickly I think I know what needs to happen.

Comment by David Nolen [ 17/Jul/15 5:21 AM ]

Sure! Have you submitted your CA yet?

Comment by Samuel Miller [ 17/Jul/15 7:13 PM ]

Yes, I did yesterday.

Comment by Samuel Miller [ 20/Jul/15 9:52 PM ]

Here is a potential patch. I implemented a basic IKVreduce based on Daniel Skarda's comment. Note: I am a little fuzzy on macros still so please look over what I have. There is probably a better way. Also added a test for reduce-kv on records.

I ran the test on Linux on V8 and SpiderMonkey. I plan to get JSC and Nashorn working and tested this week but if someone wants to test them out before that would be great.

Comment by Sebastian Bensusan [ 23/Jul/15 6:45 PM ]

Experience report:

I just tested the patch in the Node Repl and it seems to work:

cljs.user=> (defrecord A [a b])
cljs.user/A
cljs.user=> (reduce-kv (fn [m k v] (assoc m k (inc v))) {} (A. 1 2))
{:a 2, :b 3}

and the provided tests passed in Spidermonkey, V8, and Nashorn (I don't have JSC installed).

For completeness: before applying the patch the same code fails with:

Error: No protocol method IKVReduce.-kv-reduce defined for type : [object Object]
Comment by David Nolen [ 10/Aug/15 10:22 PM ]

Is this the same approach taken by Clojure?

Comment by Samuel Miller [ 10/Aug/15 10:36 PM ]

You can see the relevant current Clojure code here...
https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L6526
I think it is the same. I literally just tried to translate it over into CLJS. I might of understood something wrong though.

Comment by David Nolen [ 11/Aug/15 6:10 AM ]

Yes that's the slow path. Please use the implementation used by defrecord instead. If defrecord doesn't have one then this patch is OK.

Comment by Samuel Miller [ 11/Aug/15 8:48 PM ]

As far as I can tell there is no implementation on defrecord itself however there are separate implementations on the the java classes PersistentVector, PersistentArrayMap, PersistentTreeMap, and PersistenHashMap in pure java. I am not sure if you would want to do something similar for Clojurescript.

I can also spend some time trying to make a more performant version.





[CLJS-1280] double analysis warnings when using cljs.repl/load-file special fn Created: 21/May/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None





[CLJS-1277] relax requirement that files must declare a namespace, default to cljs.user Created: 19/May/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

This aligns better with Clojure itself supports.



 Comments   
Comment by David Nolen [ 14/Jun/15 10:30 AM ]

There are a few hurdles in order to make progress on this ticket. The first is that in order to be useful something like require etc. outside the ns needs to be supported in order to be useful.

Comment by Jonathan Boston [ 18/Jul/15 12:17 PM ]

Needs http://dev.clojure.org/jira/browse/CLJS-1346 to be useful.





[CLJS-1237] ns-unmap doesn't work on refers from cljs.core Created: 01/May/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Chouser Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: ns-unmap

Attachments: Text File 0001-CLJS-1237-ns-unmap-adds-to-namespace-s-excludes.patch     Text File 0002-CLJS-1237-ns-unmap-adds-to-namespace-s-excludes.patch    
Patch: Code

 Description   

In ClojureScript, using ns-unmap on a symbol from cljs.core doesn't exclude it from the current namespace. Note that both a function and a macro still exist, even after unmapping:

To quit, type: :cljs/quit  
cljs.user=> (ns-unmap 'cljs.user 'when) ;; macro
true  
cljs.user=> (ns-unmap 'cljs.user 'not)  ;; function
true  
cljs.user=> (when 1 2)  
2  
cljs.user=> (not false)  
true  

This differs from the behavior of Clojure's ns-unmap. Note the appropriate errors when attempting to use unmapped symbols:

Clojure 1.7.0-beta1
user=> (ns-unmap 'user 'when) ;; macro
nil
user=> (ns-unmap 'user 'not)  ;; function
nil
user=> (when 1 2)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: when in this context, compiling:(NO_SOURCE_PATH:11:1) 
user=> (not false)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: not in this context, compiling:(NO_SOURCE_PATH:12:1) 

Somehow ClojureScript's ns-unmap needs to add the symbol to the current namespace's :excludes set. Note that the def special form does this already (after it displays a warning).

We have two solutions. 0001 extends the ns form's :merge behavior to support :excludes, and then uses this in ns-unmap. If the enhancement to ns isn't wanted, patch 0002 changes ns-unmap to update :excludes directly.



 Comments   
Comment by David Nolen [ 05/May/15 7:23 AM ]

The second patch is preferred. However it seems the second patch is too permissive. The :excludes logic should only be applied if the symbol identifies a core macro or fn.

Comment by Chouser [ 05/May/15 3:46 PM ]

The ns form's own :refer-clojure :exclude accepts arbitrary symbols and adds them to the namespace's :excludes set, which seems like the same permissiveness problem. Do you want a patch that addresses the permissiveness of both ns and ns-unmap in this ticket, or should such a patch go in a new ticket?

Comment by David Nolen [ 05/May/15 4:08 PM ]

New ticket to fix the bug that :exclude doesn't check the symbol list for cljs.core declared vars, and an updated patch here please.





[CLJS-1236] `constructor` needs to munged if used as namespace segment Created: 01/May/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Will make goog.require believe that the namespace has already been loaded and throw an exception since all JS objects have the constructor property.



 Comments   
Comment by David Nolen [ 04/May/15 8:19 AM ]

This issue is not quite as simple as it sounds as we actually need a special case for namespace munging. For example we don't want to munge Foo.constructor but we do want to munge ns.foo.constructor. To implement the fix correctly it would require changing all the cases where library names are munged.





[CLJS-1228] cljs.util/topo-sort is polynomial on larger dependency graphs Created: 28/Apr/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: None


 Comments   
Comment by David Nolen [ 08/May/15 7:35 AM ]

This problem was reported due the performance of cljs.analyzer/ns-dependents when used by cljs.compiler/compile-file. However as cljs.compiler/compile-file does not need the topological sorting bit, we should supply a faster alternative to cljs.analyzer/ns-dependents which simply returns a distinct seq which can be poured into a set.





[CLJS-1207] Emit a warning if multiple resources found for a ClojureScript namespace Created: 15/Apr/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

We should emit a simple warning if a namespace doesn't not appear to be unique on the classpath.






[CLJS-1194] data_readers.cljc Created: 10/Apr/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Now that conditional reading has landed we can implement support for data_readers.cljc to get both compile time and runtime support.



 Comments   
Comment by David Nolen [ 10/Apr/15 7:45 PM ]

This needs http://dev.clojure.org/jira/browse/CLJ-1699 to be useful.

Comment by Nikita Prokopov [ 19/May/15 7:58 AM ]

CLJ-1699 has landed.

Right now CLJS tries to compile data_readers.cljc as a regular source code file:

Exception in thread "main" java.lang.AssertionError: No ns form found in src/data_readers.cljc, compiling:(/private/var/folders/0h/9vv4g3d955l6ctwwl4k9xjy40000gn/T/form-init3533791126017861878.clj:1:125)
	at clojure.lang.Compiler.load(Compiler.java:7249)
	at clojure.lang.Compiler.loadFile(Compiler.java:7175)
	at clojure.main$load_script.invoke(main.clj:275)
	at clojure.main$init_opt.invoke(main.clj:280)
	at clojure.main$initialize.invoke(main.clj:308)
	at clojure.main$null_opt.invoke(main.clj:343)
	at clojure.main$main.doInvoke(main.clj:421)
	at clojure.lang.RestFn.invoke(RestFn.java:421)
	at clojure.lang.Var.invoke(Var.java:383)
	at clojure.lang.AFn.applyToHelper(AFn.java:156)
	at clojure.lang.Var.applyTo(Var.java:700)
	at clojure.main.main(main.java:37)
Comment by David Nolen [ 19/May/15 8:53 AM ]

This should be addressed first: http://dev.clojure.org/jira/browse/CLJS-1277





[CLJS-1174] Simple warning if a namespace with dashes not found but a file path with dashes exists Created: 27/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: easy





[CLJS-1159] compiled files with warnings that otherwise don't need recompilation will not emit warnings on the next compile Created: 23/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

The aggressive caching approach is odds with warning visibility. It probably makes sense for a compiled file with warnings to always return true for requires-compilation?.






[CLJS-1147] reconnect logic for browser REPLs Created: 18/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Instead of forcing users to refresh browser and lose application state, the browser REPL should poll once a second to connect if connection is unreachable for some reason.



 Comments   
Comment by David Nolen [ 21/Mar/15 8:56 PM ]

This is firmly a major nice-to-have, but not a blocker.





[CLJS-1142] ClojureScript stream socket REPL Created: 18/Mar/15  Updated: 06/May/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3196
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Port relevant bits of CLJ-1671






[CLJS-1141] memoization of js-dependency-index and get-upstream-deps needs knobs Created: 18/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File CLJS_1141.patch     Text File CLJS-1141-with-js-dep-caching-latest.patch    

 Description   

knobs should be exposed for more dynamic compilation environments like Figwheel which may desire to add dependencies to the classpath on the fly.



 Comments   
Comment by Bruce Hauman [ 21/Mar/15 3:51 PM ]

A patch that caches upstream dependencies in the compiler env.

Comment by Bruce Hauman [ 21/Mar/15 3:59 PM ]

Actually I'm going to submit another patch that includes the memoize calls in js-deps.

Comment by Bruce Hauman [ 28/Mar/15 12:50 PM ]

New patch that moves cljs.js-deps memoization to current env/compiler as well as get-upstream-deps.

Unfortunately there is a circular dep between cljs.env and cljs.js-deps, if we want to cache in env/compiler. I overcame this with a resolve.

Compile performance is either completely unchanged or slightly improved based on several test runs.

Comment by Bruce Hauman [ 28/Mar/15 2:22 PM ]

Hold off on this. Its not behaving as expected. Doesn't seem to be caching in certain situations.

Comment by David Nolen [ 28/Mar/15 2:26 PM ]

Thanks for the update. This will definitely not land until after the pending REPL/piggieback release anyhow.

Comment by Bruce Hauman [ 28/Mar/15 2:44 PM ]

Yeah there is an obvious bug and a subtle one. Hopefully will finish it up soonish.

Comment by Bruce Hauman [ 28/Mar/15 3:43 PM ]

Alright, this latest patch works. There was a subtle memoizing nil value bug.





[CLJS-1139] Repeated applications of `ns` form at the REPL are not additive Created: 17/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Michael Griffiths Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Quick start guide with Node REPL



 Description   

In a Clojure REPL, it is possible to declare the same namespace again, without existing namespaces aliases being altered or removed:

user=> (ns my-test-ns.core (:require [clojure.string :as string]))
nil
my-test-ns.core=> (def a string/blank?)
#'my-test-ns.core/a
my-test-ns.core=> (ns my-test-ns.core)
nil
my-test-ns.core=> (def a string/blank?)
#'my-test-ns.core/a
my-test-ns.core=>

ClojureScript REPLs do not behave in the same way:

ClojureScript:cljs.user> (ns my-test-ns.core (:require [clojure.string :as string]))
true
ClojureScript:my-test-ns.core> (def a string/blank?)
#<function clojure$string$blank_QMARK_(s){
return goog.string.isEmptySafe(s);
}>
ClojureScript:my-test-ns.core> (ns my-test-ns.core)
nil
ClojureScript:my-test-ns.core> (def a string/blank?)
WARNING: No such namespace: string, could not locate string.cljs at line 1 <cljs repl>
WARNING: Use of undeclared Var string/blank? at line 1 <cljs repl>
repl:13
throw e__3919__auto__;
      ^
ReferenceError: string is not defined
    at repl:1:109
    at repl:9:3
    at repl:14:4
    at Object.exports.runInThisContext (vm.js:74:17)
    at Domain.<anonymous> ([stdin]:41:34)
    at Domain.run (domain.js:197:16)
    at Socket.<anonymous> ([stdin]:40:25)
    at Socket.emit (events.js:107:17)
    at readableAddChunk (_stream_readable.js:163:16)
    at Socket.Readable.push (_stream_readable.js:126:10)
ClojureScript:my-test-ns.core>





[CLJS-1127] validate compiled file written to disk Created: 16/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

If we validate the file written to disk then we can catch common error of running multiple build processes and abort.






[CLJS-1125] Simple corrupted compiled file detection Created: 16/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

We should include a line at the end of the file that we check for to determine that the file was not corrupted due to either an incomplete write or a clobbered write. It should be be the SHA of the ClojureScript source it was generated from.






[CLJS-1104] Compute SHA for ClojureScript compiled file Created: 10/Mar/15  Updated: 10/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Needed for shared AOT cache






[CLJS-1075] Generic inline source map support Created: 02/Mar/15  Updated: 02/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Currently hard coded to REPLs. Would simplify jsbin and similar integration.






[CLJS-1074] Externs inference Created: 02/Mar/15  Updated: 18/Aug/15

Status: In Progress
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3211
Fix Version/s: GSoC

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Maria Geller
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Given all externs generally need to be supplied for js/foo we could probably automatically compute externs based on js/foo usage in user code. For this to work correctly we need to account for property access through js/foo i.e. (def Bar js/foo.Bar). This should infer that Bar is also a foreign object. Things gets more complicated for higher order cases, we probably want to support a ^js type hint.

Finally externs inference needs to account for externs likely already supplied by the user - i.e. don't emit dupes, Google Closure will complain.






[CLJS-1024] Double analysis warnings for dependencies in JARs Created: 10/Feb/15  Updated: 10/Feb/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

If there is a problem with a CLJS source in a JAR, warnings will be emitted twice - once when analyzing the source in the JAR and again when the file is compiled to the output directory. Because the path has changed we don't realize we have already seen it. This probably just needs minor tweaks so that we drop warnings from the second pass.






[CLJS-994] print a warning when :externs file paths can't be found. Created: 30/Jan/15  Updated: 05/Aug/15

Status: Reopened
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: Crispin Wellington Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: cljs, enhancement, errormsgs, patch,
Environment:

Linux 64bit

java version "1.7.0_65"
OpenJDK Runtime Environment (IcedTea 2.5.3) (7u71-2.5.3-0ubuntu0.14.04.1)
OpenJDK 64-Bit Server VM (build 24.65-b04, mixed mode)


Attachments: Text File clojurescript-extern-missing-warning.patch    
Patch: Code

 Description   

clojurescript silently ignores missing externs files possibly leading a developer to chase their tail.

Presently it can be very confusing using advanced compilation if you have made a mistake in the path name of one of your :externs files. This patch makes the compiler print a warning on stderr so you can quickly determine the cause of the broken advanced compilation output.

As a side effect, when doing a basic lein-cljsbuild a warning is always printed:

```
WARNING: js resource path closure-js/externs does not exist
```

This is because lein-cljsbuild quietly adds this extra path to your :externs listing without you knowing.



 Comments   
Comment by David Nolen [ 31/Jan/15 1:59 PM ]

You need to bind *out* to *err*, or just print to it directly a la cljs.util/debug-prn.

Comment by Crispin Wellington [ 31/Jan/15 7:30 PM ]

I did bind out to err. Check the patch.

Comment by David Nolen [ 01/Feb/15 12:30 PM ]

Crispin, oops sorry you are correct. Thanks.

Comment by David Nolen [ 13/Mar/15 7:33 AM ]

fixed https://github.com/clojure/clojurescript/commit/5f66a78bf469a9875e51aa39c29d3e66ce890eb4

Comment by David Nolen [ 14/Mar/15 5:55 AM ]

The solution does not work for cljsbuild. It's unclear why there so much machinery in place over the approach taken for deps.clj.

Comment by David Nolen [ 15/Mar/15 10:37 AM ]

Stalled on this cljsbuild issue https://github.com/emezeske/lein-cljsbuild/issues/383

Comment by Crispin Wellington [ 23/Mar/15 2:50 AM ]

This lein-cljsbuild issue is what made me make it just a warning initially, and not a hard error like raising IllegalArgumentException does. Though I agree it should be a hard error. If we start with a warning, it enables the immediate problem for the developer to be resolved, and leaves a wart that the cljs-build project can then see that need fixing on their end. Then when that end is fixed it could be made a hard error. If cljsbuild is fixed fairly soon then all is well, but if it takes a long time, a warning might be a good first step.





[CLJS-981] Better benchmarking infrastructure Created: 17/Jan/15  Updated: 17/Jan/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Task Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

We should use ProcessBuilder to run the various benchmark scripts and control which benchmarks we test and which engines we run. Benchmarks should produce EDN data that can be written to a file, loaded into Incanter, etc.






[CLJS-968] Metadata on function literal inside of a let produces invalid Javascript Created: 07/Jan/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Bobby Eickhoff Assignee: David Nolen
Resolution: Unresolved Votes: 1
Labels: bug
Environment:

Originally found with [org.clojure/clojurescript "0.0-2496"]
Still reproducible with the latest cljsc (b5e9a5116259fc9f201bee4b9c6564f35306f9a5)



 Description   

Here is a minimal test case that produces the invalid Javascript:

(defn f []
  (let [a 0]
    ^{"meta" "data"}
    (fn [] true)))

The compiled Javascript includes the invalid token sequence "return return". (Per Chrome: Uncaught SyntaxError: Unexpected token return)

The problem does not occur if the metadata applies to a map literal instead of a function literal.
The problem only occurs when the function and metadata are inside of a let.



 Comments   
Comment by Bobby Eickhoff [ 07/Jan/15 9:45 PM ]

I forgot to try with-meta. Using with-meta does not produce this syntax error, so it's only a problem with the reader macro for metadata.

Comment by David Nolen [ 08/Jan/15 7:41 AM ]

Any quick thoughts about this one Nicola? Quite possibly a compiler issue on the CLJS side.

Comment by Nicola Mometto [ 08/Jan/15 8:07 AM ]

David, I understand why this happens but I don't know enough about how cljs's js emission to propose a fix.
The issue is that with this commit: https://github.com/clojure/clojurescript/commit/d54defd32d6c5ffcf6b0698072184fe8ccecc93a the following scenario is possible:

{:op :meta
 :env {:context :return}
 :expr {:op :fn
        :env {:context :expr}
        :methods [{:op :fn-method 
                   :env {:context :return} ..}]
        ..}
 ..}

i.e. analyze-wrap-meta changes the context of the :fn node to :expr but keeps the context of the :fn-methods to :return.

This causes both
https://github.com/clojure/clojurescript/blob/master/src/clj/cljs/compiler.clj#L575-L576
and
https://github.com/clojure/clojurescript/blob/master/src/clj/cljs/compiler.clj#L488 (https://github.com/clojure/clojurescript/blob/master/src/clj/cljs/compiler.clj#L233)

to be true and emit a "return".

Comment by David Nolen [ 06/May/15 7:15 PM ]

Hrm, it appears analyze-wrap-meta may need to defer to a helper to change the :context of the given AST node.





[CLJS-957] Parallel compilation Created: 03/Jan/15  Updated: 02/Feb/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 3
Labels: None


 Description   

For non-interacting branches of the dependency graph we could compile in parallel.



 Comments   
Comment by Thomas Heller [ 03/Jan/15 3:06 PM ]

Just a heads-up: I attempted this in shadow-build.

The problem with this is macros, specifically their require. Granted you said non-interacting but suppose two namespaces require cljs.core.async.macros, they might trigger the clojure.core/require at the same time (parallel after all).

This resulted in a very weird exception: https://www.refheap.com/91731

I'm not sure how thread-safe clojure.core/require is supposed to be but it was as close as I was able to get tracking this weird error down. Removing reducers fixed everything. FWIW the gain was minimal to begin with.

Comment by David Nolen [ 03/Jan/15 3:09 PM ]

Making this really work I think largely depends on how much time you are willing to spend on the problem. Fork/join work stealing seems ideal for this not sure if you tried that. RE: macros, seems like you could use information collected from ns analysis to serialize calls to require.

Comment by Thomas Heller [ 03/Jan/15 3:28 PM ]

Lets throw some reducers at it was about as much thought I put into it.

Just beware of macros when you attempt to do something is all I wanted to say. Treating analysis seperate from cljs.compiler/emit might be a good idea though.

Comment by Dusan Maliarik [ 06/Jan/15 7:20 AM ]

I always thought parallel compilation belongs to the build tool (process), and should be parallelized per source file. Would there be any gain in using pmap in here ? It's used by lein-cljsbuild I suppose. Or parallelize this part?

Comment by David Nolen [ 06/Jan/15 7:54 AM ]

pmap won't work because of dependencies. A work queue approach is probably going to be most fruitful. Let's keep this thread clean unless you are actually going to work on this ticket and have a clear picture how to do it. Thanks.

Comment by John Chijioke [ 02/Feb/15 8:14 AM ]

David, Where exactly should the parallelization happen?





[CLJS-910] JavaScriptCore 0xbbadbeef EXC_BAD_ACCESS when evaluating (list 0 1 ... 18) Created: 16/Dec/14  Updated: 24/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Embedded JavaScriptCore on iOS simulator
Connected via Weasel / simple-brepl
:whitespace optimization


Attachments: PNG File memory.png     Text File stacktrace.txt    

 Description   

If I evaluate

(list 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)

JavaScriptCore exhibits a 0xbbadbeef EXC_BAD_ACCESS, with a fairly deep stacktrace:

(lldb) bt

  • thread #1: tid = 0x3f7e, 0x0111e583 JavaScriptCore`WTFCrash + 67, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0xbbadbeef)
    frame #0: 0x0111e583 JavaScriptCore`WTFCrash + 67
    frame #1: 0x011395a9 JavaScriptCore`WTF::fastMalloc(unsigned long) + 1929
    frame #2: 0x00c9cb56 JavaScriptCore`WTF::Vector<JSC::UnlinkedInstruction, 0ul, WTF::UnsafeVectorOverflow>::expandCapacity(unsigned long) + 86
    frame #3: 0x00c90f27 JavaScriptCore`JSC::BytecodeGenerator::emitGetById(JSC::RegisterID*, JSC::RegisterID*, JSC::Identifier const&) + 311
    frame #4: 0x00fd4617 JavaScriptCore`JSC::DotAccessorNode::emitBytecode(JSC::BytecodeGenerator&, JSC::RegisterID*) + 551
    ...

(Full stack trace attached as stacktrace.txt)

This only occurs with :whitespace optimization and does not under :advanced.

If you evaluate (list 0), it works, and so does (list 0 1), all the way up to 17. Interestingly, it gets progressively slower as you evaluate longer lists.



 Comments   
Comment by Mike Fikes [ 16/Dec/14 12:35 PM ]

While the EXC_BAD_ACCESS is arguably a bug in JavaScriptCore, it is likely provoked by excessive memory usage of the (list 0 1 ...) form. The attached memory.png shows what appears to be 2^n memory usage for evaluating a list of size n. This graph was produced while REPLd into an iOS device, monitoring memory use from Xcode.

Comment by Mike Fikes [ 18/Dec/14 11:28 AM ]

The construct

(list 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)

works in r2014 (released Nov 6, 2013), but fails in r2024 (released Nov 8, 2013).

In r2014, the emitted JavaScript is:

cljs.core.list.call(null, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)

while in r2024 the emitted JavaScript is:

cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null,
cljs.core.List.EMPTY, 18), 17), 16), 15), 14), 13), 12), 11), 10), 9), 8), 7), 6), 5), 4), 3), 2), 1), 0)

This particular commit, between r2014 and r2014 on Nov 7 2013, is likely when the emitted JavaScript changed (I haven't pulled down that specific revision):

https://github.com/clojure/clojurescript/commit/5bcbc4745f599e352c51e01b210755a88aa4bc5f#diff-b64165608bed8fb21a132890b4e2fca2R1279

Comment by Mike Fikes [ 18/Dec/14 12:07 PM ]

Knowing this, it is trivial to reproduce this in desktop Safari (and also see that it works fine in Chrome and Firefox).

If you go to clojurescript.net, or himera.herokuapp.com, and define a function that returns a list constructed with (list 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18), you can see that those websites are built using ClojureScript r2014 or earlier, as cljs.core.list.call(null, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18) appears in the reader literal for the returned function.

But, with either of those sites, if you evaluate the following, you will cause a severe performance problem in Safari:

(conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj '() 21) 20) 19) 18) 17) 16) 15) 14) 13) 12) 11) 10) 9) 8) 7) 6) 5) 4) 3) 2) 1) 0)

My hope is that knowing this will make it easier to profile (using desktop tools) what is giving Safari grief executing the resulting JavaScript.

Comment by Mike Fikes [ 18/Dec/14 2:38 PM ]

I don't understand JavaScriptCore's evaluation strategy. I found that if you manually revise the deeply nested composition of cljs.core._conj.call(...) invocations to extract a temporary var or two, as below, then the "doubling" effect is cut short, and the code executes quickly.

This revised code essentially builds a list of length 13 first, and then conses on 3 more elements, and 3 more.

var list0 = cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null,cljs.core.List.EMPTY, 18), 17), 16), 15), 14), 13), 12), 11), 10), 9), 8), 7), 6);

var list1 = cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, list0, 5), 4), 3);

cljs.core._conj.call(null, cljs.core._conj.call(null, cljs.core._conj.call(null, list1, 2), 1), 0)

I found that I can cause something similar to occur from the ClojureScript side by adding a do form, as in:

(conj (conj (conj (conj (conj (do nil
(conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj (conj '() 21) 20) 19) 18) 17) 16) 15) 14) 13) 12) 11) 10) 9) 8) 7) 6) 5)) 4) 3) 2) 1) 0)

Comment by Mike Fikes [ 18/Dec/14 7:00 PM ]

This JavaScriptCore perf problem is easily reproduced purely with JavaScript:

inc=function( x ) { return x + 1; }

alert(
inc.call(null, inc.call(null, inc.call(null, inc.call(null,
inc.call(null, inc.call(null, inc.call(null, inc.call(null,
inc.call(null, inc.call(null, inc.call(null, inc.call(null,
inc.call(null, inc.call(null, inc.call(null, inc.call(null,
inc.call(null, inc.call(null, inc.call(null, inc.call(null,
inc.call(null, 1))))))))))))))))))))))

Try this, at, say jsfiddle.net and you will see it max a core and use several GB in Safari, but not FireFox or Chrome.

Comment by Mike Fikes [ 19/Dec/14 9:00 AM ]

As indicated in this ticket's description, this problem doesn't occur with :advanced mode optimizations. Just to confirm, I produced the JavaScript with :pseudo-names set to true for (list 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18), and you can see that it doesn't use the problematic "call" construct:

$cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$_conj$$($cljs$core$List$EMPTY$$, 18), 17), 16), 15), 14), 13), 12), 11), 10), 9), 8), 7), 6), 5), 4), 3), 2), 1), 0);

Correction 20-DEC-2014: Even with :advanced mode optimization, this occurs. The setting that is needed to avoid the "call" construct is {:static-fns true}, as was set for the above output. With {:static-fnc false}, the emitted coded under :advanced mode is:

$cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$_conj$$.call(null, $cljs$core$List$EMPTY$$, 18), 17), 17), 15), 14), 13), 12), 11), 10), 9), 8), 7), 6), 5), 4), 3), 2), 1);
Comment by Mike Fikes [ 19/Dec/14 9:09 AM ]

Since the fundamental problem is easily reproducible with pure JavaScript, I've opened a StackOverflow regarding it: http://stackoverflow.com/questions/27568249/javascriptcore-deeply-composed-call-performance

Comment by David Nolen [ 19/Dec/14 12:08 PM ]

That was the first thing I was going to suggest trying, repo in plain JavaScript. Thanks for doing this - I believe this may explain some oddities we've encountered elsewhere.

Comment by Mike Fikes [ 19/Dec/14 1:41 PM ]

I’ve filed rdar://19310764 with Apple, and copied it here for reference: http://openradar.appspot.com/radar?id=5864025753649152

Comment by Francis Avila [ 19/Dec/14 5:38 PM ]

Excellent debugging work. Shouldn't this be filed against webkit instead? http://www.webkit.org/quality/reporting.html. (I already searched for this issue in their bug tracker and found nothing.)

This still leaves open the question of whether CLJS should do anything to mitigate. Many of the cljs macros that shadow functions expand recursively--maybe they should expand to loops or reduces instead?

For example:

(defmacro list
  ([] '(.-EMPTY cljs.core/List))
  ([& xs]
    `(let [a# (array ~@(reverse xs))]
       (areduce a# i# l# (list)
         (. l# cljs$core$ICollection$_conj$arity$2 (aget a# i#))))))

Or maybe cheat and emit a ChunkedCons instead?

(defmacro cclist
  ([] '(.-EMPTY cljs.core/LIST))
  ([& xs]
    `(cljs.core/ChunkedCons.
       (cljs.core/ArrayChunk.
         (array ~@(reverse xs)) 0 ~(count xs))
       nil nil nil)))
Comment by Mike Fikes [ 19/Dec/14 6:54 PM ]

Thanks Francis. I've confirmed that it occurs in the WebKit Nightly r177573, and I've moved the ticket to https://bugs.webkit.org/show_bug.cgi?id=139847

Comment by Thomas Heller [ 20/Dec/14 4:19 AM ]

FWIW to toggle between fn.call(null, ...) and fn(...) you can use the compiler option {:static-fns true}. This works with all optimization levels (also :none) but I'm told it causes issues with the REPL. But if you don't use a REPL it is safe to use that option always, I have been for years. Maybe the best short term option.

Comment by Mike Fikes [ 20/Dec/14 8:43 AM ]

Given Thomas's comment, I now realize that my comments above regarding :whitespace and :advanced are incorrect. (My project.clj has a dev and release build, and in addition to a change in the optimization directive, I had a change in the :static-fns directive.)

The correction: This problem occurs for both :whitespace and :advanced, iff :static-fns is set to false.

Comment by David Nolen [ 20/Dec/14 3:22 PM ]

One thing I'm curious about is whether the issue manifests if you're not nesting calls to the same function? This should be easy to confirm with plain JavaScript.

Comment by Mike Fikes [ 20/Dec/14 4:26 PM ]

Hey David, it appears to still cause a problem even with distinct functions. (This fiddle exhibits the problem with distinct functions, so don't follow the link on mobile Safari: http://jsfiddle.net/mfikes/Lwr78cmk/1/ )

Comment by Mike Fikes [ 21/Dec/14 4:16 PM ]

Activity is occurring with the WebKit ticket:

1) It has been confirmed as reproducible
2) It appears to be imported into Apple's system (InRadar keyword added).

Additionally, I succeeded in reproducing the issue on an older PowerPC Mac OS X 10.4.11 Safari 4.1.3 (from 2010), so it is not a recent regression, FWIW.

Comment by Mike Fikes [ 03/Jan/15 7:43 PM ]

Note: CLJS-945 sets :static-fns true as the default.

Comment by David Nolen [ 03/Jan/15 7:49 PM ]

Mike this is true only for cljs.core.

Comment by Mike Fikes [ 06/Jan/15 1:42 PM ]

FWIW, the original ticket I filed with Apple (rdar://19310764) has subsequently been closed by Apple as a duplicate of rdar://19321122, which is the Apple ticket the WebKit team opened in association with https://bugs.webkit.org/show_bug.cgi?id=139847

Comment by Timothy Pratley [ 24/Jul/15 8:38 AM ]

Just for reference: https://github.com/google/closure-compiler/issues/1049





[CLJS-901] Protocolize compiler access to the file system Created: 03/Dec/14  Updated: 04/Feb/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Task Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None


 Description   

Currently builds are based on files on disk. It is desirable to be able to instead get in memory builds, WebDAV based builds, S3 based builds, etc. Many of these alternative strategies are not in scope for the ClojureScript compiler but this does not mean we should not supply the needed hooks for users to control the behavior.



 Comments   
Comment by Thomas Heller [ 03/Dec/14 9:31 AM ]

This and some other issues opened recently (CLJS-900, CLJS-851, CLJS-899, ...) have some overlap with what I have done in shadow-build [1]. Memory based builds are actually already possible cause it will only touch the disk when asked to, although the API could use some cleanup.

Anyways, might be worthwhile to coordinate these efforts to make CLJS more accessible for everyone.

[1] https://github.com/thheller/shadow-build

Comment by Alan Dipert [ 04/Feb/15 11:36 AM ]

I too think it would be totally awesome to have builds based on sources from disparate places.

One alternative in this spirit I have been thinking about is a "SourceSet" approach. The idea is, instead of teaching CLJS how to consume various place-types directly via protocols, provide an API for building a "SourceSet" value and also a build function that takes the SourceSet as input. I imagine the SourceSet in its simplest form as a map of namespaces to string sources.

With a value to represent sources that is place-agnostic and immutable, 3rd party tools can consume/emit/transform these values before invoking a compile without knowledge or interest in CLJS internals. Conversely CLJS need not be concerned with how SourceSets are constructed.

This whole idea is inspired by boot's FileSets, which work basically the same but can't have the "it fits in memory" assumption.





[CLJS-871] .-default property access returns nil Created: 11/Oct/14  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Joel Holdbrooks Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None

Attachments: Text File 871.patch     Text File 871.patch    
Patch: Code and Test

 Description   

Types defined with deftype/defrecord which have a default field will incorrectly return nil with property access. The following example will return nil.

(deftype Foo [default])

(let [foo (Foo. "bar")]
  (.-default foo))


 Comments   
Comment by Joel Holdbrooks [ 13/Oct/14 4:19 PM ]

Patch attached. I should point out that I had to borrow js-reserved from the compiler namespace and the warning message provided hard codes the munged symbol information instead of reusing the compiler's munge fn.

Comment by Joel Holdbrooks [ 13/Oct/14 9:41 PM ]

For the sake of history, I should provide more context to this patch (I'm unable to edit the issue title for some reason). It isn't just .-default it is any field name that is also a JavaScript identifier (eg. public, private, if).

Comment by David Nolen [ 14/Oct/14 5:26 AM ]

Please lift js-reserved and any helpers like munge into the shared namespace cljs.util so that logic an be shared and hard coding avoided. Thanks.

Comment by Joel Holdbrooks [ 14/Oct/14 5:03 PM ]

Are you sure, David? That might make this patch a bit more noisy. If it's not a problem I'm happy to do it.

Comment by David Nolen [ 14/Oct/14 6:06 PM ]

I'm sure, I'd like to avoid this kind of code duping. Cleaner in the end and better moving forward.

Comment by Joel Holdbrooks [ 18/Mar/15 11:43 AM ]

Updated to use new refactorings

Comment by David Nolen [ 18/Mar/15 11:46 AM ]

The warning is not desirable. Instead we should just munge and ensure property access always works.





[CLJS-868] no arity warnings on recursive calls Created: 03/Oct/14  Updated: 10/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

If a function recursively invokes itself within its own body the invoke will not be checked for arity mismatch.



 Comments   
Comment by Samuel Miller [ 10/Aug/15 10:06 PM ]

Took some time to look at this issue. Originally thought "Do what loop/recur does" but that does not take into account multi-arity. It seems like maybe the best option is to somehow use the second pass of the analyze(analyze-fn-methods-pass2). The entire information about the function is present and the warning section of the code gets triggered but because of no-warn is ignored. Any other ideas for a solution to this?





[CLJS-860] redundant analysis of source files in JARs Created: 18/Sep/14  Updated: 18/Sep/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Source files in JARs are analyzed once then analyzed again when copied to the output directory.






[CLJS-818] Externs don't get loaded when running under immutant as cljs.js-deps/find-js-classpath fails Created: 18/Jun/14  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: James Cash Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Java 1.8.0_05, Clojure 1.6.0, clojurescript 0.0-2234, immutant 1.1.1


Attachments: Text File 818.patch     PNG File Screen Shot 2014-06-18 at 20.14.59 .PNG    

 Description   

When compiling clojurescript that relies on library-provided externs (e.g. Om needing React.js externs), the clojurescript is compiled without errors, but the generated javascript fails to work, due to the externs not being loaded. Externs don't get loaded, as cljs.js-deps/find-js-classpath doesn't find the javascript externs file. This occurs because it uses cljs.js-deps/all-classpath-urls, which filters out the immutant classloader, since org.immutant.core.ImmutantClassLoader is not an instance of java.net.URLClassLoader (and hence lacks a .getURLs method anyway).



 Comments   
Comment by Toby Crawley [ 19/Jun/14 9:23 AM ]

Chas: Is there a reason not to depend on dynapath here? This exact case is kinda why it exists

Comment by David Nolen [ 19/Jun/14 10:47 AM ]

Patch welcome for this.

Comment by James Cash [ 19/Jun/14 2:12 PM ]

Simply replacing cljs.js-deps/all-classpath-urls with dynapath.util/all-classpath-urls worked for me. I don't know if there are policies around adding dependencies to cljs, but the following patch is working for me. Would it be preferable to re-implement the functionality instead?

Comment by David Nolen [ 19/Jun/14 2:19 PM ]

We are not going to take on a dependency for this. The code should be copied over, thanks.

Comment by James Cash [ 19/Jun/14 3:46 PM ]

Due to the way dynapath works, I don't think a straightforward copying of the code will work, since it relies on a protocol. Backing up a step though, would it be reasonable for externs to be loaded via io/resource, in the same way that the :preamble is?

Comment by Toby Crawley [ 19/Jun/14 3:54 PM ]

Unfortunately, the code can't be copied over. Dynapath works by providing a protocol that providers/users of funky classloaders can implement, allowing libraries that use dynapath to access the dynamic features of those classloaders without having to care about the loader's concrete type. Dynapath itself provides implementations for j.n.URLClassLoader and c.l.DynamicClassloader by default, so libraries don't have to do anything special to access the dynamic features of those classes.

java.classpath also provides a similar mechanism that the Immutant classloader implements as well. If you are more open to dependencies that are under org.clojure, using that will work as well. Ideally, I'd like to see java.classpath subsume dynapath.

Comment by James Cash [ 19/Jun/14 4:23 PM ]

Made a new patch that sidesteps the all-classpath-urls issue by just using io/resource instead of iterating over all urls

Comment by David Nolen [ 01/Jul/14 9:26 PM ]

Can people chime in whether the patch works for them, thanks.





[CLJS-773] Use unchecked-*-int functions for real 32-bit math Created: 26/Feb/14  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Major
Reporter: Francis Avila Assignee: Francis Avila
Resolution: Unresolved Votes: 0
Labels: numerics
Environment:

r2173



 Description   

Currently the unchecked-* functions and macros simply alias the primitive js operators. It would be nice if the unchecked-*-int family of functions and macros implemented C/Java-like signed int operations with silent overflows (just like in Clojure) using asm.js coersion idioms. This should also allow us to share such code between clojure and clojurescript without worrying about their different numerics.

A use case is that porting hash algorithms from java to clojurescript is trickier and more verbose than it needs to be.



 Comments   
Comment by David Nolen [ 08/May/14 6:43 PM ]

This sounds interesting, would like to see more thoughts on approach, benchmarks etc.

Comment by David Nolen [ 02/Dec/14 5:46 AM ]

Bump, this enhancements sound simple & fine.

Comment by Francis Avila [ 02/Dec/14 1:26 PM ]

I'll have time to do this in about a week. The implementation is straightforward (basically use xor 0 everywhere). The goal is correctness, but I expect performance to be as good as or better than it is now on most platforms. I'm not sure if advanced mode will drop intermediate truncations or what impact this has on performance.

Some higher-level numeric analysis using the asm.js type system is possible but I doubt it's worth it.

Comment by Francis Avila [ 16/Mar/15 11:14 AM ]

I completely forgot about this, sorry. I see you have scheduled it for the "next" release. Are you assigning it as well or will you still accept a patch?

Comment by David Nolen [ 16/Mar/15 11:26 AM ]

Be my guest





[CLJS-719] this-as behaves incorrectly in "scoping function" Created: 07/Dec/13  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: Kevin Marolt Assignee: David Nolen
Resolution: Unresolved Votes: 1
Labels: None


 Description   

When a this-as expression gets put in a "scoping function", e.g. in a let-binding, the value bound via this-as refers to the scoping function, and not to the outer scope.

Example:

(def foo
  (js-obj
    "bar" "baz"
    "getBarRight" (fn [] (this-as self (.-bar self)))
    "getBarWrong" (fn []
                    (let [bar (this-as self (.-bar self))]
                      bar))))
     
(.log js/console (.getBarRight foo)) ;; => "baz"
(.log js/console (.getBarWrong foo)) ;; => undefined

Whereas foo.getBarRight expands to something like

function() {
  var self = this; // this refers to foo
  return self.bar; // returns "bar"
}

foo.getBarWrong on the other hand expands to

function() {
  var bar = function() {
    var self = this; // this refers to enclosing function
    return self.bar; // returns undefined
  }();
  return bar; // returns undefined
}





[CLJS-713] optimized case Created: 04/Dec/13  Updated: 23/Jun/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File 0001-CLJS-713-Allow-test-expressions-for-case-to-be-chars.patch     Text File 0001-CLJS-713-first-cut-at-compiling-case-to-switch.patch    

 Description   

With the advent of asm.js many engines will like compile switch statements over integers into jump tables. We should provide a real `case*` ast node that compiles to JS `switch` when possible - i.e. numbers, strings, keywords etc.



 Comments   
Comment by Michał Marczyk [ 18/Feb/14 5:56 PM ]

First cut impl also available here:

https://github.com/michalmarczyk/clojurescript/tree/713-compile-case-to-switch

With this patch applied, case expressions are compiled to switch + some extra bits when all tests are numbers or strings, otherwise old logic is used.

For example, {{(fn [] (let [x 1] (case x 1 :foo (2 3) :bar :quux)))}} gets compiled to

function () {
    var x = 1;
    var G__6469 = x;
    var caseval__6470;
    switch (G__6469) {
      case 1:
        caseval__6470 = new cljs.core.Keyword(null, "foo", "foo", 1014005816);
        break;
      case 2:
      case 3:
        caseval__6470 = new cljs.core.Keyword(null, "bar", "bar", 1014001541);
        break;
      default:
        caseval__6470 = new cljs.core.Keyword(null, "quux", "quux", 1017386809);
    }
    return caseval__6470;
}

The existing test suite passes, but I suppose it wouldn't hurt to add some tests with case in all possible contexts.

Comment by Michał Marczyk [ 18/Feb/14 6:05 PM ]

As a next step, I was planning to arrange things so that numbers/strings are fished out from among the tests and compiled to switch always, with any leftover tests put in an old-style nested-ifs-based case under default:. Does this sound good?

It seems to me that to deal with symbols and keywords in a similar manner we'd have to do one of two things:

1. check symbol? and keyword? at the top, then compile separate switches (the one for keywords would extract the name from the given keyword and use strings in the switch);

2. use hashes for dispatch.

Which one sounds better? Or is there a third way?

Comment by Michał Marczyk [ 18/Feb/14 6:11 PM ]

Of course we'd need to compute hashes statically to go with 2. I'd kind of like it if it were impossible (randomized seed / universal hashing), but currently it isn't.

Comment by Francis Avila [ 19/Feb/14 12:22 AM ]

At least on v8, there are surprisingly few cases where a switch statement will be optimized to a jump table. Basically the type of the switched-over value must always (across calls) match the type of every case, and there must be fewer than 128 cases, and integer cases must be 31-bit ints (v8's smi type). So mixing string and number cases in the same switch guarantees the statement will never be compiled. In many cases an equivalent if-else will end up being significantly faster on v8 just because the optimizing jit recognizes them better. There's an oldish bug filed against v8 switch performance. Looking at the many jsperfs of switch statements, it doesn't seem that v8 has improved. Relevant jsperf

Firefox is much better at optimizing switch statements (maybe because of their asm.js/emscripten work) but I don't know what conditions trigger (de)optimization.

I suspect the best approach is probably going to be your option one: if-else dispatch on type if any case is not a number, and then a switch statement covering the values for each of the keyword/string/symbol types present (no nested switch statements, and outlining the nested switches might be necessary). Even with a good hash, to guarantee v8 optimizing-compilation you would need to truncate the hashes into an smi (signed-left-shift once?) inside the case*.

Comment by David Nolen [ 19/Feb/14 12:50 AM ]

There's no need for invention here. We should follow the strategy that Clojure adopts - compile time hash calculation.

Comment by Francis Avila [ 19/Feb/14 3:09 PM ]

The problem, as Michal alluded to, is that the hash functions in cljs's runtime environment are not available at compile-time (unlike in Clojure). This might be a good opportunity to clean up that situation or even use identical hash values across Clojure and Clojurescript (i.e. CLJS-754), but that's a much bigger project. Especially considering it will probably not bring much of a speedup over an if-else-if implementation except in very narrow circumstances.

Comment by David Nolen [ 19/Feb/14 4:38 PM ]

Francis Avila I would make no such assumptions about performance without benchmarks. One of the critical uses for case is over keywords. Keyword hashes are computed at compile time, so that's one function call and a jump on some JavaScript engines. This is particularly useful for the performance of records where you want to lookup a field via keyword before checking the extension map.

This ticket should probably wait for CLJS-754 before proceeding.

Comment by Francis Avila [ 22/Feb/14 4:44 AM ]

Record field lookup is a good narrow use case to test. I put together a jsperf to compare if-else (current) vs switch with string cases vs switch with int cases (i.e., hash-compares, assuming perfect hashing).

Comment by David Nolen [ 10/May/14 3:59 PM ]

I've merged the case* analyzer and emitter bits by hand into master.

Comment by David Nolen [ 10/May/14 4:42 PM ]

I'v merged the rest of the patch into master. If any further optimizations are done it will be around dispatching on hash code a la Clojure.

Comment by Francis Avila [ 11/May/14 12:53 AM ]

Your keyword-test optimization has a bug: https://github.com/clojure/clojurescript/commit/9872788b3caa86f639633ff14dc0db49f16d3e2a

Test case:

(let [x "a"] (case x :a 1 "a"))
;=> 1
;;; Should be "a"

Github comment suggests two possible fixes.

Comment by David Nolen [ 11/May/14 10:50 AM ]

Thanks fix in master.

Comment by Christoffer Sawicki [ 23/Jun/14 3:41 PM ]

case over "chars" is currently not being optimized to switch (in other words: (case c (\a) :a :other) uses if instead of switch).

Given that ClojureScript chars are just strings of length 1, could this perhaps simply be fixed by tweaking https://github.com/clojure/clojurescript/blob/master/src/clj/cljs/core.clj#L1187 ?

Comment by Christoffer Sawicki [ 23/Jun/14 4:11 PM ]

OK, I couldn't resist trying and it seems to be that easy. Would be great if somebody more knowledgeable could look at it and say if it has any side-effects. (See patch with name 0001-CLJS-713-Allow-test-expressions-for-case-to-be-chars.patch.)

Comment by David Nolen [ 23/Jun/14 4:15 PM ]

The patch looks good I would have applied it if I hadn't already gone and done it master myself just now

Comment by Christoffer Sawicki [ 23/Jun/14 4:22 PM ]

Hehe. Thanks! Don't forget to update the "case* tests must be numbers or strings" message on line 496 too.

Comment by David Nolen [ 23/Jun/14 4:48 PM ]

The existing docstring is inaccurate - case supports all compile time literals.





[CLJS-712] resolve-var for symbol with dot still wrong Created: 03/Dec/13  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

We need to recur on the first segment passing an new additional argument to resolve-var indicating that we should not try to resolve in the current namespace and instead warn.






[CLJS-705] locals clearing Created: 27/Nov/13  Updated: 29/Nov/13

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Without locals clearing ClojureScript is likely to suffer from the same cases as Clojure did for common uses of lazy sequences.



 Comments   
Comment by David Nolen [ 29/Nov/13 3:03 PM ]

For this we'll need to introduce some special private way to set a local to nil, i.e. (_clear_local sym)





[CLJS-575] cljsc.bat emit FileNotFoundException when compile samples in windows Created: 25/Aug/13  Updated: 19/Jun/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: Park Sang Kyu Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: Compiler, bug, patch
Environment:

in windows 7


Attachments: File cljsc.bat.diff     File cljsc-path.bat    
Patch: Code

 Description   

cljsc.bat emit FileNotFoundException when it compile samples of the ClojureScript project in windows like below.

------------------------------------------------
Exception in thread "main" java.io.FileNotFoundException: Could not locate cljs/closure__init.class
or cljs/closure.clj on classpath:
------------------------------------------------

It is caused by lack of a backslash in the end of path of the system environment variable, %CLOJURESCRIPT_HOME% set by a user.
In the case CLASSPATH is set to "C:\\clojure\clojurescriptsrc\clj;C:\\clojure\clojurescriptsrc\cljs" and this make it impossible for javac to find cljs/clojure.clj file.

So it can be solved by adding a backslash to the path of %CLOJURESCRIPT_HOME%.

I attached the patched file, "cljsc-path.bat"



 Comments   
Comment by David Nolen [ 04/Sep/13 11:04 PM ]

Can we please get a proper git diff thanks (and please send in your CA)! Also would be nice to get Windows users to check this out.

Comment by Park Sang Kyu [ 15/Sep/13 3:16 AM ]

git diff

Comment by David Nolen [ 05/Oct/13 11:55 AM ]

Thank you! Have you sent in your CA? http://clojure.org/contributing

Comment by Park Sang Kyu [ 19/Jun/14 10:24 AM ]

Yes i have sent my CA.

Comment by David Nolen [ 19/Jun/14 10:27 AM ]

Excellent, the patch is not correctly formatted. Can we get a new patch that conforms to http://github.com/clojure/clojurescript/wiki/Patches





[CLJS-525] Allow hashtable lookup used for numbers and strings to be extended to other built-in types Created: 17/Jun/13  Updated: 17/Jun/13

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: Chas Emerick Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

...which would enable safe extension of key cljs protocols to types without modifying their prototypes, e.g. CLJS-523.



 Comments   
Comment by David Nolen [ 17/Jun/13 2:56 PM ]

Date is the only JS native case that I'm aware of that we don't handle. One tricky bit is that goog.typeOf won't give us the information we need, but I think instanceof should cover us here?

Comment by Fogus [ 17/Jun/13 3:05 PM ]

instanceof or the ever-gruesome toString.call(aDate) == '[object Date]' will work.





[CLJS-497] Constant literal optimization Created: 25/Apr/13  Updated: 27/Aug/13

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

We should optimize constant literals, in particular keywords. This optimization means that we will have to decide whether to make identical? slower by testing for keywords (this means it's probably a bad idea to continue to inline it) or to provide a special keyword-identical? that does the right thing.



 Comments   
Comment by Sean Grove [ 26/Aug/13 11:03 PM ]

This is related to the reified keywords in cljs, see http://dev.clojure.org/jira/browse/CLJS-576

Comment by Sean Grove [ 27/Aug/13 4:24 PM ]

There's another interesting twist while using piggieback + brepl that relates to a missing constants_table.js. Not sure what causes it (haven't found a way to repro), but only happens in a few circumstances, so the repl still mainly works.

The runtime part continues to works fine however.





[CLJS-404] Automate Browser REPL testing Created: 23/Oct/12  Updated: 26/Dec/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Task Priority: Major
Reporter: David Nolen Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: None


 Description   

It's worth investigating Selenium, PhantomJS, etc. as solutions to sanity check the Browser REPL when we run the other tests.



 Comments   
Comment by Robert Krahn [ 22/Dec/14 1:22 PM ]

An attempt: https://github.com/clojure/clojurescript/pull/42

Comment by David Nolen [ 24/Dec/14 8:57 AM ]

This looks like an interesting patch, thanks!

Comment by Robert Krahn [ 26/Dec/14 10:57 AM ]

I'll post a patch here, first I'll investigate the load-file issue, though.





[CLJS-374] satisfies? produces strange code when the protocol is not in the fast-path list Created: 06/Sep/12  Updated: 19/Nov/13

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: None





[CLJS-365] apply needs to put all args after the 20th into an array seq Created: 29/Aug/12  Updated: 29/Aug/12

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

This ticket is related to CLJS-359






[CLJS-364] compiler needs to put all args of an invocation after 20 into an array-seq Created: 29/Aug/12  Updated: 29/Aug/12

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Major
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

This ticket is related to CLJS-359






[CLJS-1428] Add a cljs.core/*command-line-args* var Created: 16/Aug/15  Updated: 01/Sep/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None


 Description   

Add a cljs.core/*command-line-args* var that mimics Clojure's:

https://github.com/clojure/clojure/blob/4bb1dbd596f032621c00a670b1609a94acfcfcab/src/clj/clojure/core.clj#L6148

Rationale:
1) Simplifies writing command-line scripts in ClojureScript if this var is universally available.
2) Consistency with Clojure.

Existing tooling can ignore the var (it would be initialized with nil).

Command-line REPLs or other command-line environments can bind a sequence to this var when launched.



 Comments   
Comment by Justin Thomas [ 31/Aug/15 10:14 PM ]

In this file: https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/nodejs.cljs Looks like you could set this: (def *command-line-args* (.-argv process)) and it'd be accessible under nodejs/*command-line-args*. Not sure where the star vars are set in the elsewhere in the code or if that's a good solution putting it in each repo.

Maybe get the target var somehow and set it based on that--similar to how the nodejs file is loaded?

Found this: src/main/clojure/cljs/core.cljc and saw references to a ns called env.

Comment by David Nolen [ 01/Sep/15 6:44 AM ]

This ticket needs more rationale and stronger outline of the design issues before proceeding. As it stands no one should be working on this yet.





[CLJS-1424] (def) or (defn) will throw an obscure exception Created: 14/Aug/15  Updated: 14/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Sean Grove Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: cljs


 Description   

Simply trying to compile `(def)` will throw an exception: "Caused by: clojure.lang.ArityException: Wrong number of args (1) passed to: analyzer/eval1440/fn-1441/pfn-1442"

While obviously not a valid construct, the error could be significantly more descriptive, and also show the line number where it happened.

A similar issue happens with `(defn)`: "Caused by: clojure.lang.ExceptionInfo: Wrong number of args (2) passed to: core/defn--12230"






[CLJS-1421] Enable Asynchronous cljs.js/*eval-fn* Created: 14/Aug/15  Updated: 16/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Matthew Molloy Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: asynchronous, bootstrap


 Description   

In bootstrapped ClojureScript cljs.js/eval-fn receives javascript source and evaluates it, returning a result. In some contexts it is necessary to evaluate the js asynchronously, can we add this functionality?



 Comments   
Comment by David Nolen [ 14/Aug/15 7:49 PM ]

This ticket needs more rationale. Can you elaborate on the usecase?

Comment by Matthew Molloy [ 14/Aug/15 10:08 PM ]

My usecase is an asynchronous eval function

(fn *eval-fn*
  [{:keys [source]}]
  (js/chrome.devtools.inspectedWindow.eval source
    (fn [result err]
      (if result
        (callback result)
        (callback err))))

There must be other people who have situations like this.

Comment by David Nolen [ 16/Aug/15 12:16 AM ]

Interesting. I don't think this is a common use case, most JS engines provide synchronous eval. Not interested in any breaking changes but would be happy to take a patch that gives you the behavior you want via an option flag, :async-eval.





[CLJS-1420] get-in behavior differs from Clojure by always deferring to the 3 arity fn Created: 12/Aug/15  Updated: 14/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Kevin Webster Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File cljs_1420.patch    

 Description   

CLJS-464 should be reverted since the commit changes how get-in is handled by types that implement ILookup.

Since get-in always defers to the 3 arity function, the 2 arity ILookup -lookup fn never gets called. This behavior is inconsistent with how Clojure behaves.

clj:

(get-in
  (reify
    clojure.lang.ILookup
    (valAt [o k] :2-arity)
    (valAt [o k not-found] :3-arity))
  [:foo])
=> :2-arity

cljs:

(get-in
  (reify
    ILookup
    (-lookup [o k] :2-arity)
    (-lookup [o k not-found] :3-arity))
  [:foo])
=> :3-arity


 Comments   
Comment by Kevin Webster [ 12/Aug/15 3:30 PM ]

Tested patch in both V8 & Nashorn

Comment by David Nolen [ 14/Aug/15 7:51 PM ]

Please modify the patch to include a test case. The patch should be squashed into a single commit.





[CLJS-1419] enhance numeric inference, if + number? test on local var should tag local var in the successful branch Created: 12/Aug/15  Updated: 12/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Comments   
Comment by David Nolen [ 12/Aug/15 6:44 AM ]

One small complication is dealing with and as it has an optimizing case.





[CLJS-1411] make-array signature differs from clojure Created: 10/Aug/15  Updated: 10/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Lars Andersen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: newbie


 Description   

When I was porting clj code using a two-dimensional array I noticed the cljs version only accepts size, instead of dim & more-dims.

Signature in cljs: ([size] [type size])
signature int clj: ([type len] [type dim & more-dims])



 Comments   
Comment by David Nolen [ 10/Aug/15 7:28 AM ]

Unlike on the JVM there's no efficient way to allocate multi-dimensional arrays. An enhancement patch that includes a modification to the docstring that warns about the computational cost is welcome.

Comment by Lars Andersen [ 10/Aug/15 8:00 AM ]

What I want is for the signatures to be the same, so I don't have to use reader conditionals to get the same behavior on the two platforms.

When I'm asking for a two-dimensional array, I'm presumably going to do work that is O(m*n), so even though the pre-allocation is O(m*n) too it won't affect the runtime of the program significantly.

Comment by David Nolen [ 10/Aug/15 8:13 AM ]

Lars, yes I understood As I said patch welcome. This is a very ClojureScript newbie friendly ticket.





[CLJS-1406] macroexpand munging goog.string.StringBuffer Created: 09/Aug/15  Updated: 14/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Matthew Molloy Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: bootstrap
Environment:

Bootstrapped Planck REPL


Attachments: Text File cljs_1406.patch     Text File cljs_1406_test.patch    

 Description   

(macroexpand '(with-out-str)) on bootstrapped ClojureScript gives (let* [sb__13582__auto__ (string.StringBuffer.)]...
on JVM compiled ClojureScript it is (let* [sb__1155__auto__ (goog.string.StringBuffer.)]...

goog.string.StringBuffer has been incorrectly munged to string.StringBuffer when the cljs.js compiler is used.



 Comments   
Comment by Joel Martin [ 13/Aug/15 7:28 PM ]

I ran into this today. Here is a test case for the self host tests that reproduces the problem.

Comment by Joel Martin [ 13/Aug/15 7:50 PM ]

Fix the test. Propose a fix that just puts "js/" in front to prevent the broken name resolution. Probably not the correct solution, but it does work.

Comment by David Nolen [ 14/Aug/15 7:50 PM ]

Not going to take the current patches. The underlying resolution issue needs to be fixed.





[CLJS-1390] clojure.walk treats vectors diffently from Clojure version Created: 03/Aug/15  Updated: 03/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.28
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Leon Grapenthin Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

The latest patch to clojure.walk (https://github.com/clojure/clojurescript/commit/f706fabfd5f952c4dfb4dc2caeea92f9e00d8287) ports the line of the Clojure version

(instance? clojure.lang.IMapEntry form) (outer (vec (map inner form)))

with the line

(satisfies? IMapEntry form) (outer (vec (map inner form)))

ClojureScript implements IMapEntry on any vector which I assume is intended.

In Clojure, for vectors this case falls:

(coll? form) (outer (into (empty form) (map inner form)))

This makes a difference because empty preserves metadata.
I. e.

(meta (prewalk (fn [form]
                  (vary-meta form assoc :foo true))
               []))

gives {:foo true} on earlier ClojureScript versions and Clojure, but nil on the latest version.

I have relied on this which has likely not been a very good idea, but others might have too - Hence I created this ticket for consideration.






[CLJS-1327] Support Transit JSON for analysis caches Created: 03/Jul/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Task Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

A significant amount of time is spent reading caches both during cold builds and REPL start. Switching to Transit for the analysis cache format should deliver a very large performance boost. Having a direct dep on Transit is undesirable since it is a critical part of existing applications. Instead we could allow users to provide analysis cache encoder/decoder functions to the compiler options. This does mean we cannot use the AoTed analysis for cljs.core if these are provided.






[CLJS-1320] clojure.string/split adds separator matches & failed matches (nil) when the separator is a regex with alternation Created: 26/Jun/15  Updated: 27/Jun/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3308
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: lvh Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

I want to split a string on "; ", and optionally discard a final ";". So, I tried:

(clojure.string/split "ab; ab;" #"(; )|(;$)")

In Clojure, this does what I want:

["ab" "ab"]

In ClojureScript, I get:

["ab" "; " nil "ab" nil ";"]

I'm not sure to what extent this is a platform distinction and to what extent it's a bug. Returning nils and seperators from clojure.string/split's output seems like it's against string.split's contract?






[CLJS-1294] Macroexpand only accept quoted lists Created: 01/Jun/15  Updated: 14/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Julien Eluard Assignee: Julien Eluard
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File CLJS-1294.patch    

 Description   

In Clojure macroexpand and macroexpand-1 accept any quoted argument while in ClojureScript anything but quoted seq will throw an exception.



 Comments   
Comment by Julien Eluard [ 01/Jun/15 2:16 PM ]

In Clojure some special forms are handled specifically i.e. (macroexpand '(Boolean true)) => (new Boolean true).

I am not sure if/how it applies to ClojureScript.

Comment by David Nolen [ 14/Jul/15 5:58 AM ]

This patch needs to be rebased to master. Thanks!





[CLJS-1286] REPL environment should be able to provide advice if source mapping fails Created: 23/May/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

For example browser REPL will often need users to supply :host-port, :host, and :asset-path in order to correctly parse files from stacktraces.






[CLJS-1278] Asserts still fail while :require-ing .js file (either in :libs or in :source-paths) (same as CLJS-1196) Created: 20/May/15  Updated: 14/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3269
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Michal Till Assignee: David Nolen
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File cljs_1278.patch    

 Description   

Following on CLJS-1196, I can't get it to work.

In version 0.0-3264 lein-cljsbuild crashed on weird eception `Caused by: java.lang.IllegalArgumentException: No implementation of method: :make-reader of protocol: #'clojure.java.io/IOFactory found for class: nil"` but the current version 0.0-3269 gives the same failed assertion as previously.

I've put up a sample project to illustrate the issue.

Steps to reproduce:

`git clone https://github.com/tillda/stackone`
`cd stackone`
`git checkout 537e5c69b844bc53c159e85cafc24310543cc918`
`lein clean && lein cljsbuild once temp`

Expected behaviour: cljs compiled successfully with src/vendor/client/closure.js and env/stackone/helpersjs.js being included.

Actual behaviour:

```
Compiling "resources/public/lein-cljsbuild-temp/dev-mode-deps.js" failed.
Exception in thread "main" java.lang.AssertionError: Assert failed: (or (file? x) (url? x) (string? x)), compiling/private/var/folders/ym/l2qxd7l97kzfzftrdpqsclm40000gn/T/form-init3642888309490821030.clj:1:125)
at clojure.lang.Compiler.load(Compiler.java:7249)
at clojure.lang.Compiler.loadFile(Compiler.java:7175)
at clojure.main$load_script.invoke(main.clj:275)
at clojure.main$init_opt.invoke(main.clj:280)
at clojure.main$initialize.invoke(main.clj:308)
at clojure.main$null_opt.invoke(main.clj:343)
at clojure.main$main.doInvoke(main.clj:421)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
Caused by: java.lang.AssertionError: Assert failed: (or (file? x) (url? x) (string? x))
at cljs.util$ext.invoke(util.cljc:115)
at cljs.closure$source_on_disk.invoke(closure.clj:1206)
at cljs.closure$output_unoptimized$fn__3708.invoke(closure.clj:1235)
at clojure.core$map$fn__4551.invoke(core.clj:2622)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:507)
at clojure.core$seq__4126.invoke(core.clj:135)
at clojure.core$filter$fn__4578.invoke(core.clj:2677)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:507)
at clojure.core$seq__4126.invoke(core.clj:135)
at clojure.core$map$fn__4551.invoke(core.clj:2614)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.Cons.next(Cons.java:39)
at clojure.lang.RT.next(RT.java:674)
at clojure.core$next__4110.invoke(core.clj:64)
at clojure.core$str$fn__4186.invoke(core.clj:528)
at clojure.core$str.doInvoke(core.clj:526)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:628)
at cljs.closure$deps_file.invoke(closure.clj:1040)
at cljs.closure$output_deps_file.invoke(closure.clj:1060)
at cljs.closure$output_unoptimized.doInvoke(closure.clj:1243)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:630)
at cljs.closure$build.invoke(closure.clj:1514)
at cljs.closure$build.invoke(closure.clj:1426)
at cljsbuild.compiler$compile_cljs$fn__3884.invoke(compiler.clj:81)
at cljsbuild.compiler$compile_cljs.invoke(compiler.clj:80)
at cljsbuild.compiler$run_compiler.invoke(compiler.clj:187)
at user$eval4018$iter_40544058$fn4059$fn_4077.invoke(form-init3642888309490821030.clj:1)
at user$eval4018$iter_40544058$fn_4059.invoke(form-init3642888309490821030.clj:1)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:507)
at clojure.core$seq__4126.invoke(core.clj:135)
at clojure.core$dorun.invoke(core.clj:3007)
at clojure.core$doall.invoke(core.clj:3023)
at user$eval4018.invoke(form-init3642888309490821030.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6792)
at clojure.lang.Compiler.eval(Compiler.java:6782)
at clojure.lang.Compiler.load(Compiler.java:7237)
... 11 more
Subprocess failed
```



 Comments   
Comment by David Nolen [ 20/May/15 10:21 AM ]

This issue is in danger of being closed. Please supply minimal steps to reproduce that do not involve anything other than the ClojureScript compiler. We no longer have time to wade through the indirection introduced by cljsbuild or any other downstream tooling. Thanks.

Comment by Michal Till [ 20/May/15 11:14 AM ]

@David Nolen: I have created a failing minimal testcase based on the Quick Start document. Here it is: https://github.com/tillda/cljs-testcase/

Comment by David Nolen [ 20/May/15 11:27 AM ]

Michal the failing example is not correct. You are not supplying any :libs option.

Comment by Michal Till [ 20/May/15 11:45 AM ]

Ah! Thank you very much! This additional issue was therefore my error. Now it seems to work even in my "big" example.

However it would be cool if there was a meaningful error message stating that a file path can't be resolved. If one is not an expert in the cljs compiler this is almost impossible to figure out. After all the error message in the CLJS-1196 issue and in this wrongfully reported one are exactly the same.

You may close this issue.

Comment by David Nolen [ 20/May/15 11:55 AM ]

We'll leave it open for the improving the error message.

Comment by Sebastian Bensusan [ 22/May/15 7:16 AM ]

Added the check in cljs.closure/source-on-disk where there is info for the error message.

For the supplied case, the error message is:

java.lang.IllegalArgumentException: The file file:/home/carlos/Playground/cljs-testcase/src/hello_world/closure.js 
lacks an associated source file. If it is a JavaScript library please add it to :libs}}

If a different wording or location of the check is needed, I'll submit a new patch with corrections.

Notes:

  • Changed:(:provides js) to (-provides js) in order to be consistent with IJavaScript.
  • cljs.clojure/source-on-disk takes a js argument that should satisfy with IJavaScript and ISourceMap if :source-map is enabled but the implementation is hardcoded to maps because :source-map and :source-url are used instead of ISourceMap methods -source-map and -source-url. I propose to extend PersistentMap and PersistentArrayMap to ISourceMap to make source-on-disk compliant with both protocols.




[CLJS-1274] Allow assignment to namespace-qualified names in current namespace Created: 17/May/15  Updated: 17/May/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3211
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: lvh Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

In Clojure, you can def to a namespace-qualified name as long as it's in the current namespace. You can't do that in Clojurescript. This makes writing some macros that def to a local name a bit more annoying, since the syntax-quote will automatically namespace-qualify any symbols, so you have to write the somewhat unsightly and not super obvious ~'sym.






[CLJS-1271] Missing warning when assigning namespaces via def Created: 17/May/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: Sebastian Bensusan Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Currently you can assign a Closure namespace to a var without getting a warning.

Minimal sample:

(ns import-names.core
  (:import [goog debug]))

(def debug goog.debug)


 Comments   
Comment by David Nolen [ 29/May/15 12:30 PM ]

The example case is a bit complected. Besides importing a name that matches a def you are also assigning a google closure namespace to a local. This will likely cause problems on its own. We need more information.

Comment by Sebastian Bensusan [ 29/May/15 12:46 PM ]

We should check that :require ed and :import ed namespaces are not used as values and then warn about it.





[CLJS-1266] Node: Rename .cljs to .cljc -> old filenames in stacktrace Created: 12/May/15  Updated: 12/May/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3269
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Using QuickStart, set up Node REPL.

Manually add a foo/bar.cljs to filesystem with

(ns foo.bar)

(defn throw-ex [] (ffirst 1))

(defn call-me [] (throw-ex))

Check that it works:

cljs.user=> (require 'foo.bar)
nil
cljs.user=> (foo.bar/call-me)
repl:13
throw e__4210__auto__;
      ^
Error: 1 is not ISeqable
    at Object.cljs$core$seq [as seq] (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:956:20)
    at Object.cljs$core$first [as first] (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:965:16)
    at cljs$core$ffirst (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:1398:11)
    at foo$bar$throw_ex (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljs:3:20)
    at foo$bar$call_me (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljs:5:19)
    at repl:1:105
    at repl:9:3
    at repl:14:4
    at Object.exports.runInThisContext (vm.js:74:17)
    at Domain.<anonymous> ([stdin]:41:34)

Then manually move bar.cljs to bar.cljc and add a new symbol so it looks like:

(ns foo.bar)

(defn throw-ex [] (ffirst 1))

(defn call-me [] (throw-ex))

(defn call-again [] (call-me))

Then reload the ns and use the new symbol:

cljs.user=> (require 'foo.bar :reload)
nil
cljs.user=> (foo.bar/call-again)
repl:13
throw e__4210__auto__;
      ^
Error: 1 is not ISeqable
    at Object.cljs$core$seq [as seq] (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:956:20)
    at Object.cljs$core$first [as first] (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:965:16)
    at cljs$core$ffirst (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:1398:11)
    at foo$bar$throw_ex (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljs:3:20)
    at foo$bar$call_me (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljs:5:19)
    at foo$bar$call_again (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljs:5:19)
    at repl:1:108
    at repl:9:3
    at repl:14:4
    at Object.exports.runInThisContext (vm.js:74:17)

This illustrates the defect. call_again and the other symbols are shown as being in the old filename.

Stop the REPL and restart it to see correct behavior:

cljs.user=> :cljs/quit
orion:hello_world-node mfikes$ rlwrap java -cp cljs.jar:src clojure.main node_repl.clj
Reading analysis cache for jar:file:/Users/mfikes/Desktop/hello_world-node/cljs.jar!/cljs/core.cljs
Compiling src/foo/bar.cljc
ClojureScript Node.js REPL server listening on 49397
Watch compilation log available at: out/watch.log
To quit, type: :cljs/quit
cljs.user=> (require 'foo.bar)
nil
cljs.user=> (foo.bar/call-again)
repl:13
throw e__4210__auto__;
      ^
Error: 1 is not ISeqable
    at Object.cljs$core$seq [as seq] (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:956:20)
    at Object.cljs$core$first [as first] (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:965:16)
    at cljs$core$ffirst (/Users/mfikes/Desktop/hello_world-node/out/cljs/core.cljs:1398:11)
    at foo$bar$throw_ex (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljc:3:20)
    at foo$bar$call_me (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljc:5:19)
    at foo$bar$call_again (/Users/mfikes/Desktop/hello_world-node/out/foo/bar.cljc:7:22)
    at repl:1:108
    at repl:9:3
    at repl:14:4
    at Object.exports.runInThisContext (vm.js:74:17)


 Comments   
Comment by Mike Fikes [ 12/May/15 2:04 PM ]

FWIW as a comparison, the same use case works properly with Clojure 1.7.0-beta2.





[CLJS-1259] Incorrect warnings on type hinted maths Created: 09/May/15  Updated: 14/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3255
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Erik Ouchterlony Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: bug, math, typehints


 Description   

Variables type hinted as int or double are not recognized as numbers, e.g.

(def ^int i 1)
(+ i i)
WARNING: cljs.core/+, all arguments must be numbers, got [int int] instead. at line 1 <cljs repl>
2


 Comments   
Comment by David Nolen [ 14/Jul/15 6:07 AM ]

The real issue is that there is no support for numeric type hints.





[CLJS-1255] cljs.test file-and-line detection is not useful in browser testing Created: 07/May/15  Updated: 14/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3211
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Stephen Nelson Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Chrome



 Description   

cljs.test reports using do-report, which adds file and line information computed from javascript stack traces. In chrome at least, these stack traces are not useful:

"Error
    at http://localhost:3449/js/cljs/test.js:261:69
    at cljs$test$do_report (http://localhost:3449/js/cljs/test.js:268:3)
    at http://localhost:3449/js/test/test_tests.js:491:21
    at test.test_tests.test_has_fails.cljs$lang$test (http://localhost:3449/js/test/test_tests.js:502:4)
    at http://localhost:3449/js/cljs/test.js:384:42
    at http://localhost:3449/js/cljs/test.js:387:4
    at cljs$test$run_block (http://localhost:3449/js/cljs/test.js:320:13)
    ..."

The `file-and-line` stack trace parser doesn't parse this correctly, resulting in a message like this:

FAIL in (test-function) (at http:384:42)

Note the lack of a useful file/namespace reference, and that the line number refers to the compiled javascript rather than the source clojurescript.



 Comments   
Comment by Stephen Nelson [ 07/May/15 9:15 PM ]

Prior to the release of cljs.test my company maintained an internal port of clojure.test that did better reporting than cljs.test's by adding source metadata from &form to the do-report calls generated by assert-expr. This approach was great for internal use but might not be suitable for cljs.test as it could reduce portability of assert-expr between clojure and clojurescript. Another approach could be dynamically bind source metadata in the body generated by try-expr. I'd be willing to implement and contribute code if you can provide some indication of your preferred approach.

Our version of assert-expr also injected a 'reporter function', {{(function(a,b,c){a.apply(b.c)})}}, which we would invoke from report, e.g. (reporter (.-debug js/console) js/console args). This causes the clickable link on the right hand side of chrome's console output to link to the source map location of the test expression, rather than the report function.

Comment by David Nolen [ 14/Jul/15 6:09 AM ]

The correct thing to do here is to move the browser REPL stacktrace parsing into a shared library i.e. .cljc that can be loaded into either environment to handle browser difference.





[CLJS-1245] Implement bound-fn Created: 03/May/15  Updated: 05/May/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Brandon Bloom Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

See discussion on http://dev.clojure.org/jira/browse/CLJS-210






[CLJS-1242] Add cljs.core/pattern? predicate Created: 03/May/15  Updated: 27/Jul/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Brandon Bloom Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: patch

Attachments: Text File CLJS-1242_v01.patch    
Patch: Code and Test

 Description   

Just like http://dev.clojure.org/jira/browse/CLJS-1241 , this helps with clj/cljs cross-platform development.



 Comments   
Comment by Brandon Bloom [ 03/May/15 2:38 PM ]

See also http://dev.clojure.org/jira/browse/CLJ-1720

Comment by Brandon Bloom [ 10/May/15 11:36 PM ]

Note that there already is a cljs.core/regexp?, since it's used in a few places by core.

Comment by Samuel Miller [ 27/Jul/15 9:27 PM ]

The patch needs to be updated because of the folder changes

Tested on Linux with V8, SpiderMonkey, and Nashorn. Repljs also functions correctly.

You could have the `pattern?` function point to the `regexp?` function. Maybe there is some reason to keep separate (ability to have non-regex patterns?)





[CLJS-1241] Add cljs.core/boolean? predicate Created: 03/May/15  Updated: 05/May/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Brandon Bloom Assignee: Unassigned
Resolution: Unresolved Votes: 2
Labels: patch

Attachments: Text File CLJS-1241_v01.patch    
Patch: Code and Test

 Description   

I'm constantly re-implementing this predicate...

It's also important for clj/cljs compatibility.



 Comments   
Comment by Brandon Bloom [ 03/May/15 2:32 PM ]

See also: http://dev.clojure.org/jira/browse/CLJ-1719

Comment by Mike Fikes [ 03/May/15 2:52 PM ]

I tried this patch and it works correctly for me.

The docstring uses "typeof". Perhaps "type of" was intended?

cljs.user=> (doc boolean?)
-------------------------
cljs.core/boolean?
([x])
  Returns true if the typeof x is boolean, false otherwise.

nil
Comment by Brandon Bloom [ 03/May/15 7:41 PM ]

I did mean "typeof", which is a javascript-ism. A better doc string may be less javascript-specific...





[CLJS-1238] Setting *main-cli-fn* when using :target :nodejs shouldn't be manditory Created: 01/May/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Minor
Reporter: Jeremy Shoemaker Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None

Attachments: Text File nodejs-main-cli-fn.patch    
Patch: Code

 Description   

Currently, when you use :target :nodejs in the build options for ClojureScript, the resulting code requires you to set *main-cli-fn* to a function.

This prevents someone from writing a library that can be used by JavaScript developers because it forces code execution on require. It also makes writing a CLI tool that can be distributed using NPM less straightforward. I ran into this issue trying to create a Leiningen template for writing CLI tools that could be installed using npm install or npm link. I had a wrapper script to take care of the CLI use-case, and intended to write the ClojureScript module in a more library oriented way, but ran into issues. I could work around this by not using the wrapper script, but it got me thinking about the more general library issue.

I don't see any reason why you should be forced to set *main-cli-fn* and so I'm suggesting making it optional.

Attached is a patch that makes it optional but retains the check for whether the value it is set to is a function in the case where it is set.

This is my first time submitting a change to a project using a git patch and not a pull request, so let me know if I've made the patch wrong.



 Comments   
Comment by Jeremy Shoemaker [ 01/May/15 7:27 PM ]

I just noticed the priority defaulted to "Major". I don't know if I'd say it's major, so feel free to bump it down if that doesn't seem appropriate.





[CLJS-1223] cljs.repl/doc for unqualified .. macro Created: 24/Apr/15  Updated: 24/Apr/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3126
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Note: Also affects 0.0-3211 (not yet available in JIRA pulldown)
Quick Start cljs.jar
OS X



 Description   

In the REPL, (doc ..) fails, while it succeeds for similar macros like (doc ->), and it also succeeds for ns-qualified: (doc cljs.core/..).

$ java -jar cljs.jar -m cljs.repl.node
ClojureScript Node.js REPL server listening on 54359
To quit, type: :cljs/quit
cljs.user=> *clojurescript-version*
"0.0-3211"
cljs.user=> (doc ..)
-------------------------
/.
  nil

nil
cljs.user=> (doc cljs.core/..)
-------------------------
cljs.core/..
([x form] [x form & more])
Macro
  form => fieldName-symbol or (instanceMethodName-symbol args*)

  Expands into a member access (.) of the first member on the first
  argument, followed by the next member on the result, etc. For
  instance:

  (.. System (getProperties) (get "os.name"))

  expands to:

  (. (. System (getProperties)) (get "os.name"))

  but is easier to write, read, and understand.

nil





[CLJS-1222] Sequence of a stateful transducer is producing the wrong answer Created: 24/Apr/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: Lucas Cavalcanti Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: bug, cljs, collections
Environment:

OSX 10.10.3, java 1.8.0-ea-b124



 Description   

I'm producing more than one element on the 1-arity of the transducer, and sequence is only considering the last one.

Here is the transducer and the tests that fail for sequence:

(defn sliding-window [n]
  (fn [rf]
    (let [a #js []]
      (fn
        ([] (rf))
        ([result]
         (loop [] ;; Here I'm emitting more than one element
           (when (not-empty a)
             (rf result (vec (js->clj a)))
             (.shift a)
             (recur))))
        ([result input]
         (.push a input)
         (if (== n (.-length a))
           (let [v (vec (js->clj a))]
             (.shift a)
             (rf result v))
           result))))))

;;This test fails! =(
(deftest sliding-window-in-a-sequence
  (is (= [[5 4 3]
          [4 3 2]
          [3 2 1]
          [2 1]
          [1]]
         (sequence (sliding-window 3) [5 4 3 2 1])))

  (is (= [[2 1]
          [1]]
         (sequence (sliding-window 3) [2 1]))))


 Comments   
Comment by Lucas Cavalcanti [ 24/Apr/15 11:18 AM ]

I could make it work by recurring on the result.

([result]
  (loop [res result]
    (if (not-empty a)
      (let [v (vec (js->clj a))]
        (.shift a)
        (recur (rf res v)))
      res)))

even so it's weird that the previous version behaves differently on core.async and sequences in cljs and clj

Comment by David Nolen [ 26/Apr/15 4:04 AM ]

Please demonstrate the problem without core.async. Thanks.

Comment by Lucas Cavalcanti [ 26/Apr/15 7:32 PM ]

Hi,

the last test I posted on the ticket, fails in cljs, but not in clj:

;;This test fails! =(
(deftest sliding-window-in-a-sequence
  (is (= [[5 4 3]
          [4 3 2]
          [3 2 1]
          [2 1]
          [1]]
         (sequence (sliding-window 3) [5 4 3 2 1])))

  (is (= [[2 1]
          [1]]
         (sequence (sliding-window 3) [2 1]))))
Comment by David Nolen [ 27/Apr/15 7:43 AM ]

I've removed the core.async bits from the description to clarify the issue.

Comment by David Nolen [ 10/May/15 2:40 PM ]

The implementation of sliding-window above does not appear to be correct, it doesn't return the result. This ticket needs more information.

Comment by Lucas Cavalcanti [ 10/May/15 3:51 PM ]

As I said on http://dev.clojure.org/jira/browse/CLJS-1222?focusedCommentId=38620&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-38620

changing the 1-arity of the sliding-window to that fixes the transducer.

The point of this ticket now is that the behavior of the same (wrong) transducer in clj (both core.async and sequence) and cljs (core.async) is different than cljs sequence.





[CLJS-1211] Automatically requiring :main namespace under :none fails in IE9 Created: 17/Apr/15  Updated: 17/Apr/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3058, 0.0-3115, 0.0-3196, 0.0-3117, 0.0-3119, 0.0-3123, 0.0-3126
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Immo Heikkinen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Internet Explorer 9



 Description   

Automatic goog/base.js inclusion using :none & :main doesn't work in Internet Explorer 9. The following error message is printed to the console:

ClojureScript could not load :main, did you forget to specify :asset-path?

There seems to be a timing issue after writing script tags to the HTML document. Inline JavaScript for requiring the main namespace gets executed while goog is still undefined.

I played a bit with this but couldn't get it working in IE9. I tried moving the require statement to a separate JS file and adding a script tag to load that file, then goog was no longer undefined but I still got an error message:

SCRIPT5022: Undefined nameToPath for goog.string
base.js, line 753 character 9

The feature seems to work fine in other browsers (also IE10). Probably not worth fixing but at least the limitation is documented now in case someone else wonders the why it doesn't work in IE9.






[CLJS-1195] generic reusable command line argument parsing for REPLs Created: 10/Apr/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Enhancement Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: newbie


 Description   

REPLs are more or less started in the same way and all the builtin ones provide a -main entry point. We should supply reusable command line argument parsing that any REPL can use to get standard command line driven start.






[CLJS-1186] add :postamble option to compiler Created: 02/Apr/15  Updated: 02/Apr/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Michael Bradley Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: cljs

Attachments: Text File cljs_1186.patch    

 Description   

Similar to CLJS-723:

1) :postamble's value will be a vector of paths
2) the compiled output is appended with the contents of the files at those paths
3) the generated source map points to the correct/adjusted line numbers






[CLJS-1164] quot and rem are inefficient Created: 24/Mar/15  Updated: 29/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Francis Avila Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: math

Attachments: Text File cljs-1164.patch    
Patch: Code and Test

 Description   

The implementation of the quot and rem functions are needlessly complicated. Currently they are:

(defn quot [n d] (fix (/ (- n (js-mod n d)) d)))
(defn rem [n d] (- n (* d (quot n d))))

However all numbers in js are doubles already, so all this is unnecessary:

(defn quot [n d] (fix (/ n d)))
(defn rem [n d] (js-mod n d)))

Notice that "rem" is simply js-mod, and I'm not sure why no one noticed this before. I keep js-mod for now since a lot of code uses it, and if cljs ever grows a number tower the distinction may be important.

Patch attached, which also:

  • Creates a macro version of quot and rem.
  • Updates documentation for quot, rem, js-mod and mod for clarity.
  • Implement fix (private function to round to zero) with ES6 Math.trunc() if available.

Existing quot and rem tests pass, although there could be some better tests of edge cases (negative decimal num or div, NaN and +-Infinity args).



 Comments   
Comment by Francis Avila [ 24/Mar/15 12:27 PM ]

Better tests found rounding errors in my updated rem, which should stay as-is. (Not simply js-mod after all! Seems to round args first? Not obvious from the spec.) Changed quot however is correct and introduces less error than the current one. Will update patch and tests when I get a chance.

Comment by Francis Avila [ 29/Mar/15 12:39 AM ]

Working patch with tests attached. Tests expanded to cover floating-point cases. rem is now fundamentally the same as master (was more accurate than js-mod!!), but returns results consistent with js-mod for non-finite args or zero divisor.





[CLJS-1160] Source map js / cljs line number mixup Created: 23/Mar/15  Updated: 23/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3126
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

QuickStart Browser REPL Chrome OS X



 Description   

Note the source location for cljs.core.LazySeq.sval in the following 0.0-3126 stacktrace:

ClojureScript:cljs.user> (map ffirst (range))
Error: 0 is not ISeqable
	 cljs$core$seq (out/cljs/core.cljs:951:13)
	 cljs$core$first (out/cljs/core.cljs:960:7)
	 cljs$core$ffirst (out/cljs/core.cljs:1393:3)
	 cljs.core.map.cljs$core$map__2 (out/cljs/core.cljs:4046:30)
	 cljs.core.LazySeq.sval (out/cljs/core.cljs:11400:3)
	 cljs.core.LazySeq.cljs$core$ISeqable$_seq$arity$1 (out/cljs/core.cljs:2884:12)
	 cljs$core$seq (out/cljs/core.cljs:938:7)
	 cljs$core$pr_sequential_writer (out/cljs/core.cljs:8426:13)

It turns out that line 11400 is actually in core.js, but the source map logic is evidently getting tripped up by this one, and core.cljs gets reported with the JS line numbers (core.cljs doesn't even have that many lines).

Note that this also occurs on master (where CLJS-1154 and CLJS-1157 have landed):

ClojureScript:cljs.user> (map ffirst (range))
Error: 0 is not ISeqable
	 cljs.core/seq (out/cljs/core.cljs:951:20)
	 cljs.core/first (out/cljs/core.cljs:960:16)
	 cljs$core$ffirst (out/cljs/core.cljs:1393:11)
	 cljs.core.map.cljs$core$map__2 (out/cljs/core.cljs:4046:30)
	 cljs.core.LazySeq.sval (out/cljs/core.cljs:11400:3)
	 cljs.core.LazySeq.cljs$core$ISeqable$_seq$arity$1 (out/cljs/core.cljs:2884:12)
	 cljs.core/seq (out/cljs/core.cljs:938:25)
	 cljs$core$pr_sequential_writer (out/cljs/core.cljs:8426:20)





[CLJS-1153] Typed Array backed PersistentVector based on clojure.core/Vec Created: 19/Mar/15  Updated: 29/Apr/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Adrian Medina Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: enhancement

Attachments: Text File 1153.patch    
Patch: Code

 Description   

Goal is to add support for vectors based on clojure.core/Vec, built on top of JavaScript Typed Arrays.

My hope is that this would allow for both efficient creation of vectors from existing Typed Arrays without intermediate conversion to normal JavaScript arrays, as well as efficient concatenation of the composite arrays of the vector back into a Typed Array when necessary via an enhanced cljs.core/into-array.

Implementation is based heavily on clojure/core/gvec.clj, cljs.core/PersistentVector, and cljs.core/TransientVector.

Performance should be comparable to cljs.core/PersistentVector, although there is additional constant overhead with TypedArray instantiation compared to js/Array.

Adds cljs.core/Vec, cljs.core/TransientVec, cljs.core/vector-of, and updates cljs.core/into-array.



 Comments   
Comment by Adrian Medina [ 19/Mar/15 8:39 PM ]

I still have to test, I will update the issue when that is complete. I just wanted to get my first patch up for review as quickly as possible.

Comment by Francis Avila [ 19/Mar/15 11:59 PM ]

No mention of Uint8ClampedArray.

Should Vec type- or range-check assignments? In Clojure these fail (even with unchecked-math):

  • (vector-of :byte 128) returns [-128]
  • (vector-of :byte "1") returns [1]
  • (vector-of :byte (js-obj)) returns [0]

If we're going to expose host primitive arrays via cljs apis, should we also bring the various other array functions in line with Clojure (and ClojureCLR, which also has extra uint, ubyte, etc types) like you are doing with into-array? Some or all of these issues may warrant another ticket instead, or maybe even a design page:

  • make-array ignores type argument and lacks higher dimensions.
  • object-array, int-array, etc. maybe should return TypedArrays.
  • Missing ubyte-array, ushort-array, uint-array (like ClojureCLR)
  • Missing aset-* setters. (Meaningless in js unless we range-check.)
  • aclone and amap preserve type of input array in Clojure, but not in cljs.
  • missing array casters: bytes, shorts, chars, ints, etc.
  • While we're at it, primitive coercion functions (e.g. int, long, unchecked-int, etc) are either no-ops or differ from clojure. (e.g., int in cljs is like unchecked-int in clojure, but unchecked-int in cljs does nothing). Maybe these should be dropped or should match the javascript ToInt32, ToInt16, etc abstract operations (i.e. those used when assigning to TypedArrays). Maybe these match java semantics also?




[CLJS-1151] Noisy errors when referring to symbol in undefined ns Created: 19/Mar/15  Updated: 22/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3126
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Quick Start / OS X / Chrome



 Description   

Run through the Quick Start to the point where you have the browser REPL running.

If you refer to a symbol in an undefined ns you will get really noisy errors:

ClojureScript:cljs.user> foo.bar/a
WARNING: Use of undeclared Var foo.bar/a at line 1 <cljs repl>
ReferenceError: foo is not defined
ReferenceError: foo is not defined
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:1:89)
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:9:3)
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:14:4)
    at http://localhost:9000/out/clojure/browser/repl.js:42:267
    at clojure$browser$repl$evaluate_javascript (http://localhost:9000/out/clojure/browser/repl.js:45:4)
    at Object.callback (http://localhost:9000/out/clojure/browser/repl.js:242:169)
    at goog.messaging.AbstractChannel.deliver (http://localhost:9000/out/goog/messaging/abstractchannel.js:142:13)
    at goog.net.xpc.CrossPageChannel.xpcDeliver (http://localhost:9000/out/goog/net/xpc/crosspagechannel.js:733:12)
    at Function.goog.net.xpc.NativeMessagingTransport.messageReceived_ (http://localhost:9000/out/goog/net/xpc/nativemessagingtransport.js:321:13)
    at Object.goog.events.fireListener (http://localhost:9000/out/goog/events/events.js:741:21)

Now, define the namespace, but don't actually define the symbol. For example:

(ns foo.bar)

(def z 3)

Now, require the namespace and do the same. The noise will go away.

ClojureScript:cljs.user> (require 'foo.bar)
nil
ClojureScript:cljs.user> foo.bar/a
WARNING: Use of undeclared Var foo.bar/a at line 1 <cljs repl>
nil

Additional info: Now if you attempt to refer to other unknown symbols, you will get different kinds of "noise" depending on whether the ns simply starts with foo:

ClojureScript:cljs.user> foo.baz/a
WARNING: No such namespace: foo.baz, could not locate foo/baz.cljs at line 1 <cljs repl>
WARNING: Use of undeclared Var foo.baz/a at line 1 <cljs repl>
TypeError: Cannot read property 'a' of undefined
TypeError: Cannot read property 'a' of undefined
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:1:96)
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:9:3)
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:14:4)
    at http://localhost:9000/out/clojure/browser/repl.js:42:267
    at clojure$browser$repl$evaluate_javascript (http://localhost:9000/out/clojure/browser/repl.js:45:4)
    at Object.callback (http://localhost:9000/out/clojure/browser/repl.js:242:169)
    at goog.messaging.AbstractChannel.deliver (http://localhost:9000/out/goog/messaging/abstractchannel.js:142:13)
    at goog.net.xpc.CrossPageChannel.xpcDeliver (http://localhost:9000/out/goog/net/xpc/crosspagechannel.js:733:12)
    at Function.goog.net.xpc.NativeMessagingTransport.messageReceived_ (http://localhost:9000/out/goog/net/xpc/nativemessagingtransport.js:321:13)
    at Object.goog.events.fireListener (http://localhost:9000/out/goog/events/events.js:741:21)
ClojureScript:cljs.user> goo.baz/a
WARNING: No such namespace: goo.baz, could not locate goo/baz.cljs at line 1 <cljs repl>
WARNING: Use of undeclared Var goo.baz/a at line 1 <cljs repl>
ReferenceError: goo is not defined
ReferenceError: goo is not defined
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:1:89)
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:9:3)
    at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:14:4)
    at http://localhost:9000/out/clojure/browser/repl.js:42:267
    at clojure$browser$repl$evaluate_javascript (http://localhost:9000/out/clojure/browser/repl.js:45:4)
    at Object.callback (http://localhost:9000/out/clojure/browser/repl.js:242:169)
    at goog.messaging.AbstractChannel.deliver (http://localhost:9000/out/goog/messaging/abstractchannel.js:142:13)
    at goog.net.xpc.CrossPageChannel.xpcDeliver (http://localhost:9000/out/goog/net/xpc/crosspagechannel.js:733:12)
    at Function.goog.net.xpc.NativeMessagingTransport.messageReceived_ (http://localhost:9000/out/goog/net/xpc/nativemessagingtransport.js:321:13)
    at Object.goog.events.fireListener (http://localhost:9000/out/goog/events/events.js:741:21)


 Comments   
Comment by Mike Fikes [ 19/Mar/15 3:47 PM ]

Clarification on the description: When defining the foo.bar ns, I actually created a source file src/foo/bar.cljs containing the ns declaration and the def z.

Comment by David Nolen [ 22/Mar/15 9:49 AM ]

I'm not sure what we can do about this or that suppressing the errors from the JavaScript evaluation environment is a good idea. Property access on something that doesn't exist is what causes the JS environment to emit the error. In the later cases the property access is on something that does exist but doesn't have the property so no error.





[CLJS-1136] Initial require fails to fully load added symbols Created: 17/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Quick Start Browser REPL (OS X / Safari)



 Description   

In the Quick Start, a portion runs the user through adding a symbol (a function named foo) and then requiring the namespace and using that symbol. I'm finding that require fails and that I need to add the :reload directive.

To reproduce:

  1. Run through the Quick Start up through the browser REPL section.
  2. Set src/hello_world/core.cljs so that it does not have the foo function defined.
  3. Remove the out directory: rm -rf out
  4. Start up the REPL: rlwrap java -cp cljs.jar:src clojure.main repl.clj
  5. Connect Safari by going to http://localhost:9000
  6. Show the error console in Safari. (You'll see Hello world.)
  7. Run tail -f out/watch.log
  8. Add the foo function that adds a b to src/hello_world/core.cljs and save it.
  9. Observe that watch.log reflects recompilation
  10. Do {{ (require '[hello-world.core :as hello]) }}
  11. Do {{ (hello/foo 2 3) }}

At this point you will get:
TypeError: undefined is not an object (evaluating 'hello_world.core.foo.call')

But:

ClojureScript:cljs.user> (ns-interns 'hello-world.core)
{foo #'hello-world.core/foo, conn #'hello-world.core/conn}
ClojureScript:cljs.user> (source hello/foo)
(defn foo [a b]
  (+ a b))
nil

Now, if you :reload

ClojureScript:cljs.user> (require '[hello-world.core :as hello] :reload)
nil
ClojureScript:cljs.user> (hello/foo 2 3)
5


 Comments   
Comment by Mike Fikes [ 17/Mar/15 11:30 AM ]

Prior to step 8:

ClojureScript:cljs.user> (ns-interns 'hello-world.core)
{}

Between steps 9 and 10:

ClojureScript:cljs.user> (ns-interns 'hello-world.core)
{foo #'hello-world.core/foo, conn #'hello-world.core/conn}

My guess: Watching is causing symbols to be interned, but not usable, and this is interfering with require forcing you to include :reload.

Comment by David Nolen [ 22/Mar/15 9:46 AM ]

I'm not sure that this is actually an issue, the browser has already required the namespace, it's the entry point. Thus you really do need a :reload. But the reason you see interned symbols is that the watch process shares the compilation environment with the REPL. It may be the case that with the dramatically improved REPLs the watch option becomes entirely unnecessary and counterintuitive, let's see how it goes.





[CLJS-1134] Lift protocols from cljs.closure into cljs.protocols ns Created: 17/Mar/15  Updated: 10/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Task Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

This is task towards presenting a stable API to users without reaching into the implementation namespaces.






[CLJS-1133] REPL require results in warnings to be emitted twice Created: 17/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: Mike Fikes Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Quick Start Browser REPL with :watch off



 Description   

Run through the Quick Start and go down through to the Browser REPL portion (https://github.com/clojure/clojurescript/wiki/Quick-Start#browser-repl), but exclude the :watch option from repl.clj.

Then further down, where the new symbol is introduced

;; ADDED
(defn foo [a b]
  (+ a b))

instead cause some duplicate symbols to be introduced in order to provoke compiler warnings:

(def a 1)
(def a 1)

(defn foo [a b]
  (+ a b))
(defn foo [a b]
  (+ a b))

Then evaluate the require statement in the tutorial and observe that the warnings are emitted twice:

ClojureScript:cljs.user> (require '[hello-world.core :as hello])
WARNING: a at line 11 is being replaced at line 12 /Users/mfikes/Desktop/hello_world/src/hello_world/core.cljs
WARNING: foo at line 14 is being replaced at line 16 /Users/mfikes/Desktop/hello_world/src/hello_world/core.cljs
WARNING: a at line 11 is being replaced at line 12 /Users/mfikes/Desktop/hello_world/src/hello_world/core.cljs
WARNING: foo at line 14 is being replaced at line 16 /Users/mfikes/Desktop/hello_world/src/hello_world/core.cljs
nil





[CLJS-1129] :modules tutorial for wiki Created: 16/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Task Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: documentation, newbie


 Description   

The documentation is nice but something that walks people through the steps would be nicer.






[CLJS-1128] Describe internationalization strategies via Google Closure on the wiki Created: 16/Mar/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Task Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: documentation, newbie


 Description   

This can be done via Google Closure defines or via pulling a specific locale. A page should document how this can be done.






[CLJS-1123] this-as unexpectedly binds js/window when used within function with post-condition Created: 15/Mar/15  Updated: 16/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Joshua Choi Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Adding a post-condition to any function that uses cljs.core/this-as will unexpectedly cause this-as's "this" symbol to be bound to the root object (e.g., js/window) instead.

(defn f-no-post-condition [argument]
  (this-as this
    (js/console.log argument this)))

(defn f-with-post-condition [argument]
  {:post [true]}
  (this-as this
    (js/console.log argument this)))

(def test-object
  #js {:methodNoPostcondition f-no-post-condition
       :methodWithPostcondition f-with-post-condition})

(f-with-post-condition "A") ; Correctly prints js/window
(.methodNoPostcondition test-object "B") ; Correctly prints test-object
(.methodWithPostcondition test-object "C") ; Incorrectly prints js/window


 Comments   
Comment by David Nolen [ 16/Mar/15 6:17 AM ]

This is almost certainly a different manifestation of CLJS-719.

Comment by Thomas Heller [ 16/Mar/15 6:21 AM ]

Just looked at the generated javascript. As David mentioned the problem is the extra function generated to get the result for the :post condition.

dummy.f_no_post_condition = (function f_no_post_condition(argument){
var this$ = this;
var G__82157 = argument;
var G__82158 = this$;
return console.log(G__82157,G__82158);
});
dummy.f_with_post_condition = (function f_with_post_condition(argument){
var _PERCENT_ = (function (){var this$ = this;
var G__82161 = argument;
var G__82162 = this$;
return console.log(G__82161,G__82162);
})();


return _PERCENT_;
});
dummy.test_object = {"methodWithPostcondition": dummy.f_with_post_condition, "methodNoPostcondition": dummy.f_no_post_condition};
dummy.f_with_post_condition("A");
dummy.test_object.methodNoPostcondition("B");
dummy.test_object.methodWithPostcondition("C");




[CLJS-1109] Record type name and advanced optimization Created: 12/Mar/15  Updated: 12/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3058
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Daniel Skarda Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

It is not possible to query type name in advanced compilation.
Code below prints correct record name in other compilation modes, but under advanced compilation it prints constructor source code.

(defrecord FooBar [a])

(def fb (FooBar. 1))

(prn (-> fb))
(prn (-> fb type))
(prn (-> fb type pr-str))





[CLJS-1091] Compose JavaScript dependency indexes Created: 07/Mar/15  Updated: 29/Apr/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Currently hard coded to Google Closure deps.js and the one produced for a build. Users should be able to supply JS dependency indexes that can get merged in.






[CLJS-1076] :nashorn target Created: 02/Mar/15  Updated: 02/Mar/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

To run well on Nashorn the target should supply CLOSURE_IMPORT_SCRIPT as well as setTimeout or setImmediate for core.async.






[CLJS-1070] top-level boolean inference does not work Created: 28/Feb/15  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Problem for using boolean Closure defines



 Comments   
Comment by Francis Avila [ 30/Mar/15 12:02 PM ]

I am unsure if this is the same issue, but forms like ^boolean (js/isFinite n) also do not seem to analyze correctly: if, and, and or will still emit a call to truth_.





[CLJS-1067] Shared AOT cache for dependencies in JARs Created: 26/Feb/15  Updated: 27/Feb/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

3rd party library code in JARs shouldn't be recompiled across dev and prod configurations. There should be a shared AOT cache for all builds within a project for all non-project local source.






[CLJS-1059] Simple interface wanted to convert cljs forms to js Created: 22/Feb/15  Updated: 07/May/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3211
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Stuart Mitchell Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: analyzer, compiler

Patch: Code

 Description   

In our project (a clojurescript debugger) we want to convert cljs forms or a sequence of forms into javascript so that they can be executed in the javascript console.

We would like something similar to closure/compile-form-seq (https://github.com/clojure/clojurescript/blob/master/src/clj/cljs/closure.clj#L308)

However, we need to supply, the namespace requires and locals in an env like this

{:ns {:name "test.core" :requires {(quote gstring) (quote goog.string)}} :locals {}}

This code seems to do what we want.

(defn compile-form-seq
    \"Compile a sequence of forms to a JavaScript source string.\"
    [forms env]
    (env/ensure
    (compiler/with-core-cljs nil
      (fn []
        (with-out-str
            (doseq [form forms]
              (compiler/emit (analyzer/analyze env form))))))))

I am not sure why I need env/ensure.

Would you be able to patch compile-form-seq to provide the needed interface, or suggest what we should be doing.

Thanks
Stu



 Comments   
Comment by Mike Thompson [ 22/Feb/15 10:09 PM ]

Just to be clear:
1. when our debugger is at a breakpoint,
2. the user can type in an expression at the repl
3. in response, our debugger has to compile the user-typed-in expression to javascript (and then execute it, showing a result)
4. taking into account any local bindings. <---- this is the key bit.

To satisfy point 4, our tool extracts all the 'locals' from the current call-frame, and then supplies all these local bindings in env/locals, so the compiler doesn't stick a namespace on the front of them.

For example, if there was a local binding for 'x' in the callstack, and the user's repl-entered-expression involves 'x', then we want the compiler to leave the symbol 'x' alone and to not put some namespace on the front of it. In the final javascript, it must still be 'x', not 'some.namespace.x'

Our method to achieve this is to put 'x' into env/locals when compiling – and it all works. Except, with the recent changes this has become more of a challenge. Hence this ticket asking for a way to pass in env.

Comment by Thomas Heller [ 23/Feb/15 3:19 AM ]

You could wrap the user expression in an fn, that would allow you to skip messing with the locals. The REPL basically does the same trick for *1,*2,...

(fn [x]
  ~user-expression-here)
Comment by David Nolen [ 29/Apr/15 7:21 AM ]

Seems like something useful to add to a cljs.compiler.api namespace.





[CLJS-1048] support function values in static vars compile time metadata Created: 20/Feb/15  Updated: 20/Feb/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: Ivan Mikushin Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Function values are currently only supported for :test metadata key as a special case.






[CLJS-1047] externs checking for js/foo Created: 19/Feb/15  Updated: 30/Apr/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 0.0-3211
Fix Version/s: GSoC

Type: Enhancement Priority: Minor
Reporter: David Nolen Assignee: Maria Geller
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Worth looking into validating `js/foo` forms again the known externs set. Can probably be done by leveraging the Closure JS Parser.



 Comments   
Comment by Michael Griffiths [ 22/Feb/15 12:03 PM ]

Would you consider making the results of parsing available to tooling (e.g. in cljs.env/*compiler*)? I would use this to add support for autocompletion of js/ forms to CIDER.

Comment by David Nolen [ 23/Feb/15 8:15 AM ]

Definitely open to the idea of exposing this information to other tooling when we get there.





[CLJS-1029] Investigate how ns aliasing can be supported in ClojureScript macro files Created: 11/Feb/15  Updated: 11/Feb/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Task Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Currently we just require the file. Perhaps possible to control compilation of the macro file such that ClojureScript ns aliases are established. This may not bear fruit but worth thinking about.



 Comments   
Comment by David Nolen [ 11/Feb/15 4:05 PM ]

Any design ideas along this path needs to keep .cljc files in mind.





[CLJS-911] Cljs's clojure.string.replace replacement fn takes different args to Clj's clojure.string.replace Created: 17/Dec/14  Updated: 14/Jun/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Peter Taoussanis Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None
Environment:

ClojureScript 0.0-2411



 Description   

Clojure's `clojure.string.replace` takes a replacement fn which receives args `[[group1 group2 ...]]`.
ClojureScript's `clojure.string.replace` takes a replacement fn which receives args `[group1 group2 ...]` (i.e. & args).

It's my understanding that something like the `clojure.string` ns is intended partly to help pave over superficial API differences like this.

Modding ClojureScript's `string.replace` to match Clojure's behaviour would be trivial, but this would be a breaking change for anyone that's come to rely on the faulty[?] behaviour.

Would you like a patch for this? Can submit for http://dev.clojure.org/jira/browse/CLJS-794 while I'm at it (both involve a change to `clojure.string/replace`).

Thanks!



 Comments   
Comment by Joseph Smith [ 24/Feb/15 2:33 PM ]

+1 fixing this (preferably making the ClojureScript version work like the Clojure version).

Comment by Mike Fikes [ 14/Jun/15 7:25 PM ]

This ticket appears to be duplicate of CLJS-1304, which has an attached patch under consideration.





[CLJS-900] Parameterize caching strategy Created: 03/Dec/14  Updated: 03/Dec/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Task Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Currently the caching strategy is hard coded to a disk based one. It would be desirable in many situations for the caching to be in memory. We should decouple the caching strategy and support disk / memory out of the box.






[CLJS-890] Incorrect behaviour of (str obj) when obj has valueOf method Created: 24/Nov/14  Updated: 25/Apr/15

Status: Reopened
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Nikita Beloglazov Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None

Attachments: Text File cljs-890.patch     File cljs-core-str-perf.diff    

 Description   

Example

(str #js {"toString" (fn [] "hello") "valueOf" (fn [] 42)}) ; => "42"

The problem in the fact that ClojureScript uses concatenation to convert values to strings and that doesn't work well with objects which have valueOf() method overriden.

Example in js:

var obj = {
    toString: function() { return 'hello'; },
    valueOf: function() { return 42; }
};
console.log(String(obj)); => 'hello'
console.log(obj.toString()); => 'hello'
console.log('' + obj); => '42'

Potential solution might be to use String() function. Using toString() won't work as described in this issue: http://dev.clojure.org/jira/browse/CLJS-847



 Comments   
Comment by Kevin Neaton [ 24/Nov/14 10:34 AM ]

Is there a valid use case where toString and valueOf are not in sync? E.g.

(not= (.toString x) (js/String (.valueOf x))

If not, is it "incorrect" for the two methods to be out of sync?

Comment by Nikita Beloglazov [ 24/Nov/14 10:40 AM ]

Here is an example of such use case: https://github.com/processing-js/processing-js/blob/master/src/Objects/Char.js
That's how I found this bug.

Comment by Kevin Neaton [ 24/Nov/14 10:49 AM ]

Thanks for the link. I see what you mean.

Comment by David Nolen [ 02/Dec/14 5:08 AM ]

The problem with going with String appears to be a massive performance hit to printing http://jsperf.com/string-vs-tostring2/6.

Unless a brilliant idea is proposed this seems best solved / worked around in user code.

Comment by Nikita Beloglazov [ 02/Dec/14 6:41 AM ]

Append performs better on strings and numbers, but it performs worse on objects so it is not a clear performance hit. If I heavily work with objects and use (str) to convert them into strings then I actually lose on performance with current implementation.
Anyway current implementation of str is incorrect as it doesn't honor toString method. And this is what str function supposed to do. I believe a compiler should be correct first and then worry about performance.

Comment by David Nolen [ 02/Dec/14 7:38 AM ]

Sorry going back over this I believe the issue is really that we simply need to backout CLJS-801.

Comment by David Nolen [ 02/Dec/14 7:41 AM ]

reverted CLJS-801 in master

Comment by Francis Avila [ 02/Dec/14 10:32 AM ]

CLJS-801 only deals with the str macro. Aren't we still going to have str function problem because of CLJS-847? https://github.com/clojure/clojurescript/commit/08b4b1585cf0ef739e903985ee4c6b7fc6c47642 (also Yet if we use toString there, Safari 6.0.5 blows up. Maybe we need {{[o].join('')}}? Depending on where the bug is this may be wrong in Safari 6.0.5 too.

What we need to do very specifically is somehow get the return value of the (in ECMASCRIPT-ese) ToString abstract operation on the object (or the underlying ToPrimitive abstract operation with the String hint). String concat with the add operator

Options as I see it are:

  • x.toString() : Bad because of CLJS-847
  • {{[x].join('')}} : Should work (and does right thing for null/undefined), but I think we should test in Safari 6.0.5. Also very slow.
  • String
  • String.prototype.concat
  • String.prototype.slice(x,0) String.prototype.substring(x,0) String.prototype.substr(x, 0)
  • x.toString() normally, but String if we detect that we'll trigger CLJS-847. (Can specialize on startup.)
Comment by David Nolen [ 02/Dec/14 10:35 AM ]

Is there any evidence that higher usage of str is actually problematic?

Comment by Francis Avila [ 02/Dec/14 10:44 AM ]

String concat using the addition operator uses an un-hinted ToPrimitive abstract call (which will try x.valueOf() first then x.toString(), usually) and then {{ToString}}s the result of that, so it's not an option unless we are concating primitive values.

Details:

Comment by David Nolen [ 02/Dec/14 10:50 AM ]

I'm not really all that concerned about the specification, only if it matters in the wild. If this doesn't affect Safari 6.05 we don't care.

Comment by Francis Avila [ 02/Dec/14 11:01 AM ]

Is there any evidence that higher usage of str is actually problematic?

Kevin Neaton, who opened CLJS-847, was using a patch in production which only addressed the higher order case and he said the patch fixed the issue for them. He was unaffected by the str macro case because it either used ''+x already (with CLJS-801 applied) or it used {{[x].join('')}} (which hasn't been tested with Safari 6.0.5 yet, but probably works).

So if we had a problem using ''+x with the str macro, we will certainly have a problem with ''+x with a string function as long as CLJS-847 is applied.

I haven't pulled down master yet, but here is a test case which I bet will fail with the CLJS-847 patch:

(def tricky-obj #js {"toString" (fn [] "hello") "valueOf" (fn [] 42)})
(assert (= (apply str tricky-obj) "hello")) ;; will get "42"
Comment by Francis Avila [ 02/Dec/14 11:09 AM ]

I'm not really all that concerned about the specification, only if it matters in the wild. If this doesn't affect Safari 6.05 we don't care.

To be clear, there are two issues here:

CLJS-847: x.toString() fails on Safari 6.0.5. Workaround is ''+x (only done in str macro case).
CLJS-890: ''+x doesn't give expected results for objects which define valueOf. Expectation is that x.toString() is called, instead x.valueOf().toString(). Fix is to use array join instead of string concat in str macro, but it doesn't address the ''+x workaround from CLJS-847.

To make matters worse, it looks like the toString() error on Safari may only be triggered at certain JIT levels!

Comment by Francis Avila [ 02/Dec/14 11:10 AM ]

Workaround is ''+x (only done in str macro case).

I mean "Workaround is ''+x (only done in str function case)."

Comment by Nikita Beloglazov [ 08/Dec/14 6:14 PM ]

Can this bug be reopened meanwhile? If I understand correctly the fix should affect https://github.com/clojure/clojurescript/commit/08b4b1585cf0ef739e903985ee4c6b7fc6c47642 but this code still present in HEAD.

Comment by David Nolen [ 08/Dec/14 6:37 PM ]

We've switched to goog.string.buildString in master https://github.com/clojure/clojurescript/commit/94eb8a960fef6aaca4ba44b251cefbfa04d0f6ac

Comment by Nikita Beloglazov [ 08/Dec/14 8:32 PM ]

Yes, that works. Cool, thanks!

Comment by Thomas Heller [ 01/Jan/15 7:12 AM ]

Sorry for re-opening.

I was doing some profiling of my code and noticed a warning in the profiling output about cljs.core/str.

Chromes complains with: "Not optimized. Bad value context for arguments value", looking further at the implementation of goog.string.buildString

goog.string.buildString = function(var_args) {
  return Array.prototype.join.call(arguments, '');
};

Given that we don't ever call it with more than one argument it is probably not best implementation choice.

Maybe skip the call and inline it ala

(defn str
  "With no args, returns the empty string. With one arg x, returns
  x.toString().  (str nil) returns the empty string. With more than
  one arg, returns the concatenation of the str values of the args."
  ([] "")
  ([x] (if (nil? x)
         ""
         (.join #js [x] "")))
  ([x & ys]
    (loop [sb (StringBuffer. (str x)) more ys]
      (if more
        (recur (. sb  (append (str (first more)))) (next more))
        (.toString sb)))))

I didn't follow this issue but why are we not using .toString? The buildString/array approach seems kind of hackish?

I'm not too sure about the overall impact but since cljs.core/str showed up pretty high in my profile I think this should be investigated further.

Comment by Thomas Heller [ 01/Jan/15 7:28 AM ]

Before:

;;; str
[], (str "1"), 1000000 runs, 254 msecs
[], (str 1), 1000000 runs, 266 msecs
[], (str nil), 1000000 runs, 80 msecs
[], (str "1" "2" "3"), 1000000 runs, 753 msecs

After:

;;; str
[], (str "1"), 1000000 runs, 82 msecs
[], (str 1), 1000000 runs, 86 msecs
[], (str nil), 1000000 runs, 79 msecs
[], (str "1" "2" "3"), 1000000 runs, 242 msecs

But I only tested V8, probably needs some verification.

Comment by Thomas Heller [ 01/Jan/15 7:39 AM ]
(defn str
  "With no args, returns the empty string. With one arg x, returns
  x.toString().  (str nil) returns the empty string. With more than
  one arg, returns the concatenation of the str values of the args."
  ([] "")
  ([x1]
     (.join #js [x1] ""))
  ([x1 x2]
     (.join #js [x1 x2] ""))
  ([x1 x2 x3]
     (.join #js [x1 x2 x3] ""))
  ([x1 x2 x3 x4]
     (.join #js [x1 x2 x3 x4] ""))
  ...)

Does perform even better.

;;; str
[], (str "1"), 1000000 runs, 40 msecs
[], (str 1), 1000000 runs, 43 msecs
[], (str nil), 1000000 runs, 96 msecs
[], (str "1" "2" "3"), 1000000 runs, 117 msecs

How many args should it inline?

Comment by David Nolen [ 01/Jan/15 12:43 PM ]

I'd be OK with up to 4 then variadic.

Comment by Thomas Heller [ 01/Jan/15 5:05 PM ]

There is some weird interaction between the code generated by the cljs.core/str macro and function.

The macro generates

(str "hello" 1 "world" :yo nil)

yields

[cljs.core.str("hello"),cljs.core.str((1)),cljs.core.str("world"),cljs.core.str(new cljs.core.Keyword(null,"yo","yo",1207083126)),cljs.core.str(null)].join('');

Given that str with 1 arg will basically unroll to

[["hello"].join(""), ...]

I think it might be safe to completely remove the macro since cljs.core/str would then do the same and the JIT is probably smart enough to figure this out (or even Closure when compiling).

Comment by Francis Avila [ 02/Jan/15 11:14 AM ]

Chromes complains with: "Not optimized. Bad value context for arguments value", looking further at the implementation of goog.string.buildString

Chrome complains about the variadic function dispatch code in the same way, see CLJS-916 plus patch.

I think it might be safe to completely remove the macro since cljs.core/str would then do the same and the JIT is probably smart enough to figure this out (or even Closure when compiling).

The Closure compiler is not smart enough to remove the intermediate array, which is why I filed CLJS-801 (which this ticket rolled back). I don't think JITs can do it either.

I am beginning to wonder if we should ignore the Safari 6.0.5 problem in CLJS-847 that started all this string mess. To recap:

  1. CLJS-801 is accepted, which removes [123, x].join('') in the str macro case in favor of ''+123+(cljs.core/str$arity$1 x) style code, which the closure compiler can precompute. At this time, the one-arg cljs.core/str function (not macro) calls toString on its argument.
  2. CLJS-847 is filed. On Safari 6.0.5 at higher JIT levels, calling toString on some things (possibly only unboxed numbers? definitely not strings) throws a TypeError. This is unquestionably a bug in Safari. David fixes by making one-arg cljs.core/str function call js-str instead of toString. js-str uses string-concat ''+x.
  3. However, this breaks for objects that define valueOf (issue in current ticket), because in js ''+x is the same as ''+x.valueOf().toString() not ''+x.toString().
  4. David considers using String() and variations but rejects because of performance hit.
  5. David rolls back CLJS-801 from the string-concat back to the array-join style to fix.
  6. Nikita and I point out that rolling back CLJS-801 only fixes the str macro, not the string function, which still uses js-str and hence string-concat.
  7. David fixes the str function to use goog.string.buildString, which has the behavior of array.join. Behavior is now correct even on Safari 6.0.5.
  8. Thomas points out that buildString uses arguments in a way unoptimizable by v8, and now the str function (not macro) has a performance regression. He suggests using [].join() directly.

So, there's a lot of back and forth on this issue, and it's all because of a bug in Safari 6.0.5 which no one has been able to reproduce first-hand because Safari 6.0.5 is old and rare. For some perspective, Safari 6.0.x was only available on Lion and Mountain Lion between July 25,2012 and June 11,2013. Before July 25,2012 Lion used Safari 5.1.x and there was no Mountain Lion. On June 11, 2013, both Lion and Mountain Lion switched to Safari 6.1.x which does not suffer from the toString TypeError bug (I checked--I have an iMac with Lion on it). The only machines on Safari 6.0.5 are (Mountain) Lion machines which used software updates until late 2012-early 2013 and then stopped. I can't imagine this is a large number of people.

It is theoretically possible for me to run Safari 6.0.x on my Lion machine to actually test this, but I can't find a way to downgrade from 6.1.x.

I think the options are:

  1. Use array.join() for all stringification and take the performance hit (which we should quantify). Include a comment that this is only for Safari 6.0.x (only confirmed second-hand on 6.0.4 and 6.0.5) for future generations, who are going to think this is weird.
  2. Use CLJS-801 and toString (status quo before CLJS-847), and ignore this problem for Safari 6.0.x.
  3. Use CLJS-801, but add a number? check (with comment) to cljs.core/str$arity$1 for Safari 6.0.5. The number case should use js-str, and the rest toString. I think this will work, but again we have no way to test--we really need to get our hands on a Safari 6.0.x browser.

Of course we should benchmark these approaches but my hunch is that 2 is faster than 3 is faster than 1.

Comment by David Nolen [ 02/Jan/15 11:16 AM ]

We are not going to ignore Safari 6.0.X. Any decisions made about this ticket will include supporting it.

Comment by Francis Avila [ 10/Jan/15 4:12 AM ]

Update on some research I am doing into this.

I created a jsperf of alternative str implementations that I am trying out. Right now I've only looked at one-arg str. I discovered a few things:

  • {{''+[x]}} is a faster alternative to [x].join('').
  • Advanced compilation can compute {{''+[x]}} at compile time if x is a bool, str, undefined, null, or number, even through function calls! I.e. str_test.str_arr(123) compiles to "123" without macro magic.
  • However, using an intermediate array (even if a preallocated singleton) is still slower than the old (if (nil? x) "" (.toString x))
  • Using a switch statement is as least as fast as the str-tostr baseline, usually faster.
  • I am 99% sure all these implementations (except str-tostr, the baseline, which definitely fails) work on the dreaded Safari 6.0.x. If anyone has this version, point it at the jsperf link above and run the tests. I think Browserstack has this version of Safari.

I'm still investigating the variadic case (str x y z a b c). It might be better to use reduce instead of Stringbuffer+seq. (Stringbuffer just does ''+x now instead of an array-join.)

Comment by Thomas Heller [ 10/Jan/15 6:37 AM ]

Sorry, got side-tracked for a bit.

@Francis: Thanks for the recap.

Don't have Safari 6 available either, but seems wrong that we all have to suffer because is minor percentage still has this (667 users of 190k+ on my site). Don't have a solution cause I can't test whether it works, we might try String.concat.

"".concat(obj); // "42"
"".concat(obj, ""); // "hello"
String.prototype.concat(obj, "") // "hello"
String.prototype.concat("", obj) // "hello"

But no idea if String.concat works, also it behaves odd with regards to valueOf.

http://jsperf.com/js-string-concat-variants

Perf is also inconclusive since Firefox appears to be cheating.

Comment by Francis Avila [ 10/Jan/15 2:04 PM ]

Tested that jsperf with Safari 6.0.5 using Browserstack, results are there.

Note I could not reproduce CLJS-847 because str-tostr does not fail as expected. I will try harder now that I have a browser to test.

Comment by Francis Avila [ 10/Jan/15 6:55 PM ]

Still cannot reproduce CLJS-847.

This script includes my attempt at a minimum reproducible case. My theory was that certain types at higher jit levels would fail. I could not get any case to fail. I also tried flapping back and forth between types and using one type at a time, but still no failures.

In this thread I found this "minimal" script which the OP said he could get to fail reliably. I could not get it to fail. However the original post was from feb 15, 2013, which means the Safari he was using would have to be 6.0.2 or lower.

Hypotheses:

  1. This error does not affect 6.0.5 but maybe 6.0.4 or lower.
  2. BrowserStack's system somehow mitigates the bug, meaning we need a "real" Lion Safari 6.0.x to test.
  3. These tests only fail under the correct phase of the moon.

So I can code up a patch for str using the str-switch implementation (which is at least a bit faster on some browsers), but I have no idea if it may fail on Safari 6.0.5. I only know that it works so far. CLJS-801 should also be safe to reapply because the root cause of all issues is the implementation 1-arity of the cljs.core/str function.

I have also asked for Kevin's help back in CLJS-847. (Kevin was the original reporter of the Safari 6.0.x issue.)

Comment by Francis Avila [ 19/Jan/15 12:51 AM ]

Made a jsperf of variadic cases. Chrome seems to really prefer IReduce to seq+stringbuilder for vectors (other collections not tested), but there is no difference or a small slowdown on other browsers. Not sure if it's worth it.

Also updated arity-one cases with a str using switch and never using toString. Nearly 50% slower than using switch or toString on Chrome, smaller on Safari.

In terms of safety str-switch-notostr does not use toString at all so is probably safer. I think str-switch will likely work too, though, and is significantly faster. However I haven't been able to get any TypeErrors in Safari 6.0.5 so it's anyone's guess.

I suggest something like this as a new str (which doesn't use reduce, but could):

(defn str
 ([x]
  (case (js* "typeof ~{}" x)
   "string" x
   "object" (if (identical? nil x) "" (.toString x))
   ("boolean" "number") (js-str x)
   "undefined" ""
   (js-str #js [x])))                                       ;; insurance against Safari 6.0.x TypeError bug.
 ([a b] (js* "~{}+~{}" (str a) (str b)))
 ([a b c] (js* "~{}+~{}+~{}" (str a) (str b) (str c)))
 ([a b c d] (js* "~{}+~{}+~{}+~{}" (str a) (str b) (str c) (str d)))
 ([a b c d & more]
  (loop [s (str a b c d) [e f g h & r] more]
   (let [s' (js* "~{}+~{}+~{}+~{}+~{}" s e f g h)]
    (if (nil? r)
     s'
     (recur s' r))))))
Comment by Francis Avila [ 19/Jan/15 11:24 PM ]

First cut of a possible patch that resolves this while not breaking CLJS-847. Should wait for confirmation that this does not break Safari 6.0.x.

Comment by Francis Avila [ 19/Jan/15 11:34 PM ]

Oops forgot tests.

Comment by Francis Avila [ 03/Feb/15 10:24 AM ]

Update in CLJS-847: original reporter was not able to reproduce his original bug report in Safari 6.0.x running in BrowserStack. This may be because of BrowserStack, but it's the best we have.

Given how hard this bug is to reproduce, how few people it affects, and how significant the performance regression is, I still think we should go back to the simple (if (nil? x) "" (.toString x)) implementation. However, you could also try the patch on this ticket (using a typeof switch), which at least (handwaving) might fix this bug in Safari 6.0.x and is a little faster than a simple .toString in Chrome and not much slower elsewhere. (The reason I think it might avoid this bug in Safari is that it avoids calling .toString on non-Objects.)

Comment by Antonin Hildebrand [ 25/Apr/15 11:08 PM ]

I wonder if you considered swapping str function at runtime during CLJS init phase.

Implement str function using plain .toString() call (original solution). And at startup check for Safari 6.0.x presence and optionally swap str for implementation wrapping .toString() call in a try-catch block silencing TypeError exceptions by falling back to Safari 6.0.x friendly .toString() alternative.

We would get correct semantics in all cases. And price would be just slower printing execution on Safari 6.0.x not on all systems.





[CLJS-797] Nested 'for' loops (3+ deep) in Android Chrome cause "Uncaught RangeError: Maximum call stack size exceeded" Created: 16/Apr/14  Updated: 17/Apr/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: John M. Newman III Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Android 4.3, Chrome 34, ClojureScript 2202



 Description   
(do (println "for loop test: 2 deep")
  (for [a [[1]]]
    (for [b a]
      b)))
;; this compiles and runs fine in the browser

(do (println "for loop test: 3 deep")
  (doall
   (for [a [[[1]]]]
     (for [b a]
       (for [c b]
         c)))))
;; this fails while the page loads, with the error: Uncaught RangeError: Maximum call stack size exceeded

The above works fine in a desktop browser. For some reason the error condition only happens on the Android Chrome browser.

Let me know if any further details are required.






[CLJS-794] RegExp flags are being dropped by `string/replace` Created: 09/Apr/14  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: Peter Taoussanis Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

`clojure.string/replace` accepts either a string or pattern argument to match against.

For pattern arguments, the current implementation discards the original RegExp and creates a new one:
`(.replace s (js/RegExp. (.-source match) "g") replacement)`

This is killing any flags on the original pattern (case insensitivity, for example). As a result, things like `(str/replace "Foo" #"(?i)foo" "bar")` currently fail. The result is "Foo", it should be "bar".

Can I submit a patch that'll check for and preserve other (i/m/y) flags?

Thanks



 Comments   
Comment by David Nolen [ 02/Dec/14 5:42 AM ]

A patch is welcome for this. Thanks.

Comment by lvh [ 27/Jul/15 11:55 AM ]

This appears to be identical to CLJS-485, which has a patch (by someone who hasn't signed the CLA yet).





[CLJS-776] re-matches is incorrect Created: 28/Feb/14  Updated: 02/Dec/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Francis Avila Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

The re-matches function does not have the correct semantics: it performs a search (not match) against the string and returns nil if the string and matched-string are unequal. This is not the same as true matching, which is like inserting "^" and "$" at the beginning and end of the pattern.

Example in Clojure:

user=> (re-find #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1")
"0"
user=> (re-matches #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1")
"0x1"

Compare Clojurescript:

ClojureScript:cljs.user> (re-find  #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1")
"0"
ClojureScript:cljs.user> (re-matches #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1")
nil

This bug is (one of the) reasons why CLJS-775.

I'm not completely sure what to do here. My first thought is to have re-matches inspect the -source property of its regex input, wrap the string with "^$", then carefully copy all flags over to a new regexp.

Questions:

  1. Are there any valid patterns where this is not safe? E.g., where we could not put ^ first? Is "^^abc$$" ok?
  2. Can we avoid cloning if ^ and $ are already the first and last chars of the pattern?
  3. How does multiline mode play in to this, if at all?
  4. regexinstance.lastIndex is a piece of mutability on regex instances (or the RegExp global on older browsers) which is used as a string offset for multiple invocations of exec() on the same string. I have no idea what to do if re-* gets a regex with the global flag set. (BTW, this is a very good reason to reject CLJS-150: allowing clojure to accept the global flag makes regular expression objects stateful, and would completely screw up re-seq for example.)


 Comments   
Comment by Francis Avila [ 24/Jun/14 7:37 AM ]

I would like to propose a somewhat radical suggestion that would: fix this issue and CLJS-810, put us in a better position to resolve CLJS-485 CLJS-746 CLJS-794 (clojure.string/replace woes), allow us to add some regex-as-a-value niceties to patterns in js (CLJS-67 and CLJS-68), and bring clojurescript's regular expression handling closer to clojure's by implementing more of the re-* functions.

Example implementation (not a patch) at this cljsfiddle: http://cljsfiddle.net/fiddle/favila.regexp

Essential points:

  1. Create a Pattern object, created by re-pattern, which provides methods to create regexps for search (re-find) or exact match (re-matches) or repeated searches (re-seq, re-matcher + re-find). Each of these must be a different RegExp object in javascript even though they are similar regular expression strings. The re-find and re-matches patterns can be cached. All can generate RegExps lazily.
  2. regular expression literals emit these Pattern objects instead of RegExp objects.
  3. Create a Matcher object to correspond to the currently-unimplemented re-matcher. It combines a global-flagged RegExp object, a search string, and a done flag. If it keeps the last match (similar to java), cljs can also implement re-groups.
  4. Make re-seq use the Matcher object and thus the .lastIndex that native RegExps provide for global matches. (Its implementation no longer requires string slicing after every match.)
  5. If re-find is given a native RegExp object instead of a pattern, it will use it as-is. This matches current behavior.
  6. If re-matches is given a native RegExp object and it isn't suitable for exact-matching, a new RegExp is cloned from the input RegExp with ^ and $ prepended and appended and the global flag added. (This technique is used in clojure.string/replace, but incorrectly.)

Thoughts?

Comment by David Nolen [ 02/Dec/14 5:46 AM ]

This sounds interesting but I'm somewhat concerned about the interop story. I think people will expect functions to take regular RegExps as well as Pattern. You haven't mentioned this issue here?

Comment by Francis Avila [ 02/Dec/14 1:16 PM ]

I apologize if some of my thoughts are vague; I haven't thought about this in a while.

First note that a narrow class of RegExps are effectively "pure": If they do a full-string match (e.g. start with ^ and end with $) and have the global flag set to false then their lastIndex will always seem to be 0.

Interop possibilities:

  • Patterns and RegExps can be created from one another, so coercion is always an option. E.g. re-pattern can accept a RegExp and some other (cljs-specific) function can coerce from Pattern or Matcher to RegExp. (Or maybe re-matcher can return a RegExp-compatible object--see below.)
  • RegExp given to cljs re-*: "Pure" regexes can be used directly, otherwise we create a Pattern and/or Matcher. (I don't remember the details, but the fiddle should cover them.)
  • Pattern used as a RegExp: Patterns can expose all the properties of RegExp instances. If the pattern is pure, it can implement .test and .exec. .lastIndex will always be 0. Not sure what to do about impure patterns: throw exception, act pure anyway, return a new object?
  • Matcher used as a RegExp: A Matcher can exactly replicate a RegExp instance, maybe even use the same prototype. Using it like a RegExp will mutate the object and disturb its internal state, but as long as it's either used consistently as a RegExp or consistently as a Matcher this won't matter. Notes:
    • Matcher holds the matched string in Java. Javascript trusts you to always supply the same string (e.g. in a while loop).
    • Java's Matcher holds the last match (used by re-groups). Javascript's RegExp does not.
    • Javascript's RegExp will automatically reset when lastIndex reaches the end of the source string. Java's Matcher won't.
    • Matcher must be a wrapper and not a normal RegExp because of these three extra bits of state.
    • The return value of re-matcher is only consumed by the 1-arg form of re-find and re-groups.
    • re-seq can use a matcher internally, but since it is private it doesn't have to.
    • Should other Java methods of Matcher be implemented?
  • Pattern given to String.prototype.match, .replace, .search, and .split: I haven't thought about this. Considerations:
    • Problem code is any cljs code using an object created via pattern literals or re-pattern and using it as an argument to these String methods. If they use clojure.string methods instead they would be fine.
    • Such code is also impossible in java clojure: only (.split s "pattern-str") is the same in java/clj and js/cljs and it will continue to work (without flags) on both platforms. Possibly we could just make people fix such code since it is platform-specific, but I need to see how widespread this is.
    • The fix for such code is to either:
      • Use a pattern->regexp coercion function we will provide.
      • Construct those regexps directly with js/RegExp.
      • Use clojure.string functions instead of String methods. This also has the advantage of being portable between clj and cljs.
    • Possibly we can patch the RegExp constructor or mess with the String prototype chain to do pattern->regexp coercion automatically, but this is a violent solution.

Correct me if I'm wrong, but in Clojure (java) code it is extremely uncommon to use Pattern and Match methods or to use them with String methods directly. For the most part they are treated as opaque objects used via re-* and clojure.string/*. Code written in the same style in cljs would be unaffected, and we can declare any other use as platform-specific and require explicit creation of RegExps (and don't bother to make Matcher or Pattern act like RegExps). This is my preferred approach for interop if there isn't too much use of RegExp.prototype.test, .exec, and String.prototype.match, .replace, .search, and .split.





[CLJS-746] clojure.string/replace pattern/function of match API difference with clojure version Created: 10/Jan/14  Updated: 10/Jan/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Curtis Gagliardi Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Environment:

[org.clojure/clojurescript "0.0-2138"]



 Description   

When calling clojure.core/replace with a pattern and a function, the Clojurescript version delegates to Javascript's s.replace method, which calls that function with a variable number of arguments, depending on how many match groups are in your pattern. The Clojure version always calls it with a single argument, which may be a vector if you have match groups in your pattern.

I'm not sure if this was intentional. If it wasn't, I think this difference could be fixed through some use of re-find, which appears to return the same string or vector that you'd get in Clojure. If this is intentional for performance reasons, perhaps the doc string should be updated to note this, as there's no warning that the function is being called with too many arguments.



 Comments   
Comment by Curtis Gagliardi [ 10/Jan/14 1:32 AM ]

Afraid I don't see how to edit, but I wanted to include a simple example:

CLJS:
(clojure.string/replace "hello world" #"(hello) world" (fn [m] (.log js/console (str "Match: " m)) m))

will log: "Match: hello world"

CLJ
user=> (clojure.string/replace "hello world" #"(hello) world" (fn [m] (println (str "Match: " m) m)))
Match: ["hello world" "hello"] [hello world hello]

NullPointerException java.util.regex.Matcher.quoteReplacement (Matcher.java:655)





[CLJS-744] ISequential types should implement JS indexOf, lastIndexOf Created: 05/Jan/14  Updated: 05/Aug/15

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: 1.7.48
Fix Version/s: Next

Type: Defect Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None





[CLJS-736] Functions folder and reducer broken for types nil and array + fix for typo Created: 29/Dec/13  Updated: 02/Dec/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Jonas De Vuyst Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File CLJS-736-alt.patch     Text File CLJS-736.patch     Text File CLJS-736-patch-1-redux.patch     Text File CLJS-alt-satisfies.patch    
Patch: Code and Test

 Description   

1. This currently doesn't work:

(->> nil
(r/map identity)
(r/reduce + 0))
; org.mozilla.javascript.JavaScriptException: Error: No protocol method IReduce.-reduce defined for type null

The reason for this is that reducers created by r/reducer or r/folder, invoke -reduce (of IReduce) directly. They thereby bypass the special case for nil in the function r/reduce.

2. An entirely analogous problem exists for collections of type array.

3. The patch CLJS-700 mistakenly defined coll-fold for the type cljs.core/IPersistentVector. This should have been cljs.core/PersistentVector. (There exists no protocol IPersistentVector in ClojureScript.)

I will shortly attach a patch that addresses all of the above problems by implementing IReduce for nil and array. The patch also includes unit tests.



 Comments   
Comment by Jonas De Vuyst [ 29/Dec/13 2:22 PM ]

Alternative patch in which r/reduce and r/fold treat arrays and nil as special cases – as opposed to having arrays and nil implement IReduce and CollFold.

The functions r/reducer, r/folder, and the protocol methods of r/Cat now call r/reduce and r/fold instead of calling -reduce and coll-fold directly.

This patch also fixes a bug in the coll-fold implementation for Cat, which previously used (reducef) as the initial value rather than (combinef). The new code is copied and pasted from the Clojure implementation and uses the fork-join stubs.

Comment by David Nolen [ 30/Dec/13 8:23 AM ]

The implements? should probably be a satisfies? in the second patch. Have you run any benchmarks of before/after the patch?

Comment by Jonas De Vuyst [ 30/Dec/13 11:24 AM ]

If I understand correctly then (satisfies? x y) is roughly equivalent to (or (implements? x y) (natively-satisfies? x y)).

If native types (nil, array, object currently) are treated as special cases then implements? seems more appropriate.

satisfies? works also, however, so I have attached a new 'alt' patch.

Comment by Jonas De Vuyst [ 30/Dec/13 11:26 AM ]

The first patch is in fact faster when running the following code:

(time (->> (repeat 1000 (vec (range 1000)))
vec
(r/mapcat identity)
(r/map inc)
(r/filter even?)
(r/fold +)))

This takes about 700 msecs. Using the first patch this terminates 100-300 msecs faster. This is after repeated (but informal) testing.

I guess the worry is that the first patch would slow down other random code since it involves extending the types nil, array, and object. I'm not sure what exactly I should test for though.

(Note that the 2nd and 3rd patch also contain a fix for Cat and include more unit tests. The first patch should preferably not be applied as-is.)

Comment by David Nolen [ 30/Dec/13 11:35 AM ]

Yeah you're timing too many things, including vec, range, lazy sequences. Also testing a small N. Take a look at the reducers example on the Mori README - https://github.com/swannodette/mori. Thanks.

Comment by Jonas De Vuyst [ 30/Dec/13 12:52 PM ]

I tried running the following code:

(let [coll (vec (repeat 1000 (vec (range 10))))]
  (time (doseq [n (range 1000)]
               (->> coll
                    (r/mapcat identity)
                    (r/map inc)
                    (r/filter even?)
                    (r/fold +)))))

Some of the last results I got were:

1st patch: 75680 msecs
2nd patch: 76585 msecs

Truth be told, although the first patch seemed to win most of the times, sometimes the second patch was faster.

One other thing I tried was removing the implements?/satisfies? check from the second patch and overriding the protocol method coll-fold for the type object instead (as in the first patch). This 'hybrid' approach generally (but not always) seemed to result in a slowdown.

I'm not sure how I should proceed. Should I perhaps just run both patches simultaneously for several minutes?

Comment by David Nolen [ 30/Dec/13 1:21 PM ]

This is still a bad way to do timing, you're recording the cost of range and seq'ing. Use dotimes.

Comment by Jonas De Vuyst [ 30/Dec/13 4:33 PM ]

Hm. I guess the lazy sequence does lead to a lot of allocations.

Alright, I rewrote my test and ran it a few more times. I now also tested on both vectors and arrays.

Patch 1 needed a slight tweak. When coll-fold is invoked, patch 1 only specifies a fallback for type object (i.e. r/reduce is called). I had to add the same fallback for type array. (This is weird!)

So here are the results.

For vectors:

(let [coll (vec (repeat 100 (vec (range 100))))]
  (time (dotimes [n 3000]
          (->> coll
              (r/mapcat identity)
              (r/map inc)
              (r/filter even?)
              (r/fold +)))))

Patch 1: 205872 msecs
Patch 2: 210756 msecs

For arrays:

(let [coll (into-array (repeat 100 (into-array (range 100))))]
  (time (dotimes [n 3000]
          (->> coll
              (r/mapcat identity)
              (r/map inc)
              (r/filter even?)
              (r/fold +)))))

Patch 1: 123567 msecs
Patch 2: 119704 msecs

I ran my tests a few times and the results were pretty consistent. Patch 1 is faster for vectors and patch 2 is faster for arrays.

This makes sense.

In patch 1 reducer will call -reduce directly. In patch 2, reducer first calls r/reduce, which calls -reduce if the collection is a vector and array-reduce if it's an array. Hence patch 2 contains an extra function call in the case of vectors, but avoids invoking a protocol method on a native type in the case of arrays.

Using macros (or copy and paste) the extra function call can be avoided. Would that be worth trying or is it more important to keep the code clean?

I just realized that patch 2 is semantically slightly different from what Clojure does, although perhaps this is a bug in Clojure: <https://groups.google.com/forum/#!searchin/clojure-dev/kv-reduce/clojure-dev/bEqECvbExGo/iW4B2vEUh8sJ>. My suggestion to use a macro (or copy and paste) to avoid the extra function call in patch 2, could also fix this discrepancy.

Comment by David Nolen [ 30/Dec/13 4:42 PM ]

How are you benchmarking this? With V8? JavaScriptCore? SpiderMonkey? In the browser? What optimization settings, etc.

Comment by Jonas De Vuyst [ 30/Dec/13 4:48 PM ]

I used repljs (Rhino?). I'll test again in a more realistic setting tomorrow.

Comment by David Nolen [ 30/Dec/13 4:54 PM ]

Yeah, benchmarking with Rhino isn't informative.

Comment by Jonas De Vuyst [ 31/Dec/13 1:40 AM ]

I compiled the same code (with n=3000) using cljs with "{:optimizations :advanced}".

I then tested it in the latest stable releases of Firefox, Chrome, and Safari. I closed all my browsers. For each browser I then followed the following procedure:

  • Open the browser
  • Open the developer console
  • Run the benchmark for patch 1
  • Run the benchmark for patch 2
  • Run the benchmark for patch 1 and write down the result
  • Run the benchmark for patch 2 and write down the result
  • Close the browser

Firefox:

  • Patch 1. Vectors: 26057 msecs
  • Patch 1. Arrays: 25026 msecs
  • Patch 2. Vectors: 26258 msecs
  • Patch 2. Arrays: 36653 msecs
  • Summary: Patch 1 is faster for vectors and arrays

Chrome:

  • Patch 1. Vectors: 7804 msecs
  • Patch 1. Arrays: 7092 msecs
  • Patch 2. Vectors: 7754 msecs
  • Patch 2. Arrays: 6768 msecs
  • Summary: Patch 2 is faster for vectors and arrays

Safari:

  • Patch 1. Vectors: 167230 msecs
  • Patch 1. Arrays: 108780 msecs
  • Patch 2. Vectors: 173940 msecs
  • Patch 2. Arrays: 110012 msecs
  • Summary: Patch 1 is faster for vectors and arrays

I'm not sure what to make of this.

Comment by Jonas De Vuyst [ 31/Dec/13 2:47 AM ]

I have attached a new version of the first patch.

This patch fixes an issue with r/Cat. (This issue was also addressed in the second and third patch. A unit test is included.).

This patch also fixes r/fold for arrays.

To summarize, a choice needs to be made between the following patches.

  • CLJS-736-patch-1-redux.patch
  • CLJS-736-alt.patch (uses implements?) / CLJS-alt-satisfies.patch (uses satisfies?)

The implementation details are patch-1-redux is more similar in spirit to the Clojure source code. The alt patches are more similar in spirit to the ClojureScript source code.

As explained above, the alt patches are semantically a bit different from the original Clojure source—but it's not clear which behavior is 'right'.

Comment by David Nolen [ 16/Jan/14 5:27 PM ]

The benchmarks would be more informative if they explained the performance before and after that patch.

Comment by Jonas De Vuyst [ 18/Jan/14 11:55 AM ]

r/reduce previously didn't work for nil or JavaScript arrays.

One reason why I have trouble recommending a patch is that I don't know what use case you would like to optimize for.

Comment by David Nolen [ 18/Jan/14 12:30 PM ]

Yes but now that we have new logic we can at least test the lack of regression on the other types.

Comment by David Nolen [ 18/Jan/14 12:40 PM ]

Ok I tried to apply this patch and run ./script/benchmarks in the repo but the patch will no longer apply. Can we rebase the patch on master. Thanks. If you also want to give the benchmarks a shot follow these instructions to install the JS engines - http://github.com/clojure/clojurescript/wiki/Running-the-tests. Then you can also run the benchmarks at the command line. I see there aren't any reducers benchmarks, I will add some.





[CLJS-720] #queue literal behavior is incorrect Created: 07/Dec/13  Updated: 07/Dec/13

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

In order for queue to work we need to adopt an approach similar to the one for #js data literals - i.e. needs special casing in the analyzer since queues are not "atomic" values.






[CLJS-693] new and dot form work on locals Created: 20/Nov/13  Updated: 02/Dec/13

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Joshua Headapohl Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None


 Description   

Given some JavaScript library included in the same page as CLJS output:

var Lib = {};

Lib.Thing = function (val) {
    this.val = val;
};

Lib.Thing.prototype.log = function () {
    console.log(this.val);
};

I can bind the Thing property to a local and construct it using new or the dot form. This code compiles and runs without errors, and logs three lines to the console.

(ns cljs-construct-locals-bug.core)

; Legit
(let [thing (new js/Lib.Thing "hello")]
  (.log thing))

; Questionable
(let [Thing (.-Thing js/Lib)
      thing1 (new Thing "maybe")
      thing2 (Thing. "no way")]
  (.log thing1)
  (.log thing2))

I talked to David Nolen and he said this behavior is not correct.



 Comments   
Comment by David Nolen [ 02/Dec/13 8:58 PM ]

Upon further consideration this probably requires some feedback from the community. I'd forgotten that (Thing. foo bar ...) is just sugar for (new Thing foo bar ...) ...





[CLJS-677] cljs.reader doesn't support keywords starting with a digit Created: 12/Nov/13  Updated: 02/Jul/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Chas Emerick Assignee: Unassigned
Resolution: Unresolved Votes: 3
Labels: None


 Description   
ClojureScript:foo> (r/read-string ":0")
"Error evaluating:" (r/read-string ":0") :as "cljs.reader.read_string.call(null,\":0\")"
org.mozilla.javascript.EcmaError: TypeError: Cannot read property "0.0" from null (file:/home/chas/dev/clojure/cljs/.repl/cljs/reader.js#451)

The topic of leading digits in keywords came up separately, as they've been supported in Clojure for some time, but can now be considered part of the spec, as it were. See CLJ-1286.

BTW, this is another simple-check win...



 Comments   
Comment by Chas Emerick [ 21/Nov/13 9:38 AM ]

This is not a simple regex change, as I had hoped given the recent flurry in Clojure. The symbol pattern in cljs.reader is faithful to Clojure HEAD, but the processing of matches isn't. I think it may be a wash as to whether it'd be easier to fix what's there vs. porting clojure.tools.reader.impl.commons/parse-symbol (which incidentally doesn't use a regex)…either way, leaving it for another day (or someone else, if they're up for it).

Comment by Francis Avila [ 02/Jul/14 12:35 AM ]

I think I fixed the match processing issue you're talking about (CLJS-775 CLJS-776)? However I'm still confused by this and CLJ-1286. The clojure reader docs and edn spec still say they should reject `:0`, but 1.6.0 doesn't. What's the expected behavior? Is the spec going to be fixed, or clojure reader fixed once downstream packages are fixed?

Comment by Jozef Wagner [ 02/Jul/14 1:50 AM ]

AFAIK EDN specs do not reject :0 (no rule that the second character cannot be a digit). See https://github.com/wagjo/serialization-formats for my interpretation of existing specs.

Comment by Francis Avila [ 02/Jul/14 1:35 PM ]

Ah, I think I see the source of the confusion. Both EDN and the clojure reader spec both say something like "keywords are like symbols, except beginning with a colon." The confusion lies in whether we interpret that as meaning

  1. First character is a colon, then the second character and after are matched against the symbol definition.
  2. The first character is a colon, and the whole form is matched against the symbol definition.

CLJ-1003 CLJ-1252 and CLJ-1286 and myself all seem to understand the first meaning. This might be because when we say "the first character of a keyword" we typically mean the first character after the colon, as if the colon is "special" and not part of the keyword (e.g. like a reader macro character).

However clojure 1.6 seems to be following the second meaning (and explains why `:0/a` is ok but not `:0/0`), and I'm not sure from the cited tickets and google group discussions whether this is because of downstream breakage or if this is the intended interpretation and the patch from CLJ-1252 was accepted by Alex Miller erroneously.

Note if we accept the second interpretation, then the restriction "A symbol can contain one or more non-repeating ':'s." from the clojure reader docs is incorrect for keywords. (EDN doesn't allow namespace-expanded keywords, it seems, so it's not an issue there.)

Also EDN allows contiguous colons in symbols, whereas clojure 1.6 and the reader spec do not.

Comment by Francis Avila [ 02/Jul/14 2:11 PM ]

Also clojure 1.6 allows a/:a and :a/:a (where name part violates first-character rule for symbols), even though the specs do not. (This is something your table doesn't mention. Very thorough work BTW! I wish the reader spec was more formalized and unambiguous...)

Comment by Francis Avila [ 02/Jul/14 3:08 PM ]

I think this pattern follows the specs:

#"(?x)
(?!///) # edge case: / only allowed in name part.
# name or namespace part of symbol or keyword
(?:
 #division symbol
 (/
 # normal symbol
 |[a-zA-Z*!_?$%&=<>][0-9a-zA-Z*!_?$%&=<>\#:+.-]*
 # symbol starting with [-+.]
 |[-+.](?:[a-zA-Z*!_?$%&=<>\#:+.-][0-9a-zA-Z*!_?$%&=<>\#:+.-]*)?)
 # keyword
 |(::?)([0-9a-zA-Z*!_?$%&=<>\#:+.-]+))
# name part when namespace is present
(?:/(/ # division symbol
    |[a-zA-Z*!_?$%&=<>][0-9a-zA-Z*!_?$%&=<>\#:+.-]*
    |[-+.](?:[a-zA-Z*!_?$%&=<>\#:+.-][0-9a-zA-Z*!_?$%&=<>\#:+.-]*)?))?
# groups:
# 1: symbol name or namespace 2: keyword colon(s) 3: keyword name or namespace
# 4: keyword or symbol name (and groups 1 and 3 are namespaces)"

Problems:

  1. Does not enforce no-repeating-colon rule (but it is easy to validate after matching).
  2. Rejects violations of first-character-rule in symbols which clojure accepts.
  3. Accepts a trailing colon on namespace (unlike clojure).
  4. Accepts foo// or :foo//, which are not clearly addressed by the specs. (Jozef's table has more background). These are both allowed in Clojure 1.6, but not 1.5 or (arguably) edn.
Comment by Francis Avila [ 02/Jul/14 6:28 PM ]

Another problem: Accepts :::a/b, which I think is ok per the specs but is not read by 1.6. Crazy example:

user=> (require ['clojure.core :as (symbol ":a")])
nil
user=> :::a/map

RuntimeException Invalid token: :::a/map  clojure.lang.Util.runtimeException (Util.java:221)
user=> (resolve (symbol ":a" "map"))
#'clojure.core/map

Theoretically I might expect :::a/map to be read as :clojure.core/map?





[CLJS-651] optimize true branch of satisfies? usage Created: 01/Nov/13  Updated: 16/Dec/14

Status: Open
Project: ClojureScript
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Enhancement Priority: Minor
Reporter: David Nolen Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File cljs_651.patch    
Patch: Code