tinyvm-Flaxot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tinyvm contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ recursive-include src *.c *.h
3
+ recursive-include tests *.c *.py
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinyvm-Flaxot
3
+ Version: 0.1.0
4
+ Summary: A small C-powered stack-based bytecode virtual machine.
5
+ License-Expression: MIT
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: C
8
+ Classifier: Programming Language :: Python :: Implementation :: CPython
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tinyvm-Flaxot"
7
+ version = "0.1.0"
8
+ description = "A small C-powered stack-based bytecode virtual machine."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: C",
15
+ "Programming Language :: Python :: Implementation :: CPython"
16
+ ]
17
+
18
+
19
+ [tool.cibuildwheel]
20
+ build = "cp311-* cp312-* cp313-* cp314-*"
21
+ skip = "*-win32 *-manylinux_i686 *-musllinux_*"
22
+ test-requires = ["pytest"]
23
+ test-command = "pytest {project}/tests"
24
+ build-verbosity = 1
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ from setuptools import Extension, setup
2
+
3
+ extension = Extension(
4
+ name="tinyvm._native",
5
+ sources=[
6
+ "src/python/module.c",
7
+ "src/core/value.c",
8
+ "src/core/stack.c",
9
+ "src/core/vm.c",
10
+ ],
11
+ include_dirs=["src/core"],
12
+ )
13
+
14
+ setup(
15
+ packages=["tinyvm"],
16
+ ext_modules=[extension],
17
+ )
@@ -0,0 +1,26 @@
1
+ #include <stdio.h>
2
+
3
+ #include "stack.h"
4
+ #include "value.h"
5
+
6
+ int main(void)
7
+ {
8
+ TinyStack stack;
9
+ TinyValue value;
10
+
11
+ if (tiny_stack_init(&stack, 8) != TINY_STACK_OK) {
12
+ fprintf(stderr, "Could not initialize stack.\n");
13
+ return 1;
14
+ }
15
+
16
+ tiny_stack_push(&stack, tiny_value_from_i64(10));
17
+ tiny_stack_push(&stack, tiny_value_from_i64(20));
18
+ tiny_stack_dup(&stack);
19
+
20
+ while (tiny_stack_pop(&stack, &value) == TINY_STACK_OK) {
21
+ printf("%lld\n", (long long)tiny_value_as_i64(value));
22
+ }
23
+
24
+ tiny_stack_destroy(&stack);
25
+ return 0;
26
+ }
@@ -0,0 +1,27 @@
1
+ #ifndef TINYVM_OPCODE_H
2
+ #define TINYVM_OPCODE_H
3
+
4
+ #include <stdint.h>
5
+
6
+ typedef enum {
7
+ TINY_OP_PUSH_I64 = 0x01,
8
+ TINY_OP_POP = 0x02,
9
+ TINY_OP_DUP = 0x03,
10
+ TINY_OP_ADD = 0x10,
11
+ TINY_OP_SUB = 0x11,
12
+ TINY_OP_MUL = 0x12,
13
+ TINY_OP_DIV = 0x13,
14
+ TINY_OP_MOD = 0x14,
15
+ TINY_OP_EQ = 0x20,
16
+ TINY_OP_GT = 0x21,
17
+ TINY_OP_LT = 0x22,
18
+ TINY_OP_JMP = 0x30,
19
+ TINY_OP_JZ = 0x31,
20
+ TINY_OP_PRINT = 0x40,
21
+ TINY_OP_HALT = 0xFF
22
+ } TinyOpcode;
23
+
24
+ #define TINYVM_I64_OPERAND_SIZE 8u
25
+ #define TINYVM_JUMP_OPERAND_SIZE 4u
26
+
27
+ #endif
@@ -0,0 +1,79 @@
1
+ #include "stack.h"
2
+
3
+ #include <stdlib.h>
4
+
5
+ TinyStackStatus tiny_stack_init(TinyStack *stack, size_t capacity)
6
+ {
7
+ if (stack == NULL || capacity == 0) {
8
+ return TINY_STACK_INVALID_ARGUMENT;
9
+ }
10
+
11
+ stack->items = (TinyValue *)calloc(capacity, sizeof(TinyValue));
12
+ if (stack->items == NULL) {
13
+ stack->size = 0;
14
+ stack->capacity = 0;
15
+ return TINY_STACK_INVALID_ARGUMENT;
16
+ }
17
+
18
+ stack->size = 0;
19
+ stack->capacity = capacity;
20
+ return TINY_STACK_OK;
21
+ }
22
+
23
+ void tiny_stack_destroy(TinyStack *stack)
24
+ {
25
+ if (stack == NULL) {
26
+ return;
27
+ }
28
+ free(stack->items);
29
+ stack->items = NULL;
30
+ stack->size = 0;
31
+ stack->capacity = 0;
32
+ }
33
+
34
+ TinyStackStatus tiny_stack_push(TinyStack *stack, TinyValue value)
35
+ {
36
+ if (stack == NULL || stack->items == NULL) {
37
+ return TINY_STACK_INVALID_ARGUMENT;
38
+ }
39
+ if (stack->size >= stack->capacity) {
40
+ return TINY_STACK_OVERFLOW;
41
+ }
42
+ stack->items[stack->size++] = value;
43
+ return TINY_STACK_OK;
44
+ }
45
+
46
+ TinyStackStatus tiny_stack_pop(TinyStack *stack, TinyValue *out_value)
47
+ {
48
+ if (stack == NULL || out_value == NULL || stack->items == NULL) {
49
+ return TINY_STACK_INVALID_ARGUMENT;
50
+ }
51
+ if (stack->size == 0) {
52
+ return TINY_STACK_UNDERFLOW;
53
+ }
54
+ *out_value = stack->items[--stack->size];
55
+ return TINY_STACK_OK;
56
+ }
57
+
58
+ TinyStackStatus tiny_stack_peek(const TinyStack *stack, TinyValue *out_value)
59
+ {
60
+ if (stack == NULL || out_value == NULL || stack->items == NULL) {
61
+ return TINY_STACK_INVALID_ARGUMENT;
62
+ }
63
+ if (stack->size == 0) {
64
+ return TINY_STACK_UNDERFLOW;
65
+ }
66
+ *out_value = stack->items[stack->size - 1];
67
+ return TINY_STACK_OK;
68
+ }
69
+
70
+ TinyStackStatus tiny_stack_dup(TinyStack *stack)
71
+ {
72
+ TinyValue value;
73
+ TinyStackStatus status = tiny_stack_peek(stack, &value);
74
+
75
+ if (status != TINY_STACK_OK) {
76
+ return status;
77
+ }
78
+ return tiny_stack_push(stack, value);
79
+ }
@@ -0,0 +1,27 @@
1
+ #ifndef TINYVM_STACK_H
2
+ #define TINYVM_STACK_H
3
+
4
+ #include <stddef.h>
5
+ #include "value.h"
6
+
7
+ typedef enum {
8
+ TINY_STACK_OK = 0,
9
+ TINY_STACK_UNDERFLOW,
10
+ TINY_STACK_OVERFLOW,
11
+ TINY_STACK_INVALID_ARGUMENT
12
+ } TinyStackStatus;
13
+
14
+ typedef struct {
15
+ TinyValue *items;
16
+ size_t size;
17
+ size_t capacity;
18
+ } TinyStack;
19
+
20
+ TinyStackStatus tiny_stack_init(TinyStack *stack, size_t capacity);
21
+ void tiny_stack_destroy(TinyStack *stack);
22
+ TinyStackStatus tiny_stack_push(TinyStack *stack, TinyValue value);
23
+ TinyStackStatus tiny_stack_pop(TinyStack *stack, TinyValue *out_value);
24
+ TinyStackStatus tiny_stack_peek(const TinyStack *stack, TinyValue *out_value);
25
+ TinyStackStatus tiny_stack_dup(TinyStack *stack);
26
+
27
+ #endif
@@ -0,0 +1,13 @@
1
+ #include "value.h"
2
+
3
+ TinyValue tiny_value_from_i64(int64_t value)
4
+ {
5
+ TinyValue result;
6
+ result.integer = value;
7
+ return result;
8
+ }
9
+
10
+ int64_t tiny_value_as_i64(TinyValue value)
11
+ {
12
+ return value.integer;
13
+ }
@@ -0,0 +1,13 @@
1
+ #ifndef TINYVM_VALUE_H
2
+ #define TINYVM_VALUE_H
3
+
4
+ #include <stdint.h>
5
+
6
+ typedef struct {
7
+ int64_t integer;
8
+ } TinyValue;
9
+
10
+ TinyValue tiny_value_from_i64(int64_t value);
11
+ int64_t tiny_value_as_i64(TinyValue value);
12
+
13
+ #endif
@@ -0,0 +1,468 @@
1
+ #include "vm.h"
2
+ #include "opcode.h"
3
+ #include "stack.h"
4
+ #include "value.h"
5
+
6
+ #include <stdio.h>
7
+ #include <stdlib.h>
8
+
9
+
10
+
11
+
12
+
13
+
14
+ static int checked_add(int64_t a, int64_t b, int64_t *out)
15
+ {
16
+ if ((b > 0 && a > INT64_MAX - b) ||
17
+ (b < 0 && a < INT64_MIN - b)) {
18
+ return 0;
19
+ }
20
+ *out = a + b;
21
+ return 1;
22
+ }
23
+
24
+ static int checked_sub(int64_t a, int64_t b, int64_t *out)
25
+ {
26
+ if ((b < 0 && a > INT64_MAX + b) ||
27
+ (b > 0 && a < INT64_MIN + b)) {
28
+ return 0;
29
+ }
30
+ *out = a - b;
31
+ return 1;
32
+ }
33
+
34
+ static int checked_mul(int64_t a, int64_t b, int64_t *out)
35
+ {
36
+ if (a == 0 || b == 0) {
37
+ *out = 0;
38
+ return 1;
39
+ }
40
+ if ((a == -1 && b == INT64_MIN) || (b == -1 && a == INT64_MIN)) {
41
+ return 0;
42
+ }
43
+ if (a > 0) {
44
+ if ((b > 0 && a > INT64_MAX / b) ||
45
+ (b < 0 && b < INT64_MIN / a)) {
46
+ return 0;
47
+ }
48
+ } else {
49
+ if ((b > 0 && a < INT64_MIN / b) ||
50
+ (b < 0 && a < INT64_MAX / b)) {
51
+ return 0;
52
+ }
53
+ }
54
+ *out = a * b;
55
+ return 1;
56
+ }
57
+
58
+
59
+ static void set_error(TinyVMError *error, TinyVMStatus code,
60
+ size_t offset, const char *message)
61
+ {
62
+ if (error == NULL) {
63
+ return;
64
+ }
65
+ error->code = code;
66
+ error->byte_offset = offset;
67
+ snprintf(error->message, sizeof(error->message), "%s", message);
68
+ }
69
+
70
+ TinyVMConfig tinyvm_default_config(void)
71
+ {
72
+ TinyVMConfig config;
73
+ config.max_stack = 256;
74
+ config.max_instructions = 100000;
75
+ return config;
76
+ }
77
+
78
+ void tinyvm_result_free(TinyVMResult *result)
79
+ {
80
+ if (result == NULL) {
81
+ return;
82
+ }
83
+ free(result->values);
84
+ result->values = NULL;
85
+ result->count = 0;
86
+ }
87
+
88
+ static size_t operand_size(uint8_t opcode)
89
+ {
90
+ switch (opcode) {
91
+ case TINY_OP_PUSH_I64:
92
+ return TINYVM_I64_OPERAND_SIZE;
93
+ case TINY_OP_JMP:
94
+ case TINY_OP_JZ:
95
+ return TINYVM_JUMP_OPERAND_SIZE;
96
+ case TINY_OP_POP:
97
+ case TINY_OP_DUP:
98
+ case TINY_OP_ADD:
99
+ case TINY_OP_SUB:
100
+ case TINY_OP_MUL:
101
+ case TINY_OP_DIV:
102
+ case TINY_OP_MOD:
103
+ case TINY_OP_EQ:
104
+ case TINY_OP_GT:
105
+ case TINY_OP_LT:
106
+ case TINY_OP_PRINT:
107
+ case TINY_OP_HALT:
108
+ return 0;
109
+ default:
110
+ return (size_t)-1;
111
+ }
112
+ }
113
+
114
+ static uint32_t read_u32_le(const uint8_t *p)
115
+ {
116
+ return (uint32_t)p[0]
117
+ | ((uint32_t)p[1] << 8)
118
+ | ((uint32_t)p[2] << 16)
119
+ | ((uint32_t)p[3] << 24);
120
+ }
121
+
122
+ static int64_t read_i64_le(const uint8_t *p)
123
+ {
124
+ uint64_t raw = 0;
125
+ size_t i;
126
+
127
+ for (i = 0; i < 8; i++) {
128
+ raw |= ((uint64_t)p[i]) << (i * 8);
129
+ }
130
+ return (int64_t)raw;
131
+ }
132
+
133
+ static TinyVMStatus validate_bytecode(const uint8_t *code, size_t length,
134
+ uint8_t **map_out, TinyVMError *error)
135
+ {
136
+ size_t offset = 0;
137
+ uint8_t *map;
138
+
139
+ if (code == NULL || length == 0 || map_out == NULL) {
140
+ set_error(error, TINYVM_ERROR_INVALID_ARGUMENT, 0,
141
+ "Bytecode must not be empty.");
142
+ return TINYVM_ERROR_INVALID_ARGUMENT;
143
+ }
144
+
145
+ map = (uint8_t *)calloc(length + 1, 1);
146
+ if (map == NULL) {
147
+ set_error(error, TINYVM_ERROR_OUT_OF_MEMORY, 0,
148
+ "Could not allocate validation map.");
149
+ return TINYVM_ERROR_OUT_OF_MEMORY;
150
+ }
151
+
152
+ /* Pass 1: find instruction boundaries, reject bad or truncated ones. */
153
+ while (offset < length) {
154
+ size_t extra;
155
+
156
+ map[offset] = 1;
157
+ extra = operand_size(code[offset]);
158
+
159
+ if (extra == (size_t)-1) {
160
+ free(map);
161
+ set_error(error, TINYVM_ERROR_INVALID_OPCODE, offset,
162
+ "Unknown opcode.");
163
+ return TINYVM_ERROR_INVALID_OPCODE;
164
+ }
165
+ if (extra > length - offset - 1) {
166
+ free(map);
167
+ set_error(error, TINYVM_ERROR_TRUNCATED_BYTECODE, offset,
168
+ "Instruction operand is truncated.");
169
+ return TINYVM_ERROR_TRUNCATED_BYTECODE;
170
+ }
171
+ offset += 1 + extra;
172
+ }
173
+
174
+ /* Pass 2: every jump must land on an instruction boundary. */
175
+ offset = 0;
176
+ while (offset < length) {
177
+ uint8_t op = code[offset];
178
+
179
+ if (op == TINY_OP_JMP || op == TINY_OP_JZ) {
180
+ uint32_t target = read_u32_le(code + offset + 1);
181
+
182
+ if ((size_t)target >= length || map[target] == 0) {
183
+ free(map);
184
+ set_error(error, TINYVM_ERROR_INVALID_JUMP, offset,
185
+ "Jump target is not an instruction boundary.");
186
+ return TINYVM_ERROR_INVALID_JUMP;
187
+ }
188
+ }
189
+ offset += 1 + operand_size(op);
190
+ }
191
+
192
+ *map_out = map;
193
+ return TINYVM_OK;
194
+ }
195
+
196
+
197
+
198
+ static int append_output(TinyVMResult *result, size_t *capacity, int64_t value)
199
+ {
200
+ int64_t *grown;
201
+ size_t next;
202
+
203
+ if (result->count == *capacity) {
204
+ next = (*capacity == 0) ? 8 : (*capacity * 2);
205
+ if (next < *capacity || next > ((size_t)-1) / sizeof(int64_t)) {
206
+ return 0;
207
+ }
208
+ grown = (int64_t *)realloc(result->values, next * sizeof(int64_t));
209
+ if (grown == NULL) {
210
+ return 0;
211
+ }
212
+ result->values = grown;
213
+ *capacity = next;
214
+ }
215
+
216
+ result->values[result->count++] = value;
217
+ return 1;
218
+ }
219
+
220
+ TinyVMStatus tinyvm_execute(
221
+ const uint8_t *bytecode,
222
+ size_t bytecode_length,
223
+ const TinyVMConfig *config_input,
224
+ TinyVMResult *result,
225
+ TinyVMError *error
226
+ )
227
+ {
228
+ TinyVMConfig defaults = tinyvm_default_config();
229
+ const TinyVMConfig *config = config_input != NULL ? config_input : &defaults;
230
+ TinyStack stack;
231
+ uint8_t *boundaries = NULL;
232
+ size_t ip = 0;
233
+ size_t output_capacity = 0;
234
+ uint64_t steps = 0;
235
+ TinyVMStatus status;
236
+
237
+ if (result == NULL) {
238
+ set_error(error, TINYVM_ERROR_INVALID_ARGUMENT, 0,
239
+ "Result pointer is required.");
240
+ return TINYVM_ERROR_INVALID_ARGUMENT;
241
+ }
242
+ result->values = NULL;
243
+ result->count = 0;
244
+
245
+ if (config->max_stack == 0 || config->max_instructions == 0) {
246
+ set_error(error, TINYVM_ERROR_INVALID_ARGUMENT, 0,
247
+ "VM limits must be greater than zero.");
248
+ return TINYVM_ERROR_INVALID_ARGUMENT;
249
+ }
250
+
251
+ status = validate_bytecode(bytecode, bytecode_length, &boundaries, error);
252
+ if (status != TINYVM_OK) {
253
+ return status;
254
+ }
255
+
256
+ if (tiny_stack_init(&stack, config->max_stack) != TINY_STACK_OK) {
257
+ free(boundaries);
258
+ set_error(error, TINYVM_ERROR_OUT_OF_MEMORY, 0,
259
+ "Could not initialize operand stack.");
260
+ return TINYVM_ERROR_OUT_OF_MEMORY;
261
+ }
262
+
263
+ while (ip < bytecode_length) {
264
+ uint8_t op;
265
+ size_t instruction_offset = ip;
266
+ TinyValue value;
267
+
268
+ if (steps >= config->max_instructions) {
269
+ status = TINYVM_ERROR_INSTRUCTION_LIMIT;
270
+ set_error(error, status, ip, "Instruction limit exceeded.");
271
+ goto failed;
272
+ }
273
+ steps++;
274
+ op = bytecode[ip++];
275
+
276
+ switch (op) {
277
+ case TINY_OP_PUSH_I64:
278
+ value = tiny_value_from_i64(read_i64_le(bytecode + ip));
279
+ ip += TINYVM_I64_OPERAND_SIZE;
280
+ if (tiny_stack_push(&stack, value) != TINY_STACK_OK) {
281
+ status = TINYVM_ERROR_STACK_OVERFLOW;
282
+ set_error(error, status, instruction_offset,
283
+ "Operand stack overflow.");
284
+ goto failed;
285
+ }
286
+ break;
287
+
288
+ case TINY_OP_POP:
289
+ if (tiny_stack_pop(&stack, &value) != TINY_STACK_OK) {
290
+ status = TINYVM_ERROR_STACK_UNDERFLOW;
291
+ set_error(error, status, instruction_offset,
292
+ "Operand stack underflow.");
293
+ goto failed;
294
+ }
295
+ break;
296
+
297
+ case TINY_OP_DUP:
298
+ if (tiny_stack_dup(&stack) != TINY_STACK_OK) {
299
+ status = stack.size >= stack.capacity
300
+ ? TINYVM_ERROR_STACK_OVERFLOW
301
+ : TINYVM_ERROR_STACK_UNDERFLOW;
302
+ set_error(error, status, instruction_offset,
303
+ "DUP failed.");
304
+ goto failed;
305
+ }
306
+ break;
307
+
308
+ case TINY_OP_PRINT:
309
+ if (tiny_stack_pop(&stack, &value) != TINY_STACK_OK) {
310
+ status = TINYVM_ERROR_STACK_UNDERFLOW;
311
+ set_error(error, status, instruction_offset,
312
+ "PRINT needs a value.");
313
+ goto failed;
314
+ }
315
+ if (!append_output(result, &output_capacity,
316
+ tiny_value_as_i64(value))) {
317
+ status = TINYVM_ERROR_OUT_OF_MEMORY;
318
+ set_error(error, status, instruction_offset,
319
+ "Output allocation failed.");
320
+ goto failed;
321
+ }
322
+ break;
323
+
324
+ case TINY_OP_ADD:
325
+ case TINY_OP_SUB:
326
+ case TINY_OP_MUL:
327
+ case TINY_OP_DIV:
328
+ case TINY_OP_MOD: {
329
+ TinyValue left, right;
330
+ int64_t a, b, answer = 0;
331
+ int ok = 1;
332
+
333
+ if (tiny_stack_pop(&stack, &right) != TINY_STACK_OK ||
334
+ tiny_stack_pop(&stack, &left) != TINY_STACK_OK) {
335
+ status = TINYVM_ERROR_STACK_UNDERFLOW;
336
+ set_error(error, status, instruction_offset,
337
+ "Arithmetic needs two values.");
338
+ goto failed;
339
+ }
340
+
341
+ a = left.integer;
342
+ b = right.integer;
343
+
344
+ if (op == TINY_OP_ADD) {
345
+ ok = checked_add(a, b, &answer);
346
+ } else if (op == TINY_OP_SUB) {
347
+ ok = checked_sub(a, b, &answer);
348
+ } else if (op == TINY_OP_MUL) {
349
+ ok = checked_mul(a, b, &answer);
350
+ } else if (op == TINY_OP_DIV) {
351
+ if (b == 0) {
352
+ status = TINYVM_ERROR_DIVISION_BY_ZERO;
353
+ set_error(error, status, instruction_offset,
354
+ "Division by zero.");
355
+ goto failed;
356
+ }
357
+ if (a == INT64_MIN && b == -1) {
358
+ ok = 0;
359
+ } else {
360
+ answer = a / b;
361
+ }
362
+ } else {
363
+ if (b == 0) {
364
+ status = TINYVM_ERROR_DIVISION_BY_ZERO;
365
+ set_error(error, status, instruction_offset,
366
+ "Modulo by zero.");
367
+ goto failed;
368
+ }
369
+ answer = (a == INT64_MIN && b == -1) ? 0 : a % b;
370
+ }
371
+
372
+ if (!ok) {
373
+ status = TINYVM_ERROR_ARITHMETIC_OVERFLOW;
374
+ set_error(error, status, instruction_offset,
375
+ "Signed integer overflow.");
376
+ goto failed;
377
+ }
378
+ if (tiny_stack_push(&stack, tiny_value_from_i64(answer))
379
+ != TINY_STACK_OK) {
380
+ status = TINYVM_ERROR_STACK_OVERFLOW;
381
+ set_error(error, status, instruction_offset,
382
+ "Operand stack overflow.");
383
+ goto failed;
384
+ }
385
+ break;
386
+ }
387
+
388
+ case TINY_OP_EQ:
389
+ case TINY_OP_GT:
390
+ case TINY_OP_LT: {
391
+ TinyValue left, right;
392
+ int64_t answer;
393
+
394
+ if (tiny_stack_pop(&stack, &right) != TINY_STACK_OK ||
395
+ tiny_stack_pop(&stack, &left) != TINY_STACK_OK) {
396
+ status = TINYVM_ERROR_STACK_UNDERFLOW;
397
+ set_error(error, status, instruction_offset,
398
+ "Comparison needs two values.");
399
+ goto failed;
400
+ }
401
+
402
+ if (op == TINY_OP_EQ) {
403
+ answer = (left.integer == right.integer);
404
+ } else if (op == TINY_OP_GT) {
405
+ answer = (left.integer > right.integer);
406
+ } else {
407
+ answer = (left.integer < right.integer);
408
+ }
409
+
410
+ if (tiny_stack_push(&stack, tiny_value_from_i64(answer))
411
+ != TINY_STACK_OK) {
412
+ status = TINYVM_ERROR_STACK_OVERFLOW;
413
+ set_error(error, status, instruction_offset,
414
+ "Operand stack overflow.");
415
+ goto failed;
416
+ }
417
+ break;
418
+ }
419
+
420
+ case TINY_OP_JMP:
421
+ ip = (size_t)read_u32_le(bytecode + ip);
422
+ break;
423
+
424
+ case TINY_OP_JZ: {
425
+ uint32_t target = read_u32_le(bytecode + ip);
426
+
427
+ if (tiny_stack_pop(&stack, &value) != TINY_STACK_OK) {
428
+ status = TINYVM_ERROR_STACK_UNDERFLOW;
429
+ set_error(error, status, instruction_offset,
430
+ "JZ needs a condition value.");
431
+ goto failed;
432
+ }
433
+
434
+ if (value.integer == 0) {
435
+ ip = (size_t)target;
436
+ } else {
437
+ ip += TINYVM_JUMP_OPERAND_SIZE;
438
+ }
439
+ break;
440
+ }
441
+
442
+
443
+
444
+ case TINY_OP_HALT:
445
+ free(boundaries);
446
+ tiny_stack_destroy(&stack);
447
+ return TINYVM_OK;
448
+
449
+ default:
450
+ /* Arithmetic, comparisons and jumps come in later lessons. */
451
+ status = TINYVM_ERROR_INVALID_OPCODE;
452
+ set_error(error, status, instruction_offset,
453
+ "Instruction not implemented yet.");
454
+ goto failed;
455
+ }
456
+ }
457
+
458
+ free(boundaries);
459
+ tiny_stack_destroy(&stack);
460
+ return TINYVM_OK;
461
+
462
+ failed:
463
+ free(boundaries);
464
+ tiny_stack_destroy(&stack);
465
+ tinyvm_result_free(result);
466
+ return status;
467
+ }
468
+
@@ -0,0 +1,49 @@
1
+ #ifndef TINYVM_VM_H
2
+ #define TINYVM_VM_H
3
+
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+
7
+ typedef enum {
8
+ TINYVM_OK = 0,
9
+ TINYVM_ERROR_INVALID_ARGUMENT,
10
+ TINYVM_ERROR_INVALID_OPCODE,
11
+ TINYVM_ERROR_TRUNCATED_BYTECODE,
12
+ TINYVM_ERROR_INVALID_JUMP,
13
+ TINYVM_ERROR_STACK_UNDERFLOW,
14
+ TINYVM_ERROR_STACK_OVERFLOW,
15
+ TINYVM_ERROR_DIVISION_BY_ZERO,
16
+ TINYVM_ERROR_ARITHMETIC_OVERFLOW,
17
+ TINYVM_ERROR_INSTRUCTION_LIMIT,
18
+ TINYVM_ERROR_OUT_OF_MEMORY
19
+ } TinyVMStatus;
20
+
21
+ typedef struct {
22
+ TinyVMStatus code;
23
+ size_t byte_offset;
24
+ char message[128];
25
+ } TinyVMError;
26
+
27
+ typedef struct {
28
+ int64_t *values;
29
+ size_t count;
30
+ } TinyVMResult;
31
+
32
+ typedef struct {
33
+ size_t max_stack;
34
+ uint64_t max_instructions;
35
+ } TinyVMConfig;
36
+
37
+ TinyVMConfig tinyvm_default_config(void);
38
+
39
+ TinyVMStatus tinyvm_execute(
40
+ const uint8_t *bytecode,
41
+ size_t bytecode_length,
42
+ const TinyVMConfig *config,
43
+ TinyVMResult *result,
44
+ TinyVMError *error
45
+ );
46
+
47
+ void tinyvm_result_free(TinyVMResult *result);
48
+
49
+ #endif
@@ -0,0 +1,99 @@
1
+ #define PY_SSIZE_T_CLEAN
2
+ #include <Python.h>
3
+
4
+ #include "vm.h"
5
+
6
+ static PyObject *native_execute(PyObject *self, PyObject *args,
7
+ PyObject *kwargs)
8
+ {
9
+ const char *bytecode = NULL;
10
+ Py_ssize_t length = 0;
11
+ Py_ssize_t max_stack = 256;
12
+ unsigned long long max_instructions = 100000;
13
+ static char *names[] = {"bytecode", "max_stack", "max_instructions", NULL};
14
+ TinyVMConfig config;
15
+ TinyVMResult result = {0};
16
+ TinyVMError error = {0};
17
+ TinyVMStatus status;
18
+ PyObject *output;
19
+ size_t i;
20
+
21
+ (void)self;
22
+
23
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y#|nK", names,
24
+ &bytecode, &length,
25
+ &max_stack, &max_instructions)) {
26
+ return NULL;
27
+ }
28
+
29
+ if (max_stack <= 0 || max_instructions == 0) {
30
+ PyErr_SetString(PyExc_ValueError, "VM limits must be positive.");
31
+ return NULL;
32
+ }
33
+
34
+ config.max_stack = (size_t)max_stack;
35
+ config.max_instructions = (uint64_t)max_instructions;
36
+
37
+ status = tinyvm_execute((const uint8_t *)bytecode, (size_t)length,
38
+ &config, &result, &error);
39
+
40
+ if (status != TINYVM_OK) {
41
+ PyObject *exception = PyExc_RuntimeError;
42
+
43
+ if (status == TINYVM_ERROR_INVALID_ARGUMENT ||
44
+ status == TINYVM_ERROR_INVALID_OPCODE ||
45
+ status == TINYVM_ERROR_TRUNCATED_BYTECODE ||
46
+ status == TINYVM_ERROR_INVALID_JUMP) {
47
+ exception = PyExc_ValueError;
48
+ } else if (status == TINYVM_ERROR_DIVISION_BY_ZERO) {
49
+ exception = PyExc_ZeroDivisionError;
50
+ } else if (status == TINYVM_ERROR_ARITHMETIC_OVERFLOW) {
51
+ exception = PyExc_OverflowError;
52
+ } else if (status == TINYVM_ERROR_OUT_OF_MEMORY) {
53
+ exception = PyExc_MemoryError;
54
+ }
55
+
56
+ PyErr_Format(exception, "%s (byte offset %zu)",
57
+ error.message, error.byte_offset);
58
+ tinyvm_result_free(&result);
59
+ return NULL;
60
+ }
61
+
62
+ output = PyList_New((Py_ssize_t)result.count);
63
+ if (output == NULL) {
64
+ tinyvm_result_free(&result);
65
+ return NULL;
66
+ }
67
+
68
+ for (i = 0; i < result.count; i++) {
69
+ PyObject *number = PyLong_FromLongLong((long long)result.values[i]);
70
+ if (number == NULL) {
71
+ Py_DECREF(output);
72
+ tinyvm_result_free(&result);
73
+ return NULL;
74
+ }
75
+ PyList_SET_ITEM(output, (Py_ssize_t)i, number);
76
+ }
77
+
78
+ tinyvm_result_free(&result);
79
+ return output;
80
+ }
81
+
82
+ static PyMethodDef methods[] = {
83
+ {"execute", (PyCFunction)native_execute, METH_VARARGS | METH_KEYWORDS,
84
+ "Execute encoded TinyVM bytecode."},
85
+ {NULL, NULL, 0, NULL}
86
+ };
87
+
88
+ static struct PyModuleDef module = {
89
+ PyModuleDef_HEAD_INIT,
90
+ "_native",
91
+ "TinyVM native module.",
92
+ -1,
93
+ methods
94
+ };
95
+
96
+ PyMODINIT_FUNC PyInit__native(void)
97
+ {
98
+ return PyModule_Create(&module);
99
+ }
@@ -0,0 +1,54 @@
1
+ import pytest
2
+
3
+ import tinyvm
4
+
5
+
6
+ def run(program, **kwargs):
7
+ return tinyvm.VirtualMachine(**kwargs).execute(program)
8
+
9
+
10
+ def test_add():
11
+ assert run([("PUSH", 10), ("PUSH", 20), ("ADD",), ("PRINT",), ("HALT",)]) == [30]
12
+
13
+
14
+ def test_subtraction_order():
15
+ assert run([("PUSH", 20), ("PUSH", 5), ("SUB",), ("PRINT",), ("HALT",)]) == [15]
16
+
17
+
18
+ def test_comparison():
19
+ assert run([("PUSH", 10), ("PUSH", 3), ("GT",), ("PRINT",), ("HALT",)]) == [1]
20
+
21
+
22
+ def test_jump_if_zero():
23
+ assert run([
24
+ ("PUSH", 0),
25
+ ("JZ", "zero_case"),
26
+ ("PUSH", 999), ("PRINT",), ("HALT",),
27
+ ("LABEL", "zero_case"),
28
+ ("PUSH", 42), ("PRINT",), ("HALT",),
29
+ ]) == [42]
30
+
31
+
32
+ def test_division_by_zero():
33
+ with pytest.raises(ZeroDivisionError):
34
+ run([("PUSH", 8), ("PUSH", 0), ("DIV",), ("HALT",)])
35
+
36
+
37
+ def test_stack_underflow():
38
+ with pytest.raises(RuntimeError):
39
+ run([("POP",), ("HALT",)])
40
+
41
+
42
+ def test_unknown_instruction():
43
+ with pytest.raises(ValueError):
44
+ run([("NOT_AN_OPCODE",)])
45
+
46
+
47
+ def test_unknown_label():
48
+ with pytest.raises(ValueError):
49
+ run([("JMP", "nowhere")])
50
+
51
+
52
+ def test_instruction_limit():
53
+ with pytest.raises(RuntimeError):
54
+ run([("LABEL", "again"), ("JMP", "again")], max_instructions=10)
@@ -0,0 +1,64 @@
1
+ #include <assert.h>
2
+ #include <stdint.h>
3
+ #include <stdio.h>
4
+
5
+ #include "vm.h"
6
+ #include "opcode.h"
7
+
8
+ static void test_add_and_print(void)
9
+ {
10
+ const uint8_t code[] = {
11
+ TINY_OP_PUSH_I64, 10, 0, 0, 0, 0, 0, 0, 0,
12
+ TINY_OP_PUSH_I64, 20, 0, 0, 0, 0, 0, 0, 0,
13
+ TINY_OP_ADD,
14
+ TINY_OP_PRINT,
15
+ TINY_OP_HALT
16
+ };
17
+ TinyVMConfig config = tinyvm_default_config();
18
+ TinyVMResult result = {0};
19
+ TinyVMError error = {0};
20
+
21
+ assert(tinyvm_execute(code, sizeof(code), &config, &result, &error)
22
+ == TINYVM_OK);
23
+ assert(result.count == 1);
24
+ assert(result.values[0] == 30);
25
+ tinyvm_result_free(&result);
26
+ }
27
+
28
+ static void test_division_by_zero(void)
29
+ {
30
+ const uint8_t code[] = {
31
+ TINY_OP_PUSH_I64, 10, 0, 0, 0, 0, 0, 0, 0,
32
+ TINY_OP_PUSH_I64, 0, 0, 0, 0, 0, 0, 0, 0,
33
+ TINY_OP_DIV,
34
+ TINY_OP_HALT
35
+ };
36
+ TinyVMConfig config = tinyvm_default_config();
37
+ TinyVMResult result = {0};
38
+ TinyVMError error = {0};
39
+
40
+ assert(tinyvm_execute(code, sizeof(code), &config, &result, &error)
41
+ == TINYVM_ERROR_DIVISION_BY_ZERO);
42
+ tinyvm_result_free(&result);
43
+ }
44
+
45
+ static void test_invalid_opcode(void)
46
+ {
47
+ const uint8_t code[] = { 0x77 };
48
+ TinyVMConfig config = tinyvm_default_config();
49
+ TinyVMResult result = {0};
50
+ TinyVMError error = {0};
51
+
52
+ assert(tinyvm_execute(code, sizeof(code), &config, &result, &error)
53
+ == TINYVM_ERROR_INVALID_OPCODE);
54
+ tinyvm_result_free(&result);
55
+ }
56
+
57
+ int main(void)
58
+ {
59
+ test_add_and_print();
60
+ test_division_by_zero();
61
+ test_invalid_opcode();
62
+ puts("All TinyVM C tests passed.");
63
+ return 0;
64
+ }
@@ -0,0 +1,7 @@
1
+ """TinyVM: a small bytecode virtual machine."""
2
+
3
+ from .assembler import assemble
4
+ from .engine import VirtualMachine
5
+
6
+ __all__ = ["VirtualMachine", "assemble"]
7
+ __version__ = "0.1.0"
@@ -0,0 +1,75 @@
1
+ import struct
2
+
3
+ OPCODES = {
4
+ "PUSH": 0x01, "POP": 0x02, "DUP": 0x03,
5
+ "ADD": 0x10, "SUB": 0x11, "MUL": 0x12,
6
+ "DIV": 0x13, "MOD": 0x14,
7
+ "EQ": 0x20, "GT": 0x21, "LT": 0x22,
8
+ "JMP": 0x30, "JZ": 0x31,
9
+ "PRINT": 0x40, "HALT": 0xFF,
10
+ }
11
+
12
+
13
+ def _is_integer(value):
14
+ return isinstance(value, int) and not isinstance(value, bool)
15
+
16
+
17
+ def assemble(instructions):
18
+ """Convert instruction tuples (with labels) into TinyVM bytecode."""
19
+ instructions = list(instructions)
20
+ labels = {}
21
+ offset = 0
22
+
23
+ # Pass 1: validate shapes, compute byte offsets, record labels.
24
+ for item in instructions:
25
+ if not isinstance(item, tuple) or not item:
26
+ raise ValueError("Instructions must be non-empty tuples.")
27
+ name = item[0]
28
+ if not isinstance(name, str):
29
+ raise ValueError("Instruction names must be strings.")
30
+ name = name.upper()
31
+
32
+ if name == "LABEL":
33
+ if len(item) != 2 or not isinstance(item[1], str):
34
+ raise ValueError("LABEL requires one string name.")
35
+ if item[1] in labels:
36
+ raise ValueError(f"Duplicate label: {item[1]}")
37
+ labels[item[1]] = offset
38
+ elif name == "PUSH":
39
+ if len(item) != 2 or not _is_integer(item[1]):
40
+ raise ValueError("PUSH requires one integer.")
41
+ if not -(2**63) <= item[1] <= 2**63 - 1:
42
+ raise ValueError("PUSH value must fit in signed 64 bits.")
43
+ offset += 9
44
+ elif name in ("JMP", "JZ"):
45
+ if len(item) != 2:
46
+ raise ValueError(f"{name} requires one target.")
47
+ offset += 5
48
+ elif name in OPCODES:
49
+ if len(item) != 1:
50
+ raise ValueError(f"{name} takes no operands.")
51
+ offset += 1
52
+ else:
53
+ raise ValueError(f"Unknown instruction: {name}")
54
+
55
+ # Pass 2: encode, now that label offsets are known.
56
+ output = bytearray()
57
+ for item in instructions:
58
+ name = item[0].upper()
59
+ if name == "LABEL":
60
+ continue
61
+
62
+ output.append(OPCODES[name])
63
+ if name == "PUSH":
64
+ output.extend(struct.pack("<q", item[1]))
65
+ elif name in ("JMP", "JZ"):
66
+ target = item[1]
67
+ if isinstance(target, str):
68
+ if target not in labels:
69
+ raise ValueError(f"Unknown label: {target}")
70
+ target = labels[target]
71
+ if not _is_integer(target) or not 0 <= target <= 0xFFFFFFFF:
72
+ raise ValueError(f"{name} target must be a valid byte offset.")
73
+ output.extend(struct.pack("<I", target))
74
+
75
+ return bytes(output)
@@ -0,0 +1,25 @@
1
+ from . import _native
2
+ from .assembler import assemble
3
+
4
+
5
+ class VirtualMachine:
6
+ """Python interface to the TinyVM native execution engine."""
7
+
8
+ def __init__(self, max_stack=256, max_instructions=100_000):
9
+ for name, value in (("max_stack", max_stack),
10
+ ("max_instructions", max_instructions)):
11
+ if not isinstance(value, int) or isinstance(value, bool):
12
+ raise TypeError(f"{name} must be an integer.")
13
+ if value <= 0:
14
+ raise ValueError(f"{name} must be greater than zero.")
15
+ self.max_stack = max_stack
16
+ self.max_instructions = max_instructions
17
+
18
+ def execute(self, instructions):
19
+ """Assemble and run instruction tuples; return the PRINT output."""
20
+ bytecode = assemble(instructions)
21
+ return _native.execute(
22
+ bytecode,
23
+ max_stack=self.max_stack,
24
+ max_instructions=self.max_instructions,
25
+ )
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinyvm-Flaxot
3
+ Version: 0.1.0
4
+ Summary: A small C-powered stack-based bytecode virtual machine.
5
+ License-Expression: MIT
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: C
8
+ Classifier: Programming Language :: Python :: Implementation :: CPython
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
@@ -0,0 +1,22 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ pyproject.toml
4
+ setup.py
5
+ src/core/main.c
6
+ src/core/opcode.h
7
+ src/core/stack.c
8
+ src/core/stack.h
9
+ src/core/value.c
10
+ src/core/value.h
11
+ src/core/vm.c
12
+ src/core/vm.h
13
+ src/python/module.c
14
+ tests/test_python_api.py
15
+ tests/test_vm.c
16
+ tinyvm/__init__.py
17
+ tinyvm/assembler.py
18
+ tinyvm/engine.py
19
+ tinyvm_Flaxot.egg-info/PKG-INFO
20
+ tinyvm_Flaxot.egg-info/SOURCES.txt
21
+ tinyvm_Flaxot.egg-info/dependency_links.txt
22
+ tinyvm_Flaxot.egg-info/top_level.txt