From c8e6848530e3e027b17e2e6ce03273474c25d529 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 16 Dec 2025 16:15:50 +0100 Subject: [PATCH] Added multi-target support for coparun-module --- src/copapy/_target.py | 14 ++++-- src/coparun/coparun.c | 7 +-- src/coparun/coparun_module.c | 62 ++++++++++++++++++++++--- src/coparun/runmem.c | 90 +++++++++++++++++------------------- src/coparun/runmem.h | 23 ++++----- src/coparun_module.pyi | 6 ++- tests/test_multi_targets.py | 36 +++++++++++++++ 7 files changed, 164 insertions(+), 74 deletions(-) create mode 100644 tests/test_multi_targets.py diff --git a/src/copapy/_target.py b/src/copapy/_target.py index be0e6a1..67fbf9b 100644 --- a/src/copapy/_target.py +++ b/src/copapy/_target.py @@ -1,6 +1,6 @@ from typing import Iterable, overload, TypeVar, Any from . import _binwrite as binw -from coparun_module import coparun, read_data_mem +from coparun_module import coparun, read_data_mem, create_target, clear_target import struct from ._basic_types import stencil_db_from_package from ._basic_types import value, Net, Node, Write, NumLike @@ -29,6 +29,10 @@ class Target(): """ self.sdb = stencil_db_from_package(arch, optimization) self._values: dict[Net, tuple[int, int, str]] = {} + self._context = create_target() + + def __del__(self) -> None: + clear_target(self._context) def compile(self, *values: int | float | value[int] | value[float] | Iterable[int | float | value[int] | value[float]]) -> None: """Compiles the code to compute the given values. @@ -48,7 +52,7 @@ class Target(): dw, self._values = compile_to_dag(nodes, self.sdb) dw.write_com(binw.Command.END_COM) - assert coparun(dw.get_data()) > 0 + assert coparun(self._context, dw.get_data()) > 0 def run(self) -> None: """Runs the compiled code on the target device. @@ -56,7 +60,7 @@ class Target(): dw = binw.data_writer(self.sdb.byteorder) dw.write_com(binw.Command.RUN_PROG) dw.write_com(binw.Command.END_COM) - assert coparun(dw.get_data()) > 0 + assert coparun(self._context, dw.get_data()) > 0 @overload def read_value(self, net: value[T]) -> T: ... @@ -84,7 +88,7 @@ class Target(): assert net in self._values, f"Value {net} not found. It might not have been compiled for the target." addr, lengths, var_type = self._values[net] assert lengths > 0 - data = read_data_mem(addr, lengths) + data = read_data_mem(self._context, addr, lengths) assert data is not None and len(data) == lengths, f"Failed to read value {net}" en = {'little': '<', 'big': '>'}[self.sdb.byteorder] if var_type == 'float': @@ -111,4 +115,4 @@ class Target(): """Reads the raw data of a value by the runner.""" dw = binw.data_writer(self.sdb.byteorder) add_read_command(dw, self._values, net) - assert coparun(dw.get_data()) > 0 + assert coparun(self._context, dw.get_data()) > 0 diff --git a/src/coparun/coparun.c b/src/coparun/coparun.c index 9e77fe6..8d2860b 100644 --- a/src/coparun/coparun.c +++ b/src/coparun/coparun.c @@ -45,7 +45,8 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - int ret = parse_commands(file_buff); + runmem_t targ; + int ret = parse_commands(&targ, file_buff); if (ret == 2) { /* Dump code for debugging */ @@ -54,11 +55,11 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } f = fopen(argv[2], "wb"); - fwrite(executable_memory, 1, (size_t)executable_memory_len, f); + fwrite(targ.executable_memory, 1, (size_t)targ.executable_memory_len, f); fclose(f); } - free_memory(); + free_memory(&targ); return ret < 0; } diff --git a/src/coparun/coparun_module.c b/src/coparun/coparun_module.c index 4f33679..bee0dfb 100644 --- a/src/coparun/coparun_module.c +++ b/src/coparun/coparun_module.c @@ -1,30 +1,41 @@ #define PY_SSIZE_T_CLEAN #include #include "runmem.h" +#include static PyObject* coparun(PyObject* self, PyObject* args) { + PyObject *handle_obj; const char *buf; Py_ssize_t buf_len; int result; - if (!PyArg_ParseTuple(args, "y#", &buf, &buf_len)) { + // Expect: handle, bytes + if (!PyArg_ParseTuple(args, "Oy#", &handle_obj, &buf, &buf_len)) { return NULL; /* TypeError set by PyArg_ParseTuple */ } + void *ptr = PyLong_AsVoidPtr(handle_obj); + if (!ptr) { + PyErr_SetString(PyExc_ValueError, "Invalid context handle"); + return NULL; + } + runmem_t *context = (runmem_t*)ptr; + /* If parse_commands may run for a long time, release the GIL. */ Py_BEGIN_ALLOW_THREADS - result = parse_commands((uint8_t*)buf); + result = parse_commands(context, (uint8_t*)buf); Py_END_ALLOW_THREADS return PyLong_FromLong(result); } static PyObject* read_data_mem(PyObject* self, PyObject* args) { + PyObject *handle_obj; unsigned long rel_addr; unsigned long length; - // Parse arguments: unsigned long (relative address), Py_ssize_t (length) - if (!PyArg_ParseTuple(args, "nn", &rel_addr, &length)) { + // Expect: handle, rel_addr, length + if (!PyArg_ParseTuple(args, "Onn", &handle_obj, &rel_addr, &length)) { return NULL; } @@ -33,9 +44,21 @@ static PyObject* read_data_mem(PyObject* self, PyObject* args) { return NULL; } - const char *ptr = (const char *)(data_memory + rel_addr); + void *ptr = PyLong_AsVoidPtr(handle_obj); + if (!ptr) { + PyErr_SetString(PyExc_ValueError, "Invalid context handle"); + return NULL; + } + runmem_t *context = (runmem_t*)ptr; - PyObject *result = PyBytes_FromStringAndSize(ptr, length); + if (!context->data_memory || rel_addr + length > context->data_memory_len) { + PyErr_SetString(PyExc_ValueError, "Read out of bounds"); + return NULL; + } + + const char *data_ptr = (const char *)(context->data_memory + rel_addr); + + PyObject *result = PyBytes_FromStringAndSize(data_ptr, length); if (!result) { return PyErr_NoMemory(); } @@ -43,9 +66,36 @@ static PyObject* read_data_mem(PyObject* self, PyObject* args) { return result; } +static PyObject* create_target(PyObject* self, PyObject* args) { + runmem_t *context = (runmem_t*)calloc(1, sizeof(runmem_t)); + if (!context) { + return PyErr_NoMemory(); + } + // Return the pointer as a Python integer (handle) + return PyLong_FromVoidPtr((void*)context); +} + +static PyObject* clear_target(PyObject* self, PyObject* args) { + PyObject *handle_obj; + if (!PyArg_ParseTuple(args, "O", &handle_obj)) { + return NULL; + } + void *ptr = PyLong_AsVoidPtr(handle_obj); + if (!ptr) { + PyErr_SetString(PyExc_ValueError, "Invalid handle"); + return NULL; + } + runmem_t *context = (runmem_t*)ptr; + free_memory(context); + free(context); + Py_RETURN_NONE; +} + static PyMethodDef MyMethods[] = { {"coparun", coparun, METH_VARARGS, "Pass raw command data to coparun"}, {"read_data_mem", read_data_mem, METH_VARARGS, "Read memory and return as bytes"}, + {"create_target", create_target, METH_NOARGS, "Create and return a handle to a zero-initialized runmem_t struct"}, + {"clear_target", clear_target, METH_VARARGS, "Free all memory associated with the given target handle"}, {NULL, NULL, 0, NULL} }; diff --git a/src/coparun/runmem.c b/src/coparun/runmem.c index 7e0762e..1165956 100644 --- a/src/coparun/runmem.c +++ b/src/coparun/runmem.c @@ -5,14 +5,6 @@ #include "runmem.h" #include "mem_man.h" -/* Globals declared extern in runmem.h */ -uint8_t *data_memory = NULL; -uint32_t data_memory_len = 0; -uint8_t *executable_memory = NULL; -uint32_t executable_memory_len = 0; -entry_point_t entr_point = NULL; -int data_offs = 0; - void patch(uint8_t *patch_addr, uint32_t patch_mask, int32_t value) { uint32_t *val_ptr = (uint32_t*)patch_addr; uint32_t original = *val_ptr; @@ -58,23 +50,25 @@ void patch_arm32_abs(uint8_t *patch_addr, uint32_t imm16) *((uint32_t *)patch_addr) = instr; } -void free_memory() { - deallocate_memory(executable_memory, executable_memory_len); - deallocate_memory(data_memory, data_memory_len); - executable_memory_len = 0; - data_memory_len = 0; +void free_memory(runmem_t *context) { + deallocate_memory(context->executable_memory, context->executable_memory_len); + deallocate_memory(context->data_memory, context->data_memory_len); + context->executable_memory_len = 0; + context->data_memory_len = 0; + context->executable_memory = NULL; + context->data_memory = NULL; + context->entr_point = NULL; + context->data_offs = 0; } -int update_data_offs() { - if (data_memory && executable_memory && (data_memory - executable_memory > 0x7FFFFFFF || executable_memory - data_memory > 0x7FFFFFFF)) { +int update_data_offs(runmem_t *context) { + if (context->data_memory && context->executable_memory && + (context->data_memory - context->executable_memory > 0x7FFFFFFF || + context->executable_memory - context->data_memory > 0x7FFFFFFF)) { perror("Error: code and data memory to far apart"); return 0; } - if (data_memory && executable_memory && (data_memory - executable_memory > 0x7FFFFFFF || executable_memory - data_memory > 0x7FFFFFFF)) { - perror("Error: code and data memory to far apart"); - return 0; - } - data_offs = (int)(data_memory - executable_memory); + context->data_offs = (int)(context->data_memory - context->executable_memory); return 1; } @@ -82,7 +76,7 @@ int floor_div(int a, int b) { return a / b - ((a % b != 0) && ((a < 0) != (b < 0))); } -int parse_commands(uint8_t *bytes) { +int parse_commands(runmem_t *context, uint8_t *bytes) { int32_t value; uint32_t command; uint32_t patch_mask; @@ -98,33 +92,32 @@ int parse_commands(uint8_t *bytes) { switch(command) { case ALLOCATE_DATA: size = *(uint32_t*)bytes; bytes += 4; - data_memory = allocate_data_memory(size); - data_memory_len = size; - LOG("ALLOCATE_DATA size=%i mem_addr=%p\n", size, (void*)data_memory); - if (!update_data_offs()) end_flag = -4; + context->data_memory = allocate_data_memory(size); + context->data_memory_len = size; + LOG("ALLOCATE_DATA size=%i mem_addr=%p\n", size, (void*)context->data_memory); + if (!update_data_offs(context)) end_flag = -4; break; case COPY_DATA: offs = *(uint32_t*)bytes; bytes += 4; size = *(uint32_t*)bytes; bytes += 4; LOG("COPY_DATA offs=%i size=%i\n", offs, size); - memcpy(data_memory + offs, bytes, size); bytes += size; + memcpy(context->data_memory + offs, bytes, size); bytes += size; break; case ALLOCATE_CODE: size = *(uint32_t*)bytes; bytes += 4; - executable_memory = allocate_executable_memory(size); - executable_memory_len = size; - LOG("ALLOCATE_CODE size=%i mem_addr=%p\n", size, (void*)executable_memory); - //LOG("# d %i c %i off %i\n", data_memory, executable_memory, data_offs); - if (!update_data_offs()) end_flag = -4; + context->executable_memory = allocate_executable_memory(size); + context->executable_memory_len = size; + LOG("ALLOCATE_CODE size=%i mem_addr=%p\n", size, (void*)context->executable_memory); + if (!update_data_offs(context)) end_flag = -4; break; case COPY_CODE: offs = *(uint32_t*)bytes; bytes += 4; size = *(uint32_t*)bytes; bytes += 4; LOG("COPY_CODE offs=%i size=%i\n", offs, size); - memcpy(executable_memory + offs, bytes, size); bytes += size; + memcpy(context->executable_memory + offs, bytes, size); bytes += size; break; case PATCH_FUNC: @@ -134,7 +127,7 @@ int parse_commands(uint8_t *bytes) { value = *(int32_t*)bytes; bytes += 4; LOG("PATCH_FUNC patch_offs=%i patch_mask=%#08x scale=%i value=%i\n", offs, patch_mask, patch_scale, value); - patch(executable_memory + offs, patch_mask, value / patch_scale); + patch(context->executable_memory + offs, patch_mask, value / patch_scale); break; case PATCH_OBJECT: @@ -144,7 +137,7 @@ int parse_commands(uint8_t *bytes) { value = *(int32_t*)bytes; bytes += 4; LOG("PATCH_OBJECT patch_offs=%i patch_mask=%#08x scale=%i value=%i\n", offs, patch_mask, patch_scale, value); - patch(executable_memory + offs, patch_mask, value / patch_scale + data_offs / patch_scale); + patch(context->executable_memory + offs, patch_mask, value / patch_scale + context->data_offs / patch_scale); break; case PATCH_OBJECT_ABS: @@ -154,7 +147,7 @@ int parse_commands(uint8_t *bytes) { value = *(int32_t*)bytes; bytes += 4; LOG("PATCH_OBJECT_ABS patch_offs=%i patch_mask=%#08x scale=%i value=%i\n", offs, patch_mask, patch_scale, value); - patch(executable_memory + offs, patch_mask, value / patch_scale); + patch(context->executable_memory + offs, patch_mask, value / patch_scale); break; case PATCH_OBJECT_REL: @@ -163,8 +156,8 @@ int parse_commands(uint8_t *bytes) { patch_scale = *(int32_t*)bytes; bytes += 4; value = *(int32_t*)bytes; bytes += 4; LOG("PATCH_OBJECT_REL patch_offs=%i patch_addr=%p scale=%i value=%i\n", - offs, (void*)(data_memory + value), patch_scale, value); - *(void **)(executable_memory + offs) = data_memory + value; // / patch_scale; + offs, (void*)(context->data_memory + value), patch_scale, value); + *(void **)(context->executable_memory + offs) = context->data_memory + value; break; case PATCH_OBJECT_HI21: @@ -173,8 +166,8 @@ int parse_commands(uint8_t *bytes) { patch_scale = *(int32_t*)bytes; bytes += 4; value = *(int32_t*)bytes; bytes += 4; LOG("PATCH_OBJECT_HI21 patch_offs=%i scale=%i value=%i res_value=%i\n", - offs, patch_scale, value, floor_div(data_offs + value, patch_scale) - (int32_t)offs / patch_scale); - patch_hi21(executable_memory + offs, floor_div(data_offs + value, patch_scale) - (int32_t)offs / patch_scale); + offs, patch_scale, value, floor_div(context->data_offs + value, patch_scale) - (int32_t)offs / patch_scale); + patch_hi21(context->executable_memory + offs, floor_div(context->data_offs + value, patch_scale) - (int32_t)offs / patch_scale); break; case PATCH_OBJECT_ARM32_ABS: @@ -183,21 +176,24 @@ int parse_commands(uint8_t *bytes) { patch_scale = *(int32_t*)bytes; bytes += 4; value = *(int32_t*)bytes; bytes += 4; LOG("PATCH_OBJECT_ARM32_ABS patch_offs=%i patch_mask=%#08x scale=%i value=%i imm16=%#04x\n", - offs, patch_mask, patch_scale, value, (uint32_t)((uintptr_t)(data_memory + value) & patch_mask) / (uint32_t)patch_scale); - patch_arm32_abs(executable_memory + offs, (uint32_t)((uintptr_t)(data_memory + value) & patch_mask) / (uint32_t)patch_scale); + offs, patch_mask, patch_scale, value, (uint32_t)((uintptr_t)(context->data_memory + value) & patch_mask) / (uint32_t)patch_scale); + patch_arm32_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; - entr_point = (entry_point_t)(executable_memory + rel_entr_point); + context->entr_point = (entry_point_t)(context->executable_memory + rel_entr_point); LOG("ENTRY_POINT rel_entr_point=%i\n", rel_entr_point); - mark_mem_executable(executable_memory, executable_memory_len); + mark_mem_executable(context->executable_memory, context->executable_memory_len); break; case RUN_PROG: LOG("RUN_PROG\n"); - int ret = entr_point(); - BLOG("Return value: %i\n", ret); + { + int ret = context->entr_point(); + (void)ret; + BLOG("Return value: %i\n", ret); + } break; case READ_DATA: @@ -205,14 +201,14 @@ int parse_commands(uint8_t *bytes) { size = *(uint32_t*)bytes; bytes += 4; BLOG("READ_DATA offs=%i size=%i data=", offs, size); for (uint32_t i = 0; i < size; i++) { - printf("%02X ", data_memory[offs + i]); + printf("%02X ", context->data_memory[offs + i]); } printf("\n"); break; case FREE_MEMORY: LOG("FREE_MENORY\n"); - free_memory(); + free_memory(context); break; case DUMP_CODE: diff --git a/src/coparun/runmem.h b/src/coparun/runmem.h index 7f11dde..31e9905 100644 --- a/src/coparun/runmem.h +++ b/src/coparun/runmem.h @@ -32,23 +32,24 @@ #define FREE_MEMORY 257 #define DUMP_CODE 258 -/* Memory blobs accessible by other translation units */ -extern uint8_t *data_memory; -extern uint32_t data_memory_len; -extern uint8_t *executable_memory; -extern uint32_t executable_memory_len; -extern int data_offs; - -/* Entry point type and variable */ +/* Entry point type */ typedef int (*entry_point_t)(void); -extern entry_point_t entr_point; +/* Struct for run-time memory state */ +typedef struct runmem_s { + uint8_t *data_memory; // Pointer to data memory + uint32_t data_memory_len; // Length of data memory + uint8_t *executable_memory; // Pointer to executable memory + uint32_t executable_memory_len; // Length of executable memory + int data_offs; // Offset of data memory relative to executable memory + entry_point_t entr_point; // Entry point function pointer +} runmem_t; /* Command parser: takes a pointer to the command stream and returns an error flag (0 on success according to current code) */ -int parse_commands(uint8_t *bytes); +int parse_commands(runmem_t *context, uint8_t *bytes); /* Free program and data memory */ -void free_memory(); +void free_memory(runmem_t *context); #endif /* RUNMEM_H */ \ No newline at end of file diff --git a/src/coparun_module.pyi b/src/coparun_module.pyi index 5eea2be..5a1a5aa 100644 --- a/src/coparun_module.pyi +++ b/src/coparun_module.pyi @@ -1,2 +1,4 @@ -def coparun(data: bytes) -> int: ... -def read_data_mem(rel_addr: int, length: int) -> bytes: ... +def coparun(context: int, data: bytes) -> int: ... +def read_data_mem(context: int, rel_addr: int, length: int) -> bytes: ... +def create_target() -> int: ... +def clear_target(context: int) -> None: ... \ No newline at end of file diff --git a/tests/test_multi_targets.py b/tests/test_multi_targets.py new file mode 100644 index 0000000..34642e3 --- /dev/null +++ b/tests/test_multi_targets.py @@ -0,0 +1,36 @@ +import copapy as cp +import pytest + +def test_multi_target(): + # Define variables + a = cp.value(0.25) + b = cp.value(0.87) + + # Define computations + c = a + b * 2.0 + d = c ** 2 + cp.sin(a) + e = d + cp.sqrt(b) + + # Create a target, compile and run + tg1 = cp.Target() + tg1.compile(e) + + # Patch constant value + a.source = cp._basic_types.CPConstant(1000.0) + + tg2 = cp.Target() + tg2.compile(e) + + tg1.run() + tg2.run() + + print("Result tg1:", tg1.read_value(e)) + print("Result tg2:", tg2.read_value(e)) + + # Assertions to verify correctness + assert tg1.read_value(e) == pytest.approx((0.25 + 0.87 * 2.0) ** 2 + cp.sin(0.25) + cp.sqrt(0.87), 0.005) # pyright: ignore[reportUnknownMemberType] + assert tg2.read_value(e) == pytest.approx((1000.0 + 0.87 * 2.0) ** 2 + cp.sin(1000.0) + cp.sqrt(0.87), 0.005) # pyright: ignore[reportUnknownMemberType] + + +if __name__ == "__main__": + test_multi_target()