wasmgc 0.0.0

WasmGC types for Rust with a post-link toolchain
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
//! Metadata connecting Rust type checking to Wasm code generation.
//!
//! The runtime and procedural macro emit these `repr(C)` descriptors into the
//! linked module's linear memory. Their pointers are resolved by the linker;
//! the postprocessor reads the resulting bytes without executing the module.
//! [`Layout`] describes both pointer widths, independently of the host ABI.
//!
//! Schemas describe Wasm storage, not Rust object layouts. Numeric shims pass
//! reference handles, while generated GC fields and native imports and exports
//! contain actual Wasm references. This protocol is an implementation detail
//! shared by the runtime, macro, and postprocessor.

/// The byte length of each descriptor signature.
pub const MAGIC_LEN: usize = 16;

/// The signature at the start of a [`Schema`] descriptor.
pub const SCHEMA_MAGIC: [u8; MAGIC_LEN] = *b"wasmgc.schema\0\0\0";

/// The signature at the start of an [`Operation`] descriptor.
pub const OPERATION_MAGIC: [u8; MAGIC_LEN] = *b"wasmgc.opcode\0\0\0";

/// The signature at the start of an [`Export`] descriptor.
pub const EXPORT_MAGIC: [u8; MAGIC_LEN] = *b"wasmgc.export\0\0\0";

/// The signature at the start of a [`Function`] descriptor.
pub const FUNCTION_MAGIC: [u8; MAGIC_LEN] = *b"wasmgc.func\0\0\0\0\0";

/// The prefix of temporary globals exposing [`Export`] descriptor addresses.
pub const EXPORT_PREFIX: &str = "__wasmgc_export.";

/// The prefix of temporary exports exposing numeric Rust bridge functions.
pub const FUNCTION_PREFIX: &str = "__wasmgc_function.";

/// The prefix of temporary globals exposing [`Function`] descriptor addresses.
pub const SIGNATURE_PREFIX: &str = "__wasmgc_signature.";

/// Descriptor sizes and member offsets in the linked module's linear memory.
///
/// Offsets and sizes are measured in bytes. Pointers use the target's pointer
/// width; tags and counts remain little-endian `u32` values on both targets.
/// Padding follows the target C ABI rather than the host's Rust layout.
///
/// The decoder selects [`WASM32`](Self::WASM32) or [`WASM64`](Self::WASM64)
/// from the module's memory type. For example, `Schema::fields` is at byte 24
/// on both targets, but occupies four bytes on wasm32 and eight on wasm64.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Layout {
    /// The width of a data pointer or a function-table slot.
    pub pointer_size: u32,
    /// The stride of [`FieldKind`] entries.
    pub field_kind_size: u32,
    /// The offset of a field storage tag.
    pub field_kind_tag_offset: u32,
    /// The offset of a referenced schema pointer.
    pub field_kind_target_offset: u32,
    /// The complete size of a [`Schema`] descriptor, including padding.
    pub schema_size: u32,
    /// The offset of [`SCHEMA_MAGIC`].
    pub schema_magic_offset: u32,
    /// The offset of the [`SchemaKind`] discriminant.
    pub schema_kind_offset: u32,
    /// The offset of the schema field count.
    pub schema_field_count_offset: u32,
    /// The offset of the field-descriptor array pointer.
    pub schema_fields_offset: u32,
    /// The complete size of an [`Operation`] descriptor.
    pub operation_size: u32,
    /// The offset of [`OPERATION_MAGIC`].
    pub operation_magic_offset: u32,
    /// The offset of the shim function pointer, encoded as a table slot.
    pub operation_function_offset: u32,
    /// The offset of the operation schema pointer.
    pub operation_schema_offset: u32,
    /// The offset of the [`OperationKind`] discriminant.
    pub operation_kind_offset: u32,
    /// The offset of the operation field index.
    pub operation_field_offset: u32,
    /// The complete size of an [`Export`] descriptor.
    pub export_size: u32,
    /// The offset of [`EXPORT_MAGIC`].
    pub export_magic_offset: u32,
    /// The offset of the exported operation schema pointer.
    pub export_schema_offset: u32,
    /// The offset of the exported [`OperationKind`] discriminant.
    pub export_kind_offset: u32,
    /// The offset of the exported operation field index.
    pub export_field_offset: u32,
    /// The offset of the export name pointer.
    pub export_name_offset: u32,
    /// The offset of the export name byte count.
    pub export_name_len_offset: u32,
    /// The complete size of a [`Function`] descriptor.
    pub function_size: u32,
    /// The offset of [`FUNCTION_MAGIC`].
    pub function_magic_offset: u32,
    /// The offset of the [`FunctionKind`] discriminant.
    pub function_kind_offset: u32,
    /// The offset of the parameter count.
    pub function_param_count_offset: u32,
    /// The offset of the parameter-descriptor array pointer.
    pub function_params_offset: u32,
    /// The offset of the optional result-descriptor pointer.
    pub function_result_offset: u32,
    /// The offset of the import module name pointer.
    pub function_module_offset: u32,
    /// The offset of the import module name byte count.
    pub function_module_len_offset: u32,
    /// The offset of the native function name pointer.
    pub function_name_offset: u32,
    /// The offset of the native function name byte count.
    pub function_name_len_offset: u32,
}

