Compare commits

..

1 Commits

Author SHA1 Message Date
Johan B.W. de Vries
43411c4562 Removes the special casing for foldl
Had to implement both functions as arguments and type
place holders (variables) for type constructors.

Had to implement functions as a type as well.

Still have to figure out how to pass functions around.
2025-05-18 20:03:14 +02:00
8 changed files with 202 additions and 124 deletions

View File

@ -7,6 +7,8 @@
- Implement a trace() builtin for debugging - Implement a trace() builtin for debugging
- Check if we can use DataView in the Javascript examples, e.g. with setUint32 - Check if we can use DataView in the Javascript examples, e.g. with setUint32
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
- At least on static arrays, tuples and structs. For dynamic arrays, it's complicated. This needs cleaning up.
- Implement a FizzBuzz example - Implement a FizzBuzz example
- Also, check the codes for FIXME and TODO - Also, check the codes for FIXME and TODO
- Allocation is done using pointers for members, is this desired? - Allocation is done using pointers for members, is this desired?
@ -16,11 +18,9 @@
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types. - Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
- Have a set of rules or guidelines for the constraint comments, they're messy. - Have a set of rules or guidelines for the constraint comments, they're messy.
- Why is expression_subscript_bytes using a helper method but expression_subscript_static_array is not? - Why is expression_subscript_bytes using a helper method but expression_subscript_static_array is not?
- calculate_alloc_size can be reworked; is_member isn't useful with TYPE_INFO_MAP
- Parser is putting stuff in ModuleDataBlock - Parser is putting stuff in ModuleDataBlock
- Surely the compiler should build data blocks - Surely the compiler should build data blocks
- Also put the struct.pack constants in TYPE_INFO_MAP
- Make prelude more an actual thing - Make prelude more an actual thing
- Merge in compiler.INSTANCES - Merge in compiler.INSTANCES
- Make it less build in - have a environment class of some kind - Make it less build in - have a environment class of some kind

View File

