diff --git a/.gitignore b/.gitignore index 781a906..2ac0c77 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ test.copapy token.txt /src/copapy/obj/*.o runmem2 +src/*.so +bin/* diff --git a/build.sh b/build.sh index e759040..836ab23 100644 --- a/build.sh +++ b/build.sh @@ -9,5 +9,5 @@ gcc -c src/copapy/stencils.c -O2 -o src/copapy/obj/stencils_x86_64_O2.o gcc -c src/copapy/stencils.c -O3 -o src/copapy/obj/stencils_x86_64_O3.o mkdir bin -p -gcc -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wstrict-overflow -Werror -g src/coparun/coparun.c src/coparun/runmem.c -o bin/coparun +gcc -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wstrict-overflow -Werror -g src/coparun/runmem.c src/coparun/coparun.c -o bin/coparun #x86_64-w64-mingw32-gcc -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wstrict-overflow -Werror src/runner/runmem2.c -Wall -O3 -o bin/runmem2.exe \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0b32bc6..10ffc20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,6 @@ where = ["src"] [tool.setuptools.package-data] copapy = ["*.o"] -[tool.setuptools.ext_modules] -coparun = { sources = ["src/coparun/coparun_module.c"] } - [project.optional-dependencies] dev = [ "flake8", diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1618ae7 --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, Extension + +ext = Extension( + "coparun_module", + sources=[ + "src/coparun/coparun_module.c", + "src/coparun/runmem.c" + ] +) + +setup( + ext_modules=[ext], +) diff --git a/src/copapy/__init__.py b/src/copapy/__init__.py index 9bf8acb..a8d3a26 100644 --- a/src/copapy/__init__.py +++ b/src/copapy/__init__.py @@ -149,11 +149,11 @@ 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 - + Returns: List of nodes in topologically sorted order. """ - + # Track adjacency and indegrees adj: defaultdict[Node, list[Node]] = defaultdict(list) indeg: defaultdict[Node, int] = defaultdict(int) @@ -163,9 +163,11 @@ def stable_toposort(edges: Iterable[tuple[Node, Node]]) -> list[Node]: pos = 0 for u, v in edges: if u not in order: - order[u] = pos; pos += 1 + order[u] = pos + pos += 1 if v not in order: - order[v] = pos; pos += 1 + order[v] = pos + pos += 1 adj[u].append(v) indeg[v] += 1 indeg.setdefault(u, 0) @@ -254,7 +256,7 @@ def add_read_ops(node_list: list[Node]) -> Generator[tuple[Net | None, Node], No def add_write_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 - + Returns: Yields tuples of a net and a node. The associated net is provided for read and write nodes. Otherwise None is returned in the tuple. @@ -306,7 +308,6 @@ def compile_to_instruction_list(end_nodes: Iterable[Node] | Node) -> binw.data_w # Get all nets associated with heap memory variable_list = get_nets([[const_net_list]], extended_output_ops) - assert(len(set(variable_list)) == len(variable_list)), 'Duplicates!' dw = binw.data_writer(sdb.byteorder) diff --git a/src/copapy/binwrite.py b/src/copapy/binwrite.py index 8db47e4..2740ac3 100644 --- a/src/copapy/binwrite.py +++ b/src/copapy/binwrite.py @@ -7,7 +7,7 @@ Command = Enum('Command', [('ALLOCATE_DATA', 1), ('COPY_DATA', 2), ('ALLOCATE_CODE', 3), ('COPY_CODE', 4), ('PATCH_FUNC', 5), ('PATCH_OBJECT', 6), ('SET_ENTR_POINT', 64), ('READ_DATA', 65), - ('END_PROG', 255)]) + ('END_PROG', 256), ('FREE_MEMORY', 257)]) class data_writer(): diff --git a/src/copapy/generate_stencils.py b/src/copapy/generate_stencils.py index 7e975e9..65191ee 100644 --- a/src/copapy/generate_stencils.py +++ b/src/copapy/generate_stencils.py @@ -7,7 +7,7 @@ op_signs = {'add': '+', 'sub': '-', 'mul': '*', 'div': '/'} def get_function_start() -> str: return """ int function_start(){ - result_int(0); // dummy call instruction before marker gets striped + result_int(0); // dummy call instruction before marker gets striped asm volatile (".long 0xF27ECAFE"); return 1; } diff --git a/src/copapy/stencils.c b/src/copapy/stencils.c index 9f7fb56..76cf938 100644 --- a/src/copapy/stencils.c +++ b/src/copapy/stencils.c @@ -224,7 +224,7 @@ } int function_start(){ - result_int(0); // dummy call instruction before marker gets striped + result_int(0); // dummy call instruction before marker gets striped asm volatile (".long 0xF27ECAFE"); return 1; } diff --git a/src/coparun/coparun.c b/src/coparun/coparun.c index e69de29..dfb8331 100644 --- a/src/coparun/coparun.c +++ b/src/coparun/coparun.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include "runmem.h" + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + // Open the file + int fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror("open"); + return EXIT_FAILURE; + } + + // Get file size + struct stat st; + if (fstat(fd, &st) < 0) { + perror("fstat"); + close(fd); + return EXIT_FAILURE; + } + + if (st.st_size == 0) { + fprintf(stderr, "Error: File is empty\n"); + close(fd); + return EXIT_FAILURE; + } + + //uint8_t *file_buff = get_data_memory((uint32_t)st.st_size); + uint8_t *file_buff = (uint8_t*)malloc((size_t)st.st_size); + + // Read file into allocated memory + if (read(fd, file_buff, (long unsigned int)st.st_size) != st.st_size) { + perror("read"); + close(fd); + return EXIT_FAILURE; + } + close(fd); + + parse_commands(file_buff); + + free_memory(); + + return EXIT_SUCCESS; +} diff --git a/src/coparun/coparun_module.c b/src/coparun/coparun_module.c index 99f1535..735f573 100644 --- a/src/coparun/coparun_module.c +++ b/src/coparun/coparun_module.c @@ -1,31 +1,46 @@ #define PY_SSIZE_T_CLEAN #include +#include "runmem.h" -// A simple C function exposed to Python -static PyObject* my_function(PyObject* self, PyObject* args) { - int a, b; - if (!PyArg_ParseTuple(args, "ii", &a, &b)) { - return NULL; // Error if arguments aren't two integers +/* + * coparun(PyObject *self, PyObject *args) + * Accepts a Python `bytes` (or objects supporting the buffer protocol). + * We use the "y#" format in PyArg_ParseTuple which returns a pointer to + * the internal bytes buffer and its length (Py_ssize_t). For safety and + * performance we pass that pointer directly to parse_commands which expects + * a uint8_t* buffer. If parse_commands needs the length, consider + * extending its API to accept a length parameter. + */ +static PyObject* coparun(PyObject* self, PyObject* args) { + const char *buf; + Py_ssize_t buf_len; + int result; + + if (!PyArg_ParseTuple(args, "y#", &buf, &buf_len)) { + return NULL; /* TypeError set by PyArg_ParseTuple */ } - return PyLong_FromLong(a + b); // Return sum as Python integer + + /* If parse_commands may run for a long time, release the GIL. */ + Py_BEGIN_ALLOW_THREADS + result = parse_commands((uint8_t*)buf); + Py_END_ALLOW_THREADS + + return PyLong_FromLong(result); } -// Method definitions static PyMethodDef MyMethods[] = { - {"add", my_function, METH_VARARGS, "Adds two numbers"}, - {NULL, NULL, 0, NULL} // Sentinel + {"coparun", coparun, METH_VARARGS, "Pass raw command data to coparun"}, + {NULL, NULL, 0, NULL} }; -// Module definition -static struct PyModuleDef my_module = { +static struct PyModuleDef coparun_module = { PyModuleDef_HEAD_INIT, - "my_module", // Module name + "coparun_module", // Module name NULL, // Documentation -1, // Size of per-interpreter state (-1 for global) MyMethods }; -// Module initialization function -PyMODINIT_FUNC PyInit_my_module(void) { - return PyModule_Create(&my_module); +PyMODINIT_FUNC PyInit_coparun_module(void) { + return PyModule_Create(&coparun_module); } \ No newline at end of file diff --git a/src/coparun/runmem.c b/src/coparun/runmem.c index a6995ca..ebca06d 100644 --- a/src/coparun/runmem.c +++ b/src/coparun/runmem.c @@ -6,23 +6,14 @@ #include #include #include +#include "runmem.h" -#define ALLOCATE_DATA 1 -#define COPY_DATA 2 -#define ALLOCATE_CODE 3 -#define COPY_CODE 4 -#define PATCH_FUNC 5 -#define PATCH_OBJECT 6 -#define SET_ENTR_POINT 64 -#define READ_DATA 65 -#define END_PROG 255 - -#define PATCH_RELATIVE_32 0 - -uint8_t *data_memory; -uint8_t *executable_memory; -uint32_t executable_memory_len; -int (*entr_point)(); +/* Definitions for 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; uint8_t *get_executable_memory(uint32_t num_bytes){ // Allocate executable memory @@ -62,7 +53,14 @@ int patch(uint8_t *patch_addr, uint32_t reloc_type, int32_t value){ return 0; } return 1; -} +} + +void free_memory(){ + if (executable_memory_len) munmap(executable_memory, executable_memory_len); + if (data_memory_len) munmap(data_memory, data_memory_len); + data_memory_len = 0; + executable_memory_len = 0; +} int parse_commands(uint8_t *bytes){ int32_t value; @@ -78,9 +76,15 @@ int parse_commands(uint8_t *bytes){ command = *(uint32_t*)bytes; bytes += 4; switch(command) { + case FREE_MEMORY: + size = *(uint32_t*)bytes; bytes += 4; + free_memory(); + break; + case ALLOCATE_DATA: size = *(uint32_t*)bytes; bytes += 4; data_memory = get_data_memory(size); + data_memory_len = size; printf("ALLOCATE_DATA size=%i mem_addr=%p\n", size, (void*)data_memory); break; @@ -163,47 +167,3 @@ int parse_commands(uint8_t *bytes){ } return err_flag; } - -int main(int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return EXIT_FAILURE; - } - - // Open the file - int fd = open(argv[1], O_RDONLY); - if (fd < 0) { - perror("open"); - return EXIT_FAILURE; - } - - // Get file size - struct stat st; - if (fstat(fd, &st) < 0) { - perror("fstat"); - close(fd); - return EXIT_FAILURE; - } - - if (st.st_size == 0) { - fprintf(stderr, "Error: File is empty\n"); - close(fd); - return EXIT_FAILURE; - } - - //uint8_t *file_buff = get_data_memory((uint32_t)st.st_size); - uint8_t *file_buff = (uint8_t*)malloc((size_t)st.st_size); - - // Read file into allocated memory - if (read(fd, file_buff, (long unsigned int)st.st_size) != st.st_size) { - perror("read"); - close(fd); - return EXIT_FAILURE; - } - close(fd); - - parse_commands(file_buff); - - munmap(executable_memory, executable_memory_len); - return EXIT_SUCCESS; -} diff --git a/src/coparun/runmem.h b/src/coparun/runmem.h index e69de29..77b7bd6 100644 --- a/src/coparun/runmem.h +++ b/src/coparun/runmem.h @@ -0,0 +1,39 @@ +#ifndef RUNMEM_H +#define RUNMEM_H + +#include + +/* Command opcodes used by the parser */ +#define ALLOCATE_DATA 1 +#define COPY_DATA 2 +#define ALLOCATE_CODE 3 +#define COPY_CODE 4 +#define PATCH_FUNC 5 +#define PATCH_OBJECT 6 +#define SET_ENTR_POINT 64 +#define READ_DATA 65 +#define END_PROG 256 +#define FREE_MEMORY 257 + +/* Relocation types */ +#define PATCH_RELATIVE_32 0 + +/* 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; + +/* Entry point type and variable */ +typedef int (*entry_point_t)(void); +extern entry_point_t entr_point; + + +/* 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); + +/* Free program and data memory */ +void free_memory(); + +#endif /* RUNMEM_H */ \ No newline at end of file diff --git a/tests/test_ast_gen.py b/tests/test_ast_gen.py index 977409c..bc30bc3 100644 --- a/tests/test_ast_gen.py +++ b/tests/test_ast_gen.py @@ -1,6 +1,5 @@ -from copapy import Write, const, Node +from copapy import Write, const import copapy as rc -from typing import Iterable, Generator def test_ast_generation(): diff --git a/tests/test_compile.py b/tests/test_compile.py index 4d7541e..6c39bf6 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -2,7 +2,7 @@ from copapy import Write, const import copapy import subprocess import struct -from copapy import binwrite as binw +from copapy import binwrite def run_command(command: list[str], encoding: str = 'utf8') -> str: @@ -60,12 +60,12 @@ def test_compile(): copapy.read_variable(il, r1) copapy.read_variable(il, r2) - il.write_com(binw.Command.READ_DATA) + il.write_com(binwrite.Command.READ_DATA) il.write_int(0) il.write_int(36) # run program command - il.write_com(binw.Command.END_PROG) + il.write_com(binwrite.Command.END_PROG) print('* Data to runner:') il.print() diff --git a/tests/test_coparun_module.py b/tests/test_coparun_module.py new file mode 100644 index 0000000..b081f2c --- /dev/null +++ b/tests/test_coparun_module.py @@ -0,0 +1,39 @@ +from coparun_module import coparun +from copapy import Write, const +import copapy +from copapy import binwrite + + +def test_compile(): + + c1 = const(4) + c2 = const(2) + + i1 = c1 * 2 + r1 = i1 + 7 + (c2 + 7 * 9) + r2 = i1 + 9 + out = [Write(r1), Write(r2)] + + il = copapy.compile_to_instruction_list(out) + + copapy.read_variable(il, r1) + copapy.read_variable(il, r2) + + il.write_com(binwrite.Command.READ_DATA) + il.write_int(0) + il.write_int(36) + + # run program command + il.write_com(binwrite.Command.END_PROG) + + #print('* Data to runner:') + #il.print() + + print('+ run coparun') + result = coparun(il.get_data()) + + assert result == 1 + + +if __name__ == "__main__": + test_compile()