impl Layout {
    /// The descriptor layout for a module with 32-bit linear-memory pointers.
    pub const WASM32: Self = Self {
        pointer_size: 4,
        field_kind_size: 8,
        field_kind_tag_offset: 0,
        field_kind_target_offset: 4,
        schema_size: 28,
        schema_magic_offset: 0,
        schema_kind_offset: 16,
        schema_field_count_offset: 20,
        schema_fields_offset: 24,
        operation_size: 32,
        operation_magic_offset: 0,
        operation_function_offset: 16,
        operation_schema_offset: 20,
        operation_kind_offset: 24,
        operation_field_offset: 28,
        export_size: 36,
        export_magic_offset: 0,
        export_schema_offset: 16,
        export_kind_offset: 20,
        export_field_offset: 24,
        export_name_offset: 28,
        export_name_len_offset: 32,
        function_size: 48,
        function_magic_offset: 0,
        function_kind_offset: 16,
        function_param_count_offset: 20,
        function_params_offset: 24,
        function_result_offset: 28,
        function_module_offset: 32,
        function_module_len_offset: 36,
        function_name_offset: 40,
        function_name_len_offset: 44,
    };

    /// The descriptor layout for a module with 64-bit linear-memory pointers.
    pub const WASM64: Self = Self {
        pointer_size: 8,
        field_kind_size: 16,
        field_kind_tag_offset: 0,
        field_kind_target_offset: 8,
        schema_size: 32,
        schema_magic_offset: 0,
        schema_kind_offset: 16,
        schema_field_count_offset: 20,
        schema_fields_offset: 24,
        operation_size: 40,
        operation_magic_offset: 0,
        operation_function_offset: 16,
        operation_schema_offset: 24,
        operation_kind_offset: 32,
        operation_field_offset: 36,
        export_size: 48,
        export_magic_offset: 0,
        export_schema_offset: 16,
        export_kind_offset: 24,
        export_field_offset: 28,
        export_name_offset: 32,
        export_name_len_offset: 40,
        function_size: 72,
        function_magic_offset: 0,
        function_kind_offset: 16,
        function_param_count_offset: 20,
        function_params_offset: 24,
        function_result_offset: 32,
        function_module_offset: 40,
        function_module_len_offset: 48,
        function_name_offset: 56,
        function_name_len_offset: 64,
    };
}