@ -2,13 +2,12 @@
This module contains the code to convert parsed Ourlang into WebAssembly code This module contains the code to convert parsed Ourlang into WebAssembly code
""" """
import struct import struct
from typing import List from typing import List, Optional
from . import ourlang, prelude, wasm from . import ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset from .runtime import calculate_alloc_size, calculate_member_offset
from .stdlib import alloc as stdlib_alloc from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types from .stdlib import types as stdlib_types
from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
from .type3.functions import FunctionArgument, TypeVariable from .type3.functions import FunctionArgument, TypeVariable
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
from .type3.typeclasses import Type3ClassMethod from .type3.typeclasses import Type3ClassMethod
@ -28,6 +27,20 @@ from .wasmgenerator import Generator as WasmGenerator
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled' TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled'
LOAD_STORE_TYPE_MAP = {
'i8': 'i32', # Have to use an u32, since there is no native i8 type
'u8': 'i32', # Have to use an u32, since there is no native u8 type
'i32': 'i32',
'i64': 'i64',
'u32': 'i32',
'u64': 'i64',
'f32': 'f32',
'f64': 'f64',
'bytes': 'i32', # Bytes are passed around as pointers
}
def phasm_compile(inp: ourlang.Module) -> wasm.Module: def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
Public method for compiling a parsed Phasm module into Public method for compiling a parsed Phasm module into
@ -42,8 +55,53 @@ def type3(inp: Type3) -> wasm.WasmType:
Types are used for example in WebAssembly function parameters Types are used for example in WebAssembly function parameters
and return types. and return types.
""" """
typ_info = TYPE_INFO_MAP.get(inp.name, TYPE_INFO_CONSTRUCTED) assert inp is not None, TYPE3_ASSERTION_ERROR
return typ_info.wasm_type()
if inp == prelude.none:
return wasm.WasmTypeNone()
if inp == prelude.bool_:
# WebAssembly stores booleans as i32
# See e.g. f32.eq, which is [f32 f32] -> [i32]
return wasm.WasmTypeInt32()
if inp == prelude.u8:
# WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte
return wasm.WasmTypeInt32()
if inp == prelude.u32:
return wasm.WasmTypeInt32()
if inp == prelude.u64:
return wasm.WasmTypeInt64()
if inp == prelude.i8:
# WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte
return wasm.WasmTypeInt32()
if inp == prelude.i32:
return wasm.WasmTypeInt32()
if inp == prelude.i64:
return wasm.WasmTypeInt64()
if inp == prelude.f32:
return wasm.WasmTypeFloat32()
if inp == prelude.f64:
return wasm.WasmTypeFloat64()
if inp == prelude.bytes_:
# bytes are passed as pointer
# And pointers are i32
return wasm.WasmTypeInt32()
if (prelude.InternalPassAsPointer, (inp, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
return wasm.WasmTypeInt32()
raise NotImplementedError(type3, inp)
def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.TupleInstantiation) -> None: def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.TupleInstantiation) -> None:
""" """
@ -63,9 +121,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
sa_type, = inp.type3.application.arguments sa_type, = inp.type3.application.arguments
args = tuple(sa_type for _ in inp.elements) args = tuple(sa_type for _ in inp.elements)
# Can't use calculate_alloc_size directly since that doesn't alloc_size = 4 + calculate_alloc_size(sa_type, is_member=False) * len(inp.elements)
# know the dynamic array's length
alloc_size = 4 + calculate_alloc_size(sa_type, is_member=True) * len(inp.elements)
alloc_size_header = len(inp.elements) alloc_size_header = len(inp.elements)
elif isinstance(inp.type3.application, TypeApplication_TypeStar): elif isinstance(inp.type3.application, TypeApplication_TypeStar):
# Possibly paranoid assert. If we have a future variadic type, # Possibly paranoid assert. If we have a future variadic type,
@ -109,12 +165,15 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
for element, exp_type3 in zip(inp.elements, args, strict=True): for element, exp_type3 in zip(inp.elements, args, strict=True):
assert element.type3 == exp_type3 assert element.type3 == exp_type3
exp_type_info = TYPE_INFO_MAP.get(exp_type3.name, TYPE_INFO_CONSTRUCTED) if (prelude.InternalPassAsPointer, (exp_type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name]
wgn.add_statement('nop', comment='PRE') wgn.add_statement('nop', comment='PRE')
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
expression(wgn, mod, element) expression(wgn, mod, element)
wgn.add_statement(exp_type_info.wasm_store_func, 'offset=' + str(offset)) wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset))
wgn.add_statement('nop', comment='POST') wgn.add_statement('nop', comment='POST')
offset += calculate_alloc_size(exp_type3, is_member=True) offset += calculate_alloc_size(exp_type3, is_member=True)
@ -154,14 +213,14 @@ def expression_subscript_static_array(
with wgn.if_(): with wgn.if_():
wgn.unreachable(comment='Out of bounds') wgn.unreachable(comment='Out of bounds')
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
wgn.i32.const(el_type_info.alloc_size) wgn.i32.const(calculate_alloc_size(el_type))
wgn.i32.mul() wgn.i32.mul()
wgn.i32.add() wgn.i32.add()
wgn.add_statement(el_type_info.wasm_load_func) mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load')
def expression_subscript_tuple( def expression_subscript_tuple(
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
@ -175,16 +234,19 @@ def expression_subscript_tuple(
offset = 0 offset = 0
for el_type in args[0:inp.index.value]: for el_type in args[0:inp.index.value]:
assert el_type is not None, TYPE3_ASSERTION_ERROR assert el_type is not None, TYPE3_ASSERTION_ERROR
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED) offset += calculate_alloc_size(el_type)
offset += el_type_info.alloc_size
el_type = args[inp.index.value] el_type = args[inp.index.value]
assert el_type is not None, TYPE3_ASSERTION_ERROR assert el_type is not None, TYPE3_ASSERTION_ERROR
expression(wgn, mod, inp.varref) expression(wgn, mod, inp.varref)
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED) if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}') mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], None]() SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], None]()
SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes) SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes)
@ -243,7 +305,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
if inp.type3.name not in TYPE_INFO_MAP: if (prelude.InternalPassAsPointer, (inp.type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, )) assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
address = inp.variable.constant.data_block.address address = inp.variable.constant.data_block.address
@ -359,10 +421,11 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
member_type = dict(inp.struct_type3.application.arguments)[inp.member] member_type = dict(inp.struct_type3.application.arguments)[inp.member]
member_type_info = TYPE_INFO_MAP.get(member_type.name, TYPE_INFO_CONSTRUCTED)
mtyp = LOAD_STORE_TYPE_MAP[member_type.name]
expression(wgn, mod, inp.varref) expression(wgn, mod, inp.varref)
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(calculate_member_offset( wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset(
inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member
))) )))
return return
@ -463,8 +526,10 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function:
def module_data_u8(inp: int) -> bytes: def module_data_u8(inp: int) -> bytes:
""" """
Compile: module data, u8 value Compile: module data, u8 value
# FIXME: All u8 values are stored as u32
""" """
return struct.pack('<B', inp) return struct.pack('<I', inp) # Should be 'B'
def module_data_u32(inp: int) -> bytes: def module_data_u32(inp: int) -> bytes:
""" """
@ -481,8 +546,10 @@ def module_data_u64(inp: int) -> bytes:
def module_data_i8(inp: int) -> bytes: def module_data_i8(inp: int) -> bytes:
""" """
Compile: module data, i8 value Compile: module data, i8 value
# FIXME: All i8 values are stored as i32
""" """
return struct.pack('<b', inp) return struct.pack('<i', inp) # Should be a 'b'
def module_data_i32(inp: int) -> bytes: def module_data_i32(inp: int) -> bytes:
""" """
@ -671,11 +738,18 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
# Store each member individually # Store each member individually
for memname, mtyp3 in st_args: for memname, mtyp3 in st_args:
mtyp3_info = TYPE_INFO_MAP.get(mtyp3.name, TYPE_INFO_CONSTRUCTED) mtyp: Optional[str]
if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
if mtyp is None:
raise NotImplementedError(expression, inp, mtyp3)
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'${memname}') wgn.add_statement('local.get', f'${memname}')
wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(calculate_member_offset( wgn.add_statement(f'{mtyp}.store', 'offset=' + str(calculate_member_offset(
inp.struct_type3.name, st_args, memname inp.struct_type3.name, st_args, memname
))) )))

