Adds u32 and u64

Also, adds some range checks to constants.
This commit is contained in:
Johan B.W. de Vries 2022-07-09 14:22:38 +02:00
parent 89ad648f34
commit a83858aca7
6 changed files with 152 additions and 16 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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