Skip to content

Commit 3e4180e

Browse files
committed
Baby WASM interpreter, UTF8 specification
1 parent e8ee4a7 commit 3e4180e

17 files changed

Lines changed: 1423 additions & 2 deletions

pvs2c-demos/Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: run-aes run-hmac run-sha256 run-utf8_decoder run-crypto ci-pvs2c-tests ci clean-aes clean-hmac clean-sha256 clean-utf8_decoder clean
1+
.PHONY: run-aes run-hmac run-sha256 run-utf8_decoder run-bytecode-wasm run-crypto ci-pvs2c-tests ci clean-aes clean-hmac clean-sha256 clean-utf8_decoder clean-bytecode-wasm clean
22

33
run-aes:
44
$(MAKE) -C aes run
@@ -12,6 +12,9 @@ run-sha256:
1212
run-utf8_decoder:
1313
$(MAKE) -C utf8_decoder run
1414

15+
run-bytecode-wasm:
16+
$(MAKE) -C bytecode/wasm run
17+
1518
run-crypto: run-aes run-hmac run-sha256
1619

1720
ci-pvs2c-tests:
@@ -21,6 +24,7 @@ ci:
2124
python3 ../scripts/ci/run_pvs2c_tests.py --repo-root .. --demo-root pvs2c-demos
2225
$(MAKE) run-crypto
2326
$(MAKE) run-utf8_decoder
27+
$(MAKE) run-bytecode-wasm
2428

2529
clean-aes:
2630
$(MAKE) -C aes clean
@@ -34,4 +38,7 @@ clean-sha256:
3438
clean-utf8_decoder:
3539
$(MAKE) -C utf8_decoder clean
3640

37-
clean: clean-aes clean-hmac clean-sha256 clean-utf8_decoder
41+
clean-bytecode-wasm:
42+
$(MAKE) -C bytecode/wasm clean
43+
44+
clean: clean-aes clean-hmac clean-sha256 clean-utf8_decoder clean-bytecode-wasm
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Wasm i32 Bytecode Interpreter PVS2C Demo
2+
3+
This demo implements a small WebAssembly Core interpreter in executable PVS and
4+
runs the generated C against real Wasm function-body bytecode.
5+
6+
The supported subset is intentionally small:
7+
8+
- i32 values only
9+
- one-byte immediates
10+
- i32 locals
11+
- empty-result `block` and `loop`
12+
- `br`, `br_if`, `return`, `end`
13+
- `local.get`, `local.set`, `local.tee`
14+
- `i32.const`, `i32.eqz`, `i32.eq`, `i32.ne`, `i32.add`, `i32.sub`, `i32.mul`
15+
16+
The PVS model uses predicate subtyping for the stack/control discipline:
17+
18+
- `push` only accepts stacks whose height is below `max_stack`
19+
- `pop` only accepts nonempty stacks, and its result type records that the
20+
returned stack height is one smaller
21+
- machine states are indexed by the byte string being executed, so the program
22+
counter type is bounded by the current body length
23+
- control frames track `block`/`loop` bodies and matching `end` offsets
24+
- branch depth selection is checked against the current control depth
25+
- execution is total by construction through a fuel-bound runner
26+
27+
Run the demo with:
28+
29+
```sh
30+
make -C pvs2c-demos/bytecode/wasm run
31+
```
32+
33+
The checked-in examples are:
34+
35+
- `add.wat`: `add(40, 2) = 42`
36+
- `affine.wat`: `affine(5, 9) = 5 * 7 + 9 * 3 + 11 = 73`
37+
- `factorial.wat`: an iterative loop using `block`, `loop`, `br_if`, and `br`
38+
39+
Each `.body.hex` file is the WebAssembly function-body payload: the local
40+
declaration vector followed by instruction bytes. That is the payload inside a
41+
standard `.wasm` code-section function body after the body-size LEB128 wrapper.
42+
43+
To rebuild body hex from WAT, install WABT and run:
44+
45+
```sh
46+
make -C pvs2c-demos/bytecode/wasm compile-examples
47+
```
48+
49+
Or manually:
50+
51+
```sh
52+
wat2wasm examples/factorial.wat -o build/factorial.wasm
53+
python3 tools/extract_wasm_body.py build/factorial.wasm --body 0
54+
```
55+
56+
The `examples/*.c` files are equivalent source examples. Toolchains vary in
57+
which opcodes they emit, so the demo executes the WAT-derived bytecode by
58+
default; C-compiled modules can be inspected with `tools/extract_wasm_body.py`
59+
when the emitted body stays inside this demo's subset.
60+
61+
## Running Your Own Body Hex
62+
63+
The current C harness is table-driven. To run another extracted function body,
64+
put the hex payload in `examples/` and add a row to the `cases[]` table in
65+
`driver.c`:
66+
67+
```c
68+
{"my_example", "examples/my_example.body.hex", 2, {10, 20}, 128, 30},
69+
```
70+
71+
The fields are:
72+
73+
- `name`: label printed in the PASS/FAIL output
74+
- `path`: `.body.hex` file containing the extracted Wasm function-body payload
75+
- `arity`: number of i32 arguments to pass, currently `0`, `1`, or `2`
76+
- `args`: argument values; in the example, `arg0 = 10` and `arg1 = 20`
77+
- `fuel`: maximum interpreter steps before the runner returns `fuel_status`
78+
- `expected`: expected i32 return value
79+
80+
That row runs `my_example(10, 20)` with at most `128` interpreter steps and
81+
expects the function to return `30`. For arity `2`, the harness calls the
82+
generated PVS2C entry point:
83+
84+
```c
85+
wasm_i32__invoke2(pvs_body, 128, 10, 20)
86+
```
87+
88+
After editing the table, run:
89+
90+
```sh
91+
make -C pvs2c-demos/bytecode/wasm run PVS_LOCATION=/path/to/PVS
92+
```

