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.
- tinyvm_flaxot-0.1.0/LICENSE +21 -0
- tinyvm_flaxot-0.1.0/MANIFEST.in +3 -0
- tinyvm_flaxot-0.1.0/PKG-INFO +12 -0
- tinyvm_flaxot-0.1.0/pyproject.toml +27 -0
- tinyvm_flaxot-0.1.0/setup.cfg +4 -0
- tinyvm_flaxot-0.1.0/setup.py +17 -0
- tinyvm_flaxot-0.1.0/src/core/main.c +26 -0
- tinyvm_flaxot-0.1.0/src/core/opcode.h +27 -0
- tinyvm_flaxot-0.1.0/src/core/stack.c +79 -0
- tinyvm_flaxot-0.1.0/src/core/stack.h +27 -0
- tinyvm_flaxot-0.1.0/src/core/value.c +13 -0
- tinyvm_flaxot-0.1.0/src/core/value.h +13 -0
- tinyvm_flaxot-0.1.0/src/core/vm.c +468 -0
- tinyvm_flaxot-0.1.0/src/core/vm.h +49 -0
- tinyvm_flaxot-0.1.0/src/python/module.c +99 -0
- tinyvm_flaxot-0.1.0/tests/test_python_api.py +54 -0
- tinyvm_flaxot-0.1.0/tests/test_vm.c +64 -0
- tinyvm_flaxot-0.1.0/tinyvm/__init__.py +7 -0
- tinyvm_flaxot-0.1.0/tinyvm/assembler.py +75 -0
- tinyvm_flaxot-0.1.0/tinyvm/engine.py +25 -0
- tinyvm_flaxot-0.1.0/tinyvm_Flaxot.egg-info/PKG-INFO +12 -0
- tinyvm_flaxot-0.1.0/tinyvm_Flaxot.egg-info/SOURCES.txt +22 -0
- tinyvm_flaxot-0.1.0/tinyvm_Flaxot.egg-info/dependency_links.txt +1 -0
- tinyvm_flaxot-0.1.0/tinyvm_Flaxot.egg-info/top_level.txt +1 -0
|
@@ -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,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,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,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,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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tinyvm
|