/// The storage class encoded in a [`FieldKind`].
///
/// Tags describe the generated Wasm field, independently of the Rust wrapper
/// that implements [`GcValue`](crate::GcValue). Reference tags distinguish
/// struct, array, and external references so the encoder can select the
/// appropriate concrete type or root table.
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FieldTag {
    /// An `i32` field or element.
    I32 = 1,
    /// An `i64` field or element.
    I64 = 2,
    /// An `f32` field or element.
    F32 = 3,
    /// An `f64` field or element.
    F64 = 4,
    /// An `i32` field interpreted by Rust as a boolean.
    Bool = 5,
    /// A non-null reference to a concrete struct type.
    Ref = 6,
    /// A nullable reference to a concrete struct type.
    NullableRef = 7,
    /// A non-null reference to a concrete array type.
    Array = 8,
    /// A nullable reference to a concrete array type.
    NullableArray = 10,
    /// A nullable `externref`, with no target schema.
    ExternRef = 11,
    /// Packed `i8` storage, read with zero extension.
    I8 = 12,
    /// Packed `i16` storage, read with zero extension.
    I16 = 13,
}

impl FieldTag {
    /// Returns the tag with this protocol value, or `None` if it is unknown.
    pub const fn from_raw(value: u32) -> Option<Self> {
        match value {
            1 => Some(Self::I32),
            2 => Some(Self::I64),
            3 => Some(Self::F32),
            4 => Some(Self::F64),
            5 => Some(Self::Bool),
            6 => Some(Self::Ref),
            7 => Some(Self::NullableRef),
            8 => Some(Self::Array),
            10 => Some(Self::NullableArray),
            11 => Some(Self::ExternRef),
            12 => Some(Self::I8),
            13 => Some(Self::I16),
            _ => None,
        }
    }

    /// Returns whether the value needs a reference-table conversion in the Rust ABI.
    pub const fn is_reference(self) -> bool {
        matches!(
            self,
            Self::Ref | Self::NullableRef | Self::Array | Self::NullableArray | Self::ExternRef
        )
    }
}

/// An operation whose numeric Rust shim is replaced by WasmGC instructions.
///
/// Reference operands are looked up in instance-local root tables, and
/// reference results receive owned roots. The struct and array operations
/// below use the named Wasm instruction; the slice and ABI-buffer operations
/// combine multiple instructions in a generated loop.
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum OperationKind {
    /// Allocates a struct with `struct.new`.
    StructNew = 0,
    /// Reads a field with `struct.get`, or `struct.get_u` for packed storage.
    StructGet = 1,
    /// Writes a field with `struct.set`.
    StructSet = 2,
    /// Allocates repeated elements with `array.new`.
    ArrayNew = 3,
    /// Allocates an empty array with `array.new_fixed` and an immediate length of zero.
    ArrayNewFixed = 4,
    /// Allocates zero or null elements with `array.new_default`.
    ArrayNewDefault = 13,
    /// Reads the element count with `array.len`.
    ArrayLen = 5,
    /// Reads an element with `array.get`, or `array.get_u` for packed storage.
    ArrayGet = 6,
    /// Writes an element with `array.set`.
    ArraySet = 7,
    /// Fills the entire array with `array.len` followed by `array.fill`.
    ArrayFill = 8,
    /// Copies a range with `array.copy`, which permits overlapping ranges.
    ArrayCopy = 9,

    /// Allocates an array from scalar linear-memory values.
    ///
    /// Uses `array.new_default`, then loads each scalar and stores it with
    /// `array.set`. This is a library operation rather than a bulk-copy instruction.
    ArrayFromSlice = 10,
    /// Copies scalars to linear memory with `array.get`/`array.get_u` and scalar stores.
    ArrayRead = 11,
    /// Copies scalars from linear memory with scalar loads and `array.set`.
    ArrayWrite = 12,
    /// Copies elements to a numeric ABI buffer in linear memory.
    ///
    /// Uses `array.get`/`array.get_u` and scalar stores. Reference elements
    /// are converted to newly owned root handles rather than copied as references.
    ArrayReadAbi = 14,
    /// Copies elements from a numeric ABI buffer into the array.
    ///
    /// Uses scalar loads and `array.set`. Reference handles are borrowed
    /// from their root tables; writing an element does not consume its input root.
    ArrayWriteAbi = 15,
}

