From a59bc9c31df75a9ee9bea5797065c365200ec1c4 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Fri, 11 Apr 2025 15:50:52 +0200 Subject: [PATCH] Ideas [skip-ci] --- TODO.md | 3 +- phasm/codestyle.py | 15 -- phasm/compiler.py | 171 ++++++++++++----------- phasm/ourlang.py | 3 + phasm/parser.py | 13 +- phasm/runtime.py | 20 +-- phasm/type3/constraints.py | 159 +++++++++++---------- phasm/type3/constraintsgenerator.py | 5 +- phasm/type3/entry.py | 9 -- phasm/type3/typeclasses.py | 2 + phasm/type3/types.py | 164 +++++++++++++++------- tests/integration/helpers.py | 128 ++++++++--------- tests/integration/test_lang/generator.md | 26 ++-- 13 files changed, 382 insertions(+), 336 deletions(-) diff --git a/TODO.md b/TODO.md index 1de5d35..df89cef 100644 --- a/TODO.md +++ b/TODO.md @@ -15,9 +15,7 @@ - static_array and tuple should probably not be PrimitiveType3, but instead subclass AppliedType3? - See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental -- test_bitwise_or_inv_type - test_bytes_index_out_of_bounds vs static trap(?) -- test_num.py is probably best as part of the generator? - Find pytest.mark.skip - There's a weird resolve_as reference in calculate_alloc_size - Either there should be more of them or less @@ -36,3 +34,4 @@ - Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed - Move UnaryOp.operator into type class methods - Move foldr into type class methods +- PrimitiveType is just the type, nothing primitive about it (change the name). u32 are still basic types or something. diff --git a/phasm/codestyle.py b/phasm/codestyle.py index cd1804a..cb3cdc8 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -27,21 +27,6 @@ def type3(inp: Type3OrPlaceholder) -> str: if inp is type3types.none: return 'None' - if isinstance(inp, type3types.AppliedType3): - if inp.base == type3types.tuple: - return '(' + ', '.join( - type3(x) - for x in inp.args - if isinstance(x, Type3) # Skip ints, not allowed here anyhow - ) + ', )' - - if inp.base == type3types.static_array: - assert 2 == len(inp.args) - assert isinstance(inp.args[0], Type3), TYPE3_ASSERTION_ERROR - assert isinstance(inp.args[1], type3types.IntType3), TYPE3_ASSERTION_ERROR - - return type3(inp.args[0]) + '[' + inp.args[1].name + ']' - return inp.name def struct_definition(inp: ourlang.StructDefinition) -> str: diff --git a/phasm/compiler.py b/phasm/compiler.py index 8ae5ab7..1f43c4f 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -286,7 +286,7 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType: if inp == type3types.f64: return wasm.WasmTypeFloat64() - if inp == type3types.bytes: + if inp == type3types.bytes_: # bytes are passed as pointer # And pointers are i32 return wasm.WasmTypeInt32() @@ -295,14 +295,8 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType: # Structs are passed as pointer, which are i32 return wasm.WasmTypeInt32() - if isinstance(inp, type3types.AppliedType3): - if inp.base == type3types.static_array: - # Static Arrays are passed as pointer, which are i32 - return wasm.WasmTypeInt32() - - if inp.base == type3types.tuple: - # Tuples are passed as pointer, which are i32 - return wasm.WasmTypeInt32() + if type3classes.InternalPassAsPointer in inp.classes: + return wasm.WasmTypeInt32() raise NotImplementedError(type3, inp) @@ -310,9 +304,21 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> """ Compile: Instantiation (allocation) of a tuple """ - assert isinstance(inp.type3, type3types.AppliedType3) - assert inp.type3.base is type3types.tuple - assert len(inp.elements) == len(inp.type3.args) + assert isinstance(inp.type3, type3types.PrimitiveType3) + + args: list[type3types.PrimitiveType3] = [] + + sa_args = type3types.static_array.did_construct(inp.type3) + if sa_args is not None: + sa_type, sa_len = sa_args + args = [sa_type for _ in range(sa_len.value)] + + if not args: + tp_args = type3types.tuple_.did_construct(inp.type3) + if tp_args is None: + raise NotImplementedError + + args = list(tp_args) comment_elements = '' for element in inp.elements: @@ -329,14 +335,15 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> # Store each element individually offset = 0 - for element, exp_type3 in zip(inp.elements, inp.type3.args): + for element, exp_type3 in zip(inp.elements, args): if isinstance(exp_type3, type3types.PlaceholderForType): assert exp_type3.resolve_as is not None + assert isinstance(exp_type3.resolve_as, type3types.PrimitiveType3) exp_type3 = exp_type3.resolve_as assert element.type3 == exp_type3 - if isinstance(exp_type3, type3types.StructType3) or isinstance(exp_type3, type3types.AppliedType3): + if type3classes.InternalPassAsPointer in exp_type3.classes: mtyp = 'i32' else: assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') @@ -357,6 +364,9 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: """ Compile: Any expression """ + if isinstance(inp, ourlang.ConstantTuple): + raise Exception + if isinstance(inp, ourlang.ConstantPrimitive): assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR @@ -401,11 +411,20 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: if isinstance(inp.variable, ourlang.ModuleConstantDef): assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + if type3classes.InternalPassAsPointer in inp.type3.classes: + # FIXME: Artifact from older version + assert isinstance(inp.variable.constant, ourlang.ConstantTuple) + + address = inp.variable.constant.data_block.address + assert address is not None, 'Value not allocated' + wgn.i32.const(address) + return + if isinstance(inp.type3, type3types.PrimitiveType3): expression(wgn, inp.variable.constant) return - if inp.type3 is type3types.bytes: + if inp.type3 is type3types.bytes_: assert isinstance(inp.variable.constant, ourlang.ConstantBytes) address = inp.variable.constant.data_block.address @@ -421,25 +440,6 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: wgn.i32.const(address) return - if isinstance(inp.type3, type3types.AppliedType3): - if inp.type3.base == type3types.static_array: - assert isinstance(inp.variable.constant, ourlang.ConstantTuple) - - address = inp.variable.constant.data_block.address - assert address is not None, 'Value not allocated' - wgn.i32.const(address) - return - - if inp.type3.base == type3types.tuple: - assert isinstance(inp.variable.constant, ourlang.ConstantTuple) - - address = inp.variable.constant.data_block.address - assert address is not None, 'Value not allocated' - wgn.i32.const(address) - return - - raise NotImplementedError(expression, inp.variable, inp.type3.base) - raise NotImplementedError(expression, inp) raise NotImplementedError(expression, inp.variable) @@ -479,7 +479,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: if inp.type3 == type3types.u32: if inp.operator == 'len': - if inp.right.type3 == type3types.bytes: + if inp.right.type3 == type3types.bytes_: wgn.i32.load() return @@ -528,71 +528,71 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: if isinstance(inp, ourlang.Subscript): assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - if inp.varref.type3 is type3types.bytes: + if inp.varref.type3 is type3types.bytes_: expression(wgn, inp.varref) expression(wgn, inp.index) wgn.call(stdlib_types.__subscript_bytes__) return - if isinstance(inp.varref.type3, type3types.AppliedType3): - if inp.varref.type3.base == type3types.static_array: - assert 2 == len(inp.varref.type3.args) - el_type = inp.varref.type3.args[0] - assert isinstance(el_type, type3types.Type3) - el_len = inp.varref.type3.args[1] - assert isinstance(el_len, type3types.IntType3) + # if isinstance(inp.varref.type3, type3types.AppliedType3): + # if inp.varref.type3.base == type3types.static_array: + # assert 2 == len(inp.varref.type3.args) + # el_type = inp.varref.type3.args[0] + # assert isinstance(el_type, type3types.Type3) + # el_len = inp.varref.type3.args[1] + # assert isinstance(el_len, type3types.IntType3) - # OPTIMIZE: If index is a constant, we can use offset instead of multiply - # and we don't need to do the out of bounds check + # # OPTIMIZE: If index is a constant, we can use offset instead of multiply + # # and we don't need to do the out of bounds check - expression(wgn, inp.varref) + # expression(wgn, inp.varref) - tmp_var = wgn.temp_var_i32('index') - expression(wgn, inp.index) - wgn.local.tee(tmp_var) + # tmp_var = wgn.temp_var_i32('index') + # expression(wgn, inp.index) + # wgn.local.tee(tmp_var) - # Out of bounds check based on el_len.value - wgn.i32.const(el_len.value) - wgn.i32.ge_u() - with wgn.if_(): - wgn.unreachable(comment='Out of bounds') + # # Out of bounds check based on el_len.value + # wgn.i32.const(el_len.value) + # wgn.i32.ge_u() + # with wgn.if_(): + # wgn.unreachable(comment='Out of bounds') - wgn.local.get(tmp_var) - wgn.i32.const(calculate_alloc_size(el_type)) - wgn.i32.mul() - wgn.i32.add() + # wgn.local.get(tmp_var) + # wgn.i32.const(calculate_alloc_size(el_type)) + # wgn.i32.mul() + # wgn.i32.add() - assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') - mtyp = LOAD_STORE_TYPE_MAP[el_type.name] + # assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + # mtyp = LOAD_STORE_TYPE_MAP[el_type.name] - wgn.add_statement(f'{mtyp}.load') - return + # wgn.add_statement(f'{mtyp}.load') + # return - if inp.varref.type3.base == type3types.tuple: - assert isinstance(inp.index, ourlang.ConstantPrimitive) - assert isinstance(inp.index.value, int) + # if inp.varref.type3.base == type3types.tuple: + # assert isinstance(inp.index, ourlang.ConstantPrimitive) + # assert isinstance(inp.index.value, int) - offset = 0 - for el_type in inp.varref.type3.args[0:inp.index.value]: - assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - offset += calculate_alloc_size(el_type) + # offset = 0 + # for el_type in inp.varref.type3.args[0:inp.index.value]: + # assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + # offset += calculate_alloc_size(el_type) - # This doubles as the out of bounds check - el_type = inp.varref.type3.args[inp.index.value] - assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + # # This doubles as the out of bounds check + # el_type = inp.varref.type3.args[inp.index.value] + # assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - expression(wgn, inp.varref) + # expression(wgn, inp.varref) - if isinstance(el_type, type3types.StructType3): - mtyp = 'i32' - elif isinstance(el_type, type3types.AppliedType3) and el_type.base is type3types.tuple: - mtyp = 'i32' - else: - assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') - mtyp = LOAD_STORE_TYPE_MAP[el_type.name] + # if isinstance(el_type, type3types.StructType3): + # mtyp = 'i32' + # elif type3classes.InternalPassAsPointer in el_type.classes: + # mtyp = 'i32' + # else: + # assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + # mtyp = LOAD_STORE_TYPE_MAP[el_type.name] - wgn.add_statement(f'{mtyp}.load', f'offset={offset}') - return + # wgn.add_statement(f'{mtyp}.load', f'offset={offset}') + # return raise NotImplementedError(expression, inp, inp.varref.type3) @@ -618,7 +618,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None: """ assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - if inp.iter.type3 is not type3types.bytes: + if inp.iter.type3 is not type3types.bytes_: raise NotImplementedError(expression_fold, inp, inp.iter.type3) wgn.add_statement('nop', comment='acu :: u8') @@ -694,6 +694,7 @@ def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None: """ Compile: Return statement """ + print('inp', inp) expression(wgn, inp.value) wgn.return_() @@ -906,7 +907,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes: data_list.append(module_data_f64(constant.value)) continue - if constant.type3 == type3types.bytes: + if constant.type3 == type3types.bytes_: assert isinstance(constant, ourlang.ConstantBytes) assert isinstance(constant.value, bytes) data_list.append(module_data_u32(len(constant.value))) @@ -987,7 +988,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc # Store each member individually for memname, mtyp3 in inp.struct_type3.members.items(): mtyp: Optional[str] - if isinstance(mtyp3, type3types.StructType3) or isinstance(mtyp3, type3types.AppliedType3): + if type3classes.InternalPassAsPointer in mtyp3.classes: mtyp = 'i32' else: mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 407c277..2c0a4d9 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -275,6 +275,9 @@ class StatementReturn(Statement): def __init__(self, value: Expression) -> None: self.value = value + def __repr__(self) -> str: + return f'StatementReturn({repr(self.value)})' + class StatementIf(Statement): """ An if statement within a function diff --git a/phasm/parser.py b/phasm/parser.py index eb8eef9..227d84a 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -664,7 +664,7 @@ class OurVisitor: raise NotImplementedError(f'{node.value} as constant') - def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3: + def visit_type(self, module: Module, node: ast.expr) -> type3types.PrimitiveType3: if isinstance(node, ast.Constant): if node.value is None: return type3types.none @@ -693,18 +693,17 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return type3types.AppliedType3( - type3types.static_array, - [self.visit_type(module, node.value), type3types.IntType3(node.slice.value)], + return type3types.static_array( + self.visit_type(module, node.value), + type3types.IntType3(node.slice.value), ) if isinstance(node, ast.Tuple): if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return type3types.AppliedType3( - type3types.tuple, - (self.visit_type(module, elt) for elt in node.elts) + return type3types.tuple_( + *(self.visit_type(module, elt) for elt in node.elts) ) raise NotImplementedError(f'{node} as type') diff --git a/phasm/runtime.py b/phasm/runtime.py index cded709..45019da 100644 --- a/phasm/runtime.py +++ b/phasm/runtime.py @@ -11,7 +11,7 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: if typ in (type3types.u64, type3types.i64, type3types.f64, ): return 8 - if typ == type3types.bytes: + if typ == type3types.bytes_: if is_member: return 4 @@ -27,28 +27,30 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: for x in typ.members.values() ) - if isinstance(typ, type3types.AppliedType3): - if typ.base is type3types.static_array: + assert isinstance(typ, type3types.PrimitiveType3) + + sa_args = type3types.static_array.did_construct(typ) + if sa_args is not None: if is_member: # tuples referred to by other structs or tuples are pointers return 4 - assert isinstance(typ.args[0], type3types.Type3) - assert isinstance(typ.args[1], type3types.IntType3) + sa_type, sa_len = sa_args - return typ.args[1].value * calculate_alloc_size(typ.args[0], is_member=True) + return sa_len.value * calculate_alloc_size(sa_type, is_member=True) - if typ.base is type3types.tuple: + tp_args = type3types.tuple_.did_construct(typ) + if tp_args is not None: if is_member: # tuples referred to by other structs or tuples are pointers return 4 size = 0 - for arg in typ.args: + for arg in tp_args: assert not isinstance(arg, type3types.IntType3) if isinstance(arg, type3types.PlaceholderForType): - assert arg.resolve_as is not None + assert isinstance(arg.resolve_as, type3types.PrimitiveType3) arg = arg.resolve_as size += calculate_alloc_size(arg, is_member=True) diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index e0f9e43..5ce8194 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -112,10 +112,6 @@ class SameTypeConstraint(ConstraintBase): known_types.append(typ) continue - if isinstance(typ, types.AppliedType3): - known_types.append(typ) - continue - if isinstance(typ, types.PlaceholderForType): if typ.resolve_as is not None: known_types.append(typ.resolve_as) @@ -132,38 +128,6 @@ class SameTypeConstraint(ConstraintBase): first_type = known_types[0] for typ in known_types[1:]: - if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3): - if first_type.base is types.tuple and typ.base is types.static_array: - # Swap so we can reuse the code below - # Hope that it still gives proper type errors - first_type, typ = typ, first_type - - if first_type.base is types.static_array and typ.base is types.tuple: - assert isinstance(first_type.args[1], types.IntType3) - length = first_type.args[1].value - - if len(typ.args) != length: - return Error('Mismatch between applied types argument count', comment=self.comment) - - for typ_arg in typ.args: - new_constraint_list.append(SameTypeConstraint( - first_type.args[0], typ_arg - )) - continue - - if first_type.base != typ.base: - return Error('Mismatch between applied types base', comment=self.comment) - - if len(first_type.args) != len(typ.args): - return Error('Mismatch between applied types argument count', comment=self.comment) - - for first_type_arg, typ_arg in zip(first_type.args, typ.args): - new_constraint_list.append(SameTypeConstraint( - first_type_arg, typ_arg - )) - - continue - if typ != first_type: return Error(f'{typ:s} must be {first_type:s} instead', comment=self.comment) @@ -198,6 +162,47 @@ class SameTypeConstraint(ConstraintBase): return f'SameTypeConstraint({args}, comment={repr(self.comment)})' +class TupleMatchConstraint(ConstraintBase): + def __init__(self, exp_type: types.Type3OrPlaceholder, args: List[types.Type3OrPlaceholder], comment: str): + super().__init__(comment=comment) + + self.exp_type = exp_type + self.args = list(args) + + def check(self) -> CheckResult: + exp_type = self.exp_type + if isinstance(exp_type, types.PlaceholderForType): + if exp_type.resolve_as is None: + return RequireTypeSubstitutes() + + exp_type = exp_type.resolve_as + + assert isinstance(exp_type, types.PrimitiveType3) + + sa_args = types.static_array.did_construct(exp_type) + if sa_args is not None: + sa_type, sa_len = sa_args + + if sa_len.value != len(self.args): + return Error('Mismatch between applied types argument count', comment=self.comment) + + return [ + SameTypeConstraint(arg, sa_type) + for arg in self.args + ] + + tp_args = types.tuple_.did_construct(exp_type) + if tp_args is not None: + if len(tp_args) != len(self.args): + return Error('Mismatch between applied types argument count', comment=self.comment) + + return [ + SameTypeConstraint(arg, oth_arg) + for arg, oth_arg in zip(self.args, tp_args) + ] + + raise NotImplementedError(exp_type) + class IntegerCompareConstraint(ConstraintBase): """ Verifies that the given IntType3 are in order (<=) @@ -401,7 +406,7 @@ class LiteralFitsConstraint(ConstraintBase): return Error('Must be real', comment=self.comment) # FIXME: Add line information - if self.type3 is types.bytes: + if self.type3 is types.bytes_: if isinstance(self.literal.value, bytes): return None @@ -409,45 +414,47 @@ class LiteralFitsConstraint(ConstraintBase): res: NewConstraintList - if isinstance(self.type3, types.AppliedType3): - if self.type3.base == types.tuple: + assert isinstance(self.type3, types.PrimitiveType3) + + tp_args = types.tuple_.did_construct(self.type3) + if tp_args is not None: if not isinstance(self.literal, ourlang.ConstantTuple): return Error('Must be tuple', comment=self.comment) - if len(self.type3.args) != len(self.literal.value): + if len(tp_args) != len(self.literal.value): return Error('Tuple element count mismatch', comment=self.comment) res = [] res.extend( LiteralFitsConstraint(x, y) - for x, y in zip(self.type3.args, self.literal.value) + for x, y in zip(tp_args, self.literal.value) ) res.extend( SameTypeConstraint(x, y.type3) - for x, y in zip(self.type3.args, self.literal.value) + for x, y in zip(tp_args, self.literal.value) ) return res - if self.type3.base == types.static_array: + sa_args = types.static_array.did_construct(self.type3) + if sa_args is not None: if not isinstance(self.literal, ourlang.ConstantTuple): return Error('Must be tuple', comment=self.comment) - assert 2 == len(self.type3.args) - assert isinstance(self.type3.args[1], types.IntType3) + sa_type, sa_len = sa_args - if self.type3.args[1].value != len(self.literal.value): + if sa_len.value != len(self.literal.value): return Error('Member count mismatch', comment=self.comment) res = [] res.extend( - LiteralFitsConstraint(self.type3.args[0], y) + LiteralFitsConstraint(sa_type, y) for y in self.literal.value ) res.extend( - SameTypeConstraint(self.type3.args[0], y.type3) + SameTypeConstraint(sa_type, y.type3) for y in self.literal.value ) @@ -517,42 +524,42 @@ class CanBeSubscriptedConstraint(ConstraintBase): self.type3 = self.type3.resolve_as - if isinstance(self.type3, types.AppliedType3): - if self.type3.base == types.static_array: - result: List[ConstraintBase] = [ - SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), - SameTypeConstraint(self.type3.args[0], self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), - ] + # if isinstance(self.type3, types.AppliedType3): + # if self.type3.base == types.static_array: + # result: List[ConstraintBase] = [ + # SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), + # SameTypeConstraint(self.type3.args[0], self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), + # ] - if isinstance(self.index, ourlang.ConstantPrimitive): - assert isinstance(self.index.value, int) - assert isinstance(self.type3.args[1], types.IntType3) + # if isinstance(self.index, ourlang.ConstantPrimitive): + # assert isinstance(self.index.value, int) + # assert isinstance(self.type3.args[1], types.IntType3) - result.append( - IntegerCompareConstraint( - types.IntType3(0), types.IntType3(self.index.value), types.IntType3(self.type3.args[1].value - 1), - comment='Subscript static array must fit the size of the array' - ) - ) + # result.append( + # IntegerCompareConstraint( + # types.IntType3(0), types.IntType3(self.index.value), types.IntType3(self.type3.args[1].value - 1), + # comment='Subscript static array must fit the size of the array' + # ) + # ) - return result + # return result - if self.type3.base == types.tuple: - if not isinstance(self.index, ourlang.ConstantPrimitive): - return Error('Must index with literal') + # if self.type3.base == types.tuple: + # if not isinstance(self.index, ourlang.ConstantPrimitive): + # return Error('Must index with literal') - if not isinstance(self.index.value, int): - return Error('Must index with integer literal') + # if not isinstance(self.index.value, int): + # return Error('Must index with integer literal') - if self.index.value < 0 or len(self.type3.args) <= self.index.value: - return Error('Tuple index out of range') + # if self.index.value < 0 or len(self.type3.args) <= self.index.value: + # return Error('Tuple index out of range') - return [ - SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'), - SameTypeConstraint(self.type3.args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'), - ] + # return [ + # SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'), + # SameTypeConstraint(self.type3.args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'), + # ] - if self.type3 is types.bytes: + if self.type3 is types.bytes_: return [ SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'), SameTypeConstraint(types.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index 3b9e6de..c1a1f39 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -16,6 +16,7 @@ from .constraints import ( LiteralFitsConstraint, MustImplementTypeClassConstraint, SameTypeConstraint, + TupleMatchConstraint, ) ConstraintGenerator = Generator[ConstraintBase, None, None] @@ -142,9 +143,9 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: yield from expression(ctx, arg) r_type.append(arg.type3) - yield SameTypeConstraint( + yield TupleMatchConstraint( inp.type3, - type3types.AppliedType3(type3types.tuple, r_type), + r_type, comment='The type of a tuple is a combination of its members' ) diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index bf6e54a..0e0fda8 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -13,7 +13,6 @@ from .constraints import ( ) from .constraintsgenerator import phasm_type3_generate_constraints from .types import ( - AppliedType3, IntType3, PlaceholderForType, PrimitiveType3, @@ -153,14 +152,6 @@ def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[in placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1) return placeholder_id_map[placeholder_id] - if isinstance(inp, AppliedType3): - return ( - get_printable_type_name(inp.base, placeholder_id_map) - + ' (' - + ') ('.join(get_printable_type_name(x, placeholder_id_map) for x in inp.args) - + ')' - ) - raise NotImplementedError(inp) def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None: diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py index a412179..6841c73 100644 --- a/phasm/type3/typeclasses.py +++ b/phasm/type3/typeclasses.py @@ -93,6 +93,8 @@ class Type3Class: def __repr__(self) -> str: return self.name +InternalPassAsPointer = Type3Class('InternalPassAsPointer', ['a'], methods={}, operators={}) + Eq = Type3Class('Eq', ['a'], methods={}, operators={ '==': 'a -> a -> bool', '!=': 'a -> a -> bool', diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 6da5729..e393cb8 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -4,7 +4,19 @@ 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, Set, Union +from typing import ( + Any, + Dict, + Generic, + Iterable, + List, + Optional, + Protocol, + Set, + Tuple, + TypeVar, + Union, +) from .typeclasses import ( Bits, @@ -12,6 +24,7 @@ from .typeclasses import ( Floating, Fractional, Integral, + InternalPassAsPointer, IntNum, NatNum, Ord, @@ -83,7 +96,7 @@ class Type3: return not self.__eq__(other) def __hash__(self) -> int: - raise NotImplementedError + return hash(self.name) def __bool__(self) -> bool: raise NotImplementedError @@ -98,6 +111,9 @@ class PrimitiveType3(Type3): class IntType3(Type3): """ Sometimes you can have an int as type, e.g. when using static arrays + + This is not the same as an int on the language level. + [1.0, 1.2] :: Float[2] :: * -> Int -> * """ __slots__ = ('value', ) @@ -119,6 +135,9 @@ class IntType3(Type3): raise NotImplementedError + def __hash__(self) -> int: + return hash(self.value) + class PlaceholderForType: """ A placeholder type, for when we don't know the final type yet @@ -166,64 +185,100 @@ class PlaceholderForType: Type3OrPlaceholder = Union[Type3, PlaceholderForType] -class AppliedType3(Type3): - """ - A Type3 that has been applied to another type - """ - __slots__ = ('base', 'args', ) +T = TypeVar('T') - base: PrimitiveType3 +class TypeConstructor(Generic[T]): """ - The base type + Base class for type construtors + """ + __slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache') + + name: str + """ + The name of the type constructor """ - args: List[Type3OrPlaceholder] + classes: Set[Type3Class] """ - The applied types (or placeholders there for) + The type classes that this constructor implements """ - def __init__(self, base: PrimitiveType3, args: Iterable[Type3OrPlaceholder]) -> None: - args = [*args] - assert args, 'Must at least one argument' + type_classes: Set[Type3Class] + """ + The type classes that the constructed types implement + """ - super().__init__( - base.name - + ' (' - + ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution? - + ')', - [] - ) + _cache: dict[T, PrimitiveType3] + """ + When constructing a type with the same arguments, + it should produce the exact same result. + """ - self.base = base - self.args = args + _reverse_cache: dict[PrimitiveType3, T] + """ + Sometimes we need to know the key that created a type. + """ - @property - def has_placeholders(self) -> bool: - return any( - isinstance(x, PlaceholderForType) - for x in self.args - ) + def __init__(self, name: str, classes: Iterable[Type3Class], type_classes: Iterable[Type3Class]) -> None: + self.name = name + self.classes = set(classes) + self.type_classes = set(type_classes) - def __eq__(self, other: Any) -> bool: - if not isinstance(other, Type3): - raise NotImplementedError + self._cache = {} + self._reverse_cache = {} - if not isinstance(other, AppliedType3): - return False + def make_name(self, key: T) -> str: + raise NotImplementedError - return ( - self.base == other.base - and len(self.args) == len(other.args) - and all( - s == x - for s, x in zip(self.args, other.args) - ) - ) + def did_construct(self, typ: PrimitiveType3) -> T | None: + return self._reverse_cache.get(typ) - def __repr__(self) -> str: - return f'AppliedType3({repr(self.base)}, {repr(self.args)})' + def construct(self, key: T) -> PrimitiveType3: + result = self._cache.get(key, None) + if result is None: + self._cache[key] = result = PrimitiveType3(self.make_name(key), self.type_classes) + self._reverse_cache[result] = key -class StructType3(Type3): + return result + +class TypeConstructor_Type(TypeConstructor[PrimitiveType3]): + """ + Base class type constructors of kind: * -> * + """ + __slots__ = () + + def __call__(self, arg: PrimitiveType3) -> PrimitiveType3: + raise NotImplementedError + +class TypeConstructor_TypeInt(TypeConstructor[Tuple[PrimitiveType3, IntType3]]): + """ + Base class type constructors of kind: * -> Int -> * + """ + __slots__ = () + + def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str: + return f'{self.name} {key[0].name} {key[1].value}' + + def __call__(self, arg0: PrimitiveType3, arg1: IntType3) -> PrimitiveType3: + return self.construct((arg0, arg1)) + +class TypeConstructor_TypeStar(TypeConstructor[Tuple[PrimitiveType3, ...]]): + """ + Base class type constructors of variadic kind + """ + def __call__(self, *args: PrimitiveType3) -> PrimitiveType3: + key: Tuple[PrimitiveType3, ...] = tuple(args) + return self.construct(key) + +class TypeConstructor_StaticArray(TypeConstructor_TypeInt): + def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str: + return f'{key[0].name}[{key[1].value}]' + +class TypeConstructor_Tuple(TypeConstructor_TypeStar): + def make_name(self, key: Tuple[PrimitiveType3, ...]) -> str: + return '(' + ', '.join(x.name for x in key) + ', )' + +class StructType3(PrimitiveType3): """ A Type3 struct with named members """ @@ -315,20 +370,27 @@ f64 = PrimitiveType3('f64', [Eq, Floating, Fractional, IntNum, NatNum, Ord]) A 32-bits IEEE 754 float, of 64 bits width. """ -bytes = PrimitiveType3('bytes', []) +bytes_ = PrimitiveType3('bytes', []) """ This is a runtime-determined length piece of memory that can be indexed at runtime. """ -static_array = PrimitiveType3('static_array', []) +static_array = TypeConstructor_StaticArray('static_array', [], [ + Eq, + InternalPassAsPointer, +]) """ -This is a fixed length piece of memory that can be indexed at runtime. +A type constructor. + +Any static array is a fixed length piece of memory that can be indexed at runtime. It should be applied with one argument. It has a runtime-dynamic length of the same type repeated. """ -tuple = PrimitiveType3('tuple', []) # pylint: disable=W0622 +tuple_ = TypeConstructor_Tuple('tuple', [], [ + InternalPassAsPointer, +]) """ This is a fixed length piece of memory. @@ -336,7 +398,7 @@ It should be applied with zero or more arguments. It has a compile time determined length, and each argument can be different. """ -LOOKUP_TABLE: Dict[str, Type3] = { +LOOKUP_TABLE: Dict[str, PrimitiveType3] = { 'none': none, 'bool': bool_, 'u8': u8, @@ -347,5 +409,5 @@ LOOKUP_TABLE: Dict[str, Type3] = { 'i64': i64, 'f32': f32, 'f64': f64, - 'bytes': bytes, + 'bytes': bytes_, } diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index efdcc68..546df8a 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -5,6 +5,7 @@ from typing import Any, Generator, Iterable, List, TextIO, Union from phasm import compiler from phasm.codestyle import phasm_render from phasm.runtime import calculate_alloc_size +from phasm.type3 import typeclasses as type3classes from phasm.type3 import types as type3types from . import runners @@ -82,21 +83,22 @@ class Suite: wasm_args.append(arg) continue - if arg_typ is type3types.bytes: + if arg_typ is type3types.bytes_: adr = _allocate_memory_stored_value(runner, arg_typ, arg) wasm_args.append(adr) continue - if isinstance(arg_typ, type3types.AppliedType3): - if arg_typ.base is type3types.static_array: + assert isinstance(arg_typ, type3types.PrimitiveType3) + sa_args = type3types.static_array.did_construct(arg_typ) + if sa_args is not None: adr = _allocate_memory_stored_value(runner, arg_typ, arg) wasm_args.append(adr) continue - if arg_typ.base is type3types.tuple: - adr = _allocate_memory_stored_value(runner, arg_typ, arg) - wasm_args.append(adr) - continue + # if arg_typ.base is type3types.tuple: + # adr = _allocate_memory_stored_value(runner, arg_typ, arg) + # wasm_args.append(adr) + # continue if isinstance(arg_typ, type3types.StructType3): adr = _allocate_memory_stored_value(runner, arg_typ, arg) @@ -142,7 +144,7 @@ def _write_memory_stored_value( val_typ: type3types.Type3, val: Any, ) -> int: - if val_typ is type3types.bytes: + if val_typ is type3types.bytes_: adr2 = _allocate_memory_stored_value(runner, val_typ, val) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) return 4 @@ -152,11 +154,11 @@ def _write_memory_stored_value( runner.interpreter_write_memory(adr, to_write) return len(to_write) - if isinstance(val_typ, type3types.AppliedType3): - if val_typ.base in (type3types.static_array, type3types.tuple, ): - adr2 = _allocate_memory_stored_value(runner, val_typ, val) - runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) - return 4 + # if isinstance(val_typ, type3types.AppliedType3): + # if val_typ.base in (type3types.static_array, type3types.tuple, ): + # adr2 = _allocate_memory_stored_value(runner, val_typ, val) + # runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) + # return 4 if isinstance(val_typ, type3types.StructType3): adr2 = _allocate_memory_stored_value(runner, val_typ, val) @@ -170,7 +172,7 @@ def _allocate_memory_stored_value( val_typ: type3types.Type3, val: Any ) -> int: - if val_typ is type3types.bytes: + if val_typ is type3types.bytes_: assert isinstance(val, bytes) adr = runner.call('stdlib.types.__alloc_bytes__', len(val)) @@ -180,44 +182,42 @@ def _allocate_memory_stored_value( runner.interpreter_write_memory(adr + 4, val) return adr - if isinstance(val_typ, type3types.AppliedType3): - if val_typ.base is type3types.static_array: + assert isinstance(val_typ, type3types.PrimitiveType3) + sa_args = type3types.static_array.did_construct(val_typ) + if sa_args is not None: assert isinstance(val, tuple) + sa_type, sa_len = sa_args + alloc_size = calculate_alloc_size(val_typ) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') - val_el_typ = val_typ.args[0] - assert not isinstance(val_el_typ, type3types.PlaceholderForType) - - tuple_len_obj = val_typ.args[1] - assert isinstance(tuple_len_obj, type3types.IntType3) - tuple_len = tuple_len_obj.value + tuple_len = sa_len.value assert tuple_len == len(val) offset = adr for val_el_val in val: - offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) + offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val) return adr - if val_typ.base is type3types.tuple: - assert isinstance(val, tuple) + # if val_typ.base is type3types.tuple: + # assert isinstance(val, tuple) - alloc_size = calculate_alloc_size(val_typ) - adr = runner.call('stdlib.alloc.__alloc__', alloc_size) - assert isinstance(adr, int) - sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') + # alloc_size = calculate_alloc_size(val_typ) + # adr = runner.call('stdlib.alloc.__alloc__', alloc_size) + # assert isinstance(adr, int) + # sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') - assert len(val) == len(val_typ.args) + # assert len(val) == len(val_typ.args) - offset = adr - for val_el_val, val_el_typ in zip(val, val_typ.args): - assert not isinstance(val_el_typ, type3types.PlaceholderForType) + # offset = adr + # for val_el_val, val_el_typ in zip(val, val_typ.args): + # assert not isinstance(val_el_typ, type3types.PlaceholderForType) - offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) - return adr + # offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) + # return adr if isinstance(val_typ, type3types.StructType3): assert isinstance(val, dict) @@ -278,21 +278,23 @@ def _load_memory_stored_returned_value( assert isinstance(wasm_value, float), wasm_value return wasm_value - if ret_type3 is type3types.bytes: + if ret_type3 is type3types.bytes_: assert isinstance(wasm_value, int), wasm_value return _load_bytes_from_address(runner, ret_type3, wasm_value) - if isinstance(ret_type3, type3types.AppliedType3): - if ret_type3.base is type3types.static_array: + assert isinstance(ret_type3, type3types.PrimitiveType3) # Type hint + + sa_args = type3types.static_array.did_construct(ret_type3) + if sa_args is not None: assert isinstance(wasm_value, int), wasm_value - return _load_static_array_from_address(runner, ret_type3, wasm_value) + return _load_static_array_from_address(runner, sa_args[0], sa_args[1], wasm_value) - if ret_type3.base is type3types.tuple: - assert isinstance(wasm_value, int), wasm_value + # if ret_type3.base is type3types.tuple: + # assert isinstance(wasm_value, int), wasm_value - return _load_tuple_from_address(runner, ret_type3, wasm_value) + # return _load_tuple_from_address(runner, ret_type3, wasm_value) if isinstance(ret_type3, type3types.StructType3): return _load_struct_from_address(runner, ret_type3, wasm_value) @@ -334,23 +336,28 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An assert len(inp) == 8 return struct.unpack(' Generator yield all_bytes[offset:offset + size] offset += size -def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.AppliedType3, adr: int) -> Any: - sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n') - - assert 2 == len(typ.args) - sub_typ, len_typ = typ.args +def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.PrimitiveType3, len_typ: type3types.IntType3, adr: int) -> Any: + sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n') assert not isinstance(sub_typ, type3types.PlaceholderForType) assert isinstance(len_typ, type3types.IntType3) @@ -396,29 +400,19 @@ def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types. for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) ) -def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any: - sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n') - - assert isinstance(typ, type3types.AppliedType3) - assert typ.base is type3types.tuple - - typ_list = [ - x - for x in typ.args - if not isinstance(x, type3types.PlaceholderForType) - ] - assert len(typ_list) == len(typ.args) +def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.PrimitiveType3, ...], adr: int) -> Any: + sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\n') arg_sizes = [ calculate_alloc_size(x, is_member=True) - for x in typ_list + for x in typ_args ] read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes)) return tuple( _unpack(runner, arg_typ, arg_bytes) - for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes)) + for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes)) ) def _load_struct_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any: diff --git a/tests/integration/test_lang/generator.md b/tests/integration/test_lang/generator.md index b4a4e48..0acceb3 100644 --- a/tests/integration/test_lang/generator.md +++ b/tests/integration/test_lang/generator.md @@ -116,11 +116,11 @@ def testEntry() -> i32: if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): expect_type_error( 'Mismatch between applied types argument count', - 'The type of the value returned from function constant should match its return type', + 'The type of a tuple is a combination of its members', ) elif TYPE_NAME.startswith('struct_'): expect_type_error( - TYPE + ' must be tuple (u32) instead', + TYPE + ' must be (u32) instead', 'The type of the value returned from function constant should match its return type', ) else: @@ -177,17 +177,17 @@ def testEntry() -> i32: ```py if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): expect_type_error( - 'Mismatch between applied types argument count', + 'u64[32] must be (u32) instead', 'The type of the value returned from function constant should match its return type', ) elif TYPE_NAME.startswith('struct_'): expect_type_error( - TYPE + ' must be tuple (u32) instead', + TYPE + ' must be (u32) instead', 'The type of the value returned from function constant should match its return type', ) else: expect_type_error( - TYPE_NAME + ' must be tuple (u32) instead', + TYPE_NAME + ' must be (u32) instead', 'The type of the value returned from function constant should match its return type', ) ``` @@ -233,17 +233,17 @@ def select(x: $TYPE) -> (u32, ): ```py if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): expect_type_error( - 'Mismatch between applied types argument count', + 'u64[32] must be (u32) instead', 'The type of the value returned from function select should match its return type', ) elif TYPE_NAME.startswith('struct_'): expect_type_error( - TYPE + ' must be tuple (u32) instead', + TYPE + ' must be (u32) instead', 'The type of the value returned from function select should match its return type', ) else: expect_type_error( - TYPE_NAME + ' must be tuple (u32) instead', + TYPE_NAME + ' must be (u32) instead', 'The type of the value returned from function select should match its return type', ) ``` @@ -287,11 +287,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): expect_type_error( 'Mismatch between applied types argument count', # FIXME: Shouldn't this be the same as for the else statement? - 'The type of the value passed to argument x of function helper should match the type of that argument', + 'The type of a tuple is a combination of its members', ) elif TYPE_NAME.startswith('struct_'): expect_type_error( - TYPE + ' must be tuple (u32) instead', + TYPE + ' must be (u32) instead', 'The type of the value passed to argument x of function helper should match the type of that argument', ) else: @@ -342,17 +342,17 @@ def testEntry() -> i32: ```py if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): expect_type_error( - 'Mismatch between applied types argument count', + 'u64[32] must be (u32) instead', 'The type of the value passed to argument x of function helper should match the type of that argument', ) elif TYPE_NAME.startswith('struct_'): expect_type_error( - TYPE + ' must be tuple (u32) instead', + TYPE + ' must be (u32) instead', 'The type of the value passed to argument x of function helper should match the type of that argument', ) else: expect_type_error( - TYPE_NAME + ' must be tuple (u32) instead', + TYPE_NAME + ' must be (u32) instead', 'The type of the value passed to argument x of function helper should match the type of that argument', ) ```