diff --git a/README.md b/README.md index d71d61b..dffe84f 100644 --- a/README.md +++ b/README.md @@ -97,3 +97,9 @@ References [4] https://www.w3.org/TR/wasm-core-1/ [5] https://en.wikipedia.org/w/index.php?title=WebAssembly&oldid=1103639883 [6] https://github.com/WebAssembly/wabt + +Links +----- + +- https://pengowray.github.io/wasm-ops/ + Shorthand overview for supported operations in WebAssembly. diff --git a/TODO.md b/TODO.md index 93646d1..7570bdb 100644 --- a/TODO.md +++ b/TODO.md @@ -30,3 +30,4 @@ - ourlang.BinaryOp should probably always be a Type3ClassMethod - Remove U32_OPERATOR_MAP / U64_OPERATOR_MAP - Make prelude more an actual thing +- Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded diff --git a/phasm/compiler.py b/phasm/compiler.py index 8a09bfc..2d51456 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -53,29 +53,41 @@ INSTANCES = { 'a=i32': stdlib_types.i32_integral_div, 'a=i64': stdlib_types.i64_integral_div, }, - type3classes.Num.operators['+']: { - 'a=u32': stdlib_types.u32_num_add, - 'a=u64': stdlib_types.u64_num_add, - 'a=i32': stdlib_types.i32_num_add, - 'a=i64': stdlib_types.i64_num_add, - 'a=f32': stdlib_types.f32_num_add, - 'a=f64': stdlib_types.f64_num_add, + type3classes.IntNum.methods['abs']: { + 'a=i32': stdlib_types.i32_intnum_abs, + 'a=i64': stdlib_types.i64_intnum_abs, + 'a=f32': stdlib_types.f32_intnum_abs, + 'a=f64': stdlib_types.f64_intnum_abs, }, - type3classes.Num.operators['-']: { - 'a=u32': stdlib_types.u32_num_sub, - 'a=u64': stdlib_types.u64_num_sub, - 'a=i32': stdlib_types.i32_num_sub, - 'a=i64': stdlib_types.i64_num_sub, - 'a=f32': stdlib_types.f32_num_sub, - 'a=f64': stdlib_types.f64_num_sub, + type3classes.IntNum.methods['neg']: { + 'a=i32': stdlib_types.i32_intnum_neg, + 'a=i64': stdlib_types.i64_intnum_neg, + 'a=f32': stdlib_types.f32_intnum_neg, + 'a=f64': stdlib_types.f64_intnum_neg, }, - type3classes.Num.operators['*']: { - 'a=u32': stdlib_types.u32_num_mul, - 'a=u64': stdlib_types.u64_num_mul, - 'a=i32': stdlib_types.i32_num_mul, - 'a=i64': stdlib_types.i64_num_mul, - 'a=f32': stdlib_types.f32_num_mul, - 'a=f64': stdlib_types.f64_num_mul, + type3classes.NatNum.operators['+']: { + 'a=u32': stdlib_types.u32_natnum_add, + 'a=u64': stdlib_types.u64_natnum_add, + 'a=i32': stdlib_types.i32_natnum_add, + 'a=i64': stdlib_types.i64_natnum_add, + 'a=f32': stdlib_types.f32_natnum_add, + 'a=f64': stdlib_types.f64_natnum_add, + }, + type3classes.NatNum.operators['-']: { + 'a=u32': stdlib_types.u32_natnum_sub, + 'a=u64': stdlib_types.u64_natnum_sub, + 'a=i32': stdlib_types.i32_natnum_sub, + 'a=i64': stdlib_types.i64_natnum_sub, + 'a=f32': stdlib_types.f32_natnum_sub, + 'a=f64': stdlib_types.f64_natnum_sub, + }, + type3classes.NatNum.operators['*']: { + 'a=u32': stdlib_types.u32_natnum_mul, + 'a=u64': stdlib_types.u64_natnum_mul, + 'a=i32': stdlib_types.i32_natnum_mul, + 'a=i64': stdlib_types.i64_natnum_mul, + 'a=f32': stdlib_types.f32_natnum_mul, + 'a=f64': stdlib_types.f64_natnum_mul, }, } @@ -894,6 +906,8 @@ def module(inp: ourlang.Module) -> wasm.Module: stdlib_alloc.__alloc__, stdlib_types.__alloc_bytes__, stdlib_types.__subscript_bytes__, + stdlib_types.__i32_intnum_abs__, + stdlib_types.__i64_intnum_abs__, ] + [ function(x) for x in inp.functions.values() diff --git a/phasm/parser.py b/phasm/parser.py index 537a7e0..73fc1da 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -39,7 +39,8 @@ PRELUDE_OPERATORS = { **type3typeclasses.Eq.operators, **type3typeclasses.Fractional.operators, **type3typeclasses.Integral.operators, - **type3typeclasses.Num.operators, + **type3typeclasses.IntNum.operators, + **type3typeclasses.NatNum.operators, } PRELUDE_METHODS = { @@ -47,7 +48,8 @@ PRELUDE_METHODS = { **type3typeclasses.Floating.methods, **type3typeclasses.Fractional.methods, **type3typeclasses.Integral.methods, - **type3typeclasses.Num.methods, + **type3typeclasses.IntNum.methods, + **type3typeclasses.NatNum.methods, } def phasm_parse(source: str) -> Module: diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 080f545..5189d0d 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -4,6 +4,7 @@ stdlib: Standard types that are not wasm primitives from phasm.stdlib import alloc from phasm.wasmgenerator import Generator, func_wrapper from phasm.wasmgenerator import VarType_i32 as i32 +from phasm.wasmgenerator import VarType_i64 as i64 @func_wrapper() @@ -66,6 +67,59 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32: return i32('return') # To satisfy mypy +@func_wrapper() +def __i32_intnum_abs__(g: Generator, x: i32) -> i32: + # https://stackoverflow.com/a/14194764 + y = i32('y') + # z = i32('z') + + # y = x >> 31 + g.local.get(x) + g.i32.const(31) + g.i32.shr_s() # Must be arithmetic shift + g.local.set(y) + + # abs(x) = (x XOR y) - y + + # (x XOR y) + g.local.get(x) + g.local.get(y) + g.i32.xor() + + # - y + g.local.get(y) + g.i32.sub() + g.return_() + + return i32('return') + +@func_wrapper() +def __i64_intnum_abs__(g: Generator, x: i64) -> i64: + # https://stackoverflow.com/a/14194764 + y = i64('y') + # z = i64('z') + + # y = x >> 31 + g.local.get(x) + g.i64.const(31) + g.i64.shr_s() # Must be arithmetic shift + g.local.set(y) + + # abs(x) = (x XOR y) - y + + # (x XOR y) + g.local.get(x) + g.local.get(y) + g.i64.xor() + + # - y + g.local.get(y) + g.i64.sub() + g.return_() + + return i64('return') + + def u8_eq_equals(g: Generator) -> None: g.add_statement('i32.eq') @@ -114,56 +168,82 @@ def i32_integral_div(g: Generator) -> None: def i64_integral_div(g: Generator) -> None: g.add_statement('i64.div_s') -def u32_num_add(g: Generator) -> None: +def u32_natnum_add(g: Generator) -> None: g.add_statement('i32.add') -def u64_num_add(g: Generator) -> None: +def u64_natnum_add(g: Generator) -> None: g.add_statement('i64.add') -def i32_num_add(g: Generator) -> None: +def i32_natnum_add(g: Generator) -> None: g.add_statement('i32.add') -def i64_num_add(g: Generator) -> None: +def i64_natnum_add(g: Generator) -> None: g.add_statement('i64.add') -def f32_num_add(g: Generator) -> None: +def f32_natnum_add(g: Generator) -> None: g.add_statement('f32.add') -def f64_num_add(g: Generator) -> None: +def f64_natnum_add(g: Generator) -> None: g.add_statement('f64.add') -def u32_num_sub(g: Generator) -> None: +def u32_natnum_sub(g: Generator) -> None: g.add_statement('i32.sub') -def u64_num_sub(g: Generator) -> None: +def u64_natnum_sub(g: Generator) -> None: g.add_statement('i64.sub') -def i32_num_sub(g: Generator) -> None: +def i32_natnum_sub(g: Generator) -> None: g.add_statement('i32.sub') -def i64_num_sub(g: Generator) -> None: +def i64_natnum_sub(g: Generator) -> None: g.add_statement('i64.sub') -def f32_num_sub(g: Generator) -> None: +def f32_natnum_sub(g: Generator) -> None: g.add_statement('f32.sub') -def f64_num_sub(g: Generator) -> None: +def f64_natnum_sub(g: Generator) -> None: g.add_statement('f64.sub') -def u32_num_mul(g: Generator) -> None: +def u32_natnum_mul(g: Generator) -> None: g.add_statement('i32.mul') -def u64_num_mul(g: Generator) -> None: +def u64_natnum_mul(g: Generator) -> None: g.add_statement('i64.mul') -def i32_num_mul(g: Generator) -> None: +def i32_natnum_mul(g: Generator) -> None: g.add_statement('i32.mul') -def i64_num_mul(g: Generator) -> None: +def i64_natnum_mul(g: Generator) -> None: g.add_statement('i64.mul') -def f32_num_mul(g: Generator) -> None: +def f32_natnum_mul(g: Generator) -> None: g.add_statement('f32.mul') -def f64_num_mul(g: Generator) -> None: +def f64_natnum_mul(g: Generator) -> None: g.add_statement('f64.mul') + +def i32_intnum_abs(g: Generator) -> None: + g.add_statement('call $stdlib.types.__i32_intnum_abs__') + +def i64_intnum_abs(g: Generator) -> None: + g.add_statement('call $stdlib.types.__i64_intnum_abs__') + +def f32_intnum_abs(g: Generator) -> None: + g.f32.abs() + +def f64_intnum_abs(g: Generator) -> None: + g.f64.abs() + +def i32_intnum_neg(g: Generator) -> None: + g.i32.const(-1) + g.i32.mul() + +def i64_intnum_neg(g: Generator) -> None: + g.i64.const(-1) + g.i64.mul() + +def f32_intnum_neg(g: Generator) -> None: + g.f32.neg() + +def f64_intnum_neg(g: Generator) -> None: + g.f64.neg() diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py index 126ec64..29166a4 100644 --- a/phasm/type3/typeclasses.py +++ b/phasm/type3/typeclasses.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Iterable, Optional, List, Mapping, Union +from typing import Any, Dict, Iterable, List, Mapping, Optional, Union class TypeVariable: @@ -62,12 +62,13 @@ class Type3ClassMethod: return f'Type3ClassMethod({repr(self.type3_class)}, {repr(self.name)}, {repr(self.signature)})' class Type3Class: - __slots__ = ('name', 'args', 'methods', 'operators', ) + __slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', ) name: str args: List[TypeVariable] methods: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod] + inherited_classes: List['Type3Class'] def __init__( self, @@ -77,8 +78,6 @@ class Type3Class: operators: Mapping[str, str], inherited_classes: Optional[List['Type3Class']] = None, ) -> None: - del inherited_classes # Not implemented yet - self.name = name self.args = [TypeVariable(x) for x in args] self.methods = { @@ -89,6 +88,7 @@ class Type3Class: k: Type3ClassMethod(self, k, v) for k, v in operators.items() } + self.inherited_classes = inherited_classes or [] def __repr__(self) -> str: return self.name @@ -97,20 +97,25 @@ Eq = Type3Class('Eq', ['a'], methods={}, operators={ '==': 'a -> a -> bool', }) -Num = Type3Class('Num', ['a'], methods={}, operators={ +NatNum = Type3Class('NatNum', ['a'], methods={}, operators={ '+': 'a -> a -> a', '-': 'a -> a -> a', '*': 'a -> a -> a', }) +IntNum = Type3Class('IntNum', ['a'], methods={ + 'abs': 'a -> a', + 'neg': 'a -> a', +}, operators={}, inherited_classes=[NatNum]) + +Integral = Type3Class('Eq', ['a'], methods={ + 'div': 'a -> a -> a', +}, operators={}, inherited_classes=[NatNum]) + Fractional = Type3Class('Fractional', ['a'], methods={}, operators={ '/': 'a -> a -> a', -}, inherited_classes=[Num]) +}, inherited_classes=[NatNum]) Floating = Type3Class('Floating', ['a'], methods={ 'sqrt': 'a -> a', }, operators={}, inherited_classes=[Fractional]) - -Integral = Type3Class('Eq', ['a'], methods={ - 'div': 'a -> a -> a', -}, operators={}) diff --git a/phasm/type3/types.py b/phasm/type3/types.py index d141f30..7b98897 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -4,9 +4,9 @@ Contains the final types for use in Phasm These are actual, instantiated types; not the abstract types that the constraint generator works with. """ -from typing import Any, Dict, Iterable, List, Optional, Protocol, Union +from typing import Any, Dict, Iterable, List, Optional, Protocol, Set, Union -from .typeclasses import Eq, Floating, Fractional, Integral, Num, Type3Class +from .typeclasses import Eq, Floating, Fractional, Integral, IntNum, NatNum, Type3Class TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method' @@ -31,14 +31,19 @@ class Type3: The name of the string, as parsed and outputted by codestyle. """ - classes: List[Type3Class] + classes: Set[Type3Class] """ The type classes that this type implements """ def __init__(self, name: str, classes: Iterable[Type3Class]) -> None: self.name = name - self.classes = [*classes] + self.classes = set(classes) + + for cls in self.classes: + for inh_cls in cls.inherited_classes: + if inh_cls not in self.classes: + raise Exception(f'No instance for ({inh_cls} {self.name})') def __repr__(self) -> str: return f'Type3({repr(self.name)}, {repr(self.classes)})' @@ -242,28 +247,28 @@ The bool type, either True or False Suffixes with an underscores, as it's a Python builtin """ -u8 = PrimitiveType3('u8', [Eq, Integral]) +u8 = PrimitiveType3('u8', [Eq]) """ The unsigned 8-bit integer type. Operations on variables employ modular arithmetic, with modulus 2^8. """ -u32 = PrimitiveType3('u32', [Eq, Integral, Num]) +u32 = PrimitiveType3('u32', [Eq, Integral, NatNum]) """ The unsigned 32-bit integer type. Operations on variables employ modular arithmetic, with modulus 2^32. """ -u64 = PrimitiveType3('u64', [Eq, Integral, Num]) +u64 = PrimitiveType3('u64', [Eq, Integral, NatNum]) """ The unsigned 64-bit integer type. Operations on variables employ modular arithmetic, with modulus 2^64. """ -i8 = PrimitiveType3('i8', [Eq, Integral]) +i8 = PrimitiveType3('i8', [Eq]) """ The signed 8-bit integer type. @@ -271,7 +276,7 @@ Operations on variables employ modular arithmetic, with modulus 2^8, but with the middel point being 0. """ -i32 = PrimitiveType3('i32', [Eq, Integral, Num]) +i32 = PrimitiveType3('i32', [Eq, Integral, IntNum, NatNum]) """ The unsigned 32-bit integer type. @@ -279,7 +284,7 @@ Operations on variables employ modular arithmetic, with modulus 2^32, but with the middel point being 0. """ -i64 = PrimitiveType3('i64', [Eq, Integral, Num]) +i64 = PrimitiveType3('i64', [Eq, Integral, IntNum, NatNum]) """ The unsigned 64-bit integer type. @@ -287,12 +292,12 @@ Operations on variables employ modular arithmetic, with modulus 2^64, but with the middel point being 0. """ -f32 = PrimitiveType3('f32', [Eq, Floating, Fractional, Num]) +f32 = PrimitiveType3('f32', [Eq, Floating, Fractional, IntNum, NatNum]) """ A 32-bits IEEE 754 float, of 32 bits width. """ -f64 = PrimitiveType3('f64', [Eq, Floating, Fractional, Num]) +f64 = PrimitiveType3('f64', [Eq, Floating, Fractional, IntNum, NatNum]) """ A 32-bits IEEE 754 float, of 64 bits width. """ diff --git a/phasm/wasmgenerator.py b/phasm/wasmgenerator.py index 3cb55a1..8ca9c5f 100644 --- a/phasm/wasmgenerator.py +++ b/phasm/wasmgenerator.py @@ -21,6 +21,9 @@ class VarType_u8(VarType_Base): class VarType_i32(VarType_Base): wasm_type = wasm.WasmTypeInt32 +class VarType_i64(VarType_Base): + wasm_type = wasm.WasmTypeInt64 + class Generator_i32i64: def __init__(self, prefix: str, generator: 'Generator') -> None: self.prefix = prefix @@ -31,6 +34,10 @@ class Generator_i32i64: self.add = functools.partial(self.generator.add_statement, f'{prefix}.add') self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub') self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul') + self.shr_s = functools.partial(self.generator.add_statement, f'{prefix}.shr_s') + self.shr_u = functools.partial(self.generator.add_statement, f'{prefix}.shr_u') + self.rotr = functools.partial(self.generator.add_statement, f'{prefix}.rotr') + self.xor = functools.partial(self.generator.add_statement, f'{prefix}.xor') # irelop self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq') @@ -60,8 +67,23 @@ class Generator_f32f64: self.generator = generator # 2.4.1. Numeric Instructions + # funop + self.abs = functools.partial(self.generator.add_statement, f'{prefix}.abs') + self.neg = functools.partial(self.generator.add_statement, f'{prefix}.neg') + self.sqrt = functools.partial(self.generator.add_statement, f'{prefix}.sqrt') + self.ceil = functools.partial(self.generator.add_statement, f'{prefix}.ceil') + self.floor = functools.partial(self.generator.add_statement, f'{prefix}.floor') + self.trunc = functools.partial(self.generator.add_statement, f'{prefix}.trunc') + self.nearest = functools.partial(self.generator.add_statement, f'{prefix}.nearest') + # fbinop self.add = functools.partial(self.generator.add_statement, f'{prefix}.add') + self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub') + self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul') + self.div = functools.partial(self.generator.add_statement, f'{prefix}.div') + self.min = functools.partial(self.generator.add_statement, f'{prefix}.min') + self.max = functools.partial(self.generator.add_statement, f'{prefix}.max') + self.copysign = functools.partial(self.generator.add_statement, f'{prefix}.copysign') # frelop self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq') diff --git a/tests/integration/test_lang/test_eq.py b/tests/integration/test_lang/test_eq.py index ff1196d..16bbdf1 100644 --- a/tests/integration/test_lang/test_eq.py +++ b/tests/integration/test_lang/test_eq.py @@ -26,10 +26,10 @@ class Foo: @exported def testEntry(x: Foo, y: Foo) -> Foo: - return x + y + return x == y """ - with pytest.raises(Type3Exception, match='Foo does not implement the Num type class'): + with pytest.raises(Type3Exception, match='Foo does not implement the Eq type class'): Suite(code_py).run_code() @pytest.mark.integration_test diff --git a/tests/integration/test_lang/test_intnum.py b/tests/integration/test_lang/test_intnum.py new file mode 100644 index 0000000..43c3b55 --- /dev/null +++ b/tests/integration/test_lang/test_intnum.py @@ -0,0 +1,69 @@ +import pytest + +from ..helpers import Suite + +INT_TYPES = ['i32', 'i64'] +FLOAT_TYPES = ['f32', 'f64'] + +TYPE_MAP = { + 'i32': int, + 'i64': int, + 'f32': float, + 'f64': float, +} + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', INT_TYPES) +def test_abs_int(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return abs(-3) +""" + + result = Suite(code_py).run_code() + + assert 3 == result.returned_value + assert TYPE_MAP[type_] is type(result.returned_value) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', FLOAT_TYPES) +def test_abs_float(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return abs(-3.5) +""" + + result = Suite(code_py).run_code() + + assert 3.5 == result.returned_value + assert TYPE_MAP[type_] is type(result.returned_value) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', INT_TYPES) +def test_neg_int(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return neg(3) +""" + + result = Suite(code_py).run_code() + + assert -3 == result.returned_value + assert TYPE_MAP[type_] is type(result.returned_value) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', FLOAT_TYPES) +def test_neg_float(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return neg(3.5) +""" + + result = Suite(code_py).run_code() + + assert -3.5 == result.returned_value + assert TYPE_MAP[type_] is type(result.returned_value) diff --git a/tests/integration/test_lang/test_num.py b/tests/integration/test_lang/test_natnum.py similarity index 96% rename from tests/integration/test_lang/test_num.py rename to tests/integration/test_lang/test_natnum.py index 8f628e5..7740cf7 100644 --- a/tests/integration/test_lang/test_num.py +++ b/tests/integration/test_lang/test_natnum.py @@ -27,7 +27,7 @@ def testEntry(x: Foo, y: Foo) -> Foo: return x + y """ - with pytest.raises(Type3Exception, match='Foo does not implement the Num type class'): + with pytest.raises(Type3Exception, match='Foo does not implement the NatNum type class'): Suite(code_py).run_code() @pytest.mark.integration_test @@ -122,7 +122,7 @@ def testEntry() -> {type_}: result = Suite(code_py).run_code() assert 30 == result.returned_value - assert TYPE_MAP[type_] == type(result.returned_value) + assert TYPE_MAP[type_] is type(result.returned_value) @pytest.mark.integration_test @pytest.mark.parametrize('type_', FLOAT_TYPES) @@ -136,7 +136,7 @@ def testEntry() -> {type_}: result = Suite(code_py).run_code() assert 4.0 == result.returned_value - assert TYPE_MAP[type_] == type(result.returned_value) + assert TYPE_MAP[type_] is type(result.returned_value) @pytest.mark.integration_test @pytest.mark.parametrize('type_', INT_TYPES)