impl OperationKind {
    /// Returns the operation with this protocol value, or `None` if it is unknown.
    pub const fn from_raw(value: u32) -> Option<Self> {
        match value {
            0 => Some(Self::StructNew),
            1 => Some(Self::StructGet),
            2 => Some(Self::StructSet),
            3 => Some(Self::ArrayNew),
            4 => Some(Self::ArrayNewFixed),
            5 => Some(Self::ArrayLen),
            6 => Some(Self::ArrayGet),
            7 => Some(Self::ArraySet),
            8 => Some(Self::ArrayFill),
            9 => Some(Self::ArrayCopy),
            10 => Some(Self::ArrayFromSlice),
            11 => Some(Self::ArrayRead),
            12 => Some(Self::ArrayWrite),
            13 => Some(Self::ArrayNewDefault),
            14 => Some(Self::ArrayReadAbi),
            15 => Some(Self::ArrayWriteAbi),
            _ => None,
        }
    }
}

/// The Wasm storage type of a field, array element, or function value.
///
/// Scalar constants select a built-in storage type. [`reference`](Self::reference)
/// and [`array`](Self::array) additionally name a target schema. GC references
/// use numeric root handles at Rust call sites, but occupy actual reference
/// fields in the generated Wasm object.
///
/// A custom [`GcValue`](crate::GcValue) usually reuses the `KIND` of its
/// underlying value, keeping storage and ABI conversion consistent.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct FieldKind {
    tag: FieldTag,
    target: *const Schema,
}

impl FieldKind {
    /// Packed `i8` storage with an `i32` ABI and zero-extended reads.
    pub const I8: Self = Self::scalar(FieldTag::I8);

    /// Packed `i16` storage with an `i32` ABI and zero-extended reads.
    pub const I16: Self = Self::scalar(FieldTag::I16);

    /// Full-width `i32` storage and an `i32`/`u32` Rust ABI.
    pub const I32: Self = Self::scalar(FieldTag::I32);

    /// Full-width `i64` storage and ABI.
    pub const I64: Self = Self::scalar(FieldTag::I64);

    /// An IEEE 754 binary32 field and ABI.
    pub const F32: Self = Self::scalar(FieldTag::F32);

    /// An IEEE 754 binary64 field and ABI.
    pub const F64: Self = Self::scalar(FieldTag::F64);

    /// An `i32` field interpreted as false for zero and true otherwise.
    pub const BOOL: Self = Self::scalar(FieldTag::Bool);

    /// Nullable `externref` storage, represented in Rust by an owned table slot.
    ///
    /// Even a null external reference receives a nonzero handle; zero is not
    /// an external-reference root.
    pub const EXTERNREF: Self = Self::scalar(FieldTag::ExternRef);

    const fn scalar(tag: FieldTag) -> Self {
        Self {
            tag,
            target: core::ptr::null(),
        }
    }

    /// Returns whether Wasm permits zero or null initialization of this type.
    ///
    /// Non-null struct and array references require an explicit value. This
    /// describes the Wasm storage default, which need not equal a custom Rust
    /// wrapper's `Default` implementation.
    pub const fn is_defaultable(self) -> bool {
        matches!(
            self.tag,
            FieldTag::I8
                | FieldTag::I16
                | FieldTag::I32
                | FieldTag::I64
                | FieldTag::F32
                | FieldTag::F64
                | FieldTag::Bool
                | FieldTag::NullableRef
                | FieldTag::NullableArray
                | FieldTag::ExternRef
        )
    }