View File

@ -16,6 +16,7 @@ from ..type3.functions import (
from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup
from ..type3.typeclasses import Type3Class, Type3ClassMethod from ..type3.typeclasses import Type3Class, Type3ClassMethod
from ..type3.types import ( from ..type3.types import (
IntType3,
Type3, Type3,
TypeApplication_Nullary, TypeApplication_Nullary,
TypeConstructor_Base, TypeConstructor_Base,
@ -158,7 +159,10 @@ f64 = Type3('f64', TypeApplication_Nullary(None, None))
A 32-bits IEEE 754 float, of 64 bits width. A 32-bits IEEE 754 float, of 64 bits width.
""" """
dynamic_array = TypeConstructor_DynamicArray('dynamic_array') def da_on_create(args: tuple[Type3], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ)
dynamic_array = TypeConstructor_DynamicArray('dynamic_array', on_create=da_on_create)
""" """
This is a dynamic length piece of memory. This is a dynamic length piece of memory.
@ -166,7 +170,10 @@ It should be applied with two arguments. It has a runtime
determined length, and each argument is the same. determined length, and each argument is the same.
""" """
static_array = TypeConstructor_StaticArray('static_array') def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ)
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create)
""" """
This is a fixed length piece of memory. This is a fixed length piece of memory.
@ -174,7 +181,10 @@ It should be applied with two arguments. It has a compile time
determined length, and each argument is the same. determined length, and each argument is the same.
""" """
tuple_ = TypeConstructor_Tuple('tuple') def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ)
tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create)
""" """
This is a fixed length piece of memory. This is a fixed length piece of memory.
@ -182,14 +192,22 @@ It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different. determined length, and each argument can be different.
""" """
function = TypeConstructor_Function('function') def fn_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
# Not really a pointer; but still a i32
# (It's actually a table lookup)
instance_type_class(InternalPassAsPointer, typ)
function = TypeConstructor_Function('function', on_create=fn_on_create)
""" """
This is a function. This is a function.
It should be applied with one or more arguments. The last argument is the 'return' type. It should be applied with one or more arguments. The last argument is the 'return' type.
""" """
struct = TypeConstructor_Struct('struct') def st_on_create(args: tuple[tuple[str, Type3], ...], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ)
struct = TypeConstructor_Struct('struct', on_create=st_on_create)
""" """
This is like a tuple, but each argument is named, so that developers This is like a tuple, but each argument is named, so that developers
can get and set fields by name. can get and set fields by name.
@ -200,6 +218,16 @@ b = TypeVariable('b', TypeVariableApplication_Nullary(None, None))
t = TypeConstructorVariable('t') t = TypeConstructorVariable('t')
InternalPassAsPointer = Type3Class('InternalPassAsPointer', (a, ), methods={}, operators={})
"""
Internal type class to keep track which types we pass arounds as a pointer.
"""
# instance_type_class(InternalPassAsPointer, bytes_)
# instance_type_class(InternalPassAsPointer, static_array)
# instance_type_class(InternalPassAsPointer, tuple_)
# instance_type_class(InternalPassAsPointer, struct)
Eq = Type3Class('Eq', (a, ), methods={}, operators={ Eq = Type3Class('Eq', (a, ), methods={}, operators={
'==': [a, a, bool_], '==': [a, a, bool_],
'!=': [a, a, bool_], '!=': [a, a, bool_],

View File

@ -1,12 +1,11 @@
from . import prelude from . import prelude
from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
from .type3.types import IntType3, Type3 from .type3.types import IntType3, Type3
def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int: def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int:
if is_member: if is_member:
return TYPE_INFO_CONSTRUCTED.alloc_size return 4
sa_type, sa_len = args sa_type, sa_len = args
@ -14,7 +13,7 @@ def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntTyp
def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int: def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int:
if is_member: if is_member:
return TYPE_INFO_CONSTRUCTED.alloc_size return 4
return sum( return sum(
calculate_alloc_size(x, is_member=True) calculate_alloc_size(x, is_member=True)
@ -23,7 +22,7 @@ def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int:
def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int: def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int:
if is_member: if is_member:
return TYPE_INFO_CONSTRUCTED.alloc_size return 4
return sum( return sum(
calculate_alloc_size(x, is_member=True) calculate_alloc_size(x, is_member=True)
@ -36,9 +35,14 @@ ALLOC_SIZE_ROUTER.add(prelude.struct, calculate_alloc_size_struct)
ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple) ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple)
def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int: def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int:
typ_info = TYPE_INFO_MAP.get(typ.name) if typ in (prelude.u8, prelude.i8, ):
if typ_info is not None: return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32
return typ_info.alloc_size
if typ in (prelude.u32, prelude.i32, prelude.f32, ):
return 4
if typ in (prelude.u64, prelude.i64, prelude.f64, ):
return 8
try: try:
return ALLOC_SIZE_ROUTER(is_member, typ) return ALLOC_SIZE_ROUTER(is_member, typ)
@ -46,7 +50,7 @@ def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int:
if is_member: if is_member:
# By default, 'boxed' or 'constructed' types are # By default, 'boxed' or 'constructed' types are
# stored as pointers when a member of a struct or tuple # stored as pointers when a member of a struct or tuple
return TYPE_INFO_CONSTRUCTED.alloc_size return 4
raise NotImplementedError(typ) raise NotImplementedError(typ)

View File

@ -1,63 +1,15 @@
""" """
stdlib: Standard types that are not wasm primitives stdlib: Standard types that are not wasm primitives
""" """
from typing import Mapping, NamedTuple, Type
from phasm.stdlib import alloc from phasm.stdlib import alloc
from phasm.type3.routers import TypeVariableLookup from phasm.type3.routers import TypeVariableLookup
from phasm.type3.types import IntType3, Type3 from phasm.type3.types import IntType3, Type3
from phasm.wasm import (
WasmType,
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
WasmTypeNone,
)
from phasm.wasmgenerator import Generator, VarType_Base, func_wrapper from phasm.wasmgenerator import Generator, VarType_Base, func_wrapper
from phasm.wasmgenerator import VarType_f32 as f32 from phasm.wasmgenerator import VarType_f32 as f32
from phasm.wasmgenerator import VarType_f64 as f64 from phasm.wasmgenerator import VarType_f64 as f64
from phasm.wasmgenerator import VarType_i32 as i32 from phasm.wasmgenerator import VarType_i32 as i32
from phasm.wasmgenerator import VarType_i64 as i64 from phasm.wasmgenerator import VarType_i64 as i64
TypeInfo = NamedTuple('TypeInfo', [
# Name of the type
('typ', str, ),
# What WebAssembly type to use when passing this value around
# For example in function arguments
('wasm_type', Type[WasmType]),
# What WebAssembly function to use when loading a value from memory
('wasm_load_func', str),
# What WebAssembly function to use when storing a value to memory
('wasm_store_func', str),
# When storing this value in memory, how many bytes do we use?
# Only valid for non-constructed types, see calculate_alloc_size
# Should match wasm_load_func / wasm_store_func
('alloc_size', int),
])
TYPE_INFO_MAP: Mapping[str, TypeInfo] = {
'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0),
'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1),
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1),
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4),
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8),
'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4),
'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8),
'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4),
'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8),
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4),
}
# By default, constructed types are passed as pointers
# NOTE: ALLOC SIZE HERE DOES NOT WORK FOR CONSTRUCTED TYPES
# USE runtime.calculate_alloc_size FOR ACCURATE RESULTS
# Functions count as constructed types - even though they are
# not memory pointers but table addresses instead.
TYPE_INFO_CONSTRUCTED = TypeInfo('t a', WasmTypeInt32, 'i32.load', 'i32.store', 4)
@func_wrapper() @func_wrapper()
def __alloc_bytes__(g: Generator, length: i32) -> i32: def __alloc_bytes__(g: Generator, length: i32) -> i32:
@ -1166,9 +1118,26 @@ def static_array_sum(g: Generator, tvl: TypeVariableLookup) -> None:
if sa_len.value < 1: if sa_len.value < 1:
raise NotImplementedError('Default value in case sum is empty') raise NotImplementedError('Default value in case sum is empty')
sa_type_info = TYPE_INFO_MAP.get(sa_type.name, TYPE_INFO_CONSTRUCTED) # FIXME: We should probably use LOAD_STORE_TYPE_MAP for this?
mtyp_map = {
'u32': 'i32',
'u64': 'i64',
'i32': 'i32',
'i64': 'i64',
'f32': 'f32',
'f64': 'f64',
}
# FIXME: We should probably use calc_alloc_size for this?
type_var_size_map = {
'u32': 4,
'u64': 8,
'i32': 4,
'i64': 8,
'f32': 4,
'f64': 8,
}
# FIXME: This breaks when users start implementing their own NatNum classes
type_var_add_generator = { type_var_add_generator = {
'u32': u32_natnum_add, 'u32': u32_natnum_add,
'u64': u64_natnum_add, 'u64': u64_natnum_add,
@ -1177,6 +1146,11 @@ def static_array_sum(g: Generator, tvl: TypeVariableLookup) -> None:
'f32': f32_natnum_add, 'f32': f32_natnum_add,
'f64': f64_natnum_add, 'f64': f64_natnum_add,
} }
# By default, constructed types are passed as pointers
# FIXME: We don't know what add function to call
sa_type_mtyp = mtyp_map.get(sa_type.name, 'i32')
sa_type_alloc_size = type_var_size_map.get(sa_type.name, 4)
sa_type_add_gen = type_var_add_generator[sa_type.name] sa_type_add_gen = type_var_add_generator[sa_type.name]
# Definitions # Definitions
@ -1195,7 +1169,7 @@ def static_array_sum(g: Generator, tvl: TypeVariableLookup) -> None:
# Stack: [] # Stack: []
g.nop(comment='Calculate address at which to stop looping') g.nop(comment='Calculate address at which to stop looping')
g.local.get(sum_adr) g.local.get(sum_adr)
g.i32.const(sa_len.value * sa_type_info.alloc_size) g.i32.const(sa_len.value * sa_type_alloc_size)
g.i32.add() g.i32.add()
g.local.set(sum_stop) g.local.set(sum_stop)
@ -1203,30 +1177,30 @@ def static_array_sum(g: Generator, tvl: TypeVariableLookup) -> None:
# Stack: [] -> [sum] # Stack: [] -> [sum]
g.nop(comment='Get the first array value as starting point') g.nop(comment='Get the first array value as starting point')
g.local.get(sum_adr) g.local.get(sum_adr)
g.add_statement(sa_type_info.wasm_load_func) g.add_statement(f'{sa_type_mtyp}.load')
# Since we did the first one, increase adr # Since we did the first one, increase adr
# adr = adr + sa_type_alloc_size # adr = adr + sa_type_alloc_size
# Stack: [sum] -> [sum] # Stack: [sum] -> [sum]
g.local.get(sum_adr) g.local.get(sum_adr)
g.i32.const(sa_type_info.alloc_size) g.i32.const(sa_type_alloc_size)
g.i32.add() g.i32.add()
g.local.set(sum_adr) g.local.set(sum_adr)
if sa_len.value > 1: if sa_len.value > 1:
with g.loop(params=[sa_type_info.wasm_type().to_wat()], result=sa_type_info.wasm_type().to_wat()): with g.loop(params=[sa_type_mtyp], result=sa_type_mtyp):
# sum = sum + *adr # sum = sum + *adr
# Stack: [sum] -> [sum + *adr] # Stack: [sum] -> [sum + *adr]
g.nop(comment='Add array value') g.nop(comment='Add array value')
g.local.get(sum_adr) g.local.get(sum_adr)
g.add_statement(sa_type_info.wasm_load_func) g.add_statement(f'{sa_type_mtyp}.load')
sa_type_add_gen(g, ({}, {}, )) sa_type_add_gen(g, ({}, {}, ))
# adr = adr + sa_type_alloc_size # adr = adr + sa_type_alloc_size
# Stack: [sum] -> [sum] # Stack: [sum] -> [sum]
g.nop(comment='Calculate address of the next value') g.nop(comment='Calculate address of the next value')
g.local.get(sum_adr) g.local.get(sum_adr)
g.i32.const(sa_type_info.alloc_size) g.i32.const(sa_type_alloc_size)
g.i32.add() g.i32.add()
g.local.tee(sum_adr) g.local.tee(sum_adr)

View File

@ -3,6 +3,7 @@ Contains the final types for use in Phasm, as well as construtors.
""" """
from typing import ( from typing import (
Any, Any,
Callable,
Hashable, Hashable,
Self, Self,
Tuple, Tuple,
@ -140,21 +141,27 @@ class TypeConstructor_Base[T]:
""" """
Base class for type construtors Base class for type construtors
""" """
__slots__ = ('name', '_cache', ) __slots__ = ('name', 'on_create', '_cache', )
name: str name: str
""" """
The name of the type constructor The name of the type constructor
""" """
on_create: Callable[[T, Type3], None]
"""
Who to let know if a type is created
"""
_cache: dict[T, Type3] _cache: dict[T, Type3]
""" """
When constructing a type with the same arguments, When constructing a type with the same arguments,
it should produce the exact same result. it should produce the exact same result.
""" """
def __init__(self, name: str) -> None: def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None:
self.name = name self.name = name
self.on_create = on_create
self._cache = {} self._cache = {}
@ -181,6 +188,7 @@ class TypeConstructor_Base[T]:
result = self._cache.get(key, None) result = self._cache.get(key, None)
if result is None: if result is None:
self._cache[key] = result = Type3(self.make_name(key), self.make_application(key)) self._cache[key] = result = Type3(self.make_name(key), self.make_application(key))
self.on_create(key, result)
return result return result
@ -284,6 +292,7 @@ class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]
def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3: def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3:
result = Type3(name, self.make_application(args)) result = Type3(name, self.make_application(args))
self.on_create(args, result)
return result return result
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]): class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):

