;; Test the element section

;; Syntax
(module
  (table $t 10 funcref)
  (func $f)
  (elem (i32.const 0))
  (elem (i32.const 0) $f $f)
  (elem (offset (i32.const 0)))
  (elem (offset (i32.const 0)) $f $f)
  (elem 0 (i32.const 0))
  (elem 0x0 (i32.const 0) $f $f)
  (elem 0x000 (offset (i32.const 0)))
  (elem 0 (offset (i32.const 0)) $f $f)
  (elem $t (i32.const 0))
  (elem $t (i32.const 0) $f $f)
  (elem $t (offset (i32.const 0)))
  (elem $t (offset (i32.const 0)) $f $f)
)

;; Basic use

(module
  (table 10 funcref)
  (func $f)
  (elem (i32.const 0) $f)
)
(module
  (import "spectest" "table" (table 10 funcref))
  (func $f)
  (elem (i32.const 0) $f)
)

(module
  (table 10 funcref)
  (func $f)
  (elem (i32.const 0) $f)
  (elem (i32.const 3) $f)
  (elem (i32.const 7) $f)
  (elem (i32.const 5) $f)
  (elem (i32.const 3) $f)
)
(module
  (import "spectest" "table" (table 10 funcref))
  (func $f)
  (elem (i32.const 9) $f)
  (elem (i32.const 3) $f)
  (elem (i32.const 7) $f)
  (elem (i32.const 3) $f)
  (elem (i32.const 5) $f)
)

(module
  (global (import "spectest" "global_i32") i32)
  (table 1000 funcref)
  (func $f)
  (elem (global.get 0) $f)
)

(module
  (global $g (import "spectest" "global_i32") i32)
  (table 1000 funcref)
  (func $f)
  (elem (global.get $g) $f)
)

(module
  (type $out-i32 (func (result i32)))
  (table 10 funcref)
  (elem (i32.const 7) $const-i32-a)
  (elem (i32.const 9) $const-i32-b)
  (func $const-i32-a (type $out-i32) (i32.const 65))
  (func $const-i32-b (type $out-i32) (i32.const 66))
  (func (export "call-7") (type $out-i32)
    (call_indirect (type $out-i32) (i32.const 7))
  )
  (func (export "call-9") (type $out-i32)
    (call_indirect (type $out-i32) (i32.const 9))
  )
)
(assert_return (invoke "call-7") (i32.const 65))
(assert_return (invoke "call-9") (i32.const 66))

;; Corner cases

(module
  (table 10 funcref)
  (func $f)
  (elem (i32.const 9) $f)
)
(module
  (import "spectest" "table" (table 10 funcref))
  (func $f)
  (elem (i32.const 9) $f)
)

(module
  (table 0 funcref)
  (elem (i32.const 0))
)
(module
  (import "spectest" "table" (table 0 funcref))
  (elem (i32.const 0))
)

(module
  (table 0 0 funcref)
  (elem (i32.const 0))
)

(module
  (table 20 funcref)
  (elem (i32.const 20))
)

(module
  (import "spectest" "table" (table 0 funcref))
  (func $f)
  (elem (i32.const 0) $f)
)

(module
  (import "spectest" "table" (table 0 100 funcref))
  (func $f)
  (elem (i32.const 0) $f)
)

(module
  (import "spectest" "table" (table 0 funcref))
  (func $f)
  (elem (i32.const 1) $f)
)

(module
  (import "spectest" "table" (table 0 30 funcref))
  (func $f)
  (elem (i32.const 1) $f)
)

;; Invalid bounds for elements

(assert_unlinkable
  (module
    (table 0 funcref)
    (func $f)
    (elem (i32.const 0) $f)
  )
  "elements segment does not fit"
)

(assert_unlinkable
  (module
    (table 0 0 funcref)
    (func $f)
    (elem (i32.const 0) $f)
  )
  "elements segment does not fit"
)

(assert_unlinkable
  (module
    (table 0 1 funcref)
    (func $f)
    (elem (i32.const 0) $f)
  )
  "elements segment does not fit"
)

(assert_unlinkable
  (module
    (table 0 funcref)
    (elem (i32.const 1))
  )
  "elements segment does not fit"
)

(assert_unlinkable
  (module
    (table 10 funcref)
    (func $f)
    (elem (i32.const 10) $f)
  )
  "elements segment does not fit"
)
(assert_unlinkable
  (module
    (import "spectest" "table" (table 10 funcref))
    (func $f)
    (elem (i32.const 10) $f)
  )
  "elements segment does not fit"
)

(assert_unlinkable
  (module
    (table 10 20 funcref)
    (func $f)
    (elem (i32.const 10) $f)
  )
  "elements segment does not fit"
)
(assert_unlinkable
  (module
    (import "spectest" "table" (table 10 funcref))
    (func $f)
    (elem (i32.const 10) $f)
  )
  "elements segment does not fit"
)

(assert_unlinkable
  (module
    (table 10 funcref)
    (func $f)
    (elem (i32.const -1) $f)
  )
  "elements segment does not fit"
)
(assert_unlinkable
  (module
    (import "spectest" "table" (table 10 funcref))
    (func $f)
    (elem (i32.const -1) $f)
  )
  "elements segment does not fit"
)

(assert_unlinkable
  (module
    (table 10 funcref)
    (func $f)
    (elem (i32.const -10) $f)
  )
  "elements segment does not fit"
)
(assert_unlinkable
  (module
    (import "spectest" "table" (table 10 funcref))
    (func $f)
    (elem (i32.const -10) $f)
  )
  "elements segment does not fit"
)

;; Element without table

(assert_invalid
  (module
    (func $f)
    (elem (i32.const 0) $f)
  )
  "unknown table 0"
)

;; Invalid offsets

(assert_invalid
  (module
    (table 1 funcref)
    (elem (i64.const 0))
  )
  "type mismatch"
)

(assert_invalid
  (module
    (table 1 funcref)
    (elem (i32.ctz (i32.const 0)))
  )
  "constant expression required"
)

(assert_invalid
  (module
    (table 1 funcref)
    (elem (nop))
  )
  "constant expression required"
)

(assert_invalid
  (module
    (table 1 funcref)
    (elem (offset (nop) (i32.const 0)))
  )
  "constant expression required"
)

(assert_invalid
  (module
    (table 1 funcref)
    (elem (offset (i32.const 0) (nop)))
  )
  "constant expression required"
)

;; Use of internal globals in constant expressions is not allowed in MVP.
;; (assert_invalid
;;   (module (memory 1) (data (global.get $g)) (global $g (mut i32) (i32.const 0)))
;;   "constant expression required"
;; )

;; Two elements target the same slot

(module
  (type $out-i32 (func (result i32)))
  (table 10 funcref)
  (elem (i32.const 9) $const-i32-a)
  (elem (i32.const 9) $const-i32-b)
  (func $const-i32-a (type $out-i32) (i32.const 65))
  (func $const-i32-b (type $out-i32) (i32.const 66))
  (func (export "call-overwritten") (type $out-i32)
    (call_indirect (type $out-i32) (i32.const 9))
  )
)
(assert_return (invoke "call-overwritten") (i32.const 66))

(module
  (type $out-i32 (func (result i32)))
  (import "spectest" "table" (table 10 funcref))
  (elem (i32.const 9) $const-i32-a)
  (elem (i32.const 9) $const-i32-b)
  (func $const-i32-a (type $out-i32) (i32.const 65))
  (func $const-i32-b (type $out-i32) (i32.const 66))
  (func (export "call-overwritten-element") (type $out-i32)
    (call_indirect (type $out-i32) (i32.const 9))
  )
)
(assert_return (invoke "call-overwritten-element") (i32.const 66))

;; Element sections across multiple modules change the same table

(module $module1
  (type $out-i32 (func (result i32)))
  (table (export "shared-table") 10 funcref)
  (elem (i32.const 8) $const-i32-a)
  (elem (i32.const 9) $const-i32-b)
  (func $const-i32-a (type $out-i32) (i32.const 65))
  (func $const-i32-b (type $out-i32) (i32.const 66))
  (func (export "call-7") (type $out-i32)
    (call_indirect (type $out-i32) (i32.const 7))
  )
  (func (export "call-8") (type $out-i32)
    (call_indirect (type $out-i32) (i32.const 8))
  )
  (func (export "call-9") (type $out-i32)
    (call_indirect (type $out-i32) (i32.const 9))
  )
)

(register "module1" $module1)

(assert_trap (invoke $module1 "call-7") "uninitialized element 7")
(assert_return (invoke $module1 "call-8") (i32.const 65))
(assert_return (invoke $module1 "call-9") (i32.const 66))

(module $module2
  (type $out-i32 (func (result i32)))
  (import "module1" "shared-table" (table 10 funcref))
  (elem (i32.const 7) $const-i32-c)
  (elem (i32.const 8) $const-i32-d)
  (func $const-i32-c (type $out-i32) (i32.const 67))
  (func $const-i32-d (type $out-i32) (i32.const 68))
)

(assert_return (invoke $module1 "call-7") (i32.const 67))
(assert_return (invoke $module1 "call-8") (i32.const 68))
(assert_return (invoke $module1 "call-9") (i32.const 66))

(module $module3
  (type $out-i32 (func (result i32)))
  (import "module1" "shared-table" (table 10 funcref))
  (elem (i32.const 8) $const-i32-e)
  (elem (i32.const 9) $const-i32-f)
  (func $const-i32-e (type $out-i32) (i32.const 69))
  (func $const-i32-f (type $out-i32) (i32.const 70))
)

(assert_return (invoke $module1 "call-7") (i32.const 67))
(assert_return (invoke $module1 "call-8") (i32.const 69))
(assert_return (invoke $module1 "call-9") (i32.const 70))