    /// Describes a reference to the struct schema at `target`.
    ///
    /// `nullable` controls whether a zero Rust handle represents null. A
    /// non-null handle must name an object of the target schema. The descriptor
    /// is read after linking; this function does not dereference `target`.
    ///
    /// For ordinary Rust wrappers, reuse `<Gc<T> as GcValue>::KIND` or
    /// `<Option<Gc<T>> as GcValue>::KIND` instead of assembling a descriptor.
    pub const fn reference(target: *const Schema, nullable: bool) -> Self {
        Self {
            tag: if nullable {
                FieldTag::NullableRef
            } else {
                FieldTag::Ref
            },
            target,
        }
    }

    /// Describes a reference to the array schema at `target`.
    ///
    /// The target must describe exactly one element type. Nullability and
    /// handle representation follow [`reference`](Self::reference).
    pub const fn array(target: *const Schema, nullable: bool) -> Self {
        Self {
            tag: if nullable {
                FieldTag::NullableArray
            } else {
                FieldTag::Array
            },
            target,
        }
    }
}

/// The aggregate declaration represented by a [`Schema`].
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum SchemaKind {
    /// A struct with an ordered sequence of mutable fields.
    Struct = 0,
    /// An array with one mutable element storage type.
    Array = 1,
}

impl SchemaKind {
    /// Returns the aggregate kind with this protocol value, or `None` if unknown.
    pub const fn from_raw(value: u32) -> Option<Self> {
        match value {
            0 => Some(Self::Struct),
            1 => Some(Self::Array),
            _ => None,
        }
    }
}

/// A struct or array declaration emitted after Rust resolves its field types.
///
/// Field descriptors appear in declaration order. An array declaration has
/// exactly one descriptor, for its element type. References between schemas
/// may be recursive; the postprocessor emits mutually dependent declarations
/// in the same Wasm recursion group.
#[repr(C)]
pub struct Schema {
    /// The descriptor signature, initialized to `SCHEMA_MAGIC`.
    pub magic: [u8; MAGIC_LEN],
    /// Whether `fields` describes struct fields or an array element.
    pub kind: SchemaKind,
    /// The descriptor count at `fields`; one for arrays.
    pub field_count: u32,
    /// The linked address of the immutable [`FieldKind`] sequence.
    pub fields: *const FieldKind,
}

impl Schema {
    /// Describes a struct with these fields in declaration order.
    ///
    /// # Panics
    ///
    /// Panics if the field count exceeds `u32::MAX`.
    pub const fn new(fields: &'static [FieldKind]) -> Self {
        assert!(fields.len() <= u32::MAX as usize);
        Self {
            magic: SCHEMA_MAGIC,
            kind: SchemaKind::Struct,
            field_count: fields.len() as u32,
            fields: fields.as_ptr(),
        }
    }

    /// Describes an array with the supplied element storage type.
    ///
    /// # Panics
    ///
    /// Panics unless `element` contains exactly one descriptor.
    pub const fn array(element: &'static [FieldKind]) -> Self {
        assert!(element.len() == 1);
        Self {
            magic: SCHEMA_MAGIC,
            kind: SchemaKind::Array,
            field_count: 1,
            fields: element.as_ptr(),
        }
    }
}

/// A request to replace one Rust shim with a generated GC operation.
///
/// A marker call keeps this descriptor reachable from the shim's body. The
/// decoder discovers the operation by that body and its descriptor address;
/// it does not resolve the stored function pointer through the function table.
#[repr(C)]
pub struct Operation {
    /// The descriptor signature, initialized to `OPERATION_MAGIC`.
    pub magic: [u8; MAGIC_LEN],
    /// The shim address retained to keep its callable ABI observable to the compiler.
    ///
    /// Rust function pointers become function-table slots in the linked Wasm.
    /// Such a slot is distinct from the module function index used by `call`.
    pub function: *const (),
    /// The linked address of the operation owner schema.
    pub schema: *const Schema,
    /// The operation to emit in place of the shim body.
    pub kind: OperationKind,
    /// The zero-based struct field index, or zero for other operations.
    pub field: u32,
}

