/*
* nanowasm, a tiny WebAssembly/Wasm interpreter
* Copyright (C) 2023-2025 Xavier Del Campo Romero
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <nanowasm/nw.h>
#include <errno.h>
#include <stddef.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct inst
{
long retval;
struct mem
{
void *p;
size_t n;
} stack, linear, global;
};
static int io_read(void *const buf, const size_t n, void *const user)
{
FILE *const f = user;
const size_t r = fread(buf, 1, 1, f);
if (!r && ferror(f))
{
fprintf(stderr, "%s: ferror\n", __func__);
return -1;
}
return r;
}
static enum nw_state io_seek(const long offset, void *const user)
{
if (fseek(user, offset, SEEK_SET))
{
fprintf(stderr, "%s: fseek(3): %s\n", __func__, strerror(errno));
return NW_FATAL;
}
return NW_OK;
}
static enum nw_state io_tell(long *const out, void *const user)
{
const long offset = ftell(user);
if (offset < 0)
{
fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno));
return NW_FATAL;
}
*out = offset;
return NW_OK;
}
static int io_eof(void *const user)
{
FILE *const f = user;
return feof(f);
}
static int push(const void *const src, const size_t n, void *const user)
{
struct inst *const inst = user;
struct mem *const s = &inst->stack;
char *const p = realloc(s->p, s->n + n);
if (!p)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
fprintf(stderr, "pushed %zu bytes: {", n);
for (size_t i = 0; i < n; i++)
{
fprintf(stderr, "%hhu", ((const char *)src)[i]);
if (i + 1 < n)
fputs(", ", stderr);
}
memcpy(&p[s->n], src, n);
s->p = p;
s->n += n;
fprintf(stderr, "}, stack size=%zu\n", s->n);
return n;
}
static int pop(void *const dst, const size_t n, void *const user)
{
struct inst *const inst = user;
struct mem *const s = &inst->stack;
if (s->n < n)
{
fprintf(stderr, "%s: stack underflow\n", __func__);
return -1;
}
const char *const src = (const char *)s->p + (s->n - n);
fprintf(stderr, "popped %zu bytes: {", n);
for (size_t i = 0; i < n; i++)
{
fprintf(stderr, "%hhu", ((const char *)src)[i]);
if (i + 1 < n)
fputs(", ", stderr);
}
memcpy(dst, src, n);
if (s->n == n)
{
free(s->p);
s->p = NULL;
}
else
{
char *const p = realloc(s->p, s->n - n);
if (!p)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
s->p = p;
}
s->n -= n;
fprintf(stderr, "}, stack size=%zu\n", s->n);
return n;
}
static size_t ptr(void *const user)
{
return ((const struct inst *)user)->stack.n;
}
static int load(const nw_varuint32 offset, void *const dst, const size_t n,
const struct mem *const m)
{
if (n > m->n || offset > m->n - n)
{
fprintf(stderr, "%s: out-of-bounds access, offset=%lu, n=%zu\n",
__func__, (unsigned long)offset, n);
return -1;
}
fprintf(stderr, "%s: loaded %zu bytes from offset %lu: {", __func__, n,
(unsigned long)offset);
for (size_t i = 0; i < n; i++)
{
fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]);
if (i + 1 < n)
fputs(", ", stderr);
}
fputs("}\n", stderr);
memcpy(dst, (const char *)m->p + offset, n);
return n;
}
static int store(const nw_varuint32 offset, const void *const src,
const size_t n, const struct mem *const m)
{
if (n > m->n || offset > m->n - n)
{
fprintf(stderr, "%s: out-of-bounds access, offset=%lu, n=%zu\n",
__func__, (unsigned long)offset, n);
return -1;
}
fprintf(stderr, "%s: stored %zu bytes into offset %lu: {", __func__, n,
(unsigned long)offset);
for (size_t i = 0; i < n; i++)
{
fprintf(stderr, "%hhu", ((const char *)src)[i]);
if (i + 1 < n)
fputs(", ", stderr);
}
fputs("}\n", stderr);
memcpy((char *)m->p + offset, src, n);
return n;
}
static int stread(const size_t offset, void *const dst, const size_t n,
void *const user)
{
const struct inst *const inst = user;
return load(offset, dst, n, &inst->stack);
}
static int stwrite(const size_t offset, const void *const src, const size_t n,
void *const user)
{
const struct inst *const inst = user;
return store(offset, src, n, &inst->stack);
}
static int ensure_linear(struct mem *const m, const unsigned long offset,
const size_t n)
{
void *const p = realloc(m->p, offset + n);
if (!p)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
m->p = p;
m->n = offset + n;
memset((char *)m->p + offset, 0, n);
return 0;
}
static int lnload(const unsigned long offset, void *const dst, const size_t n,
void *const user)
{
struct inst *const i = user;
struct mem *const m = &i->linear;
if (offset >= m->n && ensure_linear(m, offset, n))
{
fprintf(stderr, "%s: ensure_linear failed\n", __func__);
return -1;
}
fprintf(stderr, "%s: loaded %zu bytes from offset %lu: {", __func__, n,
(unsigned long)offset);
for (size_t i = 0; i < n; i++)
{
fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]);
if (i + 1 < n)
fputs(", ", stderr);
}
fputs("}\n", stderr);
memcpy(dst, (const char *)m->p + offset, n);
return n;
}
static int lnstore(const unsigned long offset, const void *const src,
const size_t n, void *const user)
{
struct inst *const i = user;
struct mem *const m = &i->linear;
if (offset >= m->n && ensure_linear(m, offset, n))
{
fprintf(stderr, "%s: ensure_linear failed\n", __func__);
return -1;
}
fprintf(stderr, "%s: stored %zu bytes into offset %lu: {", __func__, n,
(unsigned long)offset);
for (size_t i = 0; i < n; i++)
{
fprintf(stderr, "%hhu", ((const char *)src)[i]);
if (i + 1 < n)
fputs(", ", stderr);
}
fputs("}\n", stderr);
memcpy((char *)m->p + offset, src, n);
return n;
}
static int glload(const unsigned long offset, void *const dst,
const size_t n, void *const user)
{
const struct inst *const i = user;
return load(offset, dst, n, &i->global);
}
static int glstore(const unsigned long offset, const void *const src,
const size_t n, void *const user)
{
const struct inst *const i = user;
return store(offset, src, n, &i->global);
}
static enum nw_state wasi_exit(const union nw_value *const params,
union nw_value *const ret, void *user, struct nw_next *const next)
{
struct inst *const i = user;
i->retval = params->i32;
return NW_OK;
}
int main(int argc, char *argv[])
{
int ret = EXIT_FAILURE;
FILE *f = NULL;
struct inst ins = {0};
if (argc != 2)
{
fprintf(stderr, "%s <wasm>\n", *argv);
goto end;
}
const char *const path = argv[1];
if (!(f = fopen(path, "rb")))
{
fprintf(stderr, "%s: fopen(3) %s: %s\n", __func__, path,
strerror(errno));
goto end;
}
static const struct nw_import imports[] =
{
{
.kind = NW_KIND_FUNCTION,
.module = "wasi_snapshot_preview1",
.field = "proc_exit",
.u.function =
{
.fn = wasi_exit,
.signature = "(i)"
}
}
};
const struct nw_io_cfg io =
{
.read = io_read,
.seek = io_seek,
.tell = io_tell,
.eof = io_eof,
.user = f
};
enum {N_IMPORTS = sizeof imports / sizeof *imports};
const struct nw_mod_cfg cfg =
{
.imports = imports,
.imp_indexes = (struct nw_import_index[N_IMPORTS]){{0}},
.n_imports = N_IMPORTS,
.io = io
};
struct nw_mod m;
struct nw_mod_out mout;
nw_init(&m, &cfg);
again:
switch (nw_load(&m, &mout))
{
case NW_OK:
break;
case NW_AGAIN:
goto again;
case NW_FATAL:
fprintf(stderr, "%s: nw_load failed\n", __func__);
goto end;
}
if (!(ins.global.p = malloc(ins.global.n = mout.global)))
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto end;
}
enum {ARGS = 1};
const struct nw_inst_cfg icfg =
{
.entry = "_start",
.interp_cfg =
{
.io = io,
.m = &m,
.user = &ins,
.args = (union nw_value[ARGS]){{0}},
.n_args = ARGS,
.stack =
{
.push = push,
.pop = pop,
.ptr = ptr,
.read = stread,
.write = stwrite
},
.linear =
{
.load = lnload,
.store = lnstore
},
.global =
{
.load = glload,
.store = glstore
}
}
};
struct nw_inst inst;
if (nw_start(&inst, &icfg))
{
fprintf(stderr, "%s: nw_start failed\n", __func__);
goto end;
}
again2:
switch (nw_run(&inst))
{
case NW_OK:
break;
case NW_AGAIN:
if (ins.retval)
{
ret = ins.retval;
fprintf(stderr, "instance exited with status %d\n", ret);
goto end;
}
goto again2;
case NW_FATAL:
fprintf(stderr, "%s: nw_run failed: %s\n", __func__,
nw_rexc(&inst));
goto end;
}
ret = EXIT_SUCCESS;
end:
if (f && fclose(f))
{
fprintf(stderr, "%s: fclose(3) %s: %s\n", __func__, path,
strerror(errno));
goto end;
}
free(ins.stack.p);
free(ins.linear.p);
free(ins.global.p);
return ret;
}