Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/misc_testsuite/component-model/resources.wast
1691 views
;; bare bones "intrinsics work"
(component
  (type $r (resource (rep i32)))
  (core func $rep (canon resource.rep $r))
  (core func $new (canon resource.new $r))
  (core func $drop (canon resource.drop $r))

  (core module $m
     (import "" "rep" (func $rep (param i32) (result i32)))
     (import "" "new" (func $new (param i32) (result i32)))
     (import "" "drop" (func $drop (param i32)))

     (func $start
       (local $r i32)
       (local.set $r (call $new (i32.const 100)))

       (if (i32.ne (local.get $r) (i32.const 1)) (then (unreachable)))
       (if (i32.ne (call $rep (local.get $r)) (i32.const 100)) (then (unreachable)))

       (call $drop (local.get $r))
     )

     (start $start)
  )
  (core instance (instantiate $m
     (with "" (instance
       (export "rep" (func $rep))
       (export "new" (func $new))
       (export "drop" (func $drop))
     ))
  ))
)

;; cannot call `resource.drop` on a nonexistent resource
(component
  (type $r (resource (rep i32)))
  (core func $drop (canon resource.drop $r))

  (core module $m
     (import "" "drop" (func $drop (param i32)))

     (func (export "r")
       (call $drop (i32.const 0))
     )
  )
  (core instance $i (instantiate $m
     (with "" (instance
       (export "drop" (func $drop))
     ))
  ))

  (func (export "r") (canon lift (core func $i "r")))
)
(assert_trap (invoke "r") "unknown handle index 0")

;; cannot call `resource.rep` on a nonexistent resource
(component
  (type $r (resource (rep i32)))
  (core func $rep (canon resource.rep $r))

  (core module $m
     (import "" "rep" (func $rep (param i32) (result i32)))

     (func (export "r")
       (drop (call $rep (i32.const 0)))
     )
  )
  (core instance $i (instantiate $m
     (with "" (instance
       (export "rep" (func $rep))
     ))
  ))

  (func (export "r") (canon lift (core func $i "r")))
)
(assert_trap (invoke "r") "unknown handle index 0")

;; index reuse behavior of handles
(component
  (type $r (resource (rep i32)))
  (core func $rep (canon resource.rep $r))
  (core func $new (canon resource.new $r))
  (core func $drop (canon resource.drop $r))

  (core module $m
     (import "" "rep" (func $rep (param i32) (result i32)))
     (import "" "new" (func $new (param i32) (result i32)))
     (import "" "drop" (func $drop (param i32)))

     (func $start
       (local $r1 i32)
       (local $r2 i32)
       (local $r3 i32)
       (local $r4 i32)

       ;; resources assigned sequentially
       (local.set $r1 (call $new (i32.const 100)))
       (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable)))

       (local.set $r2 (call $new (i32.const 200)))
       (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable)))

       (local.set $r3 (call $new (i32.const 300)))
       (if (i32.ne (local.get $r3) (i32.const 3)) (then (unreachable)))

       ;; representations all look good
       (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (then (unreachable)))
       (if (i32.ne (call $rep (local.get $r2)) (i32.const 200)) (then (unreachable)))
       (if (i32.ne (call $rep (local.get $r3)) (i32.const 300)) (then (unreachable)))

       ;; reallocate r2
       (call $drop (local.get $r2))
       (local.set $r2 (call $new (i32.const 400)))

       ;; should have reused index 1
       (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable)))

       ;; representations all look good
       (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (then (unreachable)))
       (if (i32.ne (call $rep (local.get $r2)) (i32.const 400)) (then (unreachable)))
       (if (i32.ne (call $rep (local.get $r3)) (i32.const 300)) (then (unreachable)))

       ;; deallocate, then reallocate
       (call $drop (local.get $r1))
       (call $drop (local.get $r2))
       (call $drop (local.get $r3))

       (local.set $r1 (call $new (i32.const 500)))
       (local.set $r2 (call $new (i32.const 600)))
       (local.set $r3 (call $new (i32.const 700)))

       ;; representations all look good
       (if (i32.ne (call $rep (local.get $r1)) (i32.const 500)) (then (unreachable)))
       (if (i32.ne (call $rep (local.get $r2)) (i32.const 600)) (then (unreachable)))
       (if (i32.ne (call $rep (local.get $r3)) (i32.const 700)) (then (unreachable)))

       ;; indices should be lifo
       (if (i32.ne (local.get $r1) (i32.const 3)) (then (unreachable)))
       (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable)))
       (if (i32.ne (local.get $r3) (i32.const 1)) (then (unreachable)))

       ;; bump one more time
       (local.set $r4 (call $new (i32.const 800)))
       (if (i32.ne (local.get $r4) (i32.const 4)) (then (unreachable)))

       ;; deallocate everything
       (call $drop (local.get $r1))
       (call $drop (local.get $r2))
       (call $drop (local.get $r3))
       (call $drop (local.get $r4))
     )

     (start $start)
  )
  (core instance (instantiate $m
     (with "" (instance
       (export "rep" (func $rep))
       (export "new" (func $new))
       (export "drop" (func $drop))
     ))
  ))
)

