jgpc42 / insn

Functional JVM bytecode generation for Clojure.
Eclipse Public License 1.0
198 stars 5 forks source link

Minimal example getting errors (newb) #3

Closed joinr closed 3 years ago

joinr commented 3 years ago

Thanks for this library, it's really opened up my eyes toward custom jvm bytecode magic.

I'm just playing around with some simple class definitions. One I'd like is to work with a primitive array, and int index, and a primitive value, and update the array at index with the value (basically a clone of clojure.core/aset without any int cast).

If I write a minimal java class, I get this:

public class demo
{public static char aset(char [] arr, int idx, char v)
  {arr[idx] = v;
   return v;
   }}

public class blah.demo
{public blah.demo();
 Code:
 0: aload_0
 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 4: return

 public static char aset(char[], int, char);
 Code:
 0: aload_0
 1: iload_1
 2: iload_2
 3: castore
 4: iload_2
 5: ireturn
 }

Translating to insn, I come up with:

(ns ultrarand.bytecode
  (:require [insn.core :as insn]))

(def class-data
  {:name    'my.pkg.Adder
   :flags  #{:public}
   :fields  []
   :methods [{:flags #{:public :static}, :name "asetLiteral", :desc [chars :int :char]
              :emit [[:aload 0]
                     [:iload 1]
                     [:iload 2]
                     [:castore]
                     [:iload 2]
                     [:ireturn]]}]})

Then I get a complaint from the classloader regarding incompatibility of types:

ultrarand.bytecode> (def class-object (insn/define class-data2))
ultrarand.bytecode> (def c (.newInstance class-object))
Execution error (VerifyError) at java.lang.Class/getDeclaredConstructors0 (Class.java:-2).
Bad local variable type
Exception Details:
  Location:
    my/pkg/Adder.asetLiteral([CI)C @2: iload_2
  Reason:
    Type top (current frame, locals[2]) is not assignable to integer
  Current Frame:
    bci: @2
    flags: { }
    locals: { '[C', integer }
    stack: { '[C', integer }
  Bytecode:
    0x0000000: 2a1b 1c55 1cac    

If I try to dumb it down to the simplest possible return, a method that takes an int array, an int, and an int, and returns the last arg (int):

public class blah.demo
{public blah.demo();
 Code:
 0: aload_0
 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 4: return

 public static int aset(int[], int, int);
 Code:
 0: iload_2
 1: ireturn}

(def class-data2
  {:name    'my.pkg.Adder
   :flags  #{:public}
   :fields  []
   :methods [{:flags #{:public :static}, :name "asetLiteral", :desc [ints :int :int]
              :emit [[:iload 2]
                     [:ireturn]]}]})

ultrarand.bytecode> (def class-data2
  {:name    'my.pkg.Adder
   :flags  #{:public}
   :fields  []
   :methods [{:flags #{:public :static}, :name "asetLiteral", :desc [ints :int :int]
              :emit [[:iload 2]
                     [:ireturn]]}]})
#'ultrarand.bytecode/class-data2
ultrarand.bytecode> (def class-object (insn/define class-data2))
#'ultrarand.bytecode/class-object
ultrarand.bytecode> (def c (.newInstance class-object))
Execution error (VerifyError) at java.lang.Class/getDeclaredConstructors0 (Class.java:-2).
Bad local variable type
Exception Details:
  Location:
    my/pkg/Adder.asetLiteral([II)I @0: iload_2
  Reason:
    Type top (current frame, locals[2]) is not assignable to integer
  Current Frame:
    bci: @0
    flags: { }
    locals: { '[I', integer }
    stack: { }
  Bytecode:
    0x0000000: 1cac    

Am I missing something in translating the bytecode to insn? It seems straightforward enough...

Platform:

Windows 10, openjdk version "1.8.0_222" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_222-b10) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.222-b10, mixed mode)

Dependencies: (the insn and asm stuff was cloned from tech.datatype, so I may be out of date!) [[org.clojure/clojure "1.10.1"] [com.clojure-goes-fast/clj-java-decompiler "0.3.0"] [insn "0.4.0" :exclusions [org.ow2.asm/asm]] [org.ow2.asm/asm "7.1"]]

jgpc42 commented 3 years ago

Hi! Thanks for the question. Unlike Java, method descriptors (via :desc) specify the return value as the last element. Your method descriptor vectors are missing the return value:

(require '[insn.core :as core])

(def a (into-array Character/TYPE "foo"))

(core/new-instance
 {:name "test.Klass"
  :flags  #{:public}
  :methods [{:flags #{:public :static}, :name "asetLiteral", :desc [chars :int :char :char]  ;; <-- here
             :emit [[:aload 0]
                    [:iload 1]
                    [:iload 2]
                    [:castore]
                    [:iload 2]
                    [:ireturn]]}]})

[(test.Klass/asetLiteral a 0 \b)
 (seq a)] 
;; => [\b (\b \o \o)]
joinr commented 3 years ago

Awesome! thanks