From a83858aca74e56532708c8f4b05a53c243f9fb5e Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sat, 9 Jul 2022 14:22:38 +0200 Subject: [PATCH] Adds u32 and u64 Also, adds some range checks to constants. --- phasm/codestyle.py | 11 ++++++- phasm/compiler.py | 50 ++++++++++++++++++++++++++++++-- phasm/ourlang.py | 28 +++++++++++++++++- phasm/parser.py | 32 +++++++++++++++++--- phasm/typing.py | 25 ++++++++++++++++ tests/integration/test_simple.py | 22 +++++++++----- 6 files changed, 152 insertions(+), 16 deletions(-) diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 5d217f5..93853dd 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -29,6 +29,12 @@ def type_(inp: typing.TypeBase) -> str: if isinstance(inp, typing.TypeUInt8): return 'u8' + if isinstance(inp, typing.TypeUInt32): + return 'u32' + + if isinstance(inp, typing.TypeUInt64): + return 'u64' + if isinstance(inp, typing.TypeInt32): return 'i32' @@ -71,7 +77,10 @@ def expression(inp: ourlang.Expression) -> str: """ Render: A Phasm expression """ - if isinstance(inp, (ourlang.ConstantUInt8, ourlang.ConstantInt32, ourlang.ConstantInt64, )): + if isinstance(inp, ( + ourlang.ConstantUInt8, ourlang.ConstantUInt32, ourlang.ConstantUInt64, + ourlang.ConstantInt32, ourlang.ConstantInt64, + )): return str(inp.value) if isinstance(inp, (ourlang.ConstantFloat32, ourlang.ConstantFloat64, )): diff --git a/phasm/compiler.py b/phasm/compiler.py index 4ad4bd2..778d11a 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -11,6 +11,8 @@ Statements = Generator[wasm.Statement, None, None] LOAD_STORE_TYPE_MAP = { typing.TypeUInt8: 'i32', + typing.TypeUInt32: 'i32', + typing.TypeUInt64: 'i64', typing.TypeInt32: 'i32', typing.TypeInt64: 'i64', typing.TypeFloat32: 'f32', @@ -39,6 +41,12 @@ def type_(inp: typing.TypeBase) -> wasm.WasmType: # So we need to store more memory per byte return wasm.WasmTypeInt32() + if isinstance(inp, typing.TypeUInt32): + return wasm.WasmTypeInt32() + + if isinstance(inp, typing.TypeUInt64): + return wasm.WasmTypeInt64() + if isinstance(inp, typing.TypeInt32): return wasm.WasmTypeInt32() @@ -66,14 +74,28 @@ OPERATOR_MAP = { '==': 'eq', } -I32_OPERATOR_MAP = { # TODO: Introduce UInt32 type +U32_OPERATOR_MAP = { + '<': 'lt_u', + '>': 'gt_u', + '<=': 'le_u', + '>=': 'ge_u', +} + +U64_OPERATOR_MAP = { + '<': 'lt_u', + '>': 'gt_u', + '<=': 'le_u', + '>=': 'ge_u', +} + +I32_OPERATOR_MAP = { '<': 'lt_s', '>': 'gt_s', '<=': 'le_s', '>=': 'ge_s', } -I64_OPERATOR_MAP = { # TODO: Introduce UInt32 type +I64_OPERATOR_MAP = { '<': 'lt_s', '>': 'gt_s', '<=': 'le_s', @@ -88,6 +110,14 @@ def expression(inp: ourlang.Expression) -> Statements: yield wasm.Statement('i32.const', str(inp.value)) return + if isinstance(inp, ourlang.ConstantUInt32): + yield wasm.Statement('i32.const', str(inp.value)) + return + + if isinstance(inp, ourlang.ConstantUInt64): + yield wasm.Statement('i64.const', str(inp.value)) + return + if isinstance(inp, ourlang.ConstantInt32): yield wasm.Statement('i32.const', str(inp.value)) return @@ -112,6 +142,20 @@ def expression(inp: ourlang.Expression) -> Statements: yield from expression(inp.left) yield from expression(inp.right) + if isinstance(inp.type, typing.TypeUInt32): + if operator := OPERATOR_MAP.get(inp.operator, None): + yield wasm.Statement(f'i32.{operator}') + return + if operator := U32_OPERATOR_MAP.get(inp.operator, None): + yield wasm.Statement(f'i32.{operator}') + return + if isinstance(inp.type, typing.TypeUInt64): + if operator := OPERATOR_MAP.get(inp.operator, None): + yield wasm.Statement(f'i64.{operator}') + return + if operator := U64_OPERATOR_MAP.get(inp.operator, None): + yield wasm.Statement(f'i64.{operator}') + return if isinstance(inp.type, typing.TypeInt32): if operator := OPERATOR_MAP.get(inp.operator, None): yield wasm.Statement(f'i32.{operator}') @@ -288,7 +332,7 @@ def function(inp: ourlang.Function) -> wasm.Function: for y in inp.statements for x in statement(y) ] - locals_ = [] # TODO + locals_ = [] # FIXME: Implement function locals, if required return wasm.Function( inp.name, diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 1903328..ef9e37a 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -12,7 +12,7 @@ from .typing import ( TypeBase, TypeNone, TypeBool, - TypeUInt8, + TypeUInt8, TypeUInt32, TypeUInt64, TypeInt32, TypeInt64, TypeFloat32, TypeFloat64, TypeBytes, @@ -49,6 +49,30 @@ class ConstantUInt8(Constant): super().__init__(type_) self.value = value +class ConstantUInt32(Constant): + """ + An UInt32 constant value expression within a statement + """ + __slots__ = ('value', ) + + value: int + + def __init__(self, type_: TypeUInt32, value: int) -> None: + super().__init__(type_) + self.value = value + +class ConstantUInt64(Constant): + """ + An UInt64 constant value expression within a statement + """ + __slots__ = ('value', ) + + value: int + + def __init__(self, type_: TypeUInt64, value: int) -> None: + super().__init__(type_) + self.value = value + class ConstantInt32(Constant): """ An Int32 constant value expression within a statement @@ -317,6 +341,8 @@ class Module: self.types = { 'None': TypeNone(), 'u8': TypeUInt8(), + 'u32': TypeUInt32(), + 'u64': TypeUInt64(), 'i32': TypeInt32(), 'i64': TypeInt64(), 'f32': TypeFloat32(), diff --git a/phasm/parser.py b/phasm/parser.py index dda06e6..a810910 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -8,6 +8,8 @@ import ast from .typing import ( TypeBase, TypeUInt8, + TypeUInt32, + TypeUInt64, TypeInt32, TypeInt64, TypeFloat32, @@ -30,7 +32,8 @@ from .ourlang import ( Expression, AccessBytesIndex, AccessStructMember, AccessTupleMember, BinaryOp, - ConstantFloat32, ConstantFloat64, ConstantInt32, ConstantInt64, ConstantUInt8, + ConstantFloat32, ConstantFloat64, ConstantInt32, ConstantInt64, + ConstantUInt8, ConstantUInt32, ConstantUInt64, FunctionCall, StructConstructor, TupleConstructor, UnaryOp, VariableReference, @@ -485,15 +488,35 @@ class OurVisitor: if not isinstance(node.value, int): _raise_static_error(node, 'Expected integer value') - # FIXME: Range check + if node.value < 0 or node.value > 255: + _raise_static_error(node, 'Integer value out of range') return ConstantUInt8(exp_type, node.value) + if isinstance(exp_type, TypeUInt32): + if not isinstance(node.value, int): + _raise_static_error(node, 'Expected integer value') + + if node.value < 0 or node.value > 4294967295: + _raise_static_error(node, 'Integer value out of range') + + return ConstantUInt32(exp_type, node.value) + + if isinstance(exp_type, TypeUInt64): + if not isinstance(node.value, int): + _raise_static_error(node, 'Expected integer value') + + if node.value < 0 or node.value > 18446744073709551615: + _raise_static_error(node, 'Integer value out of range') + + return ConstantUInt64(exp_type, node.value) + if isinstance(exp_type, TypeInt32): if not isinstance(node.value, int): _raise_static_error(node, 'Expected integer value') - # FIXME: Range check + if node.value < -2147483648 or node.value > 2147483647: + _raise_static_error(node, 'Integer value out of range') return ConstantInt32(exp_type, node.value) @@ -501,7 +524,8 @@ class OurVisitor: if not isinstance(node.value, int): _raise_static_error(node, 'Expected integer value') - # FIXME: Range check + if node.value < -9223372036854775808 or node.value > 9223372036854775807: + _raise_static_error(node, 'Integer value out of range') return ConstantInt64(exp_type, node.value) diff --git a/phasm/typing.py b/phasm/typing.py index bca76b2..71a7549 100644 --- a/phasm/typing.py +++ b/phasm/typing.py @@ -30,12 +30,37 @@ class TypeBool(TypeBase): class TypeUInt8(TypeBase): """ The Integer type, unsigned and 8 bits wide + + Note that under the hood we need to use i32 to represent + these values in expressions. So we need to add some operations + to make sure the math checks out. + + So while this does save bytes in memory, it may not actually + speed up or improve your code. """ __slots__ = () def alloc_size(self) -> int: return 4 # Int32 under the hood +class TypeUInt32(TypeBase): + """ + The Integer type, unsigned and 32 bits wide + """ + __slots__ = () + + def alloc_size(self) -> int: + return 4 + +class TypeUInt64(TypeBase): + """ + The Integer type, unsigned and 64 bits wide + """ + __slots__ = () + + def alloc_size(self) -> int: + return 8 + class TypeInt32(TypeBase): """ The Integer type, signed and 32 bits wide diff --git a/tests/integration/test_simple.py b/tests/integration/test_simple.py index 51586af..4cba3e6 100644 --- a/tests/integration/test_simple.py +++ b/tests/integration/test_simple.py @@ -4,14 +4,22 @@ from .helpers import Suite TYPE_MAP = { 'u8': int, + 'u32': int, + 'u64': int, 'i32': int, 'i64': int, 'f32': float, 'f64': float, } +COMPLETE_SIMPLE_TYPES = [ + 'u32', 'u64', + 'i32', 'i64', + 'f32', 'f64', +] + @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64', 'u8']) +@pytest.mark.parametrize('type_', TYPE_MAP.keys()) def test_return(type_): code_py = f""" @exported @@ -25,7 +33,7 @@ def testEntry() -> {type_}: assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES) def test_addition(type_): code_py = f""" @exported @@ -39,7 +47,7 @@ def testEntry() -> {type_}: assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES) def test_subtraction(type_): code_py = f""" @exported @@ -67,7 +75,7 @@ def testEntry() -> {type_}: assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64', 'u8']) +@pytest.mark.parametrize('type_', TYPE_MAP.keys()) def test_arg(type_): code_py = f""" @exported @@ -265,7 +273,7 @@ def helper(left: i32, right: i32) -> i32: assert [] == result.log_int32_list @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES) def test_call_with_expression(type_): code_py = f""" @exported @@ -298,7 +306,7 @@ def testEntry() -> i32: assert [] == result.log_int32_list @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64', 'u8']) +@pytest.mark.parametrize('type_', TYPE_MAP.keys()) def test_struct_0(type_): code_py = f""" class CheckedValue: @@ -360,7 +368,7 @@ def helper(shape1: Rectangle, shape2: Rectangle) -> i32: assert [] == result.log_int32_list @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES) def test_tuple_simple(type_): code_py = f""" @exported