(assert_unlinkable
  (component
    (import "host" (instance
      (export "missing" (type (sub resource)))
    ))
  )
  "was not found")
(assert_unlinkable
  (component
    (import "host" (instance
      (export "return-three" (type (sub resource)))
    ))
  )
  "expected resource found func")

;; all resources can be uniquely imported
(component
  (import "host" (instance
    (export "resource1" (type (sub resource)))
    (export "resource2" (type (sub resource)))
    (export "resource1-again" (type (sub resource)))
  ))
)

;; equality constraints also work
(component
  (import "host" (instance
    (export "resource1" (type $r1 (sub resource)))
    (export "resource2" (type (sub resource)))
    (export "resource1-again" (type (eq $r1)))
  ))
)

;; equality constraints are checked if resources are supplied
(assert_unlinkable
  (component
    (import "host" (instance
      (export "resource1" (type (sub resource)))
      (export "resource2" (type $r1 (sub resource)))
      (export "resource1-again" (type (eq $r1)))
    ))
  )
  "mismatched resource types")

;; equality constraints mean that types don't need to be supplied
(component
  (import "host" (instance
    (export "resource1" (type $r1 (sub resource)))
    (export "resource2" (type (sub resource)))
    (export "this-name-is-not-provided-in-the-wast-harness" (type (eq $r1)))
  ))
)

;; simple properties of handles
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
    (export "[static]resource1.assert" (func (param "r" (own $r)) (param "rep" u32)))
  ))
  (alias export $host "resource1" (type $r))
  (alias export $host "[constructor]resource1" (func $ctor))
  (alias export $host "[static]resource1.assert" (func $assert))

  (core func $drop (canon resource.drop $r))
  (core func $ctor (canon lower (func $ctor)))
  (core func $assert (canon lower (func $assert)))

  (core module $m
     (import "" "drop" (func $drop (param i32)))
     (import "" "ctor" (func $ctor (param i32) (result i32)))
     (import "" "assert" (func $assert (param i32 i32)))

     (func $start
       (local $r1 i32)
       (local $r2 i32)
       (local.set $r1 (call $ctor (i32.const 100)))
       (local.set $r2 (call $ctor (i32.const 200)))

       ;; assert r1/r2 are sequential
       (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable)))
       (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable)))

       ;; reallocate r1 and it should be reassigned the same index
       (call $drop (local.get $r1))
       (local.set $r1 (call $ctor (i32.const 300)))
       (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable)))

       ;; internal values should match
       (call $assert (local.get $r1) (i32.const 300))
       (call $assert (local.get $r2) (i32.const 200))
     )

     (start $start)
  )
  (core instance (instantiate $m
     (with "" (instance
       (export "drop" (func $drop))
       (export "ctor" (func $ctor))
       (export "assert" (func $assert))
     ))
  ))
)

;; Using an index that has never been valid is a trap
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[static]resource1.assert" (func (param "r" (own $r)) (param "rep" u32)))
  ))
  (alias export $host "resource1" (type $r))
  (alias export $host "[static]resource1.assert" (func $assert))
  (core func $assert (canon lower (func $assert)))

  (core module $m
     (import "" "assert" (func $assert (param i32 i32)))

     (func (export "f")
       (call $assert (i32.const 0) (i32.const 0))
     )
  )
  (core instance $i (instantiate $m
     (with "" (instance
       (export "assert" (func $assert))
     ))
  ))

  (func (export "f") (canon lift (core func $i "f")))
)

(assert_trap (invoke "f") "unknown handle index")