impl Operation {
    /// Binds a shim address to its schema, operation, and optional field index.
    pub const fn new(
        schema: *const Schema,
        kind: OperationKind,
        field: u32,
        function: *const (),
    ) -> Self {
        Self {
            magic: OPERATION_MAGIC,
            function,
            schema,
            kind,
            field,
        }
    }
}

/// A request for a native struct constructor, getter, or setter export.
///
/// The postprocessor generates this function directly from the schema.
/// Its parameters and result use Wasm values, including native references,
/// rather than the numeric handle ABI used by Rust shims.
#[repr(C)]
pub struct Export {
    /// The descriptor signature, initialized to `EXPORT_MAGIC`.
    pub magic: [u8; MAGIC_LEN],
    /// The linked address of the exported struct schema.
    pub schema: *const Schema,
    /// `StructNew`, `StructGet`, or `StructSet`.
    pub kind: OperationKind,
    /// The zero-based field index for accessors; zero for constructors.
    pub field: u32,
    /// The linked address of the UTF-8 export name.
    pub name: *const u8,
    /// The byte length of the export name.
    pub name_len: u32,
}

impl Export {
    /// Requests an operation exported under `name`.
    ///
    /// Constructors use [`OperationKind::StructNew`] and field index zero.
    /// Accessors use a valid field index in the target struct schema.
    ///
    /// # Panics
    ///
    /// Panics if the export name exceeds `u32::MAX` bytes.
    pub const fn new(
        schema: *const Schema,
        kind: OperationKind,
        field: u32,
        name: &'static str,
    ) -> Self {
        assert!(name.len() <= u32::MAX as usize);
        Self {
            magic: EXPORT_MAGIC,
            schema,
            kind,
            field,
            name: name.as_ptr(),
            name_len: name.len() as u32,
        }
    }
}

/// The direction in which a native function bridge crosses the Rust ABI.
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FunctionKind {
    /// The host calls a Rust function using its native Wasm signature.
    Export = 0,
    /// Rust calls a host function through a generated numeric-value adapter.
    Import = 1,
}

impl FunctionKind {
    /// Returns the direction with this protocol value, or `None` if it is unknown.
    pub const fn from_raw(value: u32) -> Option<Self> {
        match value {
            0 => Some(Self::Export),
            1 => Some(Self::Import),
            _ => None,
        }
    }
}

/// The native signature associated with a numeric Rust function bridge.
///
/// Exported bridges and signature-address globals have matching temporary
/// names. Import wrappers instead refer to this descriptor through a marker
/// call, allowing the linker to discard unused imports. The postprocessor
/// replaces these markers and temporary exports with native signatures.
#[repr(C)]
pub struct Function {
    /// The descriptor signature, initialized to `FUNCTION_MAGIC`.
    pub magic: [u8; MAGIC_LEN],
    /// Whether the native function imports from or exports to the host.
    pub kind: FunctionKind,
    /// The number of parameter descriptors at `params`.
    pub param_count: u32,
    /// The linked address of parameter descriptors in argument order.
    pub params: *const FieldKind,
    /// The linked address of one result descriptor, or null for no result.
    pub result: *const FieldKind,
    /// The linked address of the UTF-8 import module name; empty for exports.
    pub module: *const u8,
    /// The byte length of the import module name.
    pub module_len: u32,
    /// The linked address of the UTF-8 native function name.
    pub name: *const u8,
    /// The byte length of the native function name.
    pub name_len: u32,
}

