|
| 1 | +(ns cljwit.spike.host-resource |
| 2 | + "Can the host *define* a resource the guest imports? |
| 3 | +
|
| 4 | + Every `wasi:io` interface hands out a handle, so this is the shape `0017` F |
| 5 | + says is inside the host-import unit rather than after it. A map keyed |
| 6 | + `iface#func` cannot express a resource, and minting one needs a second |
| 7 | + upcall shape — a destructor `void (*)(void *, wasmtime_context_t *, u32)`. |
| 8 | +
|
| 9 | + **This does not work yet, and is committed for how far it gets.** Run it |
| 10 | + with |
| 11 | +
|
| 12 | + clojure -M:dev -m cljwit.spike.host-resource target/hres.component.wasm |
| 13 | +
|
| 14 | + and the JVM crashes inside libwasmtime's `error::source`. It is deliberately |
| 15 | + *not* a `bb` task, because a task that aborts the JVM is worse than none. |
| 16 | +
|
| 17 | + What is narrowed, by printing from inside each upcall: |
| 18 | +
|
| 19 | + * `add_resource`, both `add_func`s and `instantiate` all succeed. |
| 20 | + * `mint` runs; `host_to_any` returns no error. |
| 21 | + * `peek` runs; `any_to_host` returns no error and the rep comes back. |
| 22 | + * the destructor upcall runs, with the right rep. |
| 23 | + * the crash is *inside* `wasmtime_component_func_call`, after the |
| 24 | + destructor and before it returns. |
| 25 | +
|
| 26 | + So every piece works and the composition does not. The leading suspect is |
| 27 | + ownership of the `any` that `host_to_any` produces: this code writes it into |
| 28 | + the result val and also deletes the `host_t` it came from, and nothing here |
| 29 | + has established which of those wasmtime expects." |
| 30 | + (:require [clojure.java.io :as io]) |
| 31 | + (:import [java.lang.foreign Arena Linker SymbolLookup MemorySegment |
| 32 | + FunctionDescriptor ValueLayout] |
| 33 | + [java.lang.invoke MethodHandle MethodHandles MethodType])) |
| 34 | + |
| 35 | +(set! *warn-on-reflection* true) |
| 36 | + |
| 37 | +(def ^:private ^java.lang.foreign.AddressLayout ADDR ValueLayout/ADDRESS) |
| 38 | +(def ^:private ^java.lang.foreign.ValueLayout$OfByte I8 ValueLayout/JAVA_BYTE) |
| 39 | +(def ^:private ^java.lang.foreign.ValueLayout$OfInt I32 ValueLayout/JAVA_INT) |
| 40 | +(def ^:private ^java.lang.foreign.ValueLayout$OfLong I64 ValueLayout/JAVA_LONG) |
| 41 | +(def ^:private VAL 32) |
| 42 | +(def ^:private UNION 8) |
| 43 | +(def ^:private KIND-U32 6) |
| 44 | +(def ^:private KIND-RESOURCE 21) |
| 45 | +(def ^:private TY 7) ; the u32 tag that distinguishes this host type |
| 46 | + |
| 47 | +(definterface Import |
| 48 | + (^java.lang.foreign.MemorySegment |
| 49 | + call [^java.lang.foreign.MemorySegment data ^java.lang.foreign.MemorySegment ctx |
| 50 | + ^java.lang.foreign.MemorySegment ty |
| 51 | + ^java.lang.foreign.MemorySegment args ^long nargs |
| 52 | + ^java.lang.foreign.MemorySegment res ^long nres])) |
| 53 | + |
| 54 | +(definterface Dtor |
| 55 | + (^void call [^java.lang.foreign.MemorySegment data ^java.lang.foreign.MemorySegment ctx |
| 56 | + ^int rep])) |
| 57 | + |
| 58 | +(defn -main [& _args] |
| 59 | + (let [linker (Linker/nativeLinker) |
| 60 | + lib (or (System/getenv "CLJWIT_WASMTIME_LIB") |
| 61 | + (throw (ex-info "CLJWIT_WASMTIME_LIB unset" {})))] |
| 62 | + (with-open [arena (Arena/ofShared)] |
| 63 | + (let [lookup (SymbolLookup/libraryLookup ^String lib arena) |
| 64 | + fx (fn [nm ret & args] |
| 65 | + (let [seg ^MemorySegment (.orElseThrow (.find lookup ^String nm)) |
| 66 | + ls (into-array java.lang.foreign.MemoryLayout args)] |
| 67 | + (.downcallHandle linker seg |
| 68 | + ^FunctionDescriptor |
| 69 | + (if ret (FunctionDescriptor/of ret ls) |
| 70 | + (FunctionDescriptor/ofVoid ls)) |
| 71 | + (into-array java.lang.foreign.Linker$Option [])))) |
| 72 | + call (fn [^MethodHandle mh & as] (.invokeWithArguments mh ^java.util.List (vec as))) |
| 73 | + emsg (fx "wasmtime_error_message" nil ADDR ADDR) |
| 74 | + ok! (fn [what v] |
| 75 | + (when-not (.equals MemorySegment/NULL ^MemorySegment v) |
| 76 | + (let [nm ^MemorySegment (.allocate arena (long 16))] |
| 77 | + (call emsg v nm) |
| 78 | + (let [n (.get nm I64 (long 0)) |
| 79 | + p ^MemorySegment (.get nm ADDR (long 8)) |
| 80 | + b (byte-array n)] |
| 81 | + (MemorySegment/copy (.reinterpret p n) I8 0 b 0 (int n)) |
| 82 | + (throw (ex-info (str what ": " (String. b "UTF-8")) {}))))) |
| 83 | + v) |
| 84 | + cstr (fn [^String t] |
| 85 | + (let [b (.getBytes t "UTF-8") |
| 86 | + s ^MemorySegment (.allocate arena (long (inc (alength b))))] |
| 87 | + (MemorySegment/copy ^bytes b 0 s I8 0 (alength b)) |
| 88 | + (.set s I8 (long (alength b)) (byte 0)) |
| 89 | + s)) |
| 90 | + upcall (fn [obj iface mt desc] |
| 91 | + (.upcallStub linker |
| 92 | + (.bindTo (.findVirtual (MethodHandles/lookup) iface "call" mt) obj) |
| 93 | + desc arena (into-array java.lang.foreign.Linker$Option []))) |
| 94 | + IMPORT-MT (MethodType/methodType MemorySegment ^"[Ljava.lang.Class;" |
| 95 | + (into-array Class [MemorySegment MemorySegment MemorySegment |
| 96 | + MemorySegment Long/TYPE MemorySegment Long/TYPE])) |
| 97 | + IMPORT-FD (FunctionDescriptor/of ADDR (into-array java.lang.foreign.MemoryLayout |
| 98 | + [ADDR ADDR ADDR ADDR I64 ADDR I64])) |
| 99 | + ;; reps the host has hidden behind handles, and what happened to them |
| 100 | + table (atom {}) |
| 101 | + dropped (atom []) |
| 102 | + next-rep (atom 0) |
| 103 | + |
| 104 | + hnew (fx "wasmtime_component_resource_host_new" ADDR ValueLayout/JAVA_BOOLEAN I32 I32) |
| 105 | + hrep (fx "wasmtime_component_resource_host_rep" I32 ADDR) |
| 106 | + hdel (fx "wasmtime_component_resource_host_delete" nil ADDR) |
| 107 | + h2a (fx "wasmtime_component_resource_host_to_any" ADDR ADDR ADDR ADDR) |
| 108 | + a2h (fx "wasmtime_component_resource_any_to_host" ADDR ADDR ADDR ADDR) |
| 109 | + |
| 110 | + engine (call (fx "wasm_engine_new" ADDR)) |
| 111 | + store (call (fx "wasmtime_store_new" ADDR ADDR ADDR ADDR) |
| 112 | + engine MemorySegment/NULL MemorySegment/NULL) |
| 113 | + ctx (call (fx "wasmtime_store_context" ADDR ADDR) store) |
| 114 | + |
| 115 | + mint (reify Import |
| 116 | + (call [_ _d cx _t args _n res _nr] |
| 117 | + (let [v (.get ^MemorySegment (.reinterpret ^MemorySegment args (long VAL)) |
| 118 | + I32 (long UNION)) |
| 119 | + rep (swap! next-rep inc) |
| 120 | + _ (swap! table assoc rep v) |
| 121 | + hr ^MemorySegment (call hnew true (int rep) (int TY)) |
| 122 | + out ^MemorySegment (.allocate arena ^java.lang.foreign.MemoryLayout ADDR)] |
| 123 | + (ok! "host_to_any" (call h2a cx hr out)) |
| 124 | + (call hdel hr) |
| 125 | + (let [o ^MemorySegment (.reinterpret ^MemorySegment res (long VAL))] |
| 126 | + (.set o I8 (long 0) (byte KIND-RESOURCE)) |
| 127 | + (.set o ADDR (long UNION) ^MemorySegment (.get out ADDR (long 0)))) |
| 128 | + MemorySegment/NULL))) |
| 129 | + peek (reify Import |
| 130 | + (call [_ _d cx _t args _n res _nr] |
| 131 | + (let [a ^MemorySegment (.reinterpret ^MemorySegment args (long VAL)) |
| 132 | + any ^MemorySegment (.get a ADDR (long UNION)) |
| 133 | + out ^MemorySegment (.allocate arena ^java.lang.foreign.MemoryLayout ADDR)] |
| 134 | + (ok! "any_to_host" (call a2h cx any out)) |
| 135 | + (let [hr ^MemorySegment (.get out ADDR (long 0)) |
| 136 | + rep (call hrep hr) |
| 137 | + o ^MemorySegment (.reinterpret ^MemorySegment res (long VAL))] |
| 138 | + (call hdel hr) |
| 139 | + (.set o I8 (long 0) (byte KIND-U32)) |
| 140 | + (.set o I32 (long UNION) (int (get @table rep -1)))) |
| 141 | + MemorySegment/NULL))) |
| 142 | + dtor (reify Dtor |
| 143 | + (call [_ _d _cx rep] |
| 144 | + (swap! dropped conj [rep (get @table rep)]) |
| 145 | + (swap! table dissoc rep) |
| 146 | + nil)) |
| 147 | + |
| 148 | + bs (.readAllBytes (io/input-stream (or (first *command-line-args*) |
| 149 | + "target/hres.component.wasm"))) |
| 150 | + buf ^MemorySegment (.allocate arena (long (alength ^bytes bs))) |
| 151 | + _ (MemorySegment/copy ^bytes bs 0 buf I8 0 (alength ^bytes bs)) |
| 152 | + cout ^MemorySegment (.allocate arena ^java.lang.foreign.MemoryLayout ADDR) |
| 153 | + _ (ok! "component_new" (call (fx "wasmtime_component_new" ADDR ADDR ADDR I64 ADDR) |
| 154 | + engine buf (long (alength ^bytes bs)) cout)) |
| 155 | + comp (.get cout ADDR (long 0)) |
| 156 | + clink (call (fx "wasmtime_component_linker_new" ADDR ADDR) engine) |
| 157 | + root (call (fx "wasmtime_component_linker_root" ADDR ADDR) clink) |
| 158 | + iout ^MemorySegment (.allocate arena ^java.lang.foreign.MemoryLayout ADDR) |
| 159 | + iname "local:hres/host@0.1.0" |
| 160 | + _ (ok! "add_instance" (call (fx "wasmtime_component_linker_instance_add_instance" ADDR ADDR ADDR I64 ADDR) |
| 161 | + root (cstr iname) (long (count (.getBytes iname "UTF-8"))) iout)) |
| 162 | + li (.get iout ADDR (long 0)) |
| 163 | + rty (call (fx "wasmtime_component_resource_type_new_host" ADDR I32) (int TY)) |
| 164 | + _ (ok! "add_resource" |
| 165 | + (call (fx "wasmtime_component_linker_instance_add_resource" ADDR ADDR ADDR I64 ADDR ADDR ADDR ADDR) |
| 166 | + li (cstr "token") (long 5) rty |
| 167 | + (upcall dtor Dtor |
| 168 | + (MethodType/methodType Void/TYPE ^"[Ljava.lang.Class;" |
| 169 | + (into-array Class [MemorySegment MemorySegment Integer/TYPE])) |
| 170 | + (FunctionDescriptor/ofVoid (into-array java.lang.foreign.MemoryLayout [ADDR ADDR I32]))) |
| 171 | + MemorySegment/NULL MemorySegment/NULL)) |
| 172 | + addf (fx "wasmtime_component_linker_instance_add_func" ADDR ADDR ADDR I64 ADDR ADDR ADDR) |
| 173 | + _ (ok! "add mint" (call addf li (cstr "mint") (long 4) |
| 174 | + (upcall mint Import IMPORT-MT IMPORT-FD) |
| 175 | + MemorySegment/NULL MemorySegment/NULL)) |
| 176 | + _ (ok! "add peek" (call addf li (cstr "peek") (long 4) |
| 177 | + (upcall peek Import IMPORT-MT IMPORT-FD) |
| 178 | + MemorySegment/NULL MemorySegment/NULL)) |
| 179 | + inst ^MemorySegment (.allocate arena (long 16)) |
| 180 | + _ (ok! "instantiate" (call (fx "wasmtime_component_linker_instantiate" ADDR ADDR ADDR ADDR ADDR) |
| 181 | + clink ctx comp inst)) |
| 182 | + eidx (call (fx "wasmtime_component_instance_get_export_index" ADDR ADDR ADDR ADDR ADDR I64) |
| 183 | + inst ctx MemorySegment/NULL (cstr "run") (long 3)) |
| 184 | + f ^MemorySegment (.allocate arena (long 24)) |
| 185 | + _ (when-not (call (fx "wasmtime_component_instance_get_func" ValueLayout/JAVA_BOOLEAN ADDR ADDR ADDR ADDR) |
| 186 | + inst ctx eidx f) |
| 187 | + (throw (ex-info "no export run" {}))) |
| 188 | + args ^MemorySegment (.allocate arena (long VAL)) |
| 189 | + res ^MemorySegment (.allocate arena (long VAL))] |
| 190 | + (.set args I8 (long 0) (byte KIND-U32)) |
| 191 | + (.set args I32 (long UNION) (int 42)) |
| 192 | + (ok! "func_call" (call (fx "wasmtime_component_func_call" ADDR ADDR ADDR ADDR I64 ADDR I64) |
| 193 | + f ctx args (long 1) res (long 1))) |
| 194 | + (println "run(42) =" (.get res I32 (long UNION)) |
| 195 | + " dropped:" (pr-str @dropped) |
| 196 | + " table now:" (pr-str @table)))))) |
0 commit comments