Welcome! Please see the About page for a little more info on how this works.

+3 votes
in Clojure by

Hey folks - I seem to be getting a locals clearing issue with a type-hinted vs non-type-hinted letfn:

(defn- letfn-prim []
  (letfn [(f [coll ^long n]
            )]
    (f (take 3 (range)) 0)))

(defn- letfn-noprim []
  (letfn [(f [coll n]
            )]
    (f (take 3 (range)) 0)))

(in practice, I'm lazily processing coll within f and wanted to avoid retaining the head)

The noprim variant clears the coll param correctly; the prim version doesn't seem to.

(I'm aware letfn doesn't fully support type-hints, but this one seems to be more than just an unexpected boxing, and affects more than just the hinted variable)

In both cases, the caller is calling f.invoke(Object) (invokeinterface clojure/lang/IFn.invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;), but the difference comes in the f function. (Arguably the hinted version could call invokePrim directly, but as I say, aware letfn doesn't fully support type-hints)

In the unhinted case, invoke is trivial:

  public java.lang.Object invoke(java.lang.Object, java.lang.Object);
    Code:
       0: aconst_null
       1: areturn

(adding more of a body clears the param as soon as it can)

but in the hinted case, we get this:

  public final java.lang.Object invokePrim(java.lang.Object, long);
    Code:
       0: aconst_null
       1: areturn

  public java.lang.Object invoke(java.lang.Object, java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: aload_2
       3: checkcast     #22                 // class java/lang/Number
       6: invokestatic  #28                 // Method clojure/lang/RT.longCast:(Ljava/lang/Object;)J
       9: invokeinterface #30,  4           // InterfaceMethod clojure/lang/IFn$OLO.invokePrim:(Ljava/lang/Object;J)Ljava/lang/Object;
      14: areturn

The invoke calls through to invokePrim as normal, but it doesn't clear its locals before doing so. compare to

(defn- defn-prim [coll ^long n]
  )

whose bytecode does clear the object in its invoke method:

  public static java.lang.Object invokeStatic(java.lang.Object, long);
    Code:
       0: aconst_null
       1: areturn

  public java.lang.Object invoke(java.lang.Object, java.lang.Object);
    Code:
       0: aload_1
       1: aconst_null
       2: astore_1      // <--- local cleared here
       3: aload_2
       4: checkcast     #21                 // class java/lang/Number
       7: invokestatic  #27                 // Method clojure/lang/RT.longCast:(Ljava/lang/Object;)J
      10: invokestatic  #29                 // Method invokeStatic:(Ljava/lang/Object;J)Ljava/lang/Object;
      13: areturn

  public final java.lang.Object invokePrim(java.lang.Object, long);
    Code:
       0: aload_1
       1: aconst_null
       2: astore_1
       3: lload_2
       4: invokestatic  #29                 // Method invokeStatic:(Ljava/lang/Object;J)Ljava/lang/Object;
       7: areturn

Cheers!

James

Please log in or register to answer this question.

...