impl Function {
    /// Describes a function with these parameters and at most one result.
    ///
    /// `module` is the host module name for imports and is empty for exports.
    /// The numeric wrapper and this native signature must describe the same
    /// values, with reference handles converted at the boundary.
    ///
    /// # Panics
    ///
    /// Panics if the parameter count or either name length exceeds `u32::MAX`.
    pub const fn new(
        kind: FunctionKind,
        params: &'static [FieldKind],
        result: Option<&'static FieldKind>,
        module: &'static str,
        name: &'static str,
    ) -> Self {
        assert!(params.len() <= u32::MAX as usize);
        assert!(module.len() <= u32::MAX as usize);
        assert!(name.len() <= u32::MAX as usize);
        Self {
            magic: FUNCTION_MAGIC,
            kind,
            param_count: params.len() as u32,
            params: params.as_ptr(),
            result: match result {
                Some(result) => result,
                None => core::ptr::null(),
            },
            module: module.as_ptr(),
            module_len: module.len() as u32,
            name: name.as_ptr(),
            name_len: name.len() as u32,
        }
    }
}

// SAFETY: This descriptor stores a tag and an opaque pointer. Sharing it never
// dereferences the target.
unsafe impl Sync for FieldKind {}

// SAFETY: Sharing a schema exposes immutable metadata; no safe operation on the
// descriptor reads through its field pointer.
unsafe impl Sync for Schema {}

// SAFETY: The function and schema pointers are metadata. Sharing this descriptor
// neither calls the function nor dereferences the schema.
unsafe impl Sync for Operation {}

// SAFETY: The schema and name pointers are opaque metadata. No safe operation on
// this descriptor dereferences them.
unsafe impl Sync for Export {}

// SAFETY: The signature and name pointers are opaque metadata. No safe method
// reads through them.
unsafe impl Sync for Function {}

// Check the target C layouts at compile time so descriptor reads use rustc's
// actual offsets, including pointer-width-dependent padding.
const _: () = {
    use core::mem::{offset_of, size_of};

    let layout = match size_of::<*const ()>() {
        4 => Layout::WASM32,
        8 => Layout::WASM64,
        _ => panic!("unsupported descriptor pointer width"),
    };
    assert!(size_of::<extern "C" fn()>() == layout.pointer_size as usize);
    assert!(size_of::<FieldKind>() == layout.field_kind_size as usize);
    assert!(offset_of!(FieldKind, target) == layout.field_kind_target_offset as usize);
    assert!(size_of::<Schema>() == layout.schema_size as usize);
    assert!(offset_of!(Schema, kind) == layout.schema_kind_offset as usize);
    assert!(offset_of!(Schema, field_count) == layout.schema_field_count_offset as usize);
    assert!(offset_of!(Schema, fields) == layout.schema_fields_offset as usize);
    assert!(size_of::<Operation>() == layout.operation_size as usize);
    assert!(offset_of!(Operation, function) == layout.operation_function_offset as usize);
    assert!(size_of::<Export>() == layout.export_size as usize);
    assert!(offset_of!(Export, magic) == layout.export_magic_offset as usize);
    assert!(offset_of!(Export, schema) == layout.export_schema_offset as usize);
    assert!(offset_of!(Export, kind) == layout.export_kind_offset as usize);
    assert!(offset_of!(Export, field) == layout.export_field_offset as usize);
    assert!(offset_of!(Export, name) == layout.export_name_offset as usize);
    assert!(offset_of!(Export, name_len) == layout.export_name_len_offset as usize);
    assert!(size_of::<Function>() == layout.function_size as usize);
    assert!(offset_of!(Function, magic) == layout.function_magic_offset as usize);
    assert!(offset_of!(Function, kind) == layout.function_kind_offset as usize);
    assert!(offset_of!(Function, param_count) == layout.function_param_count_offset as usize);
    assert!(offset_of!(Function, params) == layout.function_params_offset as usize);
    assert!(offset_of!(Function, result) == layout.function_result_offset as usize);
    assert!(offset_of!(Function, module) == layout.function_module_offset as usize);
    assert!(offset_of!(Function, module_len) == layout.function_module_len_offset as usize);
    assert!(offset_of!(Function, name) == layout.function_name_offset as usize);
    assert!(offset_of!(Function, name_len) == layout.function_name_len_offset as usize);
};