;; Using an index which was previously valid but no longer valid is also a trap.
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
    (export "[static]resource1.assert" (func (param "r" (own $r)) (param "rep" u32)))
  ))
  (alias export $host "[constructor]resource1" (func $ctor))
  (alias export $host "[static]resource1.assert" (func $assert))

  (core func $assert (canon lower (func $assert)))
  (core func $ctor (canon lower (func $ctor)))

  (core module $m
     (import "" "assert" (func $assert (param i32 i32)))
     (import "" "ctor" (func $ctor (param i32) (result i32)))

     (global $handle (mut i32) i32.const 0)

     (func (export "f")
        (global.set $handle (call $ctor (i32.const 100)))
        (call $assert (global.get $handle) (i32.const 100))
     )

     (func (export "f2")
        (call $assert (global.get $handle) (i32.const 100))
     )
  )
  (core instance $i (instantiate $m
     (with "" (instance
       (export "assert" (func $assert))
       (export "ctor" (func $ctor))
     ))
  ))

  (func (export "f") (canon lift (core func $i "f")))
  (func (export "f2") (canon lift (core func $i "f2")))
)

(assert_return (invoke "f"))
(assert_trap (invoke "f2") "unknown handle index")

;; Also invalid to pass a previously valid handle to the drop intrinsic
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
  ))
  (alias export $host "resource1" (type $r))
  (alias export $host "[constructor]resource1" (func $ctor))

  (core func $drop (canon resource.drop $r))
  (core func $ctor (canon lower (func $ctor)))

  (core module $m
     (import "" "drop" (func $drop (param i32)))
     (import "" "ctor" (func $ctor (param i32) (result i32)))

     (global $handle (mut i32) i32.const 0)

     (func (export "f")
        (global.set $handle (call $ctor (i32.const 100)))
        (call $drop (global.get $handle))
     )

     (func (export "f2")
        (call $drop (global.get $handle))
     )
  )
  (core instance $i (instantiate $m
     (with "" (instance
       (export "ctor" (func $ctor))
       (export "drop" (func $drop))
     ))
  ))

  (func (export "f") (canon lift (core func $i "f")))
  (func (export "f2") (canon lift (core func $i "f2")))
)

(assert_return (invoke "f"))
(assert_trap (invoke "f2") "unknown handle index")

;; If an inner component instantiates a resource then an outer component
;; should not implicitly have access to that resource.
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
  ))

  ;; an inner component which upon instantiation will invoke the constructor,
  ;; assert that it's zero, and then forget about it.
  (component $inner
    (import "host" (instance $host
      (export "resource1" (type $r (sub resource)))
      (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
    ))
    (alias export $host "[constructor]resource1" (func $ctor))

    (core func $ctor (canon lower (func $ctor)))

    (core module $m
      (import "" "ctor" (func $ctor (param i32) (result i32)))

      (func $start
        (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (then (unreachable)))
      )
    )
    (core instance $i (instantiate $m
       (with "" (instance (export "ctor" (func $ctor))))
    ))
  )
  (instance $i (instantiate $inner (with "host" (instance $host))))

  ;; the rest of this component which is a single function that invokes `drop`
  ;; for index 0. The index 0 should be valid within the above component, but
  ;; it is not valid within this component
  (alias export $host "resource1" (type $r))
  (core func $drop (canon resource.drop $r))

  (core module $m
     (import "" "drop" (func $drop (param i32)))

     (func (export "f")
        (call $drop (i32.const 0))
     )
  )
  (core instance $i (instantiate $m
     (with "" (instance
       (export "drop" (func $drop))
     ))
  ))

  (func (export "f") (canon lift (core func $i "f")))
)

(assert_trap (invoke "f") "unknown handle index")

;; Same as the above test, but for resources defined within a component
(component
  (component $inner
    (type $r (resource (rep i32)))

    (core func $ctor (canon resource.new $r))

    (core module $m
      (import "" "ctor" (func $ctor (param i32) (result i32)))

      (func $start
        (if (i32.ne (call $ctor (i32.const 100)) (i32.const 1)) (then (unreachable)))
      )
      (start $start)
    )
    (core instance $i (instantiate $m
       (with "" (instance (export "ctor" (func $ctor))))
    ))
    (export "r" (type $r))
  )
  (instance $i (instantiate $inner))

  ;; the rest of this component which is a single function that invokes `drop`
  ;; for index 1. The index 1 should be valid within the above component, but
  ;; it is not valid within this component
  (alias export $i "r" (type $r))
  (core func $drop (canon resource.drop $r))

  (core module $m
     (import "" "drop" (func $drop (param i32)))

     (func (export "f")
        (call $drop (i32.const 1))
     )
  )
  (core instance $i (instantiate $m
     (with "" (instance
       (export "drop" (func $drop))
     ))
  ))

  (func (export "f") (canon lift (core func $i "f")))
)