pvs2c-demos/bytecode/wasm/driver.c

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#include "wasm_i32_c.h"
2+
#include "../../cavp_driver.h"
3+
4+
#include <errno.h>
5+
6+
typedef struct {
7+
const char *name;
8+
const char *path;
9+
unsigned arity;
10+
uint32_t args[2];
11+
uint32_t fuel;
12+
uint32_t expected;
13+
} wasm_case_t;
14+
15+
static int hex_value(int ch) {
16+
if (ch >= '0' && ch <= '9') {
17+
return ch - '0';
18+
}
19+
if (ch >= 'a' && ch <= 'f') {
20+
return ch - 'a' + 10;
21+
}
22+
if (ch >= 'A' && ch <= 'F') {
23+
return ch - 'A' + 10;
24+
}
25+
return -1;
26+
}
27+
28+
static uint8_t *read_hex_body(const char *path, uint32_t *length) {
29+
FILE *file = fopen(path, "r");
30+
if (file == NULL) {
31+
fprintf(stderr, "FAIL open %s: %s\n", path, strerror(errno));
32+
exit(2);
33+
}
34+
35+
size_t capacity = 64;
36+
size_t used = 0;
37+
uint8_t *bytes = malloc(capacity);
38+
if (bytes == NULL) {
39+
fprintf(stderr, "FAIL allocate hex buffer\n");
40+
exit(2);
41+
}
42+
43+
int high = -1;
44+
int in_comment = 0;
45+
for (;;) {
46+
int ch = fgetc(file);
47+
if (ch == EOF) {
48+
break;
49+
}
50+
if (in_comment) {
51+
if (ch == '\n') {
52+
in_comment = 0;
53+
}
54+
continue;
55+
}
56+
if (ch == '#') {
57+
in_comment = 1;
58+
continue;
59+
}
60+
if (isspace((unsigned char)ch)) {
61+
continue;
62+
}
63+
64+
int nibble = hex_value(ch);
65+
if (nibble < 0) {
66+
fprintf(stderr, "FAIL invalid hex character %c in %s\n", ch, path);
67+
exit(2);
68+
}
69+
if (high < 0) {
70+
high = nibble;
71+
continue;
72+
}
73+
if (used == capacity) {
74+
capacity *= 2;
75+
uint8_t *grown = realloc(bytes, capacity);
76+
if (grown == NULL) {
77+
fprintf(stderr, "FAIL grow hex buffer\n");
78+
exit(2);
79+
}
80+
bytes = grown;
81+
}
82+
bytes[used++] = (uint8_t)((high << 4) | nibble);
83+
high = -1;
84+
}
85+
fclose(file);
86+
87+
if (high >= 0) {
88+
fprintf(stderr, "FAIL odd number of hex digits in %s\n", path);
89+
exit(2);
90+
}
91+
if (used > UINT32_MAX) {
92+
fprintf(stderr, "FAIL %s too large for PVS bytestring\n", path);
93+
exit(2);
94+
}
95+
*length = (uint32_t)used;
96+
return bytes;
97+
}
98+
99+
static wasm_i32__exec_result_t run_case_body(const wasm_case_t *test,
100+
const uint8_t *body,
101+
uint32_t length) {
102+
bytestrings__bytestring_t pvs_body = cavp_make_bytestring(body, length);
103+
if (test->arity == 1) {
104+
return wasm_i32__invoke1(pvs_body, test->fuel, test->args[0]);
105+
}
106+
if (test->arity == 2) {
107+
return wasm_i32__invoke2(pvs_body, test->fuel, test->args[0], test->args[1]);
108+
}
109+
return wasm_i32__invoke0(pvs_body, test->fuel);
110+
}
111+
112+
static int run_case(const wasm_case_t *test) {
113+
uint32_t length = 0;
114+
uint8_t *body = read_hex_body(test->path, &length);
115+
wasm_i32__exec_result_t result = run_case_body(test, body, length);
116+
free(body);
117+
118+
uint8_t halt = wasm_i32__halt_status();
119+
int ok = result->status == halt && result->value == test->expected;
120+
printf("%s wasm %s", ok ? "PASS" : "FAIL", test->name);
121+
if (test->arity == 1) {
122+
printf("(%u)", test->args[0]);
123+
} else if (test->arity == 2) {
124+
printf("(%u, %u)", test->args[0], test->args[1]);
125+
} else {
126+
printf("()");
127+
}
128+
printf(" => %u\n", result->value);
129+
if (!ok) {
130+
printf(" expected status=%u value=%u\n", halt, test->expected);
131+
printf(" actual status=%u value=%u pc=%u stack=%u\n",
132+
result->status,
133+
result->value,
134+
result->final_pc,
135+
result->final_stack_height);
136+
}
137+
release_wasm_i32__exec_result(result);
138+
return ok ? 0 : 1;
139+
}
140+
141+
int main(void) {
142+
static const wasm_case_t cases[] = {
143+
{"add", "examples/add.body.hex", 2, {40, 2}, 32, 42},
144+
{"affine", "examples/affine.body.hex", 2, {5, 9}, 64, 73},
145+
{"factorial", "examples/factorial.body.hex", 1, {0, 0}, 128, 1},
146+
{"factorial", "examples/factorial.body.hex", 1, {5, 0}, 512, 120},
147+
};
148+
149+
int failures = 0;
150+
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
151+
failures += run_case(&cases[i]);
152+
}
153+
return failures == 0 ? 0 : 1;
154+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# WebAssembly function body payload for examples/add.wat.
2+
# Local declaration vector: 00
3+
# Code: local.get 0; local.get 1; i32.add; end
4+
00 20 00 20 01 6A 0B
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__attribute__((export_name("add")))
2+
int add(int a, int b) {
3+
return a + b;
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(module
2+
(func $add (export "add") (param i32 i32) (result i32)
3+
local.get 0
4+
local.get 1
5+
i32.add))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# WebAssembly function body payload for examples/affine.wat.
2+
# Computes x * 7 + y * 3 + 11.
3+
00 20 00 41 07 6C 20 01 41 03 6C 6A 41 0B 6A 0B
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__attribute__((export_name("affine")))
2+
int affine(int x, int y) {
3+
return x * 7 + y * 3 + 11;
4+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(module
2+
(func $affine (export "affine") (param i32 i32) (result i32)
3+
local.get 0
4+
i32.const 7
5+
i32.mul
6+
local.get 1
7+
i32.const 3
8+
i32.mul
9+
i32.add
10+
i32.const 11
11+
i32.add))
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# WebAssembly function body payload for examples/factorial.wat.
2+
# Local declaration vector: one group, one i32 local.
3+
01 01 7F
4+
41 01
5+
21 01
6+
02 40
7+
03 40
8+
20 00
9+
45
10+
0D 01
11+
20 01
12+
20 00
13+
6C
14+
21 01
15+
20 00
16+
41 01
17+
6B
18+
21 00
19+
0C 00
20+
0B
21+
0B
22+
20 01
23+
0B

0 commit comments

Comments
 (0)