Skip to content

Add support for non-nullable tables and init expressions#8405

Open
pufferfish101007 wants to merge 12 commits intoWebAssembly:mainfrom
pufferfish101007:non-nullable-table
Open

Add support for non-nullable tables and init expressions#8405
pufferfish101007 wants to merge 12 commits intoWebAssembly:mainfrom
pufferfish101007:non-nullable-table

Conversation

@pufferfish101007
Copy link

@pufferfish101007 pufferfish101007 commented Mar 1, 2026

Resolves #5628

Adds support for non-nullable tables with init expressions. I'm not totally sure how I should go about adding new tests, but manual test cases are below. I have also unignored previously ignored spec tests instance.wast, ref_is_null.wast, table.wast, i31.wast, which now all pass. I have also changed the reason for ignoring array.wast as it does not (and has not in the past, AFAICT) rely on non-nullable table types.

This introduces a breaking change in the C api, as it adds an extra argument to addTable. Is this ok? Should a new method be added instead? (e.g. addTableWithInit?)

Manual tests

The validation ones might overlap with the spec tests. Unchecked boxes indicate tests that currently fail.

  • Nullable tables should continue to round-trip correctly:
(module
 (type $0 (func (param i32)))
 (table $0 0 funcref)
 (elem $0 (i32.const 0))
 (func $0 (param $0 i32)
 )
)
  • Non-nullable tables with init expr should round-trip correctly:
(module
 (type $0 (func (param i32)))
 (table $0 0 (ref $0) (ref.func $0))
 (func $0 (param $0 i32)
 )
)
  • Non-nullable table without an init expr should fail validation:
(module
 (type $0 (func (param i32)))
 (table $0 0 (ref $0))
 (func $0 (param $0 i32)
 )
)

Correctly fails with [wasm-validator error in module] unexpected false: tables with non-nullable types require an initializer expression, on table

  • Nullable table with init expr should rountrip correctly:
(module
 (type $0 (func (param i32)))
 (table $0 0 funcref (ref.func $0))
 (func $0 (type $0) (param $0 i32)
 )
)
  • Non-nullable table init expr should be validated to be a subtype of the table type
(module
 (type $0 (func (param i32)))
 (table $0 0 (ref $0) (i32.const 0))
 (func $0 (param $0 i32)
 )
)

Correctly fails with [wasm-validator error in module] init expression must be a subtype of the table type, on (i32.const 0)

  • Nullable table init expr (if present) should be validated to be a subtype of the table type
(module
 (type $0 (func (param i32)))
 (table $0 0 funcref (i32.const 0))
 (func $0 (param $0 i32)
 )
)

Correctly fails with [wasm-validator error in module] init expression must be a subtype of the table type, on (i32.const 0)

  • wasm-opt doesn't remove init expr as dead code
(module
 (type $0 (func (param i32)))
 (table $0 0 funcref (ref.func $0))
 (export "table" (table $0))
 (func $0 (type $0) (param $0 i32)
 )
)
  • spec test global.wast fails at the following (validation succeeds), although I don't understand why the module should be invalid:
(assert_invalid
  (module
    (global $g funcref (ref.null func))
    (table $t 10 funcref (global.get $g))
  )
  "unknown global"
)

@stevenfontanella
Copy link
Member

I have also unignored previously ignored spec tests instance.wast, ref_is_null.wast, table.wast, i31.wast, which now all pass. I have also changed the reason for ignoring array.wast as it does not (and has not in the past, AFAICT) rely on non-nullable table types.

I don't see these changes in shared.py, can we add them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a CHANGELOG.md entry under Current Trunk describing the breakage and how to fix it for C/JS library users.

};
self['addTable'] = function(table, initial, maximum, type = Module['_BinaryenTypeFuncref']()) {
return preserveStack(() => Module['_BinaryenAddTable'](module, strToStack(table), initial, maximum, type));
self['addTable'] = function(table, initial, maximum, type = Module['_BinaryenTypeFuncref'](), init = null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setMemory has a default param for null but most other cases seem to rely on unset params being implicitly undefined in JS, maybe we should do the same here?

i.e.

function(..., init) {
  return preserveStack(() => Module['_BinaryenAddTable'](..., init)
}

(or check for undefined if needed)

cc @kripken if you have any opinion

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think optional parameters need to come after all non-optional parameters, so init would need to come before type. To me, that seems less nice, both because adding it at the end isn't breaking for the JS api, and because the init expression would naturally come at the end when writing WAT.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

@tlively tlively self-requested a review March 2, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Table init exprs appear to be unimplemented

2 participants