View File

@ -11,7 +11,6 @@ from phasm.runtime import (
calculate_alloc_size_struct, calculate_alloc_size_struct,
calculate_alloc_size_tuple, calculate_alloc_size_tuple,
) )
from phasm.stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
from phasm.type3 import types as type3types from phasm.type3 import types as type3types
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
@ -152,7 +151,7 @@ def _write_memory_stored_value(
try: try:
adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ) adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return TYPE_INFO_CONSTRUCTED.alloc_size return 4
except NoRouteForTypeException: except NoRouteForTypeException:
to_write = WRITE_LOOKUP_MAP[val_typ.name](val) to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
runner.interpreter_write_memory(adr, to_write) runner.interpreter_write_memory(adr, to_write)
@ -304,36 +303,43 @@ def _load_memory_stored_returned_value(
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3) return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any: def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
typ_info = TYPE_INFO_MAP.get(typ.name, TYPE_INFO_CONSTRUCTED)
assert len(inp) == typ_info.alloc_size
if typ is prelude.u8: if typ is prelude.u8:
return struct.unpack('<B', inp)[0] # See compiler.py, LOAD_STORE_TYPE_MAP and module_data_u8
assert len(inp) == 4
return struct.unpack('<I', inp)[0]
if typ is prelude.u32: if typ is prelude.u32:
assert len(inp) == 4
return struct.unpack('<I', inp)[0] return struct.unpack('<I', inp)[0]
if typ is prelude.u64: if typ is prelude.u64:
assert len(inp) == 8
return struct.unpack('<Q', inp)[0] return struct.unpack('<Q', inp)[0]
if typ is prelude.i8: if typ is prelude.i8:
return struct.unpack('<b', inp)[0] # See compiler.py, LOAD_STORE_TYPE_MAP and module_data_i8
assert len(inp) == 4
return struct.unpack('<i', inp)[0]
if typ is prelude.i32: if typ is prelude.i32:
assert len(inp) == 4
return struct.unpack('<i', inp)[0] return struct.unpack('<i', inp)[0]
if typ is prelude.i64: if typ is prelude.i64:
assert len(inp) == 8
return struct.unpack('<q', inp)[0] return struct.unpack('<q', inp)[0]
if typ is prelude.f32: if typ is prelude.f32:
assert len(inp) == 4
return struct.unpack('<f', inp)[0] return struct.unpack('<f', inp)[0]
if typ is prelude.f64: if typ is prelude.f64:
assert len(inp) == 8
return struct.unpack('<d', inp)[0] return struct.unpack('<d', inp)[0]
if typ_info is TYPE_INFO_CONSTRUCTED: if (prelude.InternalPassAsPointer, (typ, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
# Note: For applied types, inp should contain a 4 byte pointer # Note: For applied types, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0] adr = struct.unpack('<I', inp)[0]
return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ) return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ)

