Prior to this PR, each type would have its own handwritten test suite. The end result was that not all types were tested for all situations. This PR adds a framework based on a Markdown file, which generates the basic tests for the types defined in json files. These are auto generated and updated by the Makefile before the test suite is run. Also, a number of unsupported type combinations are now supported. Also, we now support negative literals. Also, allocation calculation fixes for nested types. Also, the test helpers can now properly import and export typed variables such as bytes, static arrays and tuples. This may come in handy when it comes to phasm platform wanting to route data. Also, adds better support for i8 type. Also, started on a runtime.py, since there's quite some code now that deals with compile time handling of WebAssembly stuff. Also, minor improvement to the type constrains, namely we better match 'tuple' literals with static array types. Also, reduced spam when printing the type analysis results; constraints that go back on the backlog are now no longer printed one by one. It now also prints the end results of the typing analysis. Also, reorganized the big test_primitives test into type classes. Also, replaced pylint with ruff.
88 lines
2.2 KiB
Python
88 lines
2.2 KiB
Python
"""
|
|
stdlib: Memory allocation
|
|
"""
|
|
from phasm.wasmgenerator import Generator, func_wrapper
|
|
from phasm.wasmgenerator import VarType_i32 as i32
|
|
|
|
IDENTIFIER = 0xA1C0
|
|
|
|
ADR_IDENTIFIER = 0
|
|
ADR_RESERVED0 = ADR_IDENTIFIER + 4
|
|
ADR_FREE_BLOCK_PTR = ADR_RESERVED0 + 4
|
|
ADR_UNALLOC_PTR = ADR_FREE_BLOCK_PTR + 4
|
|
|
|
UNALLOC_PTR = ADR_UNALLOC_PTR + 4
|
|
|
|
# For memory initialization see phasm.compiler.module_data
|
|
|
|
@func_wrapper(exported=False)
|
|
def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
|
|
# Find out if we've freed any blocks at all so far
|
|
g.i32.const(ADR_FREE_BLOCK_PTR)
|
|
g.i32.load()
|
|
g.i32.const(0)
|
|
g.i32.eq()
|
|
|
|
with g.if_():
|
|
g.i32.const(0)
|
|
g.return_()
|
|
|
|
del alloc_size # TODO: Actual implement using a previously freed block
|
|
g.unreachable()
|
|
|
|
return i32('return') # To satisfy mypy
|
|
|
|
@func_wrapper()
|
|
def __alloc__(g: Generator, alloc_size: i32) -> i32:
|
|
result = i32('result')
|
|
|
|
# Check if the memory is already initialized
|
|
g.i32.const(ADR_IDENTIFIER)
|
|
g.i32.load()
|
|
g.i32.const(IDENTIFIER)
|
|
g.i32.ne()
|
|
with g.if_():
|
|
# Not yet initialized, or memory corruption
|
|
g.unreachable()
|
|
|
|
# Try to claim a free block
|
|
g.local.get(alloc_size)
|
|
g.call(__find_free_block__)
|
|
g.local.set(result)
|
|
|
|
# Check if there was a free block
|
|
g.local.get(result)
|
|
g.i32.const(0)
|
|
g.i32.eq()
|
|
with g.if_():
|
|
# No free blocks, increase allocated memory usage
|
|
|
|
# Put the address on the stack in advance so we can store to it later
|
|
g.i32.const(ADR_UNALLOC_PTR)
|
|
|
|
# Get the current unalloc pointer value
|
|
g.i32.const(ADR_UNALLOC_PTR)
|
|
g.i32.load()
|
|
g.local.tee(result)
|
|
|
|
# Calculate new unalloc pointer value
|
|
g.i32.const(4) # Header size
|
|
g.i32.add()
|
|
g.local.get(alloc_size)
|
|
g.i32.add()
|
|
|
|
# Store new unalloc pointer value (address was set on stack in advance)
|
|
g.i32.store()
|
|
|
|
# Store block size in the header
|
|
g.local.get(result)
|
|
g.local.get(alloc_size)
|
|
g.i32.store()
|
|
|
|
# Return address of the allocated memory, skipping the allocator header
|
|
g.local.get(result)
|
|
g.i32.const(4) # Header size
|
|
g.i32.add()
|
|
|
|
return i32('return') # To satisfy mypy
|