Coverage Report

Created: 2026-09-13 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm3/source/m3_host.h
Line
Count
Source
1
//
2
//  m3_host.h
3
//
4
//  What the engine asks of the system it is hosted on.
5
//
6
//  Everything here is something only the operating system can answer: how much native
7
//  stack this thread still has, how to get at a file's bytes, and - where the build
8
//  asks for guarded memories - how to reserve address space, commit part of it, and
9
//  turn the fault from a read past the committed part back into a Wasm trap.
10
//
11
//  Exactly one implementation is ever built:
12
//
13
//    m3_host_win32.h    VirtualAlloc/VirtualQuery, file mappings, and structured
14
//                       exception handling
15
//    m3_host_posix.h    mmap/mprotect, file mappings, and a SIGSEGV handler
16
//    m3_host_none.h     a system with none of that, which is most of platforms/:
17
//                       it reads a file rather than mapping one, and says it cannot
18
//                       measure a stack
19
//
20
//  All three are headers, and m3_core.c includes whichever one it is built for: a new
21
//  .c would have to be added to every source list that names its sources one by one,
22
//  and most of those are build scripts outside this repository. Unlike the WASI host
23
//  split, the callers here are in other translation units, so these are real
24
//  definitions rather than static ones - this header is the only declaration of them
25
//  there is.
26
//
27
//  Every call is allowed to fail, and failing is not an error: m3_HostStackBase
28
//  answers NULL on a system that cannot be asked, and the engine falls back to the
29
//  compile-time budget. Only the reservation calls report failure the caller has to
30
//  act on, and only a build with d_m3GuardedMemory has any.
31
//
32
33
#ifndef m3_host_h
34
#define m3_host_h
35
36
#include "m3_core.h"
37
38
d_m3BeginExternC
39
40
// The lowest address the calling thread's stack can reach, or NULL when this
41
// build cannot ask (see d_m3HasThreadStackProbe). What sits at the very bottom
42
// is the system's business - a guard page usually - so the answer bounds the
43
// stack rather than describing what is safe to touch; d_m3NativeStackMargin is
44
// what keeps the difference.
45
void* m3_HostStackBase (void);
46
47
48
// A file's bytes and how they were come by.
49
//
50
// 'mapped' is the one that says who gives the bytes back, and it is not the same
51
// question as whether there is a handle: a Win32 mapping keeps nothing open, because
52
// the view holds the section which holds the file object and the share mode with it,
53
// while POSIX has to hold the descriptor its lock lives on. So a mapped file may have
54
// no handle, and d_m3HostNoHandle is what that looks like.
55
0
#define d_m3HostNoHandle    ((intptr_t) -1)
56
57
typedef struct M3HostFile {
58
    void*    data;
59
    size_t   size;
60
    intptr_t handle;     // the host layer's, and d_m3HostNoHandle when there is none
61
    bool     mapped;
62
} M3HostFile;
63
64
// The module's bytes, however this system can best get at them: mapped where it can
65
// map, read onto the heap where it cannot - a system with no mapping at all, a
66
// filesystem that will not, or something that is not a file, which is what a pipe
67
// from a process substitution is. The caller is told which only by 'mapped', and
68
// needs to know only because m3_HostUnmapFile is what gives the bytes back either
69
// way.
70
//
71
// i_maxBytes refuses a file larger than the caller is willing to take, before
72
// anything is allocated for it; 0 accepts any size the system will hand over. False
73
// is that, or the file not being readable at all, and o_file is written on success
74
// only.
75
bool m3_HostMapFile (const char* i_path, size_t i_maxBytes, M3HostFile* o_file);
76
77
// Give the bytes back, releasing the mapping and its lock, or the heap block
78
void m3_HostUnmapFile (M3HostFile* io_file);
79
80
// The reading half on its own: what each m3_HostMapFile falls back to, and the whole
81
// of what a system with no mapping does. The same on every system, so it is here
82
// rather than in any of them - and inline, so that a translation unit which never
83
// calls it is not the one that drags stdio's FILE machinery into the link.
84
static inline
85
bool m3_HostReadFile (const char* i_path, size_t i_maxBytes, M3HostFile* o_file)
86
0
{
87
0
    FILE* f = fopen(i_path, "rb");
88
0
    if (f == NULL) {
89
0
        return false;
90
0
    }
91
92
    // The size is taken before anything is allocated, so that a file too big to be
93
    // wanted is refused rather than read
94
0
    fseek(f, 0, SEEK_END);
95
0
    long tell = ftell(f);
96
0
    fseek(f, 0, SEEK_SET);
97
98
0
    if (tell < 0 or (i_maxBytes and (size_t) tell > i_maxBytes)) {
99
0
        fclose(f);
100
0
        return false;
101
0
    }
102
103
0
    size_t size = (size_t)tell;
104
0
    void*  data = m3_Malloc("Host File", size ? size : 1);
105
106
0
    if (data == NULL) {
107
0
        fclose(f);
108
0
        return false;
109
0
    }
110
111
0
    bool read = (fread(data, 1, size, f) == size);
112
0
    fclose(f);
113
114
0
    if (not read) {
115
0
        m3_Free(data);
116
0
        return false;
117
0
    }
118
119
0
    o_file->data = data;
120
0
    o_file->size = size;
121
0
    o_file->handle = d_m3HostNoHandle;
122
0
    o_file->mapped = false;
123
    return true;
124
0
}
Unexecuted instantiation: m3_env.c:m3_HostReadFile
Unexecuted instantiation: m3_core.c:m3_HostReadFile
125
126
// A module is parsed in place and keeps pointing into the mapped bytes for as long
127
// as it is loaded, so this is where a module's own size stops being paid for twice:
128
// what is mapped stays in the page cache, shared with anything else running the same
129
// file, and is only ever read as far as the module is walked.
130
//
131
// What is mapped is the file itself, so a file that changes underneath the mapping
132
// changes under a running module. Each system is asked to prevent that as far as it
133
// can, and the two can do very different amounts:
134
//
135
//   Win32   a share mode the kernel enforces. Another process opening the file for
136
//           writing is refused outright while the mapping lives, so this is a real
137
//           guarantee. Other readers - another wasm3 on the same module - are fine.
138
//   POSIX   a shared advisory lock, which stops only a writer that asks for a lock
139
//           of its own. Nothing stops a plain open-and-write, because POSIX has no
140
//           portable mandatory locking: Linux removed what it had in 5.15, and
141
//           O_EXLOCK is a BSD extension that is advisory as well.
142
//
143
// So on POSIX this narrows the window rather than closing it.
144
145
#if d_m3GuardedMemory
146
147
#  if M3_SIZEOF_PTR < 8
148
#    error "d_m3GuardedMemory needs a 64-bit address space"
149
#  endif
150
#  if d_m3FixedHeap
151
#    error "d_m3GuardedMemory and d_m3FixedHeap are two different places for a linear memory to come from"
152
#  endif
153
#  if defined(_WIN32) && !defined(_MSC_VER)
154
#    error "d_m3GuardedMemory on Windows needs __try/__except - build with MSVC or clang-cl"
155
#  endif
156
157
// The system's allocation granularity: what a reservation's size and a commit's
158
// bounds are rounded to. Never zero.
159
size_t m3_HostPageSize (void);
160
161
// Reserve i_bytes of address space without backing any of it. Reading or writing it
162
// faults until m3_HostCommit says otherwise, which is the whole point: the fault is
163
// what a Wasm access past the end of its memory turns into. NULL if the system will
164
// not.
165
void*  m3_HostReserve (size_t i_bytes);
166
167
// Back [i_address, i_address + i_bytes) with pages, which read as zero. The range
168
// has to be inside a reservation from m3_HostReserve; committing what is already
169
// committed is not an error and changes nothing.
170
bool   m3_HostCommit (void* i_address, size_t i_bytes);
171
172
// Undo m3_HostCommit for [i_address, i_address + i_bytes), giving the pages back to
173
// the system and leaving the range reserved and unreachable again. What was written
174
// there is gone: committing it once more reads as zero.
175
bool   m3_HostDecommit (void* i_address, size_t i_bytes);
176
177
// Give back a whole reservation. i_bytes is what was reserved.
178
void   m3_HostRelease (void* i_address, size_t i_bytes);
179
180
// Run i_body(i_context) with a fault inside [i_guardLow, i_guardLow + i_guardBytes)
181
// caught and reported rather than killing the process: false means the body touched
182
// that range and did not finish. Any other fault is left to whatever would have
183
// handled it, so an engine bug still looks like an engine bug.
184
//
185
// Nests, so a host function calling back into Wasm gets a region of its own, and is
186
// per-thread. What the body was doing when it faulted is abandoned where it stands -
187
// the caller has to be able to carry on without it, which for the interpreter means
188
// the Wasm stack and the memory, both of which are just data.
189
bool   m3_HostProtectedCall (void (*i_body)(void*), void* i_context,
190
                             void* i_guardLow, size_t i_guardBytes);
191
192
#endif // d_m3GuardedMemory
193
194
d_m3EndExternC
195
196
#endif // m3_host_h