(assert_trap (invoke "f") "unknown handle index 1")

;; Each instantiation of a component generates a unique resource type, so
;; allocating in one component and deallocating in another should fail.
(component
  (component $inner
    (type $r (resource (rep i32)))

    (core func $ctor (canon resource.new $r))
    (core func $drop (canon resource.drop $r))

    (core module $m
      (import "" "ctor" (func $ctor (param i32) (result i32)))
      (import "" "drop" (func $drop (param i32)))

      (func (export "alloc")
        (if (i32.ne (call $ctor (i32.const 100)) (i32.const 1)) (then (unreachable)))
      )
      (func (export "dealloc")
        (call $drop (i32.const 1))
      )
    )
    (core instance $i (instantiate $m
      (with "" (instance
        (export "ctor" (func $ctor))
        (export "drop" (func $drop))
      ))
    ))
    (func (export "alloc") (canon lift (core func $i "alloc")))
    (func (export "dealloc") (canon lift (core func $i "dealloc")))
  )
  (instance $i1 (instantiate $inner))
  (instance $i2 (instantiate $inner))

  (alias export $i1 "alloc" (func $alloc_in_1))
  (alias export $i1 "dealloc" (func $dealloc_in_1))
  (alias export $i2 "alloc" (func $alloc_in_2))
  (alias export $i2 "dealloc" (func $dealloc_in_2))

  (export "alloc-in1" (func $alloc_in_1))
  (export "dealloc-in1" (func $dealloc_in_1))
  (export "alloc-in2" (func $alloc_in_2))
  (export "dealloc-in2" (func $dealloc_in_2))
)

(assert_return (invoke "alloc-in1"))
(assert_return (invoke "dealloc-in1"))
(assert_return (invoke "alloc-in1"))
(assert_return (invoke "alloc-in2"))
(assert_return (invoke "dealloc-in2"))
(assert_trap (invoke "dealloc-in2") "unknown handle index")

;; Same as above, but the same host resource type is imported into a
;; component that is instantiated twice. Each component instance should
;; receive different tables tracking resources so a resource allocated in one
;; should not be visible in the other.
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
  ))
  (alias export $host "resource1" (type $r))
  (alias export $host "[constructor]resource1" (func $ctor))

  (component $inner
    (import "r" (type $r (sub resource)))
    (import "[constructor]r" (func $ctor (param "r" u32) (result (own $r))))

    (core func $ctor (canon lower (func $ctor)))
    (core func $drop (canon resource.drop $r))

    (core module $m
      (import "" "ctor" (func $ctor (param i32) (result i32)))
      (import "" "drop" (func $drop (param i32)))

      (func (export "alloc")
        (if (i32.ne (call $ctor (i32.const 100)) (i32.const 1)) (then (unreachable)))
      )
      (func (export "dealloc")
        (call $drop (i32.const 1))
      )
    )
    (core instance $i (instantiate $m
      (with "" (instance
        (export "ctor" (func $ctor))
        (export "drop" (func $drop))
      ))
    ))
    (func (export "alloc") (canon lift (core func $i "alloc")))
    (func (export "dealloc") (canon lift (core func $i "dealloc")))
  )
  (instance $i1 (instantiate $inner
    (with "r" (type $r))
    (with "[constructor]r" (func $ctor))
  ))
  (instance $i2 (instantiate $inner
    (with "r" (type $r))
    (with "[constructor]r" (func $ctor))
  ))

  (alias export $i1 "alloc" (func $alloc_in_1))
  (alias export $i1 "dealloc" (func $dealloc_in_1))
  (alias export $i2 "alloc" (func $alloc_in_2))
  (alias export $i2 "dealloc" (func $dealloc_in_2))

  (export "alloc-in1" (func $alloc_in_1))
  (export "dealloc-in1" (func $dealloc_in_1))
  (export "alloc-in2" (func $alloc_in_2))
  (export "dealloc-in2" (func $dealloc_in_2))
)

(assert_return (invoke "alloc-in1"))
(assert_return (invoke "dealloc-in1"))
(assert_return (invoke "alloc-in1"))
(assert_return (invoke "alloc-in2"))
(assert_return (invoke "dealloc-in2"))
(assert_trap (invoke "dealloc-in2") "unknown handle index")

