writeonce/runtime/test/test_objops.c
shoney.arickathil 631d277e0c feat(runtime): wovm VM core (Iteration 2)
- 16 tasks complete: arena, object model, borrow word, containers,
  RC + budgeted cycle collector, wob_build, validating loader,
  interpreter core (dual dispatch), object opcodes, drop-map unwinding,
  builtins + DB_STUB + TRAP, ICALL, wovm CLI + just recipes
- 13 test suites × 2 dispatch flavors (ASan+UBSan) + CLI smoke, all green
- .wob v1 format pinned in src/wob.h + docs/plan/oop-vm/00-wob-format.md
- wo-rt.c reference event-loop preserved for sub-project 2

This is Iteration 2 of the OOP milestone; compiler front (Iteration 3)
is in progress on this branch. They meet at Iteration 4 (emitter+e2e).
2026-08-10 09:35:55 +02:00

95 lines
2.8 KiB
C

/* test_objops — NEW / GETF / SETF / DROP through bytecode, plus the
* residual runtime checks (field bounds, null receiver) trapping BOUNDS. */
#include <stdlib.h>
#include "loader.h"
#include "t.h"
#include "vm.h"
#include "wob_build.h"
static wo_vm VM;
static int run_img(uint8_t *img, size_t len, uint64_t *ret, wo_err *err) {
wo_module mod;
char lerr[256];
if (wo_load_buf(&mod, img, len, lerr, sizeof lerr) != 0) {
fprintf(stderr, "loader rejected test image: %s\n", lerr);
return -2;
}
if (wo_vm_init(&VM, &mod, 1 << 20) != 0) {
wo_module_free(&mod);
return -2;
}
int rc = wo_vm_call(&VM, 0, NULL, 0, ret, err);
wo_vm_destroy(&VM);
wo_module_free(&mod);
return rc;
}
/* image with class Point{x,y} and a single free fn built from `code` */
static uint8_t *point_image(const uint32_t *code, uint32_t n, size_t *len) {
wb_t *b = wb_new();
uint32_t kp = wb_const_text(b, "Point");
uint32_t kf = wb_const_text(b, "go");
wb_const_int(b, 7); /* constant index 2 */
uint8_t kinds[] = {WO_K_SCALAR, WO_K_SCALAR};
wb_class(b, kp, 0, kinds, 2);
wb_method(b, kf, WOB_NONE, 0, 3, code, n, NULL, 0, NULL, 0);
return wb_finish(b, len);
}
static void test_new_set_get_drop_roundtrip(void) {
uint32_t code[] = {
wo_ins_abx(WOP_NEW, 0, 0), /* r0 = new Point */
wo_ins_abx(WOP_LOADK, 1, 2), /* r1 = 7 */
wo_ins_abc(WOP_SETF, 0, 0, 1), /* r0.f0 = r1 */
wo_ins_abc(WOP_GETF, 2, 0, 0), /* r2 = r0.f0 */
wo_ins_abc(WOP_DROP, 0, 0, 0), /* drop r0 (nulls the register) */
wo_ins_abc(WOP_RET, 2, 0, 0),
};
size_t len;
uint8_t *img = point_image(code, 6, &len);
uint64_t ret = 0;
wo_err err;
T_EQ(run_img(img, len, &ret, &err), 0);
T_EQ(ret, 7);
free(img);
}
static void test_field_index_out_of_range_traps(void) {
uint32_t code[] = {
wo_ins_abx(WOP_NEW, 0, 0),
wo_ins_abc(WOP_GETF, 2, 0, 9), /* Point has 2 fields */
wo_ins_abc(WOP_RET, 2, 0, 0),
};
size_t len;
uint8_t *img = point_image(code, 3, &len);
uint64_t ret = 0;
wo_err err = {0};
T_EQ(run_img(img, len, &ret, &err), -1);
T_EQ(err.code, WO_T_BOUNDS);
T_EQ(wo_vm_depth(&VM), 0);
free(img);
}
static void test_null_receiver_traps(void) {
uint32_t code[] = {
/* r0 was never assigned: zeroed by the frame setup */
wo_ins_abc(WOP_GETF, 2, 0, 0),
wo_ins_abc(WOP_RET, 2, 0, 0),
};
size_t len;
uint8_t *img = point_image(code, 2, &len);
uint64_t ret = 0;
wo_err err = {0};
T_EQ(run_img(img, len, &ret, &err), -1);
T_EQ(err.code, WO_T_BOUNDS);
free(img);
}
int main(void) {
test_new_set_get_drop_roundtrip();
test_field_index_out_of_range_traps();
test_null_receiver_traps();
return t_report("test_objops");
}