diff --git a/phasm/compiler.py b/phasm/compiler.py index 2a9e7ad..b94e358 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -237,6 +237,28 @@ INSTANCES = { prelude.Sized_.methods['len']: { 'a=bytes': stdlib_types.bytes_sized_len, }, + prelude.Extendable.methods['extend']: { + 'a=u8,b=u32': stdlib_types.u8_u32_extend, + 'a=u8,b=u64': stdlib_types.u8_u64_extend, + 'a=u32,b=u64': stdlib_types.u32_u64_extend, + 'a=i8,b=i32': stdlib_types.i8_i32_extend, + 'a=i8,b=i64': stdlib_types.i8_i64_extend, + 'a=i32,b=i64': stdlib_types.i32_i64_extend, + }, + prelude.Extendable.methods['wrap']: { + 'a=u8,b=u32': stdlib_types.u8_u32_wrap, + 'a=u8,b=u64': stdlib_types.u8_u64_wrap, + 'a=u32,b=u64': stdlib_types.u32_u64_wrap, + 'a=i8,b=i32': stdlib_types.i8_i32_wrap, + 'a=i8,b=i64': stdlib_types.i8_i64_wrap, + 'a=i32,b=i64': stdlib_types.i32_i64_wrap, + }, + prelude.Promotable.methods['promote']: { + 'a=f32,b=f64': stdlib_types.f32_f64_promote, + }, + prelude.Promotable.methods['demote']: { + 'a=f32,b=f64': stdlib_types.f32_f64_demote, + }, } def phasm_compile(inp: ourlang.Module) -> wasm.Module: @@ -487,7 +509,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: instance_key = ','.join( f'{k.letter}={v.name}' - for k, v in type_var_map.items() + for k, v in sorted(type_var_map.items(), key=lambda x: x[0].letter) ) instance = INSTANCES.get(inp.function, {}).get(instance_key, None) diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index 9d0ce55..86e42b1 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -14,12 +14,12 @@ from ..type3.types import ( PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3, ...]]] = set() -def instance_type_class(cls: Type3Class, typ: Type3) -> None: +def instance_type_class(cls: Type3Class, *typ: Type3) -> None: global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING # TODO: Check for required existing instantiations - PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, (typ, ), )) + PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), )) none = Type3('none') """ @@ -141,6 +141,7 @@ PRELUDE_TYPES: dict[str, Type3] = { } a = TypeVariable('a') +b = TypeVariable('b') InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) @@ -266,6 +267,25 @@ Sized_ = Type3Class('Sized', [a], methods={ instance_type_class(Sized_, bytes_) +Extendable = Type3Class('Extendable', [a, b], methods={ + 'extend': [a, b], + 'wrap': [b, a], +}, operators={}) + +instance_type_class(Extendable, u8, u32) +instance_type_class(Extendable, u8, u64) +instance_type_class(Extendable, u32, u64) +instance_type_class(Extendable, i8, i32) +instance_type_class(Extendable, i8, i64) +instance_type_class(Extendable, i32, i64) + +Promotable = Type3Class('Promotable', [a, b], methods={ + 'promote': [a, b], + 'demote': [b, a], +}, operators={}) + +instance_type_class(Promotable, f32, f64) + PRELUDE_TYPE_CLASSES = { 'Eq': Eq, 'Ord': Ord, @@ -275,6 +295,8 @@ PRELUDE_TYPE_CLASSES = { 'Integral': Integral, 'Fractional': Fractional, 'Floating': Floating, + 'Extendable': Extendable, + 'Promotable': Promotable, } PRELUDE_OPERATORS = { @@ -297,4 +319,6 @@ PRELUDE_METHODS = { **IntNum.methods, **NatNum.methods, **Sized_.methods, + **Extendable.methods, + **Promotable.methods, } diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 8075930..7a4d4a6 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -865,3 +865,59 @@ def f64_intnum_neg(g: Generator) -> None: def bytes_sized_len(g: Generator) -> None: # The length is stored in the first 4 bytes g.i32.load() + +## ### +## Extendable + +def u8_u32_extend(g: Generator) -> None: + # No-op + # u8 is already stored as u32 + pass + +def u8_u64_extend(g: Generator) -> None: + g.i64.extend_i32_u() + +def u32_u64_extend(g: Generator) -> None: + g.i64.extend_i32_u() + +def i8_i32_extend(g: Generator) -> None: + # No-op + # i8 is already stored as i32 + pass + +def i8_i64_extend(g: Generator) -> None: + g.i64.extend_i32_s() + +def i32_i64_extend(g: Generator) -> None: + g.i64.extend_i32_s() + +def u8_u32_wrap(g: Generator) -> None: + g.i32.const(0xFF) + g.i32.and_() + +def u8_u64_wrap(g: Generator) -> None: + g.i32.wrap_i64() + g.i32.const(0xFF) + g.i32.and_() + +def u32_u64_wrap(g: Generator) -> None: + g.i32.wrap_i64() + +def i8_i32_wrap(g: Generator) -> None: + g.i32.const(0xFF) + g.i32.and_() + +def i8_i64_wrap(g: Generator) -> None: + g.i32.wrap_i64() + +def i32_i64_wrap(g: Generator) -> None: + g.i32.wrap_i64() + +## ### +## Promotable + +def f32_f64_promote(g: Generator) -> None: + g.f64.promote_f32() + +def f32_f64_demote(g: Generator) -> None: + g.f32.demote_f64() diff --git a/phasm/wasmgenerator.py b/phasm/wasmgenerator.py index 7bb0c72..9268219 100644 --- a/phasm/wasmgenerator.py +++ b/phasm/wasmgenerator.py @@ -74,6 +74,9 @@ class Generator_i32(Generator_i32i64): def __init__(self, generator: 'Generator') -> None: super().__init__('i32', generator) + # 2.4.1. Numeric Instructions + self.wrap_i64 = functools.partial(self.generator.add_statement, 'i32.wrap_i64') + class Generator_i64(Generator_i32i64): def __init__(self, generator: 'Generator') -> None: super().__init__('i64', generator) @@ -132,10 +135,16 @@ class Generator_f32(Generator_f32f64): def __init__(self, generator: 'Generator') -> None: super().__init__('f32', generator) + # 2.4.1 Numeric Instructions + self.demote_f64 = functools.partial(self.generator.add_statement, 'f32.demote_f64') + class Generator_f64(Generator_f32f64): def __init__(self, generator: 'Generator') -> None: super().__init__('f64', generator) + # 2.4.1 Numeric Instructions + self.promote_f32 = functools.partial(self.generator.add_statement, 'f64.promote_f32') + class Generator_Local: def __init__(self, generator: 'Generator') -> None: self.generator = generator diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 0d8d670..26d17ae 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -264,6 +264,13 @@ def _load_memory_stored_returned_value( if ret_type3 in (prelude.i8, prelude.i32, prelude.i64): assert isinstance(wasm_value, int), wasm_value + + if ret_type3 is prelude.i8: + # Values are actually i32 + # Have to reinterpret to load proper value + data = struct.pack(' Baz: + return extend(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Extendable Foo Baz'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +@pytest.mark.parametrize('ext_from,ext_to', EXTENTABLE) +def test_extend_ok(ext_from,ext_to): + code_py = f""" +CONSTANT: {ext_from} = 10 + +@exported +def testEntry() -> {ext_to}: + return extend(CONSTANT) +""" + + result = Suite(code_py).run_code() + + assert 10 == result.returned_value + +@pytest.mark.integration_test +@pytest.mark.parametrize('ext_from,in_put,ext_to,exp_out', [ + ('u8', 241, 'u32', 241), + ('u32', 4059165169, 'u64', 4059165169), + ('u8', 241, 'u64', 241), + + ('i8', 113, 'i32', 113), + ('i32', 1911681521, 'i64', 1911681521), + ('i8', 113, 'i64', 113), + + ('i8', -15, 'i32', -15), + ('i32', -15, 'i64', -15), + ('i8', -15, 'i64', -15), + +]) +def test_extend_results(ext_from, ext_to, in_put, exp_out): + code_py = f""" +@exported +def testEntry(x: {ext_from}) -> {ext_to}: + return extend(x) +""" + + result = Suite(code_py).run_code(in_put) + + assert exp_out == result.returned_value + +@pytest.mark.integration_test +@pytest.mark.parametrize('ext_from,ext_to', EXTENTABLE) +def test_wrap_ok(ext_from,ext_to): + code_py = f""" +CONSTANT: {ext_to} = 10 + +@exported +def testEntry() -> {ext_from}: + return wrap(CONSTANT) +""" + + result = Suite(code_py).run_code() + + assert 10 == result.returned_value + +@pytest.mark.integration_test +@pytest.mark.parametrize('ext_to,in_put,ext_from,exp_out', [ + ('u32', 0xF1F1F1F1, 'u8', 0xF1), + ('u64', 0xF1F1F1F1F1F1F1F1, 'u32', 0xF1F1F1F1), + ('u64', 0xF1F1F1F1F1F1F1F1, 'u8', 0xF1), + + ('i32', 0xF1F1F171, 'i8', 113), + ('i32', 0xF1F1F1F1, 'i8', -15), + ('i64', 0x71F1F1F171F1F1F1, 'i32', 1911681521), + ('i64', 0x71F1F1F1F1F1F1F1, 'i32', -235802127), + ('i64', 0xF1F1F1F1F1F1F171, 'i8', 113), + ('i64', 0xF1F1F1F1F1F1F1F1, 'i8', -15), +]) +def test_wrap_results(ext_from, ext_to, in_put, exp_out): + code_py = f""" +@exported +def testEntry(x: {ext_to}) -> {ext_from}: + return wrap(x) +""" + + result = Suite(code_py).run_code(in_put) + + assert exp_out == result.returned_value diff --git a/tests/integration/test_lang/test_promotable.py b/tests/integration/test_lang/test_promotable.py new file mode 100644 index 0000000..7555d31 --- /dev/null +++ b/tests/integration/test_lang/test_promotable.py @@ -0,0 +1,51 @@ +import pytest + +from phasm.type3.entry import Type3Exception + +from ..helpers import Suite + + +@pytest.mark.integration_test +def test_promote_not_implemented(): + code_py = """ +class Foo: + val: i32 + +class Baz: + val: i32 + +@exported +def testEntry(x: Foo) -> Baz: + return promote(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Promotable Foo Baz'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_promote_ok(): + code_py = """ +CONSTANT: f32 = 10.5 + +@exported +def testEntry() -> f64: + return promote(CONSTANT) +""" + + result = Suite(code_py).run_code() + + assert 10.5 == result.returned_value + +@pytest.mark.integration_test +def test_demote_ok(): + code_py = """ +CONSTANT: f64 = 10.5 + +@exported +def testEntry() -> f32: + return demote(CONSTANT) +""" + + result = Suite(code_py).run_code() + + assert 10.5 == result.returned_value