From b670bb02ad01e14cf38fa1bbd96e4273faacd4b8 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sun, 25 May 2025 14:13:46 +0200 Subject: [PATCH] Exposes Wasm's convert and trunc(ate) function Also adds a missing type case for promotable --- phasm/prelude/__init__.py | 40 +++++++ phasm/stdlib/types.py | 67 ++++++++++++ phasm/wasmgenerator.py | 5 + .../test_typeclasses/test_convertable.py | 101 ++++++++++++++++++ .../test_typeclasses/test_promotable.py | 17 +++ 5 files changed, 230 insertions(+) create mode 100644 tests/integration/test_typeclasses/test_convertable.py diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index 23402dd..461a201 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -580,6 +580,45 @@ instance_type_class(Reinterpretable, f64, i64, methods={ 'reinterpret': stdtypes.f64_i64_reinterpret, }) +Convertable = Type3Class('Convertable', (a, b, ), methods={ + 'convert': [a, b], + 'truncate': [b, a], # To prevent name clas with Fractional +}, operators={}) + +instance_type_class(Convertable, u32, f32, methods={ + 'convert': stdtypes.u32_f32_convert, + 'truncate': stdtypes.u32_f32_truncate, +}) +instance_type_class(Convertable, u32, f64, methods={ + 'convert': stdtypes.u32_f64_convert, + 'truncate': stdtypes.u32_f64_truncate, +}) +instance_type_class(Convertable, u64, f32, methods={ + 'convert': stdtypes.u64_f32_convert, + 'truncate': stdtypes.u64_f32_truncate, +}) +instance_type_class(Convertable, u64, f64, methods={ + 'convert': stdtypes.u64_f64_convert, + 'truncate': stdtypes.u64_f64_truncate, +}) +instance_type_class(Convertable, i32, f32, methods={ + 'convert': stdtypes.i32_f32_convert, + 'truncate': stdtypes.i32_f32_truncate, +}) +instance_type_class(Convertable, i32, f64, methods={ + 'convert': stdtypes.i32_f64_convert, + 'truncate': stdtypes.i32_f64_truncate, +}) +instance_type_class(Convertable, i64, f32, methods={ + 'convert': stdtypes.i64_f32_convert, + 'truncate': stdtypes.i64_f32_truncate, +}) +instance_type_class(Convertable, i64, f64, methods={ + 'convert': stdtypes.i64_f64_convert, + 'truncate': stdtypes.i64_f64_truncate, +}) + + Foldable = Type3Class('Foldable', (t, ), methods={ 'sum': [t(a), a], 'foldl': [[b, a, b], b, t(a), b], @@ -651,5 +690,6 @@ PRELUDE_METHODS = { **Extendable.methods, **Promotable.methods, **Reinterpretable.methods, + **Convertable.methods, **Foldable.methods, } diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index a6621b4..1294fe4 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -1176,6 +1176,73 @@ def f64_u64_reinterpret(g: Generator, tv_map: TypeVariableLookup) -> None: del tv_map g.i64.reinterpret_f64() +## ### +## Convertable + +def u32_f32_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f32.convert_i32_u() + +def u32_f64_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f64.convert_i32_u() + +def u64_f32_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f32.convert_i64_u() + +def u64_f64_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f64.convert_i64_u() + +def i32_f32_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f32.convert_i32_s() + +def i32_f64_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f64.convert_i32_s() + +def i64_f32_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f32.convert_i64_s() + +def i64_f64_convert(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.f64.convert_i64_s() + +def u32_f32_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i32.trunc_f32_u() + +def u32_f64_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i32.trunc_f64_u() + +def u64_f32_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i64.trunc_f32_u() + +def u64_f64_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i64.trunc_f64_u() + +def i32_f32_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i32.trunc_f32_s() + +def i32_f64_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i32.trunc_f64_s() + +def i64_f32_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i64.trunc_f32_s() + +def i64_f64_truncate(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map + g.i64.trunc_f64_s() + ## ### ## Foldable diff --git a/phasm/wasmgenerator.py b/phasm/wasmgenerator.py index 47bebc8..2560e0a 100644 --- a/phasm/wasmgenerator.py +++ b/phasm/wasmgenerator.py @@ -68,6 +68,11 @@ class Generator_i32i64: self.ge_s = functools.partial(self.generator.add_statement, f'{prefix}.ge_s') self.ge_u = functools.partial(self.generator.add_statement, f'{prefix}.ge_u') + self.trunc_f32_s = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f32_s') + self.trunc_f32_u = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f32_u') + self.trunc_f64_s = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f64_s') + self.trunc_f64_u = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f64_u') + # 2.4.4. Memory Instructions self.load = functools.partial(self.generator.add_statement, f'{prefix}.load') self.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u') diff --git a/tests/integration/test_typeclasses/test_convertable.py b/tests/integration/test_typeclasses/test_convertable.py new file mode 100644 index 0000000..d0f9156 --- /dev/null +++ b/tests/integration/test_typeclasses/test_convertable.py @@ -0,0 +1,101 @@ +import pytest +import wasmtime + +from phasm.type3.entry import Type3Exception + +from ..helpers import Suite + + +@pytest.mark.integration_test +def test_convert_not_implemented(): + code_py = """ +class Foo: + val: i32 + +class Baz: + val: i32 + +@exported +def testEntry(x: Foo) -> Baz: + return convert(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Convertable Foo Baz'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +@pytest.mark.parametrize('in_typ, in_val, out_typ, exp_val', [ + ('u32', 1000, 'f32', 1000.0, ), + ('u32', 1000, 'f64', 1000.0, ), + ('u64', 1000, 'f32', 1000.0, ), + ('u64', 1000, 'f64', 1000.0, ), + ('i32', 1000, 'f32', 1000.0, ), + ('i32', 1000, 'f64', 1000.0, ), + ('i64', 1000, 'f32', 1000.0, ), + ('i64', 1000, 'f64', 1000.0, ), +]) +def test_convert_ok(in_typ, in_val, out_typ, exp_val): + code_py = f""" +@exported +def testEntry(x: {in_typ}) -> {out_typ}: + return convert(x) +""" + + result = Suite(code_py).run_code(in_val) + assert exp_val == result.returned_value + +@pytest.mark.integration_test +def test_truncate_not_implemented(): + code_py = """ +class Foo: + val: i32 + +class Baz: + val: i32 + +@exported +def testEntry(x: Foo) -> Baz: + return truncate(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Convertable Baz Foo'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +@pytest.mark.parametrize('in_typ, in_val, out_typ, exp_val', [ + ('f32', 1000.0, 'u32', 1000, ), + ('f64', 1000.0, 'u32', 1000, ), + ('f32', 1000.0, 'u64', 1000, ), + ('f64', 1000.0, 'u64', 1000, ), + ('f32', 1000.0, 'i32', 1000, ), + ('f64', 1000.0, 'i32', 1000, ), + ('f32', 1000.0, 'i64', 1000, ), + ('f64', 1000.0, 'i64', 1000, ), + + ('f32', 3e9, 'u32', 3e9, ), + ('f32', 1e19, 'u64', 9999999980506447872, ), +]) +def test_truncate_ok(in_typ, in_val, out_typ, exp_val): + code_py = f""" +@exported +def testEntry(x: {in_typ}) -> {out_typ}: + return truncate(x) +""" + + result = Suite(code_py).run_code(in_val) + assert exp_val == result.returned_value + +@pytest.mark.integration_test +@pytest.mark.parametrize('in_typ, in_val, out_typ', [ + ('f32', 3e9, 'i32', ), + ('f64', 1e19, 'i64', ), +]) +def test_truncate_not_representible(in_typ, in_val, out_typ): + code_py = f""" +@exported +def testEntry(x: {in_typ}) -> {out_typ}: + return truncate(x) +""" + + with pytest.raises(wasmtime.Trap, match='integer overflow'): + Suite(code_py).run_code(in_val) diff --git a/tests/integration/test_typeclasses/test_promotable.py b/tests/integration/test_typeclasses/test_promotable.py index 7555d31..82f29dc 100644 --- a/tests/integration/test_typeclasses/test_promotable.py +++ b/tests/integration/test_typeclasses/test_promotable.py @@ -36,6 +36,23 @@ def testEntry() -> f64: assert 10.5 == result.returned_value +@pytest.mark.integration_test +def test_demote_not_implemented(): + code_py = """ +class Foo: + val: i32 + +class Baz: + val: i32 + +@exported +def testEntry(x: Foo) -> Baz: + return demote(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Promotable Baz Foo'): + Suite(code_py).run_code() + @pytest.mark.integration_test def test_demote_ok(): code_py = """