From 4b46483895470904db56e02d5063a5dbd56f5e30 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sat, 17 Sep 2022 19:21:56 +0200 Subject: [PATCH] Worked on floats --- phasm/compiler.py | 10 +++++ phasm/typer.py | 69 ++++++++++++++++++++++++++------ phasm/typing.py | 11 +++++ tests/integration/test_simple.py | 68 ++++++++++++++++++++++++------- 4 files changed, 132 insertions(+), 26 deletions(-) diff --git a/phasm/compiler.py b/phasm/compiler.py index e3282bc..af6ae58 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -154,6 +154,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: wgn.i64.const(inp.value) return + if stp == 'f32': + assert isinstance(inp.value, float) + wgn.f32.const(inp.value) + return + + if stp == 'f64': + assert isinstance(inp.value, float) + wgn.f64.const(inp.value) + return + raise NotImplementedError(f'Constants with type {stp}') if isinstance(inp, ourlang.VariableReference): diff --git a/phasm/typer.py b/phasm/typer.py index 169a774..07f7f33 100644 --- a/phasm/typer.py +++ b/phasm/typer.py @@ -12,22 +12,48 @@ def constant(ctx: 'Context', inp: ourlang.Constant) -> 'TypeVar': if isinstance(inp, ourlang.ConstantPrimitive): result = ctx.new_var() - if not isinstance(inp.value, int): - raise NotImplementedError('Float constants in new type system') + if isinstance(inp.value, int): + result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT)) - result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT)) + # Need at least this many bits to store this constant value + result.add_constraint(TypeConstraintBitWidth(minb=len(bin(inp.value)) - 2)) + # Don't dictate anything about signedness - you can use a signed + # constant in an unsigned variable if the bits fit + result.add_constraint(TypeConstraintSigned(None)) - # Need at least this many bits to store this constant value - result.add_constraint(TypeConstraintBitWidth(minb=len(bin(inp.value)) - 2)) - # Don't dictate anything about signedness - you can use a signed - # constant in an unsigned variable if the bits fit - result.add_constraint(TypeConstraintSigned(None)) + result.add_location(str(inp.value)) - result.add_location(str(inp.value)) + inp.type_var = result - inp.type_var = result + return result - return result + if isinstance(inp.value, float): + result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT)) + + # We don't have fancy logic here to detect if the float constant + # fits in the given type. There a number of edge cases to consider, + # before implementing this. + + # 1) It may fit anyhow + # e.g., if the user has 3.14 as a float constant, neither a + # f32 nor a f64 can really fit this value. But does that mean + # we should throw an error? + + # If we'd implement it, we'd want to convert it to hex using + # inp.value.hex(), which would give us the mantissa and exponent. + # We can use those to determine what bit size the value should be in. + + # If that doesn't work out, we'd need another way to calculate the + # difference between what was written and what actually gets stored + # in memory, and warn if the difference is beyond a treshold. + + result.add_location(str(inp.value)) + + inp.type_var = result + + return result + + raise NotImplementedError(constant, inp, inp.value) raise NotImplementedError(constant, inp) @@ -39,6 +65,13 @@ def expression(ctx: 'Context', inp: ourlang.Expression) -> 'TypeVar': assert inp.variable.type_var is not None, inp return inp.variable.type_var + if isinstance(inp, ourlang.UnaryOp): + if inp.operator not in ('sqrt', ): + raise NotImplementedError(expression, inp, inp.operator) + + right = expression(ctx, inp.right) + return right + if isinstance(inp, ourlang.BinaryOp): if inp.operator not in ('+', '-', '*', '|', '&', '^'): raise NotImplementedError(expression, inp, inp.operator) @@ -51,7 +84,7 @@ def expression(ctx: 'Context', inp: ourlang.Expression) -> 'TypeVar': if isinstance(inp, ourlang.FunctionCall): assert inp.function.returns_type_var is not None if inp.function.posonlyargs: - raise NotImplementedError + raise NotImplementedError('TODO: Functions with arguments') return inp.function.returns_type_var @@ -123,4 +156,16 @@ def _convert_old_type(ctx: Context, inp: typing.TypeBase, location: str) -> Type result.add_location(location) return result + if isinstance(inp, typing.TypeFloat32): + result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT)) + result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32)) + result.add_location(location) + return result + + if isinstance(inp, typing.TypeFloat64): + result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT)) + result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64)) + result.add_location(location) + return result + raise NotImplementedError(_convert_old_type, inp) diff --git a/phasm/typing.py b/phasm/typing.py index ffd8e16..0a8982b 100644 --- a/phasm/typing.py +++ b/phasm/typing.py @@ -421,4 +421,15 @@ def simplify(inp: TypeVar) -> Optional[str]: base = 'i' if tc_sign.signed else 'u' return f'{base}{tc_bits.minb}' + if primitive is TypeConstraintPrimitive.Primitive.FLOAT: + if tc_bits is None or tc_sign is not None: # Floats should not hava sign contraint + return None + + assert isinstance(tc_bits, TypeConstraintBitWidth) # type hint + + if tc_bits.minb != tc_bits.maxb or tc_bits.minb not in (32, 64): + return None + + return f'f{tc_bits.minb}' + return None diff --git a/tests/integration/test_simple.py b/tests/integration/test_simple.py index 449f34c..aace8a7 100644 --- a/tests/integration/test_simple.py +++ b/tests/integration/test_simple.py @@ -2,14 +2,12 @@ import pytest from .helpers import Suite +ALL_INT_TYPES = ['u8', 'u32', 'u64', 'i32', 'i64'] +ALL_FLOAT_TYPES = ['f32', 'f64'] + TYPE_MAP = { - 'u8': int, - 'u32': int, - 'u64': int, - 'i32': int, - 'i64': int, - 'f32': float, - 'f64': float, + **{x: int for x in ALL_INT_TYPES}, + **{x: float for x in ALL_FLOAT_TYPES}, } COMPLETE_SIMPLE_TYPES = [ @@ -19,8 +17,8 @@ COMPLETE_SIMPLE_TYPES = [ ] @pytest.mark.integration_test -@pytest.mark.parametrize('type_', TYPE_MAP.keys()) -def test_return(type_): +@pytest.mark.parametrize('type_', ALL_INT_TYPES) +def test_return_int(type_): code_py = f""" @exported def testEntry() -> {type_}: @@ -33,8 +31,22 @@ def testEntry() -> {type_}: assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test -@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES) -def test_addition(type_): +@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES) +def test_return_float(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return 32.125 +""" + + result = Suite(code_py).run_code() + + assert 32.125 == result.returned_value + assert TYPE_MAP[type_] == type(result.returned_value) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ALL_INT_TYPES) +def test_addition_int(type_): code_py = f""" @exported def testEntry() -> {type_}: @@ -47,8 +59,22 @@ def testEntry() -> {type_}: assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test -@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES) -def test_subtraction(type_): +@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES) +def test_addition_float(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return 32.0 + 0.125 +""" + + result = Suite(code_py).run_code() + + assert 32.125 == result.returned_value + assert TYPE_MAP[type_] == type(result.returned_value) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ALL_INT_TYPES) +def test_subtraction_int(type_): code_py = f""" @exported def testEntry() -> {type_}: @@ -60,6 +86,20 @@ def testEntry() -> {type_}: assert 7 == result.returned_value assert TYPE_MAP[type_] == type(result.returned_value) +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES) +def test_subtraction(type_): + code_py = f""" +@exported +def testEntry() -> {type_}: + return 100.0 - 67.875 +""" + + result = Suite(code_py).run_code() + + assert 32.125 == result.returned_value + assert TYPE_MAP[type_] == type(result.returned_value) + @pytest.mark.integration_test @pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation def test_logical_left_shift(type_): @@ -136,7 +176,7 @@ def test_buildins_sqrt(type_): code_py = f""" @exported def testEntry() -> {type_}: - return sqrt(25) + return sqrt(25.0) """ result = Suite(code_py).run_code()