View File

@ -23,23 +23,6 @@ def testEntry(f: {type_}) -> u8:
assert exp_result == result.returned_value assert exp_result == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [
('(u8, u8, u8, )', (45, 46, 47), 46, ),
('u8[5]', (45, 46, 47, 48, 49), 46, ),
('bytes', b'This is a test', 104)
])
def test_subscript_1(type_, in_put, exp_result):
code_py = f"""
@exported
def testEntry(f: {type_}) -> u8:
return f[1]
"""
result = Suite(code_py).run_code(in_put)
assert exp_result == result.returned_value
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [ @pytest.mark.parametrize('type_, in_put, exp_result', [
('(u8, u8, u8, )', (45, 46, 47), 47, ), ('(u8, u8, u8, )', (45, 46, 47), 47, ),
@ -99,7 +82,7 @@ def testEntry(x: (u8, u32, u64)) -> u64:
@pytest.mark.parametrize('type_, in_put', [ @pytest.mark.parametrize('type_, in_put', [
('(u8, u8, )', (45, 46), ), ('(u8, u8, )', (45, 46), ),
('u8[2]', (45, 46), ), ('u8[2]', (45, 46), ),
# dynamic_array isn't known at runtime so works like normal # bytes isn't known at runtime so works like normal
]) ])
def test_subscript_oob_constant_low(type_, in_put): def test_subscript_oob_constant_low(type_, in_put):
code_py = f""" code_py = f"""