;; Multiple copies of intrinsics all work
(component
  (type $r (resource (rep i32)))

  (core func $new1 (canon resource.new $r))
  (core func $new2 (canon resource.new $r))
  (core func $drop1 (canon resource.drop $r))
  (core func $drop2 (canon resource.drop $r))

  (core module $m
    (import "" "new1" (func $new1 (param i32) (result i32)))
    (import "" "new2" (func $new2 (param i32) (result i32)))
    (import "" "drop1" (func $drop1 (param i32)))
    (import "" "drop2" (func $drop2 (param i32)))

    (func $start
      ;; 2x2 matrix of pairing new/drop
      (call $drop1 (call $new1 (i32.const 101)))
      (call $drop2 (call $new1 (i32.const 102)))
      (call $drop1 (call $new2 (i32.const 103)))
      (call $drop2 (call $new2 (i32.const 104)))

      ;; should be referencing the same namespace
      (if (i32.ne (call $new1 (i32.const 105)) (i32.const 1)) (then (unreachable)))
      (if (i32.ne (call $new2 (i32.const 105)) (i32.const 2)) (then (unreachable)))

      ;; use different drops out of order
      (call $drop2 (i32.const 1))
      (call $drop1 (i32.const 2))
    )

    (start $start)
  )

  (core instance (instantiate $m
    (with "" (instance
      (export "new1" (func $new1))
      (export "new2" (func $new2))
      (export "drop1" (func $drop1))
      (export "drop2" (func $drop2))
    ))
  ))
)

;; u32::MAX isn't special in some weird way, it's just probably always invalid
;; because that's a lot of handles.
(component
  (type $r (resource (rep i32)))

  (core func $drop (canon resource.drop $r))

  (core module $m
    (import "" "drop" (func $drop (param i32)))

    (func (export "f")
      (call $drop (i32.const 0xffffffff))
    )
  )

  (core instance $i (instantiate $m
    (with "" (instance
      (export "drop" (func $drop))
    ))
  ))
  (func (export "f") (canon lift (core func $i "f")))
)
(assert_trap (invoke "f") "unknown handle index")

;; Test behavior of running a destructor for local resources
(component
  (core module $m1
    (global $drops (mut i32) i32.const 0)
    (global $last_drop (mut i32) i32.const -1)

    (func (export "dtor") (param i32)
      (global.set $drops (i32.add (global.get $drops) (i32.const 1)))
      (global.set $last_drop (local.get 0))
    )
    (func (export "drops") (result i32) global.get $drops)
    (func (export "last-drop") (result i32) global.get $last_drop)
  )
  (core instance $i1 (instantiate $m1))

  (type $r1 (resource (rep i32)))
  (type $r2 (resource (rep i32) (dtor (func $i1 "dtor"))))

  (core func $drop1 (canon resource.drop $r1))
  (core func $drop2 (canon resource.drop $r2))
  (core func $new1 (canon resource.new $r1))
  (core func $new2 (canon resource.new $r2))

  (core module $m2
    (import "" "drop1" (func $drop1 (param i32)))
    (import "" "drop2" (func $drop2 (param i32)))
    (import "" "new1" (func $new1 (param i32) (result i32)))
    (import "" "new2" (func $new2 (param i32) (result i32)))
    (import "i1" "drops" (func $drops (result i32)))
    (import "i1" "last-drop" (func $last-drop (result i32)))

    (func $start
      (local $r1 i32)
      (local $r2 i32)

      (local.set $r1 (call $new1 (i32.const 100)))
      (local.set $r2 (call $new2 (i32.const 200)))

      ;; indexes start at 1 and while they have distinct types they should be
      ;; within the same table.
      (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable)))
      (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable)))

      ;; nothing should be dropped yet
      (if (i32.ne (call $drops) (i32.const 0)) (then (unreachable)))
      (if (i32.ne (call $last-drop) (i32.const -1)) (then (unreachable)))

      ;; dropping a resource without a destructor is ok, but shouldn't tamper
      ;; with anything.
      (call $drop1 (local.get $r1))
      (if (i32.ne (call $drops) (i32.const 0)) (then (unreachable)))
      (if (i32.ne (call $last-drop) (i32.const -1)) (then (unreachable)))

      ;; drop r2 which should record a drop and additionally record the private
      ;; representation value which was dropped
      (call $drop2 (local.get $r2))
      (if (i32.ne (call $drops) (i32.const 1)) (then (unreachable)))
      (if (i32.ne (call $last-drop) (i32.const 200)) (then (unreachable)))

      ;; do it all over again
      (local.set $r2 (call $new2 (i32.const 300)))
      (call $drop2 (local.get $r2))
      (if (i32.ne (call $drops) (i32.const 2)) (then (unreachable)))
      (if (i32.ne (call $last-drop) (i32.const 300)) (then (unreachable)))
    )

    (start $start)
  )

  (core instance $i2 (instantiate $m2
    (with "" (instance
      (export "drop1" (func $drop1))
      (export "drop2" (func $drop2))
      (export "new1" (func $new1))
      (export "new2" (func $new2))
    ))
    (with "i1" (instance $i1))
  ))
)

