Skip to main content

tenferro_gpu/webgpu/
exec_session.rs

1use tenferro_tensor::backend::{
2    BackendSession, BackendSessionHost, ElementwiseReadOp, SessionCachedDot, TensorAnalytic,
3    TensorBuffer, TensorDeviceTransfer, TensorDot, TensorElementwise, TensorFusion, TensorIndexing,
4    TensorReduction, TensorStructural,
5};
6use tenferro_tensor::config::{
7    CompareDir, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
8};
9use tenferro_tensor::DType;
10use tenferro_tensor::{with_session_entry_guard, Tensor, TensorRead, TensorWrite};
11
12use super::{
13    gemm, structural, unsupported, unsupported_op, WebGpuBackend, WebGpuRuntime,
14    WebGpuRuntimeIdentity,
15};
16
17/// Native-session marker for [`WebGpuExecSession`]; private to this crate so no other
18/// crate can create a token that claims to be this session.
19pub(super) struct WebGpuExecSessionMarker;
20
21/// Borrowed WebGPU execution capability.
22#[doc(hidden)]
23#[derive(Debug)]
24pub struct WebGpuExecSession<'a> {
25    backend: &'a mut WebGpuBackend,
26}
27
28// Typed view canonicalization runs on the session, never on the backend
29// owner: the owner is not an execution surface (#1946 F6).
30impl tenferro_tensor::TensorViewCanonicalization<f32, tenferro_tensor::DynRank>
31    for WebGpuExecSession<'_>
32{
33    fn to_contiguous(
34        &mut self,
35        view: &tenferro_tensor::TypedTensorView<'_, f32>,
36    ) -> crate::Result<tenferro_tensor::TypedTensor<f32>> {
37        super::structural::to_contiguous_f32(self.backend, view)
38    }
39
40    fn copy_into(
41        &mut self,
42        _src: &tenferro_tensor::TypedTensorView<'_, f32>,
43        _dst: &mut tenferro_tensor::TypedTensorViewMut<'_, f32>,
44    ) -> crate::Result<()> {
45        super::unsupported!("WebGpuExecSession::copy_into")
46    }
47}
48
49impl WebGpuExecSession<'_> {
50    /// Borrow the provider runtime without exposing the owning backend.
51    #[doc(hidden)]
52    pub fn runtime(&self) -> &WebGpuRuntime {
53        self.backend.runtime()
54    }
55
56    /// Return the identity of the borrowed provider runtime.
57    #[doc(hidden)]
58    pub fn runtime_identity(&self) -> WebGpuRuntimeIdentity {
59        self.backend.runtime_identity()
60    }
61}
62
63/// Visit a WebGPU execution session through the erased backend-session surface.
64///
65/// The callback receives only the lifetime-bound session capability. The
66/// owning [`WebGpuBackend`] never crosses this boundary.
67#[doc(hidden)]
68pub fn with_webgpu_exec_session<B, R>(
69    session: &mut B,
70    f: impl for<'a> FnOnce(&'a mut WebGpuExecSession<'a>) -> R,
71) -> Option<R>
72where
73    B: BackendSession + ?Sized,
74{
75    let data = session
76        .native_session()?
77        .into_marked_ptr::<WebGpuExecSessionMarker>()?;
78    // SAFETY: only `WebGpuExecSession::native_session` creates a token with the
79    // crate-private `WebGpuExecSessionMarker`, and it points that token at a live
80    // `WebGpuExecSession`. The token borrowed `*session` exclusively, and this function
81    // keeps holding `session: &mut B` for the whole scoped visit.
82    Some(unsafe { f(data.cast::<WebGpuExecSession<'static>>().as_mut()) })
83}
84
85macro_rules! delegate {
86    ($trait:path {
87        $(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) -> $ret:ty;)*
88    }) => {
89        impl $trait for WebGpuExecSession<'_> {
90            $(
91                fn $method(&mut self, $($arg: $arg_ty),*) -> $ret {
92                    self.backend.$method($($arg),*)
93                }
94            )*
95        }
96    };
97}
98
99impl TensorElementwise for WebGpuExecSession<'_> {
100    fn elementwise_read_into(
101        &mut self,
102        _op: ElementwiseReadOp,
103        _inputs: &[TensorRead<'_>],
104        _out: TensorWrite<'_>,
105    ) -> crate::Result<()> {
106        unsupported!("webgpu_elementwise_read_into")
107    }
108
109    // The old chain was: owned input -> the one-shot method -> its unsupported
110    // error; view input -> the read-boundary error. WebGPU rejects the operation
111    // either way, so evaluate the read inputs first and then raise the same
112    // unsupported error. Written against the read inputs directly so the later
113    // removal of the one-shot methods does not need to revisit this.
114    fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
115        let _ = tenferro_tensor::backend::read_owned_tensor("add", lhs)?;
116        let _ = tenferro_tensor::backend::read_owned_tensor("add", rhs)?;
117        unsupported!("webgpu_add")
118    }
119
120    // The old chain was: owned input -> the one-shot method -> its unsupported
121    // error; view input -> the read-boundary error. WebGPU rejects the operation
122    // either way, so evaluate the read inputs first and then raise the same
123    // unsupported error. Written against the read inputs directly so the later
124    // removal of the one-shot methods does not need to revisit this.
125    fn sub_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
126        let _ = tenferro_tensor::backend::read_owned_tensor("sub", lhs)?;
127        let _ = tenferro_tensor::backend::read_owned_tensor("sub", rhs)?;
128        unsupported!("webgpu_sub")
129    }
130
131    // The old chain was: owned input -> the one-shot method -> its unsupported
132    // error; view input -> the read-boundary error. WebGPU rejects the operation
133    // either way, so evaluate the read inputs first and then raise the same
134    // unsupported error. Written against the read inputs directly so the later
135    // removal of the one-shot methods does not need to revisit this.
136    fn mul_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
137        let _ = tenferro_tensor::backend::read_owned_tensor("mul", lhs)?;
138        let _ = tenferro_tensor::backend::read_owned_tensor("mul", rhs)?;
139        unsupported!("webgpu_mul")
140    }
141
142    // The old chain was: owned input -> the one-shot method -> its unsupported
143    // error; view input -> the read-boundary error. WebGPU rejects the operation
144    // either way, so evaluate the read inputs first and then raise the same
145    // unsupported error. Written against the read inputs directly so the later
146    // removal of the one-shot methods does not need to revisit this.
147    fn neg_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
148        let _ = tenferro_tensor::backend::read_owned_tensor("neg", input)?;
149        unsupported!("webgpu_neg")
150    }
151
152    // The old chain was: owned input -> the one-shot method -> its unsupported
153    // error; view input -> the read-boundary error. WebGPU rejects the operation
154    // either way, so evaluate the read inputs first and then raise the same
155    // unsupported error. Written against the read inputs directly so the later
156    // removal of the one-shot methods does not need to revisit this.
157    fn conj_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
158        let _ = tenferro_tensor::backend::read_owned_tensor("conj", input)?;
159        unsupported!("webgpu_conj")
160    }
161
162    // The old chain was: owned input -> the one-shot method -> its unsupported
163    // error; view input -> the read-boundary error. WebGPU rejects the operation
164    // either way, so evaluate the read inputs first and then raise the same
165    // unsupported error. Written against the read inputs directly so the later
166    // removal of the one-shot methods does not need to revisit this.
167    fn div_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
168        let _ = tenferro_tensor::backend::read_owned_tensor("div", lhs)?;
169        let _ = tenferro_tensor::backend::read_owned_tensor("div", rhs)?;
170        unsupported!("webgpu_div")
171    }
172
173    // The old chain was: owned input -> the one-shot method -> its unsupported
174    // error; view input -> the read-boundary error. WebGPU rejects the operation
175    // either way, so evaluate the read inputs first and then raise the same
176    // unsupported error. Written against the read inputs directly so the later
177    // removal of the one-shot methods does not need to revisit this.
178    fn abs_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
179        let _ = tenferro_tensor::backend::read_owned_tensor("abs", input)?;
180        unsupported!("webgpu_abs")
181    }
182
183    // The old chain was: owned input -> the one-shot method -> its unsupported
184    // error; view input -> the read-boundary error. WebGPU rejects the operation
185    // either way, so evaluate the read inputs first and then raise the same
186    // unsupported error. Written against the read inputs directly so the later
187    // removal of the one-shot methods does not need to revisit this.
188    fn sign_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
189        let _ = tenferro_tensor::backend::read_owned_tensor("sign", input)?;
190        unsupported!("webgpu_sign")
191    }
192
193    // The old chain was: owned input -> the one-shot method -> its unsupported
194    // error; view input -> the read-boundary error. WebGPU rejects the operation
195    // either way, so evaluate the read inputs first and then raise the same
196    // unsupported error. Written against the read inputs directly so the later
197    // removal of the one-shot methods does not need to revisit this.
198    fn maximum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
199        let _ = tenferro_tensor::backend::read_owned_tensor("maximum", lhs)?;
200        let _ = tenferro_tensor::backend::read_owned_tensor("maximum", rhs)?;
201        unsupported!("webgpu_maximum")
202    }
203
204    // The old chain was: owned input -> the one-shot method -> its unsupported
205    // error; view input -> the read-boundary error. WebGPU rejects the operation
206    // either way, so evaluate the read inputs first and then raise the same
207    // unsupported error. Written against the read inputs directly so the later
208    // removal of the one-shot methods does not need to revisit this.
209    fn minimum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
210        let _ = tenferro_tensor::backend::read_owned_tensor("minimum", lhs)?;
211        let _ = tenferro_tensor::backend::read_owned_tensor("minimum", rhs)?;
212        unsupported!("webgpu_minimum")
213    }
214
215    // The old chain was: owned input -> the one-shot method -> its unsupported
216    // error; view input -> the read-boundary error. WebGPU rejects the operation
217    // either way, so evaluate the read inputs first and then raise the same
218    // unsupported error. Written against the read inputs directly so the later
219    // removal of the one-shot methods does not need to revisit this.
220    fn compare_read(
221        &mut self,
222        lhs: TensorRead<'_>,
223        rhs: TensorRead<'_>,
224        _dir: &CompareDir,
225    ) -> crate::Result<Tensor> {
226        let _ = tenferro_tensor::backend::read_owned_tensor("compare", lhs)?;
227        let _ = tenferro_tensor::backend::read_owned_tensor("compare", rhs)?;
228        unsupported!("webgpu_compare")
229    }
230
231    // The old chain was: owned input -> the one-shot method -> its unsupported
232    // error; view input -> the read-boundary error. WebGPU rejects the operation
233    // either way, so evaluate the read inputs first and then raise the same
234    // unsupported error. Written against the read inputs directly so the later
235    // removal of the one-shot methods does not need to revisit this.
236    fn select_read(
237        &mut self,
238        pred: TensorRead<'_>,
239        on_true: TensorRead<'_>,
240        on_false: TensorRead<'_>,
241    ) -> crate::Result<Tensor> {
242        let _ = tenferro_tensor::backend::read_owned_tensor("select", pred)?;
243        let _ = tenferro_tensor::backend::read_owned_tensor("select", on_true)?;
244        let _ = tenferro_tensor::backend::read_owned_tensor("select", on_false)?;
245        unsupported!("webgpu_select")
246    }
247
248    // The old chain was: owned input -> the one-shot method -> its unsupported
249    // error; view input -> the read-boundary error. WebGPU rejects the operation
250    // either way, so evaluate the read inputs first and then raise the same
251    // unsupported error. Written against the read inputs directly so the later
252    // removal of the one-shot methods does not need to revisit this.
253    fn clamp_read(
254        &mut self,
255        input: TensorRead<'_>,
256        lower: TensorRead<'_>,
257        upper: TensorRead<'_>,
258    ) -> crate::Result<Tensor> {
259        let _ = tenferro_tensor::backend::read_owned_tensor("clamp", input)?;
260        let _ = tenferro_tensor::backend::read_owned_tensor("clamp", lower)?;
261        let _ = tenferro_tensor::backend::read_owned_tensor("clamp", upper)?;
262        unsupported!("webgpu_clamp")
263    }
264}
265
266impl TensorAnalytic for WebGpuExecSession<'_> {
267    // The old chain was: owned input -> the one-shot method -> its unsupported
268    // error; view input -> the read-boundary error. WebGPU rejects the operation
269    // either way, so evaluate the read input first and then raise the same
270    // unsupported error. Written against the read input directly so the later
271    // removal of the one-shot methods does not need to revisit this.
272    fn exp_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
273        let _ = tenferro_tensor::backend::read_owned_tensor("exp", input)?;
274        unsupported!("webgpu_exp")
275    }
276
277    // The old chain was: owned input -> the one-shot method -> its unsupported
278    // error; view input -> the read-boundary error. WebGPU rejects the operation
279    // either way, so evaluate the read input first and then raise the same
280    // unsupported error. Written against the read input directly so the later
281    // removal of the one-shot methods does not need to revisit this.
282    fn log_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
283        let _ = tenferro_tensor::backend::read_owned_tensor("log", input)?;
284        unsupported!("webgpu_log")
285    }
286
287    // The old chain was: owned input -> the one-shot method -> its unsupported
288    // error; view input -> the read-boundary error. WebGPU rejects the operation
289    // either way, so evaluate the read input first and then raise the same
290    // unsupported error. Written against the read input directly so the later
291    // removal of the one-shot methods does not need to revisit this.
292    fn sin_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
293        let _ = tenferro_tensor::backend::read_owned_tensor("sin", input)?;
294        unsupported!("webgpu_sin")
295    }
296
297    // The old chain was: owned input -> the one-shot method -> its unsupported
298    // error; view input -> the read-boundary error. WebGPU rejects the operation
299    // either way, so evaluate the read input first and then raise the same
300    // unsupported error. Written against the read input directly so the later
301    // removal of the one-shot methods does not need to revisit this.
302    fn cos_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
303        let _ = tenferro_tensor::backend::read_owned_tensor("cos", input)?;
304        unsupported!("webgpu_cos")
305    }
306
307    // The old chain was: owned input -> the one-shot method -> its unsupported
308    // error; view input -> the read-boundary error. WebGPU rejects the operation
309    // either way, so evaluate the read input first and then raise the same
310    // unsupported error. Written against the read input directly so the later
311    // removal of the one-shot methods does not need to revisit this.
312    fn tanh_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
313        let _ = tenferro_tensor::backend::read_owned_tensor("tanh", input)?;
314        unsupported!("webgpu_tanh")
315    }
316
317    // The old chain was: owned input -> the one-shot method -> its unsupported
318    // error; view input -> the read-boundary error. WebGPU rejects the operation
319    // either way, so evaluate the read input first and then raise the same
320    // unsupported error. Written against the read input directly so the later
321    // removal of the one-shot methods does not need to revisit this.
322    fn sqrt_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
323        let _ = tenferro_tensor::backend::read_owned_tensor("sqrt", input)?;
324        unsupported!("webgpu_sqrt")
325    }
326
327    // The old chain was: owned input -> the one-shot method -> its unsupported
328    // error; view input -> the read-boundary error. WebGPU rejects the operation
329    // either way, so evaluate the read input first and then raise the same
330    // unsupported error. Written against the read input directly so the later
331    // removal of the one-shot methods does not need to revisit this.
332    fn rsqrt_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
333        let _ = tenferro_tensor::backend::read_owned_tensor("rsqrt", input)?;
334        unsupported!("webgpu_rsqrt")
335    }
336
337    // See the unary analytic read halves above: evaluate the read inputs and then
338    // raise the same unsupported error.
339    fn pow_read(
340        &mut self,
341        lhs: tenferro_tensor::TensorRead<'_>,
342        rhs: tenferro_tensor::TensorRead<'_>,
343    ) -> crate::Result<Tensor> {
344        let _ = tenferro_tensor::backend::read_owned_tensor("pow", lhs)?;
345        let _ = tenferro_tensor::backend::read_owned_tensor("pow", rhs)?;
346        unsupported!("webgpu_pow")
347    }
348
349    // The old chain was: owned input -> the one-shot method -> its unsupported
350    // error; view input -> the read-boundary error. WebGPU rejects the operation
351    // either way, so evaluate the read input first and then raise the same
352    // unsupported error. Written against the read input directly so the later
353    // removal of the one-shot methods does not need to revisit this.
354    fn expm1_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
355        let _ = tenferro_tensor::backend::read_owned_tensor("expm1", input)?;
356        unsupported!("webgpu_expm1")
357    }
358
359    // The old chain was: owned input -> the one-shot method -> its unsupported
360    // error; view input -> the read-boundary error. WebGPU rejects the operation
361    // either way, so evaluate the read input first and then raise the same
362    // unsupported error. Written against the read input directly so the later
363    // removal of the one-shot methods does not need to revisit this.
364    fn log1p_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
365        let _ = tenferro_tensor::backend::read_owned_tensor("log1p", input)?;
366        unsupported!("webgpu_log1p")
367    }
368
369    fn erf_read(&mut self, input: tenferro_tensor::TensorRead<'_>) -> crate::Result<Tensor> {
370        let _ = tenferro_tensor::backend::read_owned_tensor("erf", input)?;
371        unsupported!("webgpu_erf")
372    }
373}
374
375impl TensorStructural for WebGpuExecSession<'_> {
376    fn to_contiguous_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
377        structural::to_contiguous_read(self.backend, input)
378    }
379
380    fn copy_read_into(&mut self, _src: TensorRead<'_>, _dst: TensorWrite<'_>) -> crate::Result<()> {
381        unsupported!("WebGpuBackend::copy_read_into")
382    }
383
384    fn cast(&mut self, _input: &Tensor, _to: DType) -> crate::Result<Tensor> {
385        unsupported!("webgpu_cast")
386    }
387
388    fn extract_diagonal(
389        &mut self,
390        _input: &Tensor,
391        _axis_a: usize,
392        _axis_b: usize,
393    ) -> crate::Result<Tensor> {
394        unsupported!("webgpu_extract_diagonal")
395    }
396
397    fn embed_diagonal(
398        &mut self,
399        _input: &Tensor,
400        _axis_a: usize,
401        _axis_b: usize,
402    ) -> crate::Result<Tensor> {
403        unsupported!("webgpu_embed_diagonal")
404    }
405
406    fn tril(&mut self, _input: &Tensor, _k: i64) -> crate::Result<Tensor> {
407        unsupported!("webgpu_tril")
408    }
409
410    fn triu(&mut self, _input: &Tensor, _k: i64) -> crate::Result<Tensor> {
411        unsupported!("webgpu_triu")
412    }
413
414    // The old chain was: owned input -> the one-shot method -> its unsupported
415    // error; view input -> the read-boundary error. WebGPU rejects the operation
416    // either way, so evaluate the read input first and then raise the same
417    // unsupported error. Written against the read input directly so the later
418    // removal of the one-shot methods does not need to revisit this.
419    fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor> {
420        // The one-shot entry used to run the device transpose; the read half is
421        // now that entry, so it must keep the operation rather than report it
422        // unsupported.
423        let input = tenferro_tensor::backend::read_owned_tensor("transpose", input)?;
424        structural::transpose(self.backend, input, perm)
425    }
426
427    // The old chain was: owned input -> the one-shot method -> its unsupported
428    // error; view input -> the read-boundary error. WebGPU rejects the operation
429    // either way, so evaluate the read input first and then raise the same
430    // unsupported error. Written against the read input directly so the later
431    // removal of the one-shot methods does not need to revisit this.
432    fn reshape_read(&mut self, input: TensorRead<'_>, _shape: &[usize]) -> crate::Result<Tensor> {
433        let _ = tenferro_tensor::backend::read_owned_tensor("reshape", input)?;
434        unsupported!("webgpu_reshape")
435    }
436
437    // The old chain was: owned input -> the one-shot method -> its unsupported
438    // error; view input -> the read-boundary error. WebGPU rejects the operation
439    // either way, so evaluate the read input first and then raise the same
440    // unsupported error. Written against the read input directly so the later
441    // removal of the one-shot methods does not need to revisit this.
442    fn broadcast_in_dim_read(
443        &mut self,
444        input: TensorRead<'_>,
445        _shape: &[usize],
446        _dims: &[usize],
447    ) -> crate::Result<Tensor> {
448        let _ = tenferro_tensor::backend::read_owned_tensor("broadcast_in_dim", input)?;
449        unsupported!("webgpu_broadcast_in_dim")
450    }
451}
452
453impl TensorReduction for WebGpuExecSession<'_> {
454    // The old chain was: owned input -> the one-shot method -> its unsupported
455    // error; view input -> the read-boundary error. WebGPU rejects the reduction
456    // either way, so evaluate the read input first and then raise the same
457    // unsupported error. Written against the read input directly so the later
458    // removal of the one-shot methods does not need to revisit this.
459    fn reduce_sum_read(&mut self, input: TensorRead<'_>, _axes: &[usize]) -> crate::Result<Tensor> {
460        let _ = tenferro_tensor::backend::read_owned_tensor("reduce_sum", input)?;
461        unsupported!("webgpu_reduce_sum")
462    }
463    // The old chain was: owned input -> the one-shot method -> its unsupported
464    // error; view input -> the read-boundary error. WebGPU rejects the reduction
465    // either way, so evaluate the read input first and then raise the same
466    // unsupported error. Written against the read input directly so the later
467    // removal of the one-shot methods does not need to revisit this.
468    fn reduce_prod_read(
469        &mut self,
470        input: TensorRead<'_>,
471        _axes: &[usize],
472    ) -> crate::Result<Tensor> {
473        let _ = tenferro_tensor::backend::read_owned_tensor("reduce_prod", input)?;
474        unsupported!("webgpu_reduce_prod")
475    }
476    // The old chain was: owned input -> the one-shot method -> its unsupported
477    // error; view input -> the read-boundary error. WebGPU rejects the reduction
478    // either way, so evaluate the read input first and then raise the same
479    // unsupported error. Written against the read input directly so the later
480    // removal of the one-shot methods does not need to revisit this.
481    fn reduce_max_read(&mut self, input: TensorRead<'_>, _axes: &[usize]) -> crate::Result<Tensor> {
482        let _ = tenferro_tensor::backend::read_owned_tensor("reduce_max", input)?;
483        unsupported!("webgpu_reduce_max")
484    }
485    // The old chain was: owned input -> the one-shot method -> its unsupported
486    // error; view input -> the read-boundary error. WebGPU rejects the reduction
487    // either way, so evaluate the read input first and then raise the same
488    // unsupported error. Written against the read input directly so the later
489    // removal of the one-shot methods does not need to revisit this.
490    fn reduce_min_read(&mut self, input: TensorRead<'_>, _axes: &[usize]) -> crate::Result<Tensor> {
491        let _ = tenferro_tensor::backend::read_owned_tensor("reduce_min", input)?;
492        unsupported!("webgpu_reduce_min")
493    }
494}
495
496impl TensorDot for WebGpuExecSession<'_> {
497    // The previous read-half default delegated an owned pair to the one-shot
498    // method and materialized views through to_contiguous_read before
499    // contracting. That default is inlined here so the later removal of the
500    // one-shot methods does not need to revisit WebGPU.
501    fn dot_general_read(
502        &mut self,
503        lhs: TensorRead<'_>,
504        rhs: TensorRead<'_>,
505        config: &DotGeneralConfig,
506    ) -> crate::Result<Tensor> {
507        match (lhs.as_tensor(), rhs.as_tensor()) {
508            (Some(lhs), Some(rhs)) => gemm::dot_general(self.backend, lhs, rhs, config),
509            _ => {
510                let lhs = self.to_contiguous_read(lhs)?;
511                let rhs = self.to_contiguous_read(rhs)?;
512                gemm::dot_general(self.backend, &lhs, &rhs, config)
513            }
514        }
515    }
516
517    fn dot_general_with_conj(
518        &mut self,
519        lhs: &Tensor,
520        rhs: &Tensor,
521        config: &DotGeneralConfig,
522        lhs_conj: bool,
523        rhs_conj: bool,
524    ) -> crate::Result<Tensor> {
525        gemm::dot_general_with_conj(self.backend, lhs, rhs, config, lhs_conj, rhs_conj)
526    }
527}
528
529impl TensorIndexing for WebGpuExecSession<'_> {
530    fn gather(
531        &mut self,
532        _operand: &Tensor,
533        _start_indices: &Tensor,
534        _config: &GatherConfig,
535    ) -> crate::Result<Tensor> {
536        unsupported!("webgpu_gather")
537    }
538
539    fn scatter(
540        &mut self,
541        _operand: &Tensor,
542        _scatter_indices: &Tensor,
543        _updates: &Tensor,
544        _config: &ScatterConfig,
545    ) -> crate::Result<Tensor> {
546        unsupported!("webgpu_scatter")
547    }
548
549    fn slice(&mut self, _input: &Tensor, _config: &SliceConfig) -> crate::Result<Tensor> {
550        unsupported!("webgpu_slice")
551    }
552
553    fn dynamic_slice(
554        &mut self,
555        _input: &Tensor,
556        _starts: &Tensor,
557        _slice_sizes: &[usize],
558    ) -> crate::Result<Tensor> {
559        unsupported!("webgpu_dynamic_slice")
560    }
561
562    fn dynamic_update_slice(
563        &mut self,
564        _operand: &Tensor,
565        _update: &Tensor,
566        _starts: &Tensor,
567    ) -> crate::Result<Tensor> {
568        unsupported!("webgpu_dynamic_update_slice")
569    }
570
571    fn pad(&mut self, _input: &Tensor, _config: &PadConfig) -> crate::Result<Tensor> {
572        unsupported!("webgpu_pad")
573    }
574
575    fn concatenate(&mut self, _inputs: &[&Tensor], _axis: usize) -> crate::Result<Tensor> {
576        unsupported!("webgpu_concatenate")
577    }
578
579    fn reverse(&mut self, _input: &Tensor, _axes: &[usize]) -> crate::Result<Tensor> {
580        unsupported!("webgpu_reverse")
581    }
582}
583
584impl TensorFusion for WebGpuExecSession<'_> {}
585
586// WebGPU device buffers return to the runtime allocator on drop, so the
587// session keeps the trait's no-op reclaim; the owner is not a buffer surface.
588impl TensorBuffer for WebGpuExecSession<'_> {}
589
590delegate!(TensorDeviceTransfer {
591    fn download_to_host(tensor: TensorRead<'_>) -> crate::Result<Tensor>;
592    fn upload_host_tensor(tensor: TensorRead<'_>) -> crate::Result<Tensor>;
593});
594
595// The cached reads are the trait's provided defaults here: WebGPU owns no
596// runtime cache of its own, so the session implements them directly.
597impl SessionCachedDot for WebGpuExecSession<'_> {}
598
599impl BackendSession for WebGpuExecSession<'_> {
600    fn native_session(&mut self) -> Option<tenferro_tensor::NativeSessionRef<'_>> {
601        // SAFETY: `WebGpuExecSessionMarker` is private to this crate, and this is the only
602        // place a token carrying it is created; it always points to a
603        // `WebGpuExecSession`, exclusively borrowed for the token lifetime.
604        Some(unsafe { tenferro_tensor::NativeSessionRef::new::<WebGpuExecSessionMarker, _>(self) })
605    }
606}
607
608impl BackendSessionHost for WebGpuBackend {
609    fn with_backend_session<R: Send>(
610        &mut self,
611        f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
612    ) -> Result<R, tenferro_tensor::SessionEntryError> {
613        let mut session = WebGpuExecSession { backend: self };
614        // The portable in-session guard rejects nested entry before `f` runs;
615        // the WebGPU runtime must never re-enter a session closure.
616        with_session_entry_guard("WebGpuBackend", || f(&mut session))
617    }
618}