Compare commits

..

32 Commits

Author SHA1 Message Date
Nicolas Kruse ee771e3ac1
Merge pull request #32 from Nonannet/dev
CI: Updated dependency for build-docs updated to insure arm thumb is included in the docs
2026-03-04 16:25:56 +01:00
Nicolas 548ca6fab8 CI: Updated dependency for build-docs updated to insure arm thumb is included in the docs 2026-03-04 16:03:31 +01:00
Nicolas Kruse 58f796513c
Merge pull request #31 from Nonannet/dev
- ARM Thumb suport for Cortex-A and Cortex-M
2026-03-04 15:29:05 +01:00
Nicolas Kruse 5ca947ea5b
Merge pull request #30 from Nonannet/feature_arm_thumb
Feature: Adding support for ARM thumb
2026-03-04 15:17:50 +01:00
Nicolas 01f02cc9ba Readme updated 2026-03-04 15:16:51 +01:00
Nicolas 031249241e Removed un-required bulk stack allocation and added 4 Byte alignment check. CI updated. 2026-03-04 14:58:52 +01:00
Nicolas e48fc7c485 test for arm thumb variants split into two tests 2026-03-03 16:09:06 +01:00
Nicolas c6fd69d61b CI: different armv7 variates separated 2026-03-03 15:50:48 +01:00
Nicolas b7d5f3a129 exclude non-stencil functions in the test "test_start_end_function" 2026-03-03 15:05:45 +01:00
Nicolas 0212fa77a3 type annotation for data_writer.copy() fixed 2026-03-03 14:25:50 +01:00
Nicolas c31601853b CI: LIBGCC version fixed for thumb builds 2026-03-03 13:31:22 +01:00
Nicolas 04cdf50a04 Updated pelfy dependency 2026-03-03 13:24:46 +01:00
Nicolas 7a3088ec48 added add_sign_int32 function, since pelfy returns addend-values for 32 bit x86 as unsigned int32 2026-03-03 13:05:47 +01:00
Nicolas Kruse a924d42e6a
Merge branch 'dev' into feature_arm_thumb 2026-03-03 08:59:22 +01:00
Nicolas 7f963d7e43 CI: Testing for ARMv7 extended to armv7thumb and armv7mthumb 2026-03-02 21:33:24 +01:00
Nicolas dd7fb12c64 Helper bash script added for debugging ARM thumb stencils 2026-03-02 21:32:45 +01:00
Nicolas d2069d5d07 build script for local stencil builds updated for ARM64, ARM-Thumb and ARM-CortexM-Thumb 2026-03-02 21:32:12 +01:00
Nicolas accb03f042 Fix in test function "get_42" 2026-03-02 21:31:07 +01:00
Nicolas 2eb49cc2e5 test for ARM thumb updated 2026-03-02 21:29:46 +01:00
Nicolas c7c8db6332 R_ARM_THM_MOV* support added 2026-03-02 21:28:46 +01:00
Nicolas 8fcf0dedac patch type added: PATCH_OBJECT_ARM32_ABS_THM (for R_ARM_THM_MOVW_ABS_NC and R_ARM_THM_MOVT_ABS) 2026-03-02 21:28:05 +01:00
Nicolas afc442ada6 stencil build script updated 2026-02-28 22:09:12 +01:00
Nicolas 436a09c1ea copy method to data_writer added 2026-02-28 22:08:22 +01:00
Nicolas cabfda4ec6 CI: stencil build script updated with Cortex-A Thumb version 2026-02-28 22:07:27 +01:00
Nicolas 83ce6ce0e7 musl functions for math on ARM thumb added to stencil build pipeline 2026-02-13 01:13:01 +01:00
Nicolas a81236a3fc 4-Byte-Alignment error on ARM thumb fixed by using section size instead of function size to include nop padding 2026-02-13 00:51:55 +01:00
Nicolas bc0ccd90b7 Updated pelfy and using offset_in_section instead of fields['st_value'] 2026-02-12 23:54:20 +01:00
Nicolas Kruse e52cbe9e1b docstrings added and descriptions for c files added 2026-02-05 14:26:07 +01:00
Nicolas 58120f292c partial arm thumb implementation added 2026-01-26 23:57:15 +01:00
Nicolas 7131483a22 CI: Added -static for building the runner 2026-01-25 17:16:33 +01:00
Nicolas 3ec0ba10c3 Skiping qemo based ARM32 bit tests on windows since they are not
compatible to wsl1
2026-01-25 17:13:01 +01:00
Nicolas Kruse d394b2d249 add_read_value_remote backend function renamed and docstring updated 2026-01-12 16:57:54 +01:00
38 changed files with 1169 additions and 248 deletions

View File