;; Test dropping a host resource
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
    (export "[static]resource1.last-drop" (func (result u32)))
    (export "[static]resource1.drops" (func (result u32)))
  ))

  (alias export $host "resource1" (type $r))
  (alias export $host "[constructor]resource1" (func $ctor))
  (alias export $host "[static]resource1.last-drop" (func $last-drop))
  (alias export $host "[static]resource1.drops" (func $drops))

  (core func $drop (canon resource.drop $r))
  (core func $ctor (canon lower (func $ctor)))
  (core func $last-drop (canon lower (func $last-drop)))
  (core func $drops (canon lower (func $drops)))

  (core module $m
    (import "" "drop" (func $drop (param i32)))
    (import "" "ctor" (func $ctor (param i32) (result i32)))
    (import "" "last-drop" (func $last-drop (result i32)))
    (import "" "drops" (func $raw-drops (result i32)))

    (global $init-drop-cnt (mut i32) i32.const 0)

    (func $drops (result i32)
      (i32.sub (call $raw-drops) (global.get $init-drop-cnt))
    )

    (func $start
      (local $r1 i32)
      (global.set $init-drop-cnt (call $raw-drops))

      (local.set $r1 (call $ctor (i32.const 100)))

      ;; should be no drops yet
      (if (i32.ne (call $drops) (i32.const 0)) (then (unreachable)))

      ;; should count a drop
      (call $drop (local.get $r1))
      (if (i32.ne (call $drops) (i32.const 1)) (then (unreachable)))
      (if (i32.ne (call $last-drop) (i32.const 100)) (then (unreachable)))

      ;; do it again to be sure
      (local.set $r1 (call $ctor (i32.const 200)))
      (call $drop (local.get $r1))
      (if (i32.ne (call $drops) (i32.const 2)) (then (unreachable)))
      (if (i32.ne (call $last-drop) (i32.const 200)) (then (unreachable)))
    )

    (start $start)
  )
  (core instance (instantiate $m
    (with "" (instance
      (export "drop" (func $drop))
      (export "ctor" (func $ctor))
      (export "last-drop" (func $last-drop))
      (export "drops" (func $drops))
    ))
  ))
)

;; Test some bare-bones basics of borrowed resources
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
    (export "[method]resource1.simple" (func (param "self" (borrow $r)) (param "rep" u32)))
    (export "[method]resource1.take-borrow" (func (param "self" (borrow $r)) (param "b" (borrow $r))))
    (export "[method]resource1.take-own" (func (param "self" (borrow $r)) (param "b" (own $r))))
  ))

  (alias export $host "resource1" (type $r))
  (alias export $host "[constructor]resource1" (func $ctor))
  (alias export $host "[method]resource1.simple" (func $simple))
  (alias export $host "[method]resource1.take-borrow" (func $take-borrow))
  (alias export $host "[method]resource1.take-own" (func $take-own))

  (core func $drop (canon resource.drop $r))
  (core func $ctor (canon lower (func $ctor)))
  (core func $simple (canon lower (func $simple)))
  (core func $take-own (canon lower (func $take-own)))
  (core func $take-borrow (canon lower (func $take-borrow)))

  (core module $m
    (import "" "drop" (func $drop (param i32)))
    (import "" "ctor" (func $ctor (param i32) (result i32)))
    (import "" "simple" (func $simple (param i32 i32)))
    (import "" "take-own" (func $take-own (param i32 i32)))
    (import "" "take-borrow" (func $take-borrow (param i32 i32)))


    (func $start
      (local $r1 i32)
      (local $r2 i32)
      (local.set $r1 (call $ctor (i32.const 100)))
      (local.set $r2 (call $ctor (i32.const 200)))

      (call $simple (local.get $r1) (i32.const 100))
      (call $simple (local.get $r1) (i32.const 100))
      (call $simple (local.get $r2) (i32.const 200))
      (call $simple (local.get $r1) (i32.const 100))
      (call $simple (local.get $r2) (i32.const 200))
      (call $simple (local.get $r2) (i32.const 200))

      (call $drop (local.get $r1))
      (call $drop (local.get $r2))


      (local.set $r1 (call $ctor (i32.const 200)))
      (local.set $r2 (call $ctor (i32.const 300)))
      (call $take-borrow (local.get $r1) (local.get $r2))
      (call $take-borrow (local.get $r2) (local.get $r1))
      (call $take-borrow (local.get $r1) (local.get $r1))
      (call $take-borrow (local.get $r2) (local.get $r2))

      (call $take-own (local.get $r1) (call $ctor (i32.const 400)))
      (call $take-own (local.get $r2) (call $ctor (i32.const 500)))
      (call $take-own (local.get $r2) (local.get $r1))
      (call $drop (local.get $r2))

      ;; table should be empty at this point, so a fresh allocation should get
      ;; index 0
      (if (i32.ne (call $ctor (i32.const 600)) (i32.const 1)) (then (unreachable)))
    )

    (start $start)
  )
  (core instance (instantiate $m
    (with "" (instance
      (export "drop" (func $drop))
      (export "ctor" (func $ctor))
      (export "simple" (func $simple))
      (export "take-own" (func $take-own))
      (export "take-borrow" (func $take-borrow))
    ))
  ))
)

