Exposes Wasm's convert and trunc(ate) function

Also adds a missing type case for promotable
This commit is contained in:
Johan B.W. de Vries 2025-05-25 14:13:46 +02:00
parent 56ab88db2c
commit b670bb02ad
5 changed files with 230 additions and 0 deletions

View File

@ -580,6 +580,45 @@ instance_type_class(Reinterpretable, f64, i64, methods={
'reinterpret': stdtypes.f64_i64_reinterpret, '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={ Foldable = Type3Class('Foldable', (t, ), methods={
'sum': [t(a), a], 'sum': [t(a), a],
'foldl': [[b, a, b], b, t(a), b], 'foldl': [[b, a, b], b, t(a), b],
@ -651,5 +690,6 @@ PRELUDE_METHODS = {
**Extendable.methods, **Extendable.methods,
**Promotable.methods, **Promotable.methods,
**Reinterpretable.methods, **Reinterpretable.methods,
**Convertable.methods,
**Foldable.methods, **Foldable.methods,
} }

View File

@ -1176,6 +1176,73 @@ def f64_u64_reinterpret(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map del tv_map
g.i64.reinterpret_f64() 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 ## Foldable

View File

@ -68,6 +68,11 @@ class Generator_i32i64:
self.ge_s = functools.partial(self.generator.add_statement, f'{prefix}.ge_s') 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.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 # 2.4.4. Memory Instructions
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load') self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
self.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u') self.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u')

View File

@ -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)

View File

@ -36,6 +36,23 @@ def testEntry() -> f64:
assert 10.5 == result.returned_value 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 @pytest.mark.integration_test
def test_demote_ok(): def test_demote_ok():
code_py = """ code_py = """