@ -104,7 +104,7 @@ jobs:
- name: Compile coparun
run: |
mkdir -p build/runner
gcc -O3 -DENABLE_BASIC_LOGGING -o build/runner/coparun src/coparun/runmem.c src/coparun/coparun.c src/coparun/mem_man.c
gcc -O3 -static -DENABLE_BASIC_LOGGING -o build/runner/coparun src/coparun/runmem.c src/coparun/coparun.c src/coparun/mem_man.c
- name: Generate debug asm files
if: strategy.job-index == 0
@ -151,9 +151,9 @@ jobs:
- name: Use ARM64 container
run: |
docker run --rm -v $PWD:/app -w /app --platform linux/arm64 ghcr.io/nonannet/arm64_test:1 \
bash -lc "pip install . && \
bash -lc "pip install .[mindev] && \
mkdir -p build/runner && \
gcc -O3 -DENABLE_LOGGING -o build/runner/coparun src/coparun/runmem.c \
gcc -O3 -static -DENABLE_LOGGING -o build/runner/coparun src/coparun/runmem.c \
src/coparun/coparun.c src/coparun/mem_man.c && \
pytest && \
bash tools/create_asm.sh"
@ -180,9 +180,10 @@ jobs:
- name: Use ARMv6 container
run: |
docker run --rm -v $PWD:/app -w /app --platform linux/arm/v6 ghcr.io/nonannet/armv6_test:1 \
bash -lc "pip install . && \
bash -lc "set -x && \
pip install .[mindev] && \
mkdir -p build/runner && \
gcc -O3 -DENABLE_LOGGING -o build/runner/coparun src/coparun/runmem.c \
gcc -O3 -static -DENABLE_LOGGING -o build/runner/coparun src/coparun/runmem.c \
src/coparun/coparun.c src/coparun/mem_man.c && \
pytest && \
bash tools/create_asm.sh"
@ -209,9 +210,14 @@ jobs:
- name: Use ARMv7 container
run: |
docker run --rm -v $PWD:/app -w /app --platform linux/arm/v7 ghcr.io/nonannet/armv7_test:1 \
bash -lc "pip install . && \
bash -lc "set -x && \
pip install .[mindev] && \
mkdir -p build/runner && \
gcc -O3 -DENABLE_LOGGING -o build/runner/coparun src/coparun/runmem.c \
gcc -march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm -static \
-Wall -Wextra -Wconversion -Wsign-conversion \
-Wshadow -Wstrict-overflow -O3 \
-DENABLE_LOGGING \
-o build/runner/coparun src/coparun/runmem.c \
src/coparun/coparun.c src/coparun/mem_man.c && \
pytest && \
bash tools/create_asm.sh"
@ -221,6 +227,76 @@ jobs:
name: runner-linux-armv7
path: build/runner/*
build-armv7thumb:
needs: [build_stencils]
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: stencil-object-files
path: src/copapy/obj
- name: Set up QEMU for ARMv7
uses: docker/setup-qemu-action@v3
with:
platforms: linux/arm/v7
- name: Use ARMv7 container
run: |
docker run --rm -v $PWD:/app -w /app --platform linux/arm/v7 ghcr.io/nonannet/armv7_test:1 \
bash -lc "set -x && \
pip install .[mindev] && \
mkdir -p build/runner && \
gcc -march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm -static \
-Wall -Wextra -Wconversion -Wsign-conversion \
-Wshadow -Wstrict-overflow -O3 \
-DENABLE_LOGGING \
-o build/runner/coparun src/coparun/runmem.c \
src/coparun/coparun.c src/coparun/mem_man.c && \
export CP_TARGET_ARCH=armv7thumb && \
pytest && \
bash tools/create_asm.sh"
- uses: actions/upload-artifact@v4
with:
name: runner-linux-armv7thumb
path: build/runner/*
build-armv7mthumb:
needs: [build_stencils]
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: stencil-object-files
path: src/copapy/obj
- name: Set up QEMU for ARMv7
uses: docker/setup-qemu-action@v3
with:
platforms: linux/arm/v7
- name: Use ARMv7 container
run: |
docker run --rm -v $PWD:/app -w /app --platform linux/arm/v7 ghcr.io/nonannet/armv7_test:1 \
bash -lc "set -x && \
pip install .[mindev] && \
mkdir -p build/runner && \
gcc -march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm -static \
-Wall -Wextra -Wconversion -Wsign-conversion \
-Wshadow -Wstrict-overflow -O3 \
-DENABLE_LOGGING \
-o build/runner/coparun src/coparun/runmem.c \
src/coparun/coparun.c src/coparun/mem_man.c && \
export CP_TARGET_ARCH=armv7mthumb && \
pytest && \
bash tools/create_asm.sh"
- uses: actions/upload-artifact@v4
with:
name: runner-linux-armv7mthumb
path: build/runner/*
build-windows:
needs: [build_stencils]
runs-on: windows-latest
@ -244,7 +320,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies
run: python -m pip install .[dev]
run: python -m pip install .[mindev]
- name: Set up MSVC environment
uses: microsoft/setup-msbuild@v2
@ -270,7 +346,7 @@ jobs:
path: build/runner/*
release-stencils:
needs: [build_stencils, build-ubuntu, build-windows, build-arm64, build-armv6, build-armv7]
needs: [build_stencils, build-ubuntu, build-windows, build-arm64, build-armv6, build-armv7, build-armv7thumb, build-armv7mthumb]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
permissions:
@ -319,7 +395,7 @@ jobs:
fi
build-docs:
needs: [build_stencils, build-ubuntu, build-windows, build-arm64, build-armv6, build-armv7]
needs: [build_stencils, build-ubuntu, build-windows, build-arm64, build-armv6, build-armv7, build-armv7thumb, build-armv7mthumb]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

1
.gitignore vendored
View File

@ -30,3 +30,4 @@ core
*.log
docs/source/start.md
/src/copapy/_version.py
sketch*.py

View File

@ -13,7 +13,7 @@ The main features can be summarized as:
- Memory and type safety with a minimal set of runtime errors
- Deterministic execution
- Automatic differentiation for efficient realtime optimization (reverse-mode)
- Optimized machine code for x86_64, ARMv6, ARMv7 and AArch64
- Optimized machine code for x86_64, 32 Bit ARM (Cortex-A and Cortex-M) and AArch64
- Highly portable to new architectures
- Small Python package with minimal dependencies and no cross-compile toolchain required
@ -31,7 +31,6 @@ While hardware I/O is obviously a core aspect of the project, it is not yet avai
Currently in development:
- Array stencils for handling very large arrays and generating SIMD-optimized code - e.g., for machine vision and neural network applications
- Support for Thumb instructions required by ARM*-M targets (for MCUs)
- Constant regrouping for further symbolic optimization of the computation graph
Despite missing SIMD-optimization, benchmark performance shows promising numbers. The following chart plots the results in comparison to NumPy 2.3.5:
@ -253,4 +252,4 @@ This project is licensed under the MIT license - see the [LICENSE](LICENSE) file
[^2]: The compiler must support tail-call optimization (TCO). Currently, GCC is supported. Porting to a new architecture requires implementing a subset of relocation types used by that architecture.
[^3]: Supported architectures: x86_64, AArch64, ARMv6 and 7 (non-Thumb). ARMv6/7-M (Thumb) support is in development. Code for x86 32-bit exists but has unresolved issues and a low priority.
[^3]: Supported architectures: x86_64, AArch64, ARMv6/7 (non-Thumb) and ARMv7 Thumb for Cortex-A and Cortex-M. Code for x86 32-bit exists but has unresolved issues and a low priority.

View File

@ -2,7 +2,7 @@
name = "copapy"
dynamic = ["version"]
authors = [
{ name="Nicolas Kruse", email="nicolas.kruse@nonan.net" },
{ name="Nicolas Kruse", email="nicolas.kruse@nonan.net" },
]
description = "Copy-Patch Compiler"
readme = "README.md"
@ -45,14 +45,18 @@ dev = [
"ruff",
"mypy",
"pytest",
"pelfy>=1.0.7"
"pelfy>=1.0.8"
]
mindev = [
"pytest",
"pelfy>=1.0.8"
]
doc_build = [
"sphinx",
"pydata_sphinx_theme",
"sphinx-autodoc-typehints",
"myst-parser",
"pelfy>=1.0.7"
"sphinx",
"pydata_sphinx_theme",
"sphinx-autodoc-typehints",
"myst-parser",
"pelfy>=1.0.8"
]
[tool.mypy]

View File

@ -429,8 +429,8 @@ class Op(Node):
def __hash__(self) -> int:
return self.node_hash
# Interface for vector and tensor types
class ArrayType(Generic[TNum]):
"""Interface for vector and tensor types."""
def __init__(self, shape: tuple[int, ...]) -> None:
self.shape = shape
self.values: tuple[TNum | value[TNum], ...] = ()

View File

@ -6,11 +6,14 @@ ByteOrder = Literal['little', 'big']
Command = Enum('Command', [('ALLOCATE_DATA', 1), ('COPY_DATA', 2),
('ALLOCATE_CODE', 3), ('COPY_CODE', 4),
('PATCH_FUNC', 0x1000), ('PATCH_OBJECT', 0x2000),
('PATCH_FUNC', 0x1000),
('PATCH_FUNC_ARM32_THM', 0x1005),
('PATCH_OBJECT', 0x2000),
('PATCH_OBJECT_HI21', 0x2001),
('PATCH_OBJECT_ABS', 0x2002),
('PATCH_OBJECT_REL', 0x2003),
('PATCH_OBJECT_ARM32_ABS', 0x2004),
('PATCH_OBJECT_ARM32_ABS_THM', 0x2006),
('ENTRY_POINT', 7),
('RUN_PROG', 64), ('READ_DATA', 65),
('END_COM', 256), ('FREE_MEMORY', 257), ('DUMP_CODE', 258)])
@ -22,6 +25,11 @@ class data_writer():
self._data: list[tuple[str, bytes, int]] = []
self.byteorder: ByteOrder = byteorder
def copy(self) -> 'data_writer':
cp = data_writer(self.byteorder)
cp._data = self._data.copy()
return cp
def write_int(self, value: int, num_bytes: int = 4, signed: bool = False) -> None:
self._data.append((f"INT {value}", value.to_bytes(length=num_bytes, byteorder=self.byteorder, signed=signed), 0))

View File

@ -7,6 +7,7 @@ from ._basic_types import Net, Node, Store, CPConstant, Op, transl_type
def stable_toposort(edges: Iterable[tuple[Node, Node]]) -> list[Node]:
"""Perform a stable topological sort on a directed acyclic graph (DAG).
Arguments:
edges: Iterable of (u, v) pairs meaning u -> v
@ -133,7 +134,7 @@ def get_const_nets(nodes: list[Node]) -> list[Net]:
def add_load_ops(node_list: list[Node]) -> Generator[tuple[Net | None, Node], None, None]:
"""Add read node before each op where arguments are not already positioned
"""Add load/read node before each op where arguments are not already positioned
correctly in the registers
Arguments:
@ -171,7 +172,7 @@ def add_load_ops(node_list: list[Node]) -> Generator[tuple[Net | None, Node], No
def add_store_ops(net_node_list: list[tuple[Net | None, Node]], const_nets: list[Net]) -> Generator[tuple[Net | None, Node], None, None]:
"""Add write operation for each new defined net if a read operation is later followed
"""Add store/write operation for each new defined net if a read operation is later followed
Returns:
Yields tuples of a net and a node. The associated net is provided for read and write nodes.
@ -392,6 +393,7 @@ def compile_to_dag(node_list: Iterable[Node], sdb: stencil_database) -> tuple[bi
# assemble stencils to main program and patch stencils
data = sdb.get_function_code('entry_function_shell', 'start')
data_list.append(data)
#print(f"* entry_function_shell (0) " + ' '.join(f'{d:02X}' for d in data))
offset = aux_func_len + len(data)
for associated_net, node in extended_output_ops:
@ -450,10 +452,8 @@ def compile_to_dag(node_list: Iterable[Node], sdb: stencil_database) -> tuple[bi
#print('--> ', name, list(sdb.get_relocations(name)))
for reloc in sdb.get_relocations(name):
#assert reloc.target_symbol_info != 'STT_FUNC', "Not tested yet!"
if not reloc.target_section_index:
assert reloc.pelfy_reloc.type == 'R_ARM_V4BX'
assert reloc.pelfy_reloc.type == 'R_ARM_V4BX', (reloc.pelfy_reloc.type, name, reloc.pelfy_reloc.symbol.name)
elif reloc.target_symbol_info in {'STT_OBJECT', 'STT_NOTYPE', 'STT_SECTION'}:
# Patch constants/variable addresses on heap
@ -488,6 +488,6 @@ def compile_to_dag(node_list: Iterable[Node], sdb: stencil_database) -> tuple[bi
dw.write_int(patch.value, signed=True)
dw.write_com(binw.Command.ENTRY_POINT)
dw.write_int(aux_func_len)
dw.write_int(aux_func_len + sdb.thumb_mode)
return dw, variables

View File

@ -310,7 +310,7 @@ def get_42(x: value[Any]) -> value[float]: ...
def get_42(x: NumLike) -> value[float] | float:
"""Returns the value representing the constant 42"""
if isinstance(x, value):
return add_op('get_42', [x, x])
return add_op('get_42', [x])
return float((int(x) * 3.0 + 42.0) * 5.0 + 21.0)

View File

@ -2,6 +2,7 @@ from dataclasses import dataclass
from typing import Generator, Literal, Iterable, TYPE_CHECKING
import struct
import platform
import os
if TYPE_CHECKING:
import pelfy
@ -32,11 +33,14 @@ class relocation_entry:
@dataclass
class patch_entry:
"""
A dataclass for representing a relocation entry
A dataclass for representing a patch entry
Attributes:
addr (int): address of first byte to patch relative to the start of the symbol
type (RelocationType): relocation type
mask (int): Bit-mask to apply to the patched value
address (int): Address where to patch
value (int): The value to write at the patch address
scale (int): The scale factor for the patch value
patch_type (int): The type of patch
"""
mask: int
address: int
@ -46,6 +50,14 @@ class patch_entry:
def detect_process_arch() -> str:
"""For running the code locally in the python module
the architecture of the current process is detected
by this function to load the correct stencil database.
"""
cp_target_arch = os.environ.get("CP_TARGET_ARCH")
if cp_target_arch:
return cp_target_arch
bits = struct.calcsize("P") * 8
arch = platform.machine().lower()
@ -80,11 +92,20 @@ def get_return_function_type(symbol: pelfy.elf_symbol) -> str:
def get_stencil_position(func: pelfy.elf_symbol) -> tuple[int, int]:
start_index = 0 # There must be no prolog
# Find last relocation in function
last_instr = get_last_call_in_function(func)
function_size = func.fields['st_size']
if last_instr + 5 >= function_size: # Check if jump is last instruction
end_index = last_instr # Jump can be striped
assert func.section, f"No code section specified for symbol {func.name}"
# func.section.fields['sh_size'] is equivalent to func.fields['st_size']
# expect for ARM thumb, here nop padding at the end for 4-byte alignment
# is not included in st_size
function_size = func.section.fields['sh_size']
# Check if jump is the last instruction and can be striped
if last_instr + 5 >= function_size:
end_index = last_instr
else:
end_index = function_size
@ -98,11 +119,12 @@ def get_last_call_in_function(func: pelfy.elf_symbol) -> int:
if reloc.symbol.name.startswith('dummy_'):
return -0xFFFF # Last relocation is not a jump
else:
# Assume the call instruction is 4 bytes long for relocations with less than 32 bit and 5 bytes otherwise
# Assume the jump/call instruction is 4 bytes long for relocations
# with less than 32 bit and 5 bytes otherwise
instruction_lengths = 4 if reloc.bits < 32 else 5
address_field_length = 4
#print(f"-> {[r.fields['r_offset'] - func.fields['st_value'] for r in func.relocations]}")
return reloc.fields['r_offset'] - func.fields['st_value'] + address_field_length - instruction_lengths
return reloc.fields['r_offset'] - func.offset_in_section + address_field_length - instruction_lengths
def get_op_after_last_call_in_function(func: pelfy.elf_symbol) -> int:
@ -110,7 +132,12 @@ def get_op_after_last_call_in_function(func: pelfy.elf_symbol) -> int:
assert func.relocations, f'No call function in stencil function {func.name}.'
reloc = func.relocations[-1]
assert reloc.bits <= 32, "Relocation segment might be larger then 32 bit"
return reloc.fields['r_offset'] - func.fields['st_value'] + 4
return reloc.fields['r_offset'] - func.offset_in_section + 4
def add_sign_int32(value: int) -> int:
"""Convert a 32-bit unsigned integer to a signed integer."""
return value - 0x100000000 if value > 0x7FFFFFFF else value
class stencil_database():
@ -121,6 +148,7 @@ class stencil_database():
var_size (dict[str, int]): dictionary of object names and their sizes
byteorder (ByteOrder): byte order of the ELF file
elf (elf_file): the loaded ELF file
thumb_mode (bool): entry_function_shell in ARM thumb mode
"""
def __init__(self, obj_file: str | bytes):
@ -147,6 +175,8 @@ class stencil_database():
# if s.info == 'STT_OBJECT'}
self.byteorder: ByteOrder = self.elf.byteorder
self.thumb_mode = self.elf.symbols['entry_function_shell'].thumb_mode
#for name in self.function_definitions.keys():
# sym = self.elf.symbols[name]
# sym.relocations
@ -188,19 +218,20 @@ class stencil_database():
for reloc in symbol.relocations:
# address to fist byte to patch relative to the start of the symbol
patch_offset = reloc.fields['r_offset'] - symbol.fields['st_value'] - start_index
patch_offset = reloc.fields['r_offset'] - symbol.offset_in_section - start_index
if patch_offset < end_index - start_index: # Exclude the call to the result_* function
reloc_entry = relocation_entry(reloc.symbol.name,
reloc.symbol.info,
reloc.symbol.fields['st_value'],
reloc.symbol.fields['st_value'], # LSB on ARM indicates thumb mode
reloc.symbol.fields['st_shndx'],
symbol.fields['st_value'],
symbol.offset_in_section,
start_index,
reloc)
cache.append(reloc_entry)
yield reloc_entry
def get_patch(self, relocation: relocation_entry, symbol_address: int, function_offset: int, symbol_type: int) -> patch_entry:
"""Return patch positions for a provided symbol (function or object)
@ -226,12 +257,14 @@ class stencil_database():
if pr.type.endswith('64_PC32') or pr.type.endswith('64_PLT32'):
# S + A - P
patch_value = symbol_address + pr.fields['r_addend'] - patch_offset
addend = add_sign_int32(pr.fields['r_addend'])
patch_value = symbol_address + addend - patch_offset
#print(f" *> {pr.type} {patch_value=} {symbol_address=} {pr.fields['r_addend']=} {pr.bits=}, {function_offset=} {patch_offset=}")
elif pr.type == 'R_386_PC32':
# S + A - P
patch_value = symbol_address + pr.fields['r_addend'] - patch_offset
addend = add_sign_int32(pr.fields['r_addend'])
patch_value = symbol_address + addend - patch_offset
#print(f" *> {pr.type} {pr.symbol.name} {patch_value=} {symbol_address=} {pr.fields['r_addend']=} {bin(pr.fields['r_addend'])} {pr.bits=}, {function_offset=} {patch_offset=}")
elif pr.type == 'R_386_32':
@ -292,28 +325,52 @@ class stencil_database():
scale = 8
#print(f" *> {patch_value=} {symbol_address=} {pr.fields['r_addend']=}, {function_offset=}")
elif pr.type.endswith('_MOVW_ABS_NC'):
# R_ARM_MOVW_ABS_NC
elif pr.type == 'R_ARM_MOVW_ABS_NC':
# (S + A) & 0xFFFF
mask = 0xFFFF
patch_value = symbol_address + pr.fields['r_addend']
symbol_type = symbol_type + 0x04 # Absolut value
#print(f" *> {pr.type} {patch_value=} {symbol_address=}, {function_offset=}")
elif pr.type.endswith('_MOVT_ABS'):
# R_ARM_MOVT_ABS
elif pr.type =='R_ARM_MOVT_ABS':
# (S + A) & 0xFFFF0000
mask = 0xFFFF0000
patch_value = symbol_address + pr.fields['r_addend']
symbol_type = symbol_type + 0x04 # Absolut value
scale = 0x10000
#print(f" *> {pr.type} {patch_value=} {symbol_address=}, {function_offset=}, {pr.fields['r_addend']=}")
elif pr.type.endswith('_ABS32'):
# R_ARM_ABS32
# S + A (replaces full 32 bit)
assert not patch_offset % 4, 'R_ARM_ABS32 patched data like literals needs to be 4 Byte aligned'
# This might be caused by the call in entry_function_shell if not aligned
patch_value = symbol_address + pr.fields['r_addend']
symbol_type = symbol_type + 0x03 # Relative to data section
elif pr.type.endswith('_THM_JUMP24') or pr.type.endswith('_THM_CALL'):
# R_ARM_THM_JUMP24
# S + A - P
patch_value = symbol_address - patch_offset + pr.fields['r_addend']
symbol_type = symbol_type + 0x05 # PATCH_FUNC_ARM32_THM
#print(f" *> {pr.type} {patch_value=} {symbol_address=} {pr.fields['r_addend']=} {pr.bits=}, {function_offset=} {patch_offset=}")
elif pr.type == 'R_ARM_THM_MOVW_ABS_NC':
# (S + A) & 0xFFFF
mask = 0xFFFF
patch_value = symbol_address + pr.fields['r_addend']
symbol_type = symbol_type + 0x06 # PATCH_OBJECT_ARM32_ABS_THM
#print(f" *> {pr.type} {patch_value=} {symbol_address=}, {function_offset=}, {pr.fields['r_addend']=}")
elif pr.type == 'R_ARM_THM_MOVT_ABS':
# (S + A) & 0xFFFF0000
mask = 0xFFFF0000
patch_value = symbol_address + pr.fields['r_addend']
symbol_type = symbol_type + 0x06 # PATCH_OBJECT_ARM32_ABS_THM
scale = 0x10000
#print(f" *> {pr.type} {patch_value=} {symbol_address=}, {function_offset=}, {pr.fields['r_addend']=}")
else:
raise NotImplementedError(f"Relocation type {pr.type} in {relocation.pelfy_reloc.target_section.name} pointing to {relocation.pelfy_reloc.symbol.name} not implemented")
@ -334,7 +391,8 @@ class stencil_database():
func = self.elf.symbols[name]
start_stencil, end_stencil = get_stencil_position(func)
assert func.section
start_index = func.section['sh_offset'] + func['st_value'] + start_stencil
start_index = func.offset_in_file + start_stencil
lengths = end_stencil - start_stencil
self._stencil_cache[name] = (start_index, lengths)
@ -372,7 +430,7 @@ class stencil_database():
def get_symbol_offset(self, name: str) -> int:
"""Returns the offset of a specified symbol in the section."""
return self.elf.symbols[name].fields['st_value']
return self.elf.symbols[name].offset_in_section
def get_symbol_section_index(self, name: str) -> int:
"""Returns the section index for a specified symbol name."""

View File

@ -13,7 +13,15 @@ TRet = TypeVar("TRet", Iterable[int | float], int, float)
_jit_cache: dict[Any, tuple['Target', tuple[value[Any] | Iterable[value[Any]], ...], NumLike | Iterable[NumLike]]] = {}
def add_read_command(dw: binw.data_writer, variables: dict[Net, tuple[int, int, str]], net: Net) -> None:
def add_read_value_remote(dw: binw.data_writer, variables: dict[Net, tuple[int, int, str]], net: Net) -> None:
"""Adds a read memory to stdout command to the data_writer dw.
Arguments:
dw: data_writer to add the command to
variables: A dict for looking up variables by Net. The value is a tuple.
of relative address in memory, size in bytes and data type.
net: Variable specified by Net to read from memory and write to stdout.
"""
assert net in variables, f"Variable {net} not found in data writer variables"
addr, lengths, _ = variables[net]
dw.write_com(binw.Command.READ_DATA)
@ -188,5 +196,5 @@ class Target():
def read_value_remote(self, variable: value[Any]) -> None:
"""Reads the raw data of a value by the runner."""
dw = binw.data_writer(self.sdb.byteorder)
add_read_command(dw, self._values, variable.net)
add_read_value_remote(dw, self._values, variable.net)
assert coparun(self._context, dw.get_data()) > 0

View File

@ -3,14 +3,14 @@ Backend module for Copapy: contains internal data types
and give access to compiler internals and debugging tools.
"""
from ._target import add_read_command
from ._target import add_read_value_remote
from ._basic_types import Net, Op, Node, CPConstant, Store, stencil_db_from_package
from ._compiler import compile_to_dag, \
stable_toposort, get_const_nets, get_all_dag_edges, add_load_ops, get_all_dag_edges_between, \
add_store_ops, get_dag_stats
__all__ = [
"add_read_command",
"add_read_value_remote",
"Net",
"Op",
"Node",

View File

@ -1,3 +1,15 @@
/*
* file: coparun.c
* Description: This file alows to run copapy programs in the command line
* reading copapy data, code and patch instructions from a file and jump to the
* entry point to execute it or dump the patched code memory for debugging to a file.
*
* It's intended for testing and debugging purposes, to run copapy programs in a
* debugger or emulator like qemu.
*
* Usage: coparun <code_file> [memory_dump_file]
*/
#include <stdio.h>
#include <stdlib.h>
#include "runmem.h"

