Worked on floats

This commit is contained in:
Johan B.W. de Vries 2022-09-17 19:21:56 +02:00
parent b2816164f9
commit 4b46483895
4 changed files with 132 additions and 26 deletions

View File

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

View File

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

View File

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

View File

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