;; Cannot pass out an owned resource when it's borrowed by the same call
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
    (export "[method]resource1.take-own" (func (param "self" (borrow $r)) (param "b" (own $r))))
  ))

  (alias export $host "resource1" (type $r))
  (alias export $host "[constructor]resource1" (func $ctor))
  (alias export $host "[method]resource1.take-own" (func $take-own))

  (core func $drop (canon resource.drop $r))
  (core func $ctor (canon lower (func $ctor)))
  (core func $take-own (canon lower (func $take-own)))

  (core module $m
    (import "" "drop" (func $drop (param i32)))
    (import "" "ctor" (func $ctor (param i32) (result i32)))
    (import "" "take-own" (func $take-own (param i32 i32)))


    (func (export "f")
      (local $r i32)
      (local.set $r (call $ctor (i32.const 100)))
      (call $take-own (local.get $r) (local.get $r))
    )
  )
  (core instance $i (instantiate $m
    (with "" (instance
      (export "drop" (func $drop))
      (export "ctor" (func $ctor))
      (export "take-own" (func $take-own))
    ))
  ))

  (func (export "f") (canon lift (core func $i "f")))
)

(assert_trap (invoke "f") "cannot remove owned resource while borrowed")

;; Borrows must actually exist
(component
  (import "host" (instance $host
    (export "resource1" (type $r (sub resource)))
    (export "[method]resource1.simple" (func (param "self" (borrow $r)) (param "b" u32)))
  ))

  (alias export $host "resource1" (type $r))
  (alias export $host "[method]resource1.simple" (func $simple))

  (core func $drop (canon resource.drop $r))
  (core func $simple (canon lower (func $simple)))

  (core module $m
    (import "" "drop" (func $drop (param i32)))
    (import "" "simple" (func $simple (param i32 i32)))


    (func (export "f")
      (call $simple (i32.const 0) (i32.const 0))
    )
  )
  (core instance $i (instantiate $m
    (with "" (instance
      (export "drop" (func $drop))
      (export "simple" (func $simple))
    ))
  ))

  (func (export "f") (canon lift (core func $i "f")))
)

(assert_trap (invoke "f") "unknown handle index 0")

(component
  (component $A
    (type $t' (resource (rep i32)))
    (export $t "t" (type $t'))

    (core func $ctor (canon resource.new $t))
    (core func $dtor (canon resource.drop $t))
    (core func $rep (canon resource.rep $t))

    (core module $m
      (import "" "dtor" (func $dtor (param i32)))
      (import "" "rep" (func $rep (param i32) (result i32)))

      (func (export "[method]t.assert") (param i32 i32)
        (if (i32.ne (local.get 0) (local.get 1)) (then (unreachable)))
      )
      (func (export "[static]t.assert-own") (param i32 i32)
        (if (i32.ne (call $rep (local.get 0)) (local.get 1)) (then (unreachable)))
        (call $dtor (local.get 0))
      )
    )
    (core instance $i (instantiate $m
      (with "" (instance
        (export "dtor" (func $dtor))
        (export "rep" (func $rep))
      ))
    ))
    (func (export "[constructor]t") (param "x" u32) (result (own $t))
      (canon lift (core func $ctor)))
    (func (export "[method]t.assert") (param "self" (borrow $t)) (param "x" u32)
      (canon lift (core func $i "[method]t.assert")))
    (func (export "[static]t.assert-own") (param "self" (own $t)) (param "x" u32)
      (canon lift (core func $i "[static]t.assert-own")))
  )
  (instance $a (instantiate $A))

  (component $B
    (import "a" (instance $i
      (export "t" (type $t (sub resource)))
      (export "[constructor]t" (func (param "x" u32) (result (own $t))))
      (export "[method]t.assert" (func (param "self" (borrow $t)) (param "x" u32)))
      (export "[static]t.assert-own" (func (param "self" (own $t)) (param "x" u32)))
    ))

    (alias export $i "t" (type $t))
    (alias export $i "[constructor]t" (func $ctor))
    (alias export $i "[method]t.assert" (func $assert-borrow))
    (alias export $i "[static]t.assert-own" (func $assert-own))

    (core func $ctor (canon lower (func $ctor)))
    (core func $dtor (canon resource.drop $t))
    (core func $assert-own (canon lower (func $assert-own)))
    (core func $assert-borrow (canon lower (func $assert-borrow)))

    (core module $m
      (import "" "ctor" (func $ctor (param i32) (result i32)))
      (import "" "dtor" (func $dtor (param i32)))
      (import "" "assert-own" (func $assert-own (param i32 i32)))
      (import "" "assert-borrow" (func $assert-borrow (param i32 i32)))

      (func (export "f")
        (local $r1 i32)
        (local $r2 i32)

        (local.set $r1 (call $ctor (i32.const 100)))
        (local.set $r2 (call $ctor (i32.const 200)))

        (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable)))
        (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable)))

        (call $assert-borrow (local.get $r2) (i32.const 200))
        (call $assert-borrow (local.get $r1) (i32.const 100))

        (call $assert-own (local.get $r2) (i32.const 200))
        (call $dtor (local.get $r1))
      )
    )
    (core instance $i (instantiate $m
      (with "" (instance
        (export "ctor" (func $ctor))
        (export "dtor" (func $dtor))
        (export "assert-own" (func $assert-own))
        (export "assert-borrow" (func $assert-borrow))
      ))
    ))
    (func (export "f") (canon lift (core func $i "f")))
  )
  (instance $b (instantiate $B (with "a" (instance $a))))
  (export "f" (func $b "f"))
)

(assert_return (invoke "f"))

;; Test destructor behavior when using the wrong resource type
(component definition $C
  (type $r1 (resource (rep i32)))
  (type $r2 (resource (rep i32)))

  (core func $drop1 (canon resource.drop $r1))
  (core func $drop2 (canon resource.drop $r2))
  (core func $new1 (canon resource.new $r1))
  (core func $new2 (canon resource.new $r2))

  (core module $m2
    (import "" "drop1" (func $drop1 (param i32)))
    (import "" "drop2" (func $drop2 (param i32)))
    (import "" "new1" (func $new1 (param i32) (result i32)))
    (import "" "new2" (func $new2 (param i32) (result i32)))

    (func (export "drop-r1-as-r2") (call $drop2 (call $new1 (i32.const 100))))
    (func (export "return-r1-as-r2") (result i32) (call $new1 (i32.const 100)))
  )

  (core instance $i2 (instantiate $m2
    (with "" (instance
      (export "drop1" (func $drop1))
      (export "drop2" (func $drop2))
      (export "new1" (func $new1))
      (export "new2" (func $new2))
    ))
  ))

  (export $r2' "r2" (type $r2))
  (func (export "drop-r1-as-r2") (canon lift (core func $i2 "drop-r1-as-r2")))
  (func (export "return-r1-as-r2") (result (own $r2')) (canon lift (core func $i2 "return-r1-as-r2")))
)

(component instance $C1 $C)
(assert_trap (invoke "drop-r1-as-r2") "handle index 1 used with the wrong type, expected guest-defined resource but found a different guest-defined resource")
(component instance $C1 $C)
(assert_trap (invoke "return-r1-as-r2") "handle index 1 used with the wrong type, expected guest-defined resource but found a different guest-defined resource")