View File

@ -1,3 +1,10 @@
/*
* file: coparun_module.c
* Description: This file defines a Python C extension module that provides an
* interface to the core functions of the coparun runner, to run copapy programs
* directly local in Python.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "runmem.h"

View File

@ -1,3 +1,11 @@
/*
* file: mem_man.c
* Description: This file contains memory management functions for the coparun
* runner, including allocation and deallocation of executable and data memory.
* Depending of the target operating system or bare metal environment, it
* handles memory management accordingly.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

View File

@ -1,3 +1,10 @@
/*
* file: runmem.c
* Description: This file contain the core functions of the runner
* to receive data, code and patch instruction, does the patching
* and jumps to the entry point of the copapy program
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -50,6 +57,67 @@ void patch_arm32_abs(uint8_t *patch_addr, uint32_t imm16)
*((uint32_t *)patch_addr) = instr;
}
void patch_arm_thm_abs(uint8_t *patch_addr, uint32_t imm16)
{
// Thumb MOVW (T3) / MOVT (T1) encoding
uint16_t *instr16 = (uint16_t *)patch_addr;
uint16_t first_half = instr16[0];
uint16_t second_half = instr16[1];
// Extract fields from imm16
uint32_t imm4 = (imm16 >> 12) & 0xF;
uint32_t i = (imm16 >> 11) & 0x1;
uint32_t imm3 = (imm16 >> 8) & 0x7;
uint32_t imm8 = imm16 & 0xFF;
// Clear bits
first_half &= (uint16_t)(~(0x000F | (1 << 10)));
second_half &= (uint16_t)(~(0x00FF | (0x7 << 12)));
// Set new fields
first_half |= (uint16_t)((imm4 << 0) | (i << 10));
second_half |= (uint16_t)(imm8 | (imm3 << 12));
instr16[0] = first_half;
instr16[1] = second_half;
}
void patch_arm_thm_jump24(uint8_t *patch_addr, int32_t imm24)
{
// Read the 32-bit instruction (two halfwords)
uint16_t *instr16 = (uint16_t *)patch_addr;
uint16_t first_half = instr16[0];
uint16_t second_half = instr16[1];
// Thumb branch instructions always have LSB = 0 (halfword aligned)
// The imm24 offset in Thumb is shifted right by 1 when encoded
int32_t offset = imm24 >> 1;
// Split into S, J1, J2, imm10, imm11
uint32_t S = (offset >> 23) & 0x1;
uint32_t I1 = (offset >> 22) & 0x1;
uint32_t I2 = (offset >> 21) & 0x1;
uint32_t imm10 = (offset >> 11) & 0x3FF;
uint32_t imm11 = offset & 0x7FF;
// Re-encode J1 and J2
uint32_t J1 = (~(I1 ^ S)) & 0x1;
uint32_t J2 = (~(I2 ^ S)) & 0x1;
// Clear old imm fields
first_half &= 0xF800; // Keep upper 5 bits
second_half &= 0xD000; // Keep upper 5 bits
// Set new imm fields
first_half |= (uint16_t)((S << 10) | imm10);
second_half |= (uint16_t)((J1 << 13) | (J2 << 11) | imm11);
// Write back
instr16[0] = first_half;
instr16[1] = second_half;
}
void free_memory(runmem_t *context) {
deallocate_memory(context->executable_memory, context->executable_memory_len);
deallocate_memory(context->data_memory, context->data_memory_len);
@ -180,6 +248,26 @@ int parse_commands(runmem_t *context, uint8_t *bytes) {
patch_arm32_abs(context->executable_memory + offs, (uint32_t)((uintptr_t)(context->data_memory + value) & patch_mask) / (uint32_t)patch_scale);
break;
case PATCH_FUNC_ARM32_THM:
offs = *(uint32_t*)bytes; bytes += 4;
patch_mask = *(uint32_t*)bytes; bytes += 4;
patch_scale = *(int32_t*)bytes; bytes += 4;
value = *(int32_t*)bytes; bytes += 4;
LOG("PATCH_FUNC_ARM32_THM patch_offs=%i patch_mask=%#08x scale=%i value=%i\n",
offs, patch_mask, patch_scale, value);
patch_arm_thm_jump24(context->executable_memory + offs, value);
break;
case PATCH_OBJECT_ARM32_ABS_THM:
offs = *(uint32_t*)bytes; bytes += 4;
patch_mask = *(uint32_t*)bytes; bytes += 4;
patch_scale = *(int32_t*)bytes; bytes += 4;
value = *(int32_t*)bytes; bytes += 4;
LOG("PATCH_OBJECT_ARM32_ABS_THM patch_offs=%i patch_mask=%#08x scale=%i value=%i imm16=%#04x\n",
offs, patch_mask, patch_scale, value, (uint32_t)((uintptr_t)(context->data_memory + value) & patch_mask) / (uint32_t)patch_scale);
patch_arm_thm_abs(context->executable_memory + offs, (uint32_t)((uintptr_t)(context->data_memory + value) & patch_mask) / (uint32_t)patch_scale);
break;
case ENTRY_POINT:
rel_entr_point = *(uint32_t*)bytes; bytes += 4;
context->entr_point = (entry_point_t)(context->executable_memory + rel_entr_point);

View File

@ -1,3 +1,10 @@
/**
* @file runmem.h
* @brief Header file for runmem.c, which contains core functions of
* the runner to receive data, code and patch instructions, perform
* patching, and jump to the entry point of the copapy program.
*/
#ifndef RUNMEM_H
#define RUNMEM_H
@ -20,11 +27,13 @@
#define ALLOCATE_CODE 3
#define COPY_CODE 4
#define PATCH_FUNC 0x1000
#define PATCH_FUNC_ARM32_THM 0x1005
#define PATCH_OBJECT 0x2000
#define PATCH_OBJECT_HI21 0x2001
#define PATCH_OBJECT_ABS 0x2002
#define PATCH_OBJECT_REL 0x2003
#define PATCH_OBJECT_ARM32_ABS 0x2004
#define PATCH_OBJECT_ARM32_ABS_THM 0x2006
#define ENTRY_POINT 7
#define RUN_PROG 64
#define READ_DATA 65

View File

@ -57,8 +57,8 @@ def norm_indent(f: Callable[..., str]) -> Callable[..., str]:
def get_entry_function_shell() -> str:
return f"""
{entry_func_prefix}int entry_function_shell(){{
volatile char stack_place_holder[{stack_size}];
stack_place_holder[0] = 0;
//volatile char stack_place_holder[{stack_size}];
//stack_place_holder[0] = 0;
result_int(0);
return 1;
}}

View File

@ -1,9 +1,8 @@
from copapy import value
from copapy.backend import Store, compile_to_dag, add_read_command
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import copapy as cp
import subprocess
from copapy import _binwrite
import copapy.backend
import pytest
@ -24,7 +23,7 @@ def test_compile():
out = [Store(r) for r in ret_test]
il, variables = compile_to_dag(out, copapy.generic_sdb)
il, variables = compile_to_dag(out, cp.generic_sdb)
# run program command
il.write_com(_binwrite.Command.RUN_PROG)
@ -32,7 +31,7 @@ def test_compile():
for v in ret_test:
assert isinstance(v, value)
add_read_command(il, variables, v.net)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)

View File

@ -1,10 +1,9 @@
from copapy import NumLike
from copapy.backend import Store, compile_to_dag, add_read_command
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import copapy as cp
import subprocess
import struct
from copapy import _binwrite
import copapy.backend
import pytest
@ -60,14 +59,14 @@ def test_compile():
out = [Store(r) for r in ret]
il, variables = compile_to_dag(out, copapy.generic_sdb)
il, variables = compile_to_dag(out, cp.generic_sdb)
# run program command
il.write_com(_binwrite.Command.RUN_PROG)
for v in ret:
assert isinstance(v, cp.value)
add_read_command(il, variables, v.net)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)

View File

@ -1,5 +1,5 @@
from copapy import NumLike
from copapy.backend import Store, compile_to_dag, add_read_command
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import subprocess
from copapy import _binwrite
import copapy.backend as backend
@ -62,7 +62,7 @@ def test_compile():
for v in ret:
assert isinstance(v, cp.value)
add_read_command(il, variables, v.net)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)

View File

@ -1,32 +1,45 @@
from copapy import NumLike
from copapy.backend import Store, compile_to_dag, add_read_command
import subprocess
from copapy import _binwrite
import copapy.backend as backend
import copapy as cp
import os
import subprocess
import warnings
import pytest
if os.name == 'nt':
import copapy as cp
import copapy.backend as backend
from copapy import NumLike, _binwrite
from copapy.backend import Store, add_read_value_remote, compile_to_dag
if os.name == "nt":
# On Windows wsl and qemu-user is required:
# sudo apt install qemu-user
qemu_command = ['wsl', 'qemu-arm']
qemu_command = ["wsl", "qemu-arm"]
else:
qemu_command = ['qemu-arm']
qemu_command = ["qemu-arm"]
def run_command(command: list[str]) -> str:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8', check=False)
assert result.returncode != 11, f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
assert result.returncode == 0, f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf8",
check=False,
)
assert result.returncode != 11, (
f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
)
assert result.returncode == 0, (
f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
)
return result.stdout
def check_for_qemu() -> bool:
command = qemu_command + ['--version']
command = qemu_command + ["--version"]
try:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
result = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
)
except Exception:
return False
return result.returncode == 0
@ -54,36 +67,42 @@ def test_compile():
out = [Store(r) for r in ret]
sdb = backend.stencil_db_from_package('armv7')
sdb = backend.stencil_db_from_package("armv7")
il, variables = compile_to_dag(out, sdb)
# run program command
il.write_com(_binwrite.Command.RUN_PROG)
#il.write_com(_binwrite.Command.DUMP_CODE)
# il.write_com(_binwrite.Command.DUMP_CODE)
for v in ret:
assert isinstance(v, cp.value)
add_read_command(il, variables, v.net)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)
#print('* Data to runner:')
#il.print()
# print('* Data to runner:')
# il.print()
il.to_file('build/runner/test-armv7.copapy')
il.to_file("build/runner/test-armv7.copapy")
if not check_for_qemu():
warnings.warn("qemu-armv7 not found, aarch64 test skipped!", UserWarning)
elif not os.path.isfile('build/runner/coparun-armv7'):
warnings.warn("qemu-armv7 not found, test skipped!", UserWarning)
elif "wsl" in qemu_command:
warnings.warn("qemu-armv7 seams not work on wsl1, test skipped!", UserWarning)
elif not os.path.isfile("build/runner/coparun-armv7"):
warnings.warn("armv7 runner not found, aarch64 test skipped!", UserWarning)
else:
command = ['build/runner/coparun-armv7', 'build/runner/test-armv7.copapy', 'build/runner/test-armv7.copapy.bin']
command = [
"build/runner/coparun-armv7",
"build/runner/test-armv7.copapy",
"build/runner/test-armv7.copapy.bin",
]
result = run_command(qemu_command + command)
print('* Output from runner:\n--')
print("* Output from runner:\n--")
print(result)
print('--')
print("--")
assert 'Return value: 1' in result
assert "Return value: 1" in result
# Compare to x86_64 reference results
assert " size=4 data=24 00 00 00" in result
@ -92,5 +111,5 @@ def test_compile():
if __name__ == "__main__":
#test_example()
# test_example()
test_compile()

View File

@ -1,6 +1,6 @@
from copapy import value, NumLike
from copapy.backend import Store, compile_to_dag, add_read_command
import copapy
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import copapy as cp
import subprocess
from copapy import _binwrite
import pytest
@ -28,14 +28,14 @@ def test_compile():
out = [Store(r) for r in ret]
il, vars = compile_to_dag(out, copapy.generic_sdb)
il, vars = compile_to_dag(out, cp.generic_sdb)
# run program command
il.write_com(_binwrite.Command.RUN_PROG)
for v in ret:
assert isinstance(v, value)
add_read_command(il, vars, v.net)
add_read_value_remote(il, vars, v.net)
il.write_com(_binwrite.Command.END_COM)

View File

@ -1,9 +1,8 @@
from copapy import value
from copapy.backend import Store, compile_to_dag, add_read_command
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import copapy as cp
import subprocess
from copapy import _binwrite
import copapy.backend
import pytest
@ -23,14 +22,14 @@ def test_compile_sqrt():
out = [Store(r) for r in ret]
il, variables = compile_to_dag(out, copapy.generic_sdb)
il, variables = compile_to_dag(out, cp.generic_sdb)
# run program command
il.write_com(_binwrite.Command.RUN_PROG)
for v in ret:
assert isinstance(v, value)
add_read_command(il, variables, v.net)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)
@ -57,14 +56,14 @@ def test_compile_log():
out = [Store(r) for r in ret]
il, variables = compile_to_dag(out, copapy.generic_sdb)
il, variables = compile_to_dag(out, cp.generic_sdb)
# run program command
il.write_com(_binwrite.Command.RUN_PROG)
for v in ret:
assert isinstance(v, value)
add_read_command(il, variables, v.net)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)
@ -91,14 +90,14 @@ def test_compile_sin():
out = [Store(r) for r in ret]
il, variables = compile_to_dag(out, copapy.generic_sdb)
il, variables = compile_to_dag(out, cp.generic_sdb)
# run program command
il.write_com(_binwrite.Command.RUN_PROG)
for v in ret:
assert isinstance(v, copapy.value)
add_read_command(il, variables, v.net)
assert isinstance(v, cp.value)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)

View File

@ -1,5 +1,5 @@
from copapy import NumLike, iif, value
from copapy.backend import Store, compile_to_dag, add_read_command
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import subprocess
from copapy import _binwrite
import copapy.backend as backend
@ -109,7 +109,7 @@ def test_compile():
for v in ret_test:
assert isinstance(v, value)
add_read_command(dw, variables, v.net)
add_read_value_remote(dw, variables, v.net)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)

View File

@ -1,21 +1,22 @@
from copapy import NumLike, iif, value
from copapy.backend import Store, compile_to_dag, add_read_command
import subprocess
from copapy import _binwrite
import copapy.backend as backend
import os
import warnings
import re
import struct
import pytest
import copapy as cp
import subprocess
import warnings
if os.name == 'nt':
import pytest
import copapy as cp
import copapy.backend as backend
from copapy import NumLike, _binwrite, iif, value
from copapy.backend import Store, add_read_value_remote, compile_to_dag
if os.name == "nt":
# On Windows wsl and qemu-user is required:
# sudo apt install qemu-user
qemu_command = ['wsl', 'qemu-arm']
qemu_command = ["wsl", "qemu-arm"]
else:
qemu_command = ['qemu-arm']
qemu_command = ["qemu-arm"]
def parse_results(log_text: str) -> dict[int, bytes]:
@ -24,8 +25,8 @@ def parse_results(log_text: str) -> dict[int, bytes]:
var_dict: dict[int, bytes] = {}
for match in matches:
value_str: list[str] = match.group(3).strip().split(' ')
#print('--', value_str)
value_str: list[str] = match.group(3).strip().split(" ")
# print('--', value_str)
value = bytes(int(v, base=16) for v in value_str)
if len(value) <= 8:
var_dict[int(match.group(1))] = value
@ -34,26 +35,49 @@ def parse_results(log_text: str) -> dict[int, bytes]:
def run_command(command: list[str]) -> str:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8', check=False)
assert result.returncode != 11, f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
assert result.returncode == 0, f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf8",
check=False,
)
assert result.returncode != 11, (
f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
)
assert result.returncode == 0, (
f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
)
return result.stdout
def check_for_qemu() -> bool:
command = qemu_command + ['--version']
command = qemu_command + ["--version"]
try:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
result = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
)
except Exception:
return False
return result.returncode == 0
def function1(c1: NumLike) -> list[NumLike]:
return [c1 / 4, c1 / -4, c1 // 4, c1 // -4, (c1 * -1) // 4,
c1 * 4, c1 * -4,
c1 + 4, c1 - 4,
c1 > 2, c1 > 100, c1 < 4, c1 < 100]
return [
c1 / 4,
c1 / -4,
c1 // 4,
c1 // -4,
(c1 * -1) // 4,
c1 * 4,
c1 * -4,
c1 + 4,
c1 - 4,
c1 > 2,
c1 > 100,
c1 < 4,
c1 < 100,
]
def function2(c1: NumLike) -> list[NumLike]:
@ -77,11 +101,13 @@ def function6(c1: NumLike) -> list[NumLike]:
def iiftests(c1: NumLike) -> list[NumLike]:
return [iif(c1 > 5, 8, 9),
iif(c1 < 5, 8.5, 9.5),
iif(1 > 5, 3.3, 8.8) + c1,
iif(1 < 5, c1 * 3.3, 8.8),
iif(c1 < 5, c1 * 3.3, 8.8)]
return [
iif(c1 > 5, 8, 9),
iif(c1 < 5, 8.5, 9.5),
iif(1 > 5, 3.3, 8.8) + c1,
iif(1 < 5, c1 * 3.3, 8.8),
iif(c1 < 5, c1 * 3.3, 8.8),
]
@pytest.mark.runner
@ -90,59 +116,90 @@ def test_compile():
c_f = value(1.111)
c_b = value(True)
ret_test = function1(c_i) + function1(c_f) + function2(c_i) + function2(c_f) + function3(c_i) + function4(c_i) + function5(c_b) + [value(9) % 2] + iiftests(c_i) + iiftests(c_f) + [cp.asin(c_i/10)]
ret_ref = function1(9) + function1(1.111) + function2(9) + function2(1.111) + function3(9) + function4(9) + function5(True) + [9 % 2] + iiftests(9) + iiftests(1.111) + [cp.asin(9/10)]
ret_test = (
function1(c_i)
+ function1(c_f)
+ function2(c_i)
+ function2(c_f)
+ function3(c_i)
+ function4(c_i)
+ function5(c_b)
+ [value(9) % 2]
+ iiftests(c_i)
+ iiftests(c_f)
+ [cp.asin(c_i / 10)]
)
ret_ref = (
function1(9)
+ function1(1.111)
+ function2(9)
+ function2(1.111)
+ function3(9)
+ function4(9)
+ function5(True)
+ [9 % 2]
+ iiftests(9)
+ iiftests(1.111)
+ [cp.asin(9 / 10)]
)
#ret_test = (c_i * 100 // 5, c_f * 10 // 5)
#ret_ref = (9 * 100 // 5, 1.111 * 10 // 5)
# ret_test = (c_i * 100 // 5, c_f * 10 // 5)
# ret_ref = (9 * 100 // 5, 1.111 * 10 // 5)
out = [Store(r) for r in ret_test]
sdb = backend.stencil_db_from_package('armv6')
sdb = backend.stencil_db_from_package("armv6")
dw, variables = compile_to_dag(out, sdb)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
# dw.write_com(_binwrite.Command.READ_DATA)
# dw.write_int(0)
# dw.write_int(28)
# run program command
dw.write_com(_binwrite.Command.RUN_PROG)
#dw.write_com(_binwrite.Command.DUMP_CODE)
# dw.write_com(_binwrite.Command.DUMP_CODE)
for v in ret_test:
assert isinstance(v, value)
add_read_command(dw, variables, v.net)
add_read_value_remote(dw, variables, v.net)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
# dw.write_com(_binwrite.Command.READ_DATA)
# dw.write_int(0)
# dw.write_int(28)
dw.write_com(_binwrite.Command.END_COM)
#print('* Data to runner:')
#dw.print()
# print('* Data to runner:')
# dw.print()
dw.to_file('build/runner/test-armv6.copapy')
dw.to_file("build/runner/test-armv6.copapy")
if not check_for_qemu():
warnings.warn("qemu-armv6 not found, armv6 test skipped!", UserWarning)
return
if not os.path.isfile('build/runner/coparun-armv6'):
if "wsl" in qemu_command:
warnings.warn("qemu-armv6 seams not work on wsl1, test skipped!", UserWarning)
return
if not os.path.isfile("build/runner/coparun-armv6"):
warnings.warn("armv6 runner not found, armv6 test skipped!", UserWarning)
return
command = qemu_command + ['build/runner/coparun-armv6', 'build/runner/test-armv6.copapy'] + ['build/runner/test-armv6.copapy.bin']
#try:
command = (
qemu_command
+ ["build/runner/coparun-armv6", "build/runner/test-armv6.copapy"]
+ ["build/runner/test-armv6.copapy.bin"]
)
# try:
result = run_command(command)
#except FileNotFoundError:
# except FileNotFoundError:
# warnings.warn(f"Test skipped, executable not found.", UserWarning)
# return
#print('* Output from runner:\n--')
#print(result)
#print('--')
# print('* Output from runner:\n--')
# print(result)
# print('--')
assert 'Return value: 1' in result
assert "Return value: 1" in result
result_data = parse_results(result)
@ -150,22 +207,25 @@ def test_compile():
assert isinstance(test, value)
address = variables[test.net][0]
data = result_data[address]
if test.dtype == 'int':
if test.dtype == "int":
val = int.from_bytes(data, sdb.byteorder, signed=True)
elif test.dtype == 'bool':
elif test.dtype == "bool":
val = bool.from_bytes(data, sdb.byteorder)
elif test.dtype == 'float':
en = {'little': '<', 'big': '>'}[sdb.byteorder]
val = struct.unpack(en + 'f', data)[0]
elif test.dtype == "float":
en = {"little": "<", "big": ">"}[sdb.byteorder]
val = struct.unpack(en + "f", data)[0]
assert isinstance(val, float)
else:
raise Exception(f"Unknown type: {test.dtype}")
print('+', val, ref, test.dtype, f" addr={address}")
print("+", val, ref, test.dtype, f" addr={address}")
for t in (int, float, bool):
assert isinstance(val, t) == isinstance(ref, t), f"Result type does not match for {val} and {ref}"
assert val == pytest.approx(ref, 1e-5), f"Result does not match: {val} and reference: {ref}" # pyright: ignore[reportUnknownMemberType]
assert isinstance(val, t) == isinstance(ref, t), (
f"Result type does not match for {val} and {ref}"
)
assert val == pytest.approx(ref, 1e-5), (
f"Result does not match: {val} and reference: {ref}"
) # pyright: ignore[reportUnknownMemberType]
if __name__ == "__main__":
#test_compile()
test_slow_31bit_int_list_hash()
test_compile()

View File

@ -1,21 +1,22 @@
from copapy import NumLike, iif, value
from copapy.backend import Store, compile_to_dag, add_read_command
import subprocess
from copapy import _binwrite
import copapy.backend as backend
import os
import warnings
import re
import struct
import pytest
import copapy as cp
import subprocess
import warnings
if os.name == 'nt':
import pytest
import copapy as cp
import copapy.backend as backend
from copapy import NumLike, _binwrite, iif, value
from copapy.backend import Store, add_read_value_remote, compile_to_dag
if os.name == "nt":
# On Windows wsl and qemu-user is required:
# sudo apt install qemu-user
qemu_command = ['wsl', 'qemu-arm']
qemu_command = ["wsl", "qemu-arm"]
else:
qemu_command = ['qemu-arm']
qemu_command = ["qemu-arm"]
def parse_results(log_text: str) -> dict[int, bytes]:
@ -24,8 +25,8 @@ def parse_results(log_text: str) -> dict[int, bytes]:
var_dict: dict[int, bytes] = {}
for match in matches:
value_str: list[str] = match.group(3).strip().split(' ')
#print('--', value_str)
value_str: list[str] = match.group(3).strip().split(" ")
# print('--', value_str)
value = bytes(int(v, base=16) for v in value_str)
if len(value) <= 8:
var_dict[int(match.group(1))] = value
@ -34,26 +35,49 @@ def parse_results(log_text: str) -> dict[int, bytes]:
def run_command(command: list[str]) -> str:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8', check=False)
assert result.returncode != 11, f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
assert result.returncode == 0, f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf8",
check=False,
)
assert result.returncode != 11, (
f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
)
assert result.returncode == 0, (
f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
)
return result.stdout
def check_for_qemu() -> bool:
command = qemu_command + ['--version']
command = qemu_command + ["--version"]
try:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
result = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
)
except Exception:
return False
return result.returncode == 0
def function1(c1: NumLike) -> list[NumLike]:
return [c1 / 4, c1 / -4, c1 // 4, c1 // -4, (c1 * -1) // 4,
c1 * 4, c1 * -4,
c1 + 4, c1 - 4,
c1 > 2, c1 > 100, c1 < 4, c1 < 100]
return [
c1 / 4,
c1 / -4,
c1 // 4,
c1 // -4,
(c1 * -1) // 4,
c1 * 4,
c1 * -4,
c1 + 4,
c1 - 4,
c1 > 2,
c1 > 100,
c1 < 4,
c1 < 100,
]
def function2(c1: NumLike) -> list[NumLike]:
@ -77,11 +101,13 @@ def function6(c1: NumLike) -> list[NumLike]:
def iiftests(c1: NumLike) -> list[NumLike]:
return [iif(c1 > 5, 8, 9),
iif(c1 < 5, 8.5, 9.5),
iif(1 > 5, 3.3, 8.8) + c1,
iif(1 < 5, c1 * 3.3, 8.8),
iif(c1 < 5, c1 * 3.3, 8.8)]
return [
iif(c1 > 5, 8, 9),
iif(c1 < 5, 8.5, 9.5),
iif(1 > 5, 3.3, 8.8) + c1,
iif(1 < 5, c1 * 3.3, 8.8),
iif(c1 < 5, c1 * 3.3, 8.8),
]
@pytest.mark.runner
@ -90,59 +116,90 @@ def test_compile():
c_f = value(1.111)
c_b = value(True)
ret_test = function1(c_i) + function1(c_f) + function2(c_i) + function2(c_f) + function3(c_i) + function4(c_i) + function5(c_b) + [value(9) % 2] + iiftests(c_i) + iiftests(c_f) + [cp.asin(c_i/10)]
ret_ref = function1(9) + function1(1.111) + function2(9) + function2(1.111) + function3(9) + function4(9) + function5(True) + [9 % 2] + iiftests(9) + iiftests(1.111) + [cp.asin(9/10)]
ret_test = (
function1(c_i)
+ function1(c_f)
+ function2(c_i)
+ function2(c_f)
+ function3(c_i)
+ function4(c_i)
+ function5(c_b)
+ [value(9) % 2]
+ iiftests(c_i)
+ iiftests(c_f)
+ [cp.asin(c_i / 10)]
)
ret_ref = (
function1(9)
+ function1(1.111)
+ function2(9)
+ function2(1.111)
+ function3(9)
+ function4(9)
+ function5(True)
+ [9 % 2]
+ iiftests(9)
+ iiftests(1.111)
+ [cp.asin(9 / 10)]
)
#ret_test = (c_i * 100 // 5, c_f * 10 // 5)
#ret_ref = (9 * 100 // 5, 1.111 * 10 // 5)
# ret_test = (c_i * 100 // 5, c_f * 10 // 5)
# ret_ref = (9 * 100 // 5, 1.111 * 10 // 5)
out = [Store(r) for r in ret_test]
sdb = backend.stencil_db_from_package('armv7')
sdb = backend.stencil_db_from_package("armv7")
dw, variables = compile_to_dag(out, sdb)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
# dw.write_com(_binwrite.Command.READ_DATA)
# dw.write_int(0)
# dw.write_int(28)
# run program command
dw.write_com(_binwrite.Command.RUN_PROG)
#dw.write_com(_binwrite.Command.DUMP_CODE)
# dw.write_com(_binwrite.Command.DUMP_CODE)
for v in ret_test:
assert isinstance(v, value)
add_read_command(dw, variables, v.net)
add_read_value_remote(dw, variables, v.net)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
# dw.write_com(_binwrite.Command.READ_DATA)
# dw.write_int(0)
# dw.write_int(28)
dw.write_com(_binwrite.Command.END_COM)
#print('* Data to runner:')
#dw.print()
# print('* Data to runner:')
# dw.print()
dw.to_file('build/runner/test-armv7.copapy')
dw.to_file("build/runner/test-armv7.copapy")
if not check_for_qemu():
warnings.warn("qemu-armv7 not found, armv7 test skipped!", UserWarning)
return
if not os.path.isfile('build/runner/coparun-armv7'):
if "wsl" in qemu_command:
warnings.warn("qemu-armv7 seams not work on wsl1, test skipped!", UserWarning)
return
if not os.path.isfile("build/runner/coparun-armv7"):
warnings.warn("armv7 runner not found, armv7 test skipped!", UserWarning)
return
command = qemu_command + ['build/runner/coparun-armv7', 'build/runner/test-armv7.copapy'] + ['build/runner/test-armv7.copapy.bin']
#try:
command = (
qemu_command
+ ["build/runner/coparun-armv7", "build/runner/test-armv7.copapy"]
+ ["build/runner/test-armv7.copapy.bin"]
)
# try:
result = run_command(command)
#except FileNotFoundError:
# except FileNotFoundError:
# warnings.warn(f"Test skipped, executable not found.", UserWarning)
# return
print('* Output from runner:\n--')
print("* Output from runner:\n--")
print(result)
print('--')
print("--")
assert 'Return value: 1' in result
assert "Return value: 1" in result
result_data = parse_results(result)
@ -150,22 +207,25 @@ def test_compile():
assert isinstance(test, value)
address = variables[test.net][0]
data = result_data[address]
if test.dtype == 'int':
if test.dtype == "int":
val = int.from_bytes(data, sdb.byteorder, signed=True)
elif test.dtype == 'bool':
elif test.dtype == "bool":
val = bool.from_bytes(data, sdb.byteorder)
elif test.dtype == 'float':
en = {'little': '<', 'big': '>'}[sdb.byteorder]
val = struct.unpack(en + 'f', data)[0]
elif test.dtype == "float":
en = {"little": "<", "big": ">"}[sdb.byteorder]
val = struct.unpack(en + "f", data)[0]
assert isinstance(val, float)
else:
raise Exception(f"Unknown type: {test.dtype}")
print('+', val, ref, test.dtype, f" addr={address}")
print("+", val, ref, test.dtype, f" addr={address}")
for t in (int, float, bool):
assert isinstance(val, t) == isinstance(ref, t), f"Result type does not match for {val} and {ref}"
assert val == pytest.approx(ref, 1e-5), f"Result does not match: {val} and reference: {ref}" # pyright: ignore[reportUnknownMemberType]
assert isinstance(val, t) == isinstance(ref, t), (
f"Result type does not match for {val} and {ref}"
)
assert val == pytest.approx(ref, 1e-5), (
f"Result does not match: {val} and reference: {ref}"
) # pyright: ignore[reportUnknownMemberType]
if __name__ == "__main__":
#test_example()
test_slow_31bit_int_list_hash()
test_compile()

View File

@ -0,0 +1,174 @@
from copapy import NumLike, iif, value
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import subprocess
from copapy import _binwrite
import copapy.backend as backend
import os
import warnings
import re
import struct
import pytest
import copapy as cp
if os.name == 'nt':
# On Windows wsl and qemu-user is required:
# sudo apt install qemu-user
qemu_command = ['wsl', 'qemu-arm']
else:
qemu_command = ['qemu-arm']
def parse_results(log_text: str) -> dict[int, bytes]:
regex = r"^READ_DATA offs=(\d*) size=(\d*) data=(.*)$"
matches = re.finditer(regex, log_text, re.MULTILINE)
var_dict: dict[int, bytes] = {}
for match in matches:
value_str: list[str] = match.group(3).strip().split(' ')
#print('--', value_str)
value = bytes(int(v, base=16) for v in value_str)
if len(value) <= 8:
var_dict[int(match.group(1))] = value
return var_dict
def run_command(command: list[str]) -> str:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8', check=False)
assert result.returncode != 11, f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
assert result.returncode == 0, f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
return result.stdout
def check_for_qemu() -> bool:
command = qemu_command + ['--version']
try:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
except Exception:
return False
return result.returncode == 0
def function1(c1: NumLike) -> list[NumLike]:
return [c1 / 4, c1 / -4, c1 // 4, c1 // -4, (c1 * -1) // 4,
c1 * 4, c1 * -4,
c1 + 4, c1 - 4,
c1 > 2, c1 > 100, c1 < 4, c1 < 100]
def function1ex(c1: NumLike) -> list[NumLike]:
return [c1 // 4]
def function2(c1: NumLike) -> list[NumLike]:
return [c1 * 4.44, c1 * -4.44]
def function3(c1: NumLike) -> list[NumLike]:
return [c1 / 4]
def function4(c1: NumLike) -> list[NumLike]:
return [c1 == 9, c1 == 4, c1 != 9, c1 != 4]
def function5(c1: NumLike) -> list[NumLike]:
return [c1 == True, c1 == False, c1 != True, c1 != False, c1 / 2, c1 + 2]
def function6(c1: NumLike) -> list[NumLike]:
return [c1 == True]
def iiftests(c1: NumLike) -> list[NumLike]:
return [iif(c1 > 5, 8, 9),
iif(c1 < 5, 8.5, 9.5),
iif(1 > 5, 3.3, 8.8) + c1,
iif(1 < 5, c1 * 3.3, 8.8),
iif(c1 < 5, c1 * 3.3, 8.8)]
@pytest.mark.runner
def test_compile():
c_i = value(9)
c_f = value(1.111)
c_b = value(True)
ret_test = function1(c_i) + function1(c_f) + function2(c_i) + function2(c_f) + function3(c_i) + function4(c_i) + function5(c_b) + [value(9) % 2] + iiftests(c_i) + iiftests(c_f) + [cp.asin(c_i/10)]
ret_ref = function1(9) + function1(1.111) + function2(9) + function2(1.111) + function3(9) + function4(9) + function5(True) + [9 % 2] + iiftests(9) + iiftests(1.111) + [cp.asin(9/10)]
out = [Store(r) for r in ret_test]
sdb = backend.stencil_db_from_package('armv7mthumb')
dw, variables = compile_to_dag(out, sdb)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
du = dw.copy()
dw.write_com(_binwrite.Command.RUN_PROG)
du.write_com(_binwrite.Command.DUMP_CODE)
for v in ret_test:
assert isinstance(v, value)
add_read_value_remote(dw, variables, v.net)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
dw.write_com(_binwrite.Command.END_COM)
du.write_com(_binwrite.Command.END_COM)
#print('* Data to runner:')
#dw.print()
dw.to_file('build/runner/test-armv7mthumb.copapy')
du.to_file('build/runner/test-armv7mthumb-dump.copapy')
if not check_for_qemu():
warnings.warn("qemu-armv7 not found, armv7 test skipped!", UserWarning)
return
if not os.path.isfile('build/runner/coparun-armv7'):
warnings.warn("armv7 runner not found, armv7 test skipped!", UserWarning)
return
print('----- Dump code...')
command = qemu_command + ['build/runner/coparun-armv7', 'build/runner/test-armv7mthumb-dump.copapy', 'build/runner/test.copapy-armv7mthumb.bin']
result = run_command(command)
print('----- Run code...')
command = qemu_command + ['build/runner/coparun-armv7', 'build/runner/test-armv7mthumb.copapy']
result = run_command(command)
print('* Output from runner:\n--')
print(result)
print('--')
assert 'Return value: 1' in result
result_data = parse_results(result)
for test, ref in zip(ret_test, ret_ref):
assert isinstance(test, value)
address = variables[test.net][0]
data = result_data[address]
if test.dtype == 'int':
val = int.from_bytes(data, sdb.byteorder, signed=True)
elif test.dtype == 'bool':
val = bool.from_bytes(data, sdb.byteorder)
elif test.dtype == 'float':
en = {'little': '<', 'big': '>'}[sdb.byteorder]
val = struct.unpack(en + 'f', data)[0]
assert isinstance(val, float)
else:
raise Exception(f"Unknown type: {test.dtype}")
print('+', val, ref, test.dtype, f" addr={address}")
for t in (int, float, bool):
assert isinstance(val, t) == isinstance(ref, t), f"Result type does not match for {val} and {ref}"
assert val == pytest.approx(ref, 1e-5), f"Result does not match: {val} and reference: {ref}" # pyright: ignore[reportUnknownMemberType]
if __name__ == "__main__":
test_compile()

View File

@ -0,0 +1,184 @@
from copapy import NumLike, iif, value
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import subprocess
from copapy import _binwrite
import copapy.backend as backend
import os
import warnings
import re
import struct
import pytest
import copapy as cp
if os.name == 'nt':
# On Windows wsl and qemu-user is required:
# sudo apt install qemu-user
qemu_command = ['wsl', 'qemu-arm']
else:
qemu_command = ['qemu-arm']
def parse_results(log_text: str) -> dict[int, bytes]:
regex = r"^READ_DATA offs=(\d*) size=(\d*) data=(.*)$"
matches = re.finditer(regex, log_text, re.MULTILINE)
var_dict: dict[int, bytes] = {}
for match in matches:
value_str: list[str] = match.group(3).strip().split(' ')
#print('--', value_str)
value = bytes(int(v, base=16) for v in value_str)
if len(value) <= 8:
var_dict[int(match.group(1))] = value
return var_dict
def run_command(command: list[str]) -> str:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8', check=False)
assert result.returncode != 11, f"SIGSEGV (segmentation fault)\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
assert result.returncode == 0, f"\n -Error occurred: {result.stderr}\n -Output: {result.stdout}"
return result.stdout
def check_for_qemu() -> bool:
command = qemu_command + ['--version']
try:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
except Exception:
return False
return result.returncode == 0
def function1(c1: NumLike) -> list[NumLike]:
return [c1 / 4, c1 / -4, c1 // 4, c1 // -4, (c1 * -1) // 4,
c1 * 4, c1 * -4,
c1 + 4, c1 - 4,
c1 > 2, c1 > 100, c1 < 4, c1 < 100]
def function1ex(c1: NumLike) -> list[NumLike]:
return [c1 // 4]
def function2(c1: NumLike) -> list[NumLike]:
return [c1 * 4.44, c1 * -4.44]
def function3(c1: NumLike) -> list[NumLike]:
return [c1 / 4]
def function4(c1: NumLike) -> list[NumLike]:
return [c1 == 9, c1 == 4, c1 != 9, c1 != 4]
def function5(c1: NumLike) -> list[NumLike]:
return [c1 == True, c1 == False, c1 != True, c1 != False, c1 / 2, c1 + 2]
def function6(c1: NumLike) -> list[NumLike]:
return [c1 == True]
def iiftests(c1: NumLike) -> list[NumLike]:
return [iif(c1 > 5, 8, 9),
iif(c1 < 5, 8.5, 9.5),
iif(1 > 5, 3.3, 8.8) + c1,
iif(1 < 5, c1 * 3.3, 8.8),
iif(c1 < 5, c1 * 3.3, 8.8)]
@pytest.mark.runner
def test_compile():
c_i = value(9)
c_f = value(1.111)
c_b = value(True)
ret_test = function1(c_i) + function1(c_f) + function2(c_i) + function2(c_f) + function3(c_i) + function4(c_i) + function5(c_b) + [value(9) % 2] + iiftests(c_i) + iiftests(c_f) + [cp.asin(c_i/10)]
ret_ref = function1(9) + function1(1.111) + function2(9) + function2(1.111) + function3(9) + function4(9) + function5(True) + [9 % 2] + iiftests(9) + iiftests(1.111) + [cp.asin(9/10)]
out = [Store(r) for r in ret_test]
sdb = backend.stencil_db_from_package('armv7thumb')
dw, variables = compile_to_dag(out, sdb)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
du = dw.copy()
dw.write_com(_binwrite.Command.RUN_PROG)
du.write_com(_binwrite.Command.DUMP_CODE)
for v in ret_test:
assert isinstance(v, value)
add_read_value_remote(dw, variables, v.net)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
#dw.write_int(28)
dw.write_com(_binwrite.Command.END_COM)
du.write_com(_binwrite.Command.END_COM)
#print('* Data to runner:')
#dw.print()
dw.to_file('build/runner/test-armv7thumb.copapy')
du.to_file('build/runner/test-armv7thumb-dump.copapy')
if not check_for_qemu():
warnings.warn("qemu-armv7 not found, armv7 test skipped!", UserWarning)
return
if not os.path.isfile('build/runner/coparun-armv7'):
warnings.warn("armv7 runner not found, armv7 test skipped!", UserWarning)
return
print('----- Dump code...')
command = qemu_command + ['build/runner/coparun-armv7', 'build/runner/test-armv7thumb-dump.copapy', 'build/runner/test.copapy-armv7thumb.bin']
result = run_command(command)
print('----- Run code...')
command = qemu_command + ['build/runner/coparun-armv7', 'build/runner/test-armv7thumb.copapy']
result = run_command(command)
print('* Output from runner:\n--')
print(result)
print('--')
assert 'Return value: 1' in result
result_data = parse_results(result)
for test, ref in zip(ret_test, ret_ref):
assert isinstance(test, value)
address = variables[test.net][0]
data = result_data[address]
if test.dtype == 'int':
val = int.from_bytes(data, sdb.byteorder, signed=True)
elif test.dtype == 'bool':
val = bool.from_bytes(data, sdb.byteorder)
elif test.dtype == 'float':
en = {'little': '<', 'big': '>'}[sdb.byteorder]
val = struct.unpack(en + 'f', data)[0]
assert isinstance(val, float)
else:
raise Exception(f"Unknown type: {test.dtype}")
print('+', val, ref, test.dtype, f" addr={address}")
for t in (int, float, bool):
assert isinstance(val, t) == isinstance(ref, t), f"Result type does not match for {val} and {ref}"
assert val == pytest.approx(ref, 1e-5), f"Result does not match: {val} and reference: {ref}" # pyright: ignore[reportUnknownMemberType]
if __name__ == "__main__":
test_compile()
"""
qemu-arm -d in_asm,exec,cpu_reset -D qemu.log build/runner/coparun-armv7thumb build/runner/test-armv7thumb.copapy build/runner/test.copapy-armv7thumb.bin
qemu-arm -d in_asm,exec -D qemu_trace.log \
-global driver=pl011.audiomaddr,property=addr,value=0xff7ec000 \
-global driver=pl011.audiomaddr,property=size,value=0x100000 \
your_binary
"""

View File

@ -1,5 +1,5 @@
from copapy import NumLike, iif, value
from copapy.backend import Store, compile_to_dag, add_read_command
from copapy.backend import Store, compile_to_dag, add_read_value_remote
import subprocess
from copapy import _binwrite
import copapy.backend as backend
@ -122,7 +122,7 @@ def test_compile():
for v in ret_test:
assert isinstance(v, value)
add_read_command(dw, variables, v.net)
add_read_value_remote(dw, variables, v.net)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)
@ -196,7 +196,7 @@ def test_vector_compile():
for v in ret:
assert isinstance(v, cp.value)
add_read_command(il, variables, v.net)
add_read_value_remote(il, variables, v.net)
il.write_com(_binwrite.Command.END_COM)
@ -258,7 +258,7 @@ def test_sinus():
for v in ret_test:
assert isinstance(v, value)
add_read_command(dw, variables, v.net)
add_read_value_remote(dw, variables, v.net)
#dw.write_com(_binwrite.Command.READ_DATA)
#dw.write_int(0)

View File

@ -19,11 +19,18 @@ def test_start_end_function():
if symbol.relocations and symbol.relocations[-1].symbol.info == 'STT_NOTYPE':
print('-', sym_name, get_stencil_position(symbol), len(symbol.data))
if symbol.section and symbol.section.name == '.text':
print('SKIP', sym_name, '(Aux function, not a stencil)')
continue
start, end = get_stencil_position(symbol)
if symbol.section:
function_size = symbol.section.fields['sh_size'] # len(symbol.data) excludes nop after the function
assert start >= 0 and end >= start and end <= len(symbol.data)
print('-', sym_name, get_stencil_position(symbol), function_size)
start, end = get_stencil_position(symbol)
assert (start >= 0 and end >= start and end <= function_size)
def test_aux_functions():

View File

@ -1,13 +1,13 @@
#!/bin/bash
set -eux
set -eu
ARCH=${1:-x86_64}
case "$ARCH" in
(x86_64|arm-v6|arm-v7|all)
(x86_64|arm64|arm-v6|arm-v7|arm-v7-thumb|arm-v7m-thumb|all)
;;
(*)
echo "Usage: $0 [x86_64|arm-v6|arm-v7|all]"
echo "Usage: $0 [x86_64|arm64|arm-v6|arm-v7|arm-v6-thumb|arm-v7m-thumb|all]"
exit 1
;;
esac
@ -42,13 +42,44 @@ if [[ "$ARCH" == "x86_64" || "$ARCH" == "all" ]]; then
-o build/runner/coparun
fi
#######################################
# ARM 64
#######################################
if [[ "$ARCH" == "arm64" || "$ARCH" == "all" ]]; then
echo "--------------arm64----------------"
LIBGCC=$(aarch64-linux-gnu-gcc -print-libgcc-file-name)
aarch64-linux-gnu-gcc -fno-pic -ffunction-sections \
-c $SRC -O3 -o build/stencils/stencils.o
aarch64-linux-gnu-ld -r \
build/stencils/stencils.o \
build/musl/musl_objects_arm64.o \
$LIBGCC \
-o $DEST/stencils_arm64_O3.o
aarch64-linux-gnu-objdump -d -x \
$DEST/stencils_arm64_O3.o \
> build/stencils/stencils_arm64_O3.asm
aarch64-linux-gnu-gcc \
-Wall -Wextra -Wconversion -Wsign-conversion -static \
-Wshadow -Wstrict-overflow -O3 \
-DENABLE_LOGGING \
src/coparun/runmem.c \
src/coparun/coparun.c \
src/coparun/mem_man.c \
-o build/runner/coparun-arm64
fi
#######################################
# ARM v6
#######################################
if [[ "$ARCH" == "arm-v6" || "$ARCH" == "all" ]]; then
echo "--------------arm-v6 32 bit----------------"
LIBGCC=$(arm-none-eabi-gcc -print-libgcc-file-name)
LIBGCC=$(arm-none-eabi-gcc -march=armv6 -mfpu=vfp -mfloat-abi=hard -marm -print-libgcc-file-name)
arm-none-eabi-gcc -fno-pic -ffunction-sections \
-march=armv6 -mfpu=vfp -mfloat-abi=hard -marm \
@ -81,7 +112,7 @@ fi
if [[ "$ARCH" == "arm-v7" || "$ARCH" == "all" ]]; then
echo "--------------arm-v7 32 bit----------------"
LIBGCC=$(arm-none-eabi-gcc -print-libgcc-file-name)
LIBGCC=$(arm-none-eabi-gcc -march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm -print-libgcc-file-name)
arm-none-eabi-gcc -fno-pic -ffunction-sections \
-march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm \
@ -97,6 +128,7 @@ if [[ "$ARCH" == "arm-v7" || "$ARCH" == "all" ]]; then
$DEST/stencils_armv7_O3.o \
> build/stencils/stencils_armv7_O3.asm
# The same runner for all ARM7
arm-linux-gnueabihf-gcc \
-march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm -static \
-Wall -Wextra -Wconversion -Wsign-conversion \
@ -107,3 +139,71 @@ if [[ "$ARCH" == "arm-v7" || "$ARCH" == "all" ]]; then
src/coparun/mem_man.c \
-o build/runner/coparun-armv7
fi
#######################################
# ARM v7 thumb Cortex-A
#######################################
if [[ "$ARCH" == "arm-v7-thumb" || "$ARCH" == "all" ]]; then
echo "--------------arm-v7a-thumb 32 bit----------------"
LIBGCC=$(arm-none-eabi-gcc -march=armv7 -mfpu=vfp3 -mthumb -print-libgcc-file-name)
arm-none-eabi-gcc -fno-pic -ffunction-sections \
-march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -mthumb \
-c $SRC -O3 -o build/stencils/stencils.o
arm-none-eabi-ld -r \
build/stencils/stencils.o \
build/musl/musl_objects_armv7thumb.o \
$LIBGCC \
-o $DEST/stencils_armv7thumb_O3.o
arm-none-eabi-objdump -d -x \
$DEST/stencils_armv7thumb_O3.o \
> build/stencils/stencils_armv7thumb_O3.asm
# The same runner for all ARM7
arm-linux-gnueabihf-gcc \
-march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -static \
-Wall -Wextra -Wconversion -Wsign-conversion \
-Wshadow -Wstrict-overflow -O3 \
-DENABLE_LOGGING \
src/coparun/runmem.c \
src/coparun/coparun.c \
src/coparun/mem_man.c \
-o build/runner/coparun-armv7thumb
fi
#######################################
# ARM v7 thumb Cortex-M
#######################################
if [[ "$ARCH" == "arm-v7m-thumb" || "$ARCH" == "all" ]]; then
echo "--------------arm-v7m-thumb 32 bit----------------"
LIBGCC=$(arm-none-eabi-gcc -march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -print-libgcc-file-name)
arm-none-eabi-gcc -fno-pic -ffunction-sections \
-march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb \
-c $SRC -O3 -o build/stencils/stencils.o
arm-none-eabi-ld -r \
build/stencils/stencils.o \
build/musl/musl_objects_armv7mthumb.o \
$LIBGCC \
-o $DEST/stencils_armv7mthumb_O3.o
arm-none-eabi-objdump -d -x \
$DEST/stencils_armv7mthumb_O3.o \
> build/stencils/stencils_armv7mthumb_O3.asm
# The same runner for all ARM7
arm-linux-gnueabihf-gcc \
-march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -static \
-Wall -Wextra -Wconversion -Wsign-conversion \
-Wshadow -Wstrict-overflow -O3 \
-DENABLE_LOGGING \
src/coparun/runmem.c \
src/coparun/coparun.c \
src/coparun/mem_man.c \
-o build/runner/coparun-armv7thumb
fi

View File

@ -10,7 +10,6 @@ cparch=$(python3 -c "import copapy; print(copapy._stencils.detect_process_arch()
# Disassemble stencil object file
objdump -d -x src/copapy/obj/stencils_${cparch}_O3.o > build/runner/stencils.asm
# Create example code disassembly
python3 tools/make_example.py
build/runner/coparun build/runner/test.copapy build/runner/test.copapy.bin
@ -28,6 +27,10 @@ fi
echo "Archtitecture: '$cparch'"
objdump -D -b binary -m $cparch --adjust-vma=0x10000 build/runner/test.copapy.bin > build/runner/example.asm
if [[ "$cparch" == *"thumb"* ]]; then
objdump -D -b binary -marm -M force-thumb --adjust-vma=0x10000 build/runner/test.copapy.bin > build/runner/example.asm
else
objdump -D -b binary -m $cparch --adjust-vma=0x10000 build/runner/test.copapy.bin > build/runner/example.asm
fi
rm build/runner/test.copapy.bin

View File

@ -26,8 +26,11 @@ sh ../packobjs.sh arm-none-eabi-gcc arm-none-eabi-ld /object_files/musl_objects_
# Armv7
sh ../packobjs.sh arm-none-eabi-gcc arm-none-eabi-ld /object_files/musl_objects_armv7.o "-march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm"
# Armv7 Thumb for Cortex-A
sh ../packobjs.sh arm-none-eabi-gcc arm-none-eabi-ld /object_files/musl_objects_armv7thumb.o "-march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -mthumb"
# Armv7 Thumb for Cortex-M3..7
sh ../packobjs.sh arm-none-eabi-gcc arm-none-eabi-ld /object_files/musl_objects_armv7thumb.o "-march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb"
sh ../packobjs.sh arm-none-eabi-gcc arm-none-eabi-ld /object_files/musl_objects_armv7mthumb.o "-march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb"
#sh ../packobjs.sh mips mips-linux-gnu-gcc-13 mips-linux-gnu-ld

View File

@ -25,14 +25,16 @@ ar x ../../musl/lib/libc.a sinf.o cosf.o tanf.o asinf.o acosf.o atanf.o atan2f.o
ar x ../../musl/lib/libc.a sqrtf.o logf.o expf.o sqrt.o
ar x ../../musl/lib/libc.a logf_data.o __tandf.o __cosdf.o __sindf.o
ar x ../../musl/lib/libc.a fabsf.o scalbn.o floor.o floorf.o exp2f_data.o powf.o powf_data.o
ar x ../../musl/lib/libc.a __rem_pio2f.o __math_invalidf.o __stack_chk_fail.o __math_divzerof.o __math_oflowf.o __rem_pio2_large.o __math_uflowf.o __math_xflowf.o
ar x ../../musl/lib/libc.a __rem_pio2f.o __math_invalid.o __math_invalidf.o __stack_chk_fail.o
ar x ../../musl/lib/libc.a __math_divzerof.o __math_oflowf.o __rem_pio2_large.o __math_uflowf.o __math_xflowf.o sqrt_data.o
# Check out .lo (PIC)
ar x ../../musl/lib/libc.a sinf.lo cosf.lo tanf.lo asinf.lo acosf.lo atanf.lo atan2f.lo
ar x ../../musl/lib/libc.a sqrtf.lo logf.lo expf.lo sqrt.lo
ar x ../../musl/lib/libc.a logf_data.lo __tandf.lo __cosdf.lo __sindf.lo
ar x ../../musl/lib/libc.a fabsf.lo scalbn.lo floor.lo floorf.o exp2f_data.lo powf.lo powf_data.lo
ar x ../../musl/lib/libc.a __rem_pio2f.lo __math_invalidf.lo __stack_chk_fail.lo __math_divzerof.lo __math_oflowf.lo __rem_pio2_large.lo __math_uflowf.lo __math_xflowf.lo
ar x ../../musl/lib/libc.a __rem_pio2f.lo __math_invalid.lo __math_invalidf.lo __stack_chk_fail.lo
ar x ../../musl/lib/libc.a __math_divzerof.lo __math_oflowf.lo __rem_pio2_large.lo __math_uflowf.lo __math_xflowf.lo sqrt_data.lo
cd ../../musl

View File

@ -36,15 +36,20 @@ arm-none-eabi-gcc -march=armv6 -mfpu=vfp -mfloat-abi=hard -marm $FLAGS -$OPT -c
LIBGCC=$(arm-none-eabi-gcc -print-libgcc-file-name)
arm-none-eabi-ld -r $STMP /object_files/musl_objects_armv6.o $LIBGCC -o $DEST/stencils_armv6_$OPT.o
# ARMv7 hardware fp
# ARMv7 hardware fp for Cortex-A
arm-none-eabi-gcc -march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -marm $FLAGS -$OPT -c $SRC -o $STMP
LIBGCC=$(arm-none-eabi-gcc -print-libgcc-file-name)
arm-none-eabi-ld -r $STMP /object_files/musl_objects_armv7.o $LIBGCC -o $DEST/stencils_armv7_$OPT.o
# ARMv7 Thumb for Cortex-A with hardware fp
arm-none-eabi-gcc -march=armv7-a -mfpu=neon-vfpv3 -mfloat-abi=hard -mthumb $FLAGS -$OPT -c $SRC -o $STMP
LIBGCC=$(arm-none-eabi-gcc -march=armv7 -mfpu=vfp3 -mthumb -print-libgcc-file-name)
arm-none-eabi-ld -r $STMP /object_files/musl_objects_armv7thumb.o $LIBGCC -o $DEST/stencils_armv7thumb_$OPT.o
# Armv7 Thumb for Cortex-M3..7 hardware fp
arm-none-eabi-gcc -march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb $FLAGS -$OPT -c $SRC -o $STMP
LIBGCC=$(arm-none-eabi-gcc -print-libgcc-file-name)
arm-none-eabi-ld -r $STMP /object_files/musl_objects_armv7thumb.o $LIBGCC -o $DEST/stencils_armv7thumb_$OPT.o
LIBGCC=$(arm-none-eabi-gcc -march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -print-libgcc-file-name)
arm-none-eabi-ld -r $STMP /object_files/musl_objects_armv7mthumb.o $LIBGCC -o $DEST/stencils_armv7mthumb_$OPT.o
# PowerPC64LE
# powerpc64le-linux-gnu-gcc-13 $FLAGS -$OPT -c $SRC -o $DEST/stencils_ppc64le_$OPT.o

View File

@ -14,3 +14,5 @@ objdump -D -b binary -m i386:x86-64 --adjust-vma=0x1000 build/runner/test.copapy
build/runner/coparun-armv7 build/runner/test-armv7.copapy build/runner/test.copapy-armv7.bin
arm-none-eabi-objdump -D -b binary -marm --adjust-vma=0x50000 build/runner/test.copapy-armv7.bin > build/runner/test.copapy-armv7.asm
# arm-none-eabi-objdump -D -b binary -marm -M force-thumb --adjust-vma=0x50001 build/runner/test.copapy-armv7thumb.bin > build/runner/test.copapy-armv7thumb.asm

21
tools/test_example_code.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
# Build arm-v7 runner and stencils
bash tools/build.sh arm-v7
# Build arm-v7-thumb stencils
bash tools/build.sh arm-v7-thumb
# Build arm-v7-thumb example code
export CP_TARGET_ARCH=armv7thumb
python3 tools/make_example.py
build/runner/coparun-armv7 build/runner/test.copapy build/runner/test.copapy.bin
arm-none-eabi-objdump -D -b binary -marm -M force-thumb --adjust-vma=0x1000000 build/runner/test.copapy.bin > build/runner/test.copapy-example-armv7thumb.asm
# Build arm-v7-thumb example code
export CP_TARGET_ARCH=armv7
python3 tools/make_example.py
build/runner/coparun-armv7 build/runner/test.copapy build/runner/test.copapy.bin
arm-none-eabi-objdump -D -b binary -marm --adjust-vma=0x1000000 build/runner/test.copapy.bin > build/runner/test.copapy-example-armv7.asm

6
tools/test_thumb_stancils.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
bash tools/build.sh arm-v7-thumb
python tests/test_ops_armv7thumb.py
qemu-arm -d in_asm -D qemu.log build/runner/coparun-armv7thumb build/runner/test-armv7thumb.copapy build/runner/test.copapy-armv7thumb.bin
arm-none-eabi-objdump -D -b binary -marm -M force-thumb --adjust-vma=0xff7ed000 build/runner/test.copapy-armv7thumb.bin > build/runner/test.copapy-armv7thumb.asm