| import os | |
| from scripts.test import shared | |
| from . import utils | |
| # Consider adding new tests to test/lit/validation instead | |
| class FeatureValidationTest(utils.BinaryenTestCase): | |
| def check_feature(self, module, error, flag, const_flags=[]): | |
| p = shared.run_process(shared.WASM_OPT + | |
| ['--mvp-features', '--print', '-o', os.devnull] + | |
| const_flags, | |
| input=module, check=False, capture_output=True) | |
| self.assertIn(error, p.stderr) | |
| self.assertIn('Fatal: error validating input', p.stderr) | |
| self.assertNotEqual(p.returncode, 0) | |
| p = shared.run_process( | |
| shared.WASM_OPT + ['--mvp-features', '--print', '-o', os.devnull] + | |
| const_flags + [flag], | |
| input=module, | |
| check=False, | |
| capture_output=True) | |
| self.assertEqual(p.returncode, 0) | |
| def check_simd(self, module, error): | |
| self.check_feature(module, error, '--enable-simd') | |
| def check_sign_ext(self, module, error): | |
| self.check_feature(module, error, '--enable-sign-ext') | |
| def check_bulk_mem(self, module, error): | |
| self.check_feature(module, error, '--enable-bulk-memory') | |
| def check_bulk_mem_opt(self, module, error): | |
| self.check_feature(module, error, '--enable-bulk-memory-opt') | |
| def check_exception_handling(self, module, error): | |
| self.check_feature(module, error, '--enable-exception-handling') | |
| def check_tail_call(self, module, error): | |
| self.check_feature(module, error, '--enable-tail-call') | |
| def check_reference_types(self, module, error): | |
| self.check_feature(module, error, '--enable-reference-types') | |
| def check_multivalue(self, module, error): | |
| self.check_feature(module, error, '--enable-multivalue') | |
| def check_multivalue_exception_handling(self, module, error): | |
| self.check_feature(module, error, '--enable-multivalue', | |
| ['--enable-exception-handling']) | |
| def check_gc(self, module, error): | |
| # GC implies reference types | |
| self.check_feature(module, error, '--enable-gc', | |
| ['--enable-reference-types']) | |
| def check_stack_switching(self, module, error): | |
| # Stack switching implies function references (which is provided by | |
| # gc in binaryen, and implies reference types) and exceptions | |
| self.check_feature(module, error, '--enable-stack-switching', | |
| ['--enable-gc', '--enable-reference-types', '--enable-exception-handling']) | |
| def test_v128_signature(self): | |
| module = ''' | |
| (module | |
| (func $foo (param $0 v128) (result v128) | |
| (local.get $0) | |
| ) | |
| ) | |
| ''' | |
| self.check_simd(module, 'all used types should be allowed') | |
| def test_v128_global(self): | |
| module = ''' | |
| (module | |
| (global $foo (mut v128) (v128.const i32x4 0 0 0 0)) | |
| ) | |
| ''' | |
| self.check_simd(module, 'all used types should be allowed') | |
| def test_v128_local(self): | |
| module = ''' | |
| (module | |
| (func $foo | |
| (local v128) | |
| ) | |
| ) | |
| ''' | |
| self.check_simd(module, 'all used types should be allowed') | |
| def test_simd_const(self): | |
| module = ''' | |
| (module | |
| (func $foo | |
| (drop (v128.const i32x4 0 0 0 0)) | |
| ) | |
| ) | |
| ''' | |
| self.check_simd(module, 'all used features should be allowed') | |
| def test_simd_load(self): | |
| module = ''' | |
| (module | |
| (memory 1 1) | |
| (func $foo | |
| (drop (v128.load (i32.const 0))) | |
| ) | |
| ) | |
| ''' | |
| self.check_simd(module, 'SIMD operations require SIMD [--enable-simd]') | |
| def test_simd_splat(self): | |
| module = ''' | |
| (module | |
| (func $foo | |
| (drop (i32x4.splat (i32.const 0))) | |
| ) | |
| ) | |
| ''' | |
| self.check_simd(module, 'all used features should be allowed') | |
| def test_sign_ext(self): | |
| module = ''' | |
| (module | |
| (func $foo | |
| (drop (i32.extend8_s (i32.const 7))) | |
| ) | |
| ) | |
| ''' | |
| self.check_sign_ext(module, 'all used features should be allowed') | |
| def test_bulk_mem_inst(self): | |
| module = ''' | |
| (module | |
| (memory 1 1) | |
| (func $foo | |
| (memory.copy (i32.const 0) (i32.const 8) (i32.const 8)) | |
| ) | |
| ) | |
| ''' | |
| self.check_bulk_mem_opt(module, | |
| 'memory.copy operations require bulk memory operations [--enable-bulk-memory-opt]') | |
| # Test that enabling bulk-memory also enables bulk-memory-opt | |
| self.check_bulk_mem(module, | |
| 'memory.copy operations require bulk memory operations [--enable-bulk-memory-opt]') | |
| def test_bulk_mem_segment(self): | |
| module = ''' | |
| (module | |
| (memory 256 256) | |
| (data "42") | |
| ) | |
| ''' | |
| self.check_bulk_mem(module, 'nonzero segment flags require bulk memory [--enable-bulk-memory]') | |
| def test_tail_call(self): | |
| module = ''' | |
| (module | |
| (func $bar) | |
| (func $foo | |
| (return_call $bar) | |
| ) | |
| ) | |
| ''' | |
| self.check_tail_call(module, 'return_call* requires tail calls [--enable-tail-call]') | |
| def test_tail_call_indirect(self): | |
| module = ''' | |
| (module | |
| (type $T (func)) | |
| (table $0 1 1 funcref) | |
| (func $foo | |
| (return_call_indirect (type $T) | |
| (i32.const 0) | |
| ) | |
| ) | |
| ) | |
| ''' | |
| self.check_tail_call(module, 'return_call* requires tail calls [--enable-tail-call]') | |
| def test_reference_types_externref(self): | |
| module = ''' | |
| (module | |
| (import "env" "test1" (func $test1 (param externref) (result externref))) | |
| (import "env" "test2" (global $test2 externref)) | |
| (export "test1" (func $test1)) | |
| (export "test2" (global $test2)) | |
| (func $externref_test (param $0 externref) (result externref) | |
| (return | |
| (call $test1 | |
| (local.get $0) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ''' | |
| self.check_reference_types(module, 'all used types should be allowed') | |
| def test_tag(self): | |
| module = ''' | |
| (module | |
| (tag $e (param i32)) | |
| (func $foo | |
| (throw $e (i32.const 0)) | |
| ) | |
| ) | |
| ''' | |
| self.check_exception_handling(module, 'Tags require exception-handling [--enable-exception-handling]') | |
| def test_multivalue_import(self): | |
| module = ''' | |
| (module | |
| (import "env" "foo" (func $foo (result i32 i64))) | |
| ) | |
| ''' | |
| self.check_multivalue(module, 'Imported multivalue function requires multivalue [--enable-multivalue]') | |
| def test_multivalue_function(self): | |
| module = ''' | |
| (module | |
| (func $foo (result i32 i64) | |
| (tuple.make 2 | |
| (i32.const 42) | |
| (i64.const 42) | |
| ) | |
| ) | |
| ) | |
| ''' | |
| self.check_multivalue(module, 'Multivalue function results ' + | |
| '(multivalue is not enabled)') | |
| def test_multivalue_tag(self): | |
| module = ''' | |
| (module | |
| (tag $foo (param i32 i64)) | |
| ) | |
| ''' | |
| self.check_multivalue_exception_handling(module, 'Multivalue tag type requires multivalue [--enable-multivalue]') | |
| def test_multivalue_block(self): | |
| module = ''' | |
| (module | |
| (func $foo | |
| (tuple.drop 2 | |
| (block (result i32 i64) | |
| (tuple.make 2 | |
| (i32.const 42) | |
| (i64.const 42) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ''' | |
| self.check_multivalue(module, 'Block type requires additional features') | |
| def test_i31_global(self): | |
| module = ''' | |
| (module | |
| (global $foo (ref null i31) (ref.null i31)) | |
| ) | |
| ''' | |
| self.check_gc(module, 'all used types should be allowed') | |
| def test_i31_local(self): | |
| module = ''' | |
| (module | |
| (func $foo | |
| (local $0 (ref null i31)) | |
| ) | |
| ) | |
| ''' | |
| self.check_gc(module, 'all used types should be allowed') | |
| def test_eqref_global(self): | |
| module = ''' | |
| (module | |
| (global $foo eqref (ref.null eq)) | |
| ) | |
| ''' | |
| self.check_gc(module, 'all used types should be allowed') | |
| def test_eqref_local(self): | |
| module = ''' | |
| (module | |
| (func $foo | |
| (local $0 eqref) | |
| ) | |
| ) | |
| ''' | |
| self.check_gc(module, 'all used types should be allowed') | |
| def test_tag_results(self): | |
| module = ''' | |
| (module | |
| (tag $foo (result i32)) | |
| ) | |
| ''' | |
| self.check_stack_switching(module, | |
| 'Tags with result types require stack ' | |
| 'switching feature [--enable-stack-switching]') | |
| def test_cont_type(self): | |
| module = ''' | |
| (module | |
| (type $ft (func (param i32) (result i32))) | |
| (type $ct (cont $ft)) | |
| (func $foo | |
| (local $0 (ref $ct)) | |
| ) | |
| ) | |
| ''' | |
| self.check_stack_switching(module, 'all used types should be allowed') | |
| def test_call_indirect_overlong(self): | |
| # Check that the call-indirect-overlong enable and disable are ignored. | |
| module = ''' | |
| (module) | |
| ''' | |
| def check_nop(flag): | |
| p = shared.run_process( | |
| shared.WASM_OPT + ['--mvp-features', '--print', '-o', os.devnull] + | |
| [flag], | |
| input=module, | |
| check=False, | |
| capture_output=True) | |
| self.assertEqual(p.returncode, 0) | |
| check_nop('--enable-call-indirect-overlong') | |
| check_nop('--disable-call-indirect-overlong') | |
| class TargetFeaturesSectionTest(utils.BinaryenTestCase): | |
| def test_atomics(self): | |
| filename = 'atomics_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['threads']) | |
| self.assertIn('i32.atomic.rmw.add', self.disassemble(filename)) | |
| def test_bulk_memory_opt(self): | |
| filename = 'bulkmem_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['bulk-memory-opt']) | |
| self.assertIn('memory.copy', self.disassemble(filename)) | |
| def test_nontrapping_fptoint(self): | |
| filename = 'truncsat_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['nontrapping-float-to-int']) | |
| self.assertIn('i32.trunc_sat_f32_u', self.disassemble(filename)) | |
| def test_mutable_globals(self): | |
| filename = 'mutable_globals_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['mutable-globals']) | |
| self.assertIn('(import "env" "global-mut" (global $gimport$0 (mut i32)))', | |
| self.disassemble(filename)) | |
| def test_sign_ext(self): | |
| filename = 'signext_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['sign-ext']) | |
| self.assertIn('i32.extend8_s', self.disassemble(filename)) | |
| def test_simd(self): | |
| filename = 'simd_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['simd']) | |
| self.assertIn('i32x4.splat', self.disassemble(filename)) | |
| def test_tailcall(self): | |
| filename = 'tail_call_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['tail-call']) | |
| self.assertIn('return_call', self.disassemble(filename)) | |
| def test_reference_types(self): | |
| filename = 'reference_types_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['reference-types']) | |
| self.assertIn('anyref', self.disassemble(filename)) | |
| def test_exception_handling(self): | |
| filename = 'exception_handling_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['exception-handling']) | |
| self.assertIn('throw', self.disassemble(filename)) | |
| def test_gc(self): | |
| filename = 'gc_target_feature.wasm' | |
| self.roundtrip(filename) | |
| self.check_features(filename, ['reference-types', 'gc']) | |
| disassembly = self.disassemble(filename) | |
| self.assertIn('externref', disassembly) | |
| self.assertIn('eqref', disassembly) | |
| def test_superset(self): | |
| # It is ok to enable additional features past what is in the section. | |
| shared.run_process( | |
| shared.WASM_OPT + ['--print', '--detect-features', '-mvp', | |
| '--enable-simd', '--enable-sign-ext', | |
| self.input_path('signext_target_feature.wasm')]) | |
| def test_superset_even_without_detect_features(self): | |
| # It is ok to enable additional features past what is in the section, | |
| # even without passing --detect-features (which is now a no-op). | |
| path = self.input_path('signext_target_feature.wasm') | |
| shared.run_process( | |
| shared.WASM_OPT + ['--print', '--enable-simd', '-o', os.devnull, | |
| path]) | |
| def test_superset_with_detect_features(self): | |
| path = self.input_path('signext_target_feature.wasm') | |
| shared.run_process( | |
| shared.WASM_OPT + ['--print', '--detect-features', | |
| '--enable-simd', '-o', os.devnull, path]) | |
| def test_explicit_detect_features(self): | |
| self.check_features('signext_target_feature.wasm', ['simd', 'sign-ext'], | |
| opts=['-mvp', '--detect-features', '--enable-simd']) | |
| def test_emit_all_features(self): | |
| temp_path = os.path.join(shared.options.out_dir, 'test_emit_all_features.wasm') | |
| p = shared.run_process(shared.WASM_OPT + | |
| ['--emit-target-features', '-all', '-o', temp_path], | |
| input="(module)", check=False, | |
| capture_output=True, decode_output=False) | |
| self.assertEqual(p.returncode, 0) | |
| p2 = shared.run_process(shared.WASM_OPT + | |
| ['--print-features', '-o', os.devnull, temp_path], | |
| check=False, | |
| capture_output=True) | |
| self.assertEqual(p2.returncode, 0) | |
| self.assertEqual([ | |
| '--enable-threads', | |
| '--enable-mutable-globals', | |
| '--enable-nontrapping-float-to-int', | |
| '--enable-simd', | |
| '--enable-bulk-memory', | |
| '--enable-sign-ext', | |
| '--enable-exception-handling', | |
| '--enable-tail-call', | |
| '--enable-reference-types', | |
| '--enable-multivalue', | |
| '--enable-gc', | |
| '--enable-memory64', | |
| '--enable-relaxed-simd', | |
| '--enable-extended-const', | |
| '--enable-strings', | |
| '--enable-multimemory', | |
| '--enable-stack-switching', | |
| '--enable-shared-everything', | |
| '--enable-fp16', | |
| '--enable-bulk-memory-opt', | |
| '--enable-call-indirect-overlong', | |
| '--enable-custom-descriptors', | |
| '--enable-acquire-release-atomics', | |
| '--enable-custom-page-sizes', | |
| '--enable-wide-arithmetic', | |
| '--enable-compact-imports', | |
| '--enable-relaxed-atomics', | |
| ], p2.stdout.splitlines()) |