Skip to content

Commit 334de91

Browse files
Passing spec tests
1 parent 9bff1fe commit 334de91

File tree

5 files changed

+419
-251
lines changed

5 files changed

+419
-251
lines changed

src/ir/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(ir_SOURCES
2020
public-type-validator.cpp
2121
ReFinalize.cpp
2222
return-utils.cpp
23+
runtime-memory.cpp
2324
runtime-table.cpp
2425
stack-utils.cpp
2526
table-utils.cpp

src/ir/runtime-memory.cpp

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "ir/runtime-memory.h"
18+
#include "fp16.h"
19+
#include "interpreter/exception.h"
20+
21+
namespace wasm {
22+
23+
RuntimeMemory::RuntimeMemory(Memory memory, ExternalInterface* externalInterface)
24+
: externalInterface(externalInterface), memoryDefinition(std::move(memory)) {}
25+
26+
namespace {
27+
28+
Address getFinalAddress(const RuntimeMemory& runtimeMemory,
29+
Address addr,
30+
Address offset,
31+
Index bytes,
32+
Address memorySizeBytes) {
33+
if (offset > memorySizeBytes) {
34+
std::string msg = "offset > memory: ";
35+
msg += std::to_string(uint64_t(offset));
36+
msg += " > ";
37+
msg += std::to_string(uint64_t(memorySizeBytes));
38+
runtimeMemory.trap(msg);
39+
}
40+
if (addr > memorySizeBytes - offset) {
41+
std::string msg = "final > memory: ";
42+
msg += std::to_string(uint64_t(addr));
43+
msg += " > ";
44+
msg += std::to_string(uint64_t(memorySizeBytes - offset));
45+
runtimeMemory.trap(msg);
46+
}
47+
48+
addr = size_t(addr) + offset;
49+
50+
if (bytes > memorySizeBytes - addr) {
51+
std::string msg = "highest > memory: ";
52+
msg += std::to_string(uint64_t(addr));
53+
msg += " + ";
54+
msg += std::to_string(uint64_t(bytes));
55+
msg += " > ";
56+
msg += std::to_string(uint64_t(memorySizeBytes));
57+
runtimeMemory.trap(msg);
58+
}
59+
return addr;
60+
}
61+
62+
void checkLoadAddress(const RuntimeMemory& runtimeMemory,
63+
Address addr,
64+
Index bytes,
65+
Address memorySizeBytes) {
66+
if (addr > memorySizeBytes || bytes > memorySizeBytes - addr) {
67+
std::string msg = "highest > memory: ";
68+
msg += std::to_string(uint64_t(addr));
69+
msg += " + ";
70+
msg += std::to_string(uint64_t(bytes));
71+
msg += " > ";
72+
msg += std::to_string(uint64_t(memorySizeBytes));
73+
runtimeMemory.trap(msg);
74+
}
75+
}
76+
77+
void checkAtomicAddress(const RuntimeMemory& runtimeMemory,
78+
Address addr,
79+
Index bytes,
80+
Address memorySizeBytes) {
81+
checkLoadAddress(runtimeMemory, addr, bytes, memorySizeBytes);
82+
// Unaligned atomics trap.
83+
if (bytes > 1) {
84+
if (addr & (bytes - 1)) {
85+
runtimeMemory.trap("unaligned atomic operation");
86+
}
87+
}
88+
}
89+
90+
template<typename T> bool aligned(const uint8_t* address) {
91+
static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2");
92+
return 0 == (reinterpret_cast<uintptr_t>(address) & (sizeof(T) - 1));
93+
}
94+
95+
} // namespace
96+
97+
RealRuntimeMemory::RealRuntimeMemory(Memory memory,
98+
ExternalInterface* externalInterface)
99+
: RuntimeMemory(std::move(memory), externalInterface) {
100+
resize(memoryDefinition.initialByteSize());
101+
}
102+
103+
Literal RealRuntimeMemory::load(Address addr,
104+
Address offset,
105+
uint8_t byteCount,
106+
MemoryOrder order,
107+
Type type,
108+
bool signed_) const {
109+
Address final = getFinalAddress(*this, addr, offset, byteCount, size());
110+
if (order != MemoryOrder::Unordered) {
111+
checkAtomicAddress(*this, final, byteCount, size());
112+
}
113+
switch (type.getBasic()) {
114+
case Type::i32: {
115+
switch (byteCount) {
116+
case 1:
117+
return signed_ ? Literal((int32_t)get<int8_t>(final))
118+
: Literal((int32_t)get<uint8_t>(final));
119+
case 2:
120+
return signed_ ? Literal((int32_t)get<int16_t>(final))
121+
: Literal((int32_t)get<uint16_t>(final));
122+
case 4:
123+
return Literal((int32_t)get<int32_t>(final));
124+
default:
125+
WASM_UNREACHABLE("invalid size");
126+
}
127+
}
128+
case Type::i64: {
129+
switch (byteCount) {
130+
case 1:
131+
return signed_ ? Literal((int64_t)get<int8_t>(final))
132+
: Literal((int64_t)get<uint8_t>(final));
133+
case 2:
134+
return signed_ ? Literal((int64_t)get<int16_t>(final))
135+
: Literal((int64_t)get<uint16_t>(final));
136+
case 4:
137+
return signed_ ? Literal((int64_t)get<int32_t>(final))
138+
: Literal((int64_t)get<uint32_t>(final));
139+
case 8:
140+
return Literal((int64_t)get<int64_t>(final));
141+
default:
142+
WASM_UNREACHABLE("invalid size");
143+
}
144+
}
145+
case Type::f32: {
146+
switch (byteCount) {
147+
case 2:
148+
return Literal(bit_cast<int32_t>(
149+
fp16_ieee_to_fp32_value(get<uint16_t>(final))))
150+
.castToF32();
151+
case 4:
152+
return Literal(get<uint32_t>(final)).castToF32();
153+
default:
154+
WASM_UNREACHABLE("invalid size");
155+
}
156+
}
157+
case Type::f64:
158+
return Literal(get<uint64_t>(final)).castToF64();
159+
case Type::v128:
160+
return Literal(get<std::array<uint8_t, 16>>(final).data());
161+
default:
162+
WASM_UNREACHABLE("unexpected type");
163+
}
164+
}
165+
166+
void RealRuntimeMemory::store(Address addr,
167+
Address offset,
168+
uint8_t byteCount,
169+
MemoryOrder order,
170+
Literal value,
171+
Type type) {
172+
Address final = getFinalAddress(*this, addr, offset, byteCount, size());
173+
if (order != MemoryOrder::Unordered) {
174+
checkAtomicAddress(*this, final, byteCount, size());
175+
}
176+
switch (type.getBasic()) {
177+
case Type::i32: {
178+
switch (byteCount) {
179+
case 1:
180+
set<int8_t>(final, value.geti32());
181+
break;
182+
case 2:
183+
set<int16_t>(final, value.geti32());
184+
break;
185+
case 4:
186+
set<int32_t>(final, value.geti32());
187+
break;
188+
default:
189+
WASM_UNREACHABLE("invalid size");
190+
}
191+
break;
192+
}
193+
case Type::i64: {
194+
switch (byteCount) {
195+
case 1:
196+
set<int8_t>(final, value.geti64());
197+
break;
198+
case 2:
199+
set<int16_t>(final, value.geti64());
200+
break;
201+
case 4:
202+
set<int32_t>(final, value.geti64());
203+
break;
204+
case 8:
205+
set<int64_t>(final, value.geti64());
206+
break;
207+
default:
208+
WASM_UNREACHABLE("invalid size");
209+
}
210+
break;
211+
}
212+
case Type::f32: {
213+
switch (byteCount) {
214+
case 2:
215+
set<uint16_t>(final,
216+
fp16_ieee_from_fp32_value(
217+
bit_cast<float>(value.reinterpreti32())));
218+
break;
219+
case 4:
220+
set<int32_t>(final, value.reinterpreti32());
221+
break;
222+
default:
223+
WASM_UNREACHABLE("invalid size");
224+
}
225+
break;
226+
}
227+
case Type::f64:
228+
set<int64_t>(final, value.reinterpreti64());
229+
break;
230+
case Type::v128:
231+
set<std::array<uint8_t, 16>>(final, value.getv128());
232+
break;
233+
default:
234+
WASM_UNREACHABLE("unexpected type");
235+
}
236+
}
237+
238+
bool RealRuntimeMemory::grow(Address delta) {
239+
Address pageSize = memoryDefinition.pageSize();
240+
Address oldPages = intendedSize / pageSize;
241+
Address newPages = oldPages + delta;
242+
if (newPages > memoryDefinition.max && memoryDefinition.hasMax()) {
243+
return false;
244+
}
245+
// Apply a reasonable limit on memory size, 1GB, to avoid DOS on the
246+
// interpreter.
247+
if (newPages * pageSize > 1024 * 1024 * 1024) {
248+
return false;
249+
}
250+
resize(newPages * pageSize);
251+
return true;
252+
}
253+
254+
Address RealRuntimeMemory::size() const { return intendedSize; }
255+
256+
void RealRuntimeMemory::init(Address dest,
257+
Address src,
258+
Address byteCount,
259+
const DataSegment* data) {
260+
if (src > data->data.size() || byteCount > data->data.size() - src) {
261+
trap("out of bounds segment access in memory.init");
262+
}
263+
Address final = getFinalAddress(*this, dest, 0, byteCount, size());
264+
if (byteCount > 0) {
265+
std::memcpy(&memory[final], &data->data[src], byteCount);
266+
}
267+
}
268+
269+
void RealRuntimeMemory::copy(Address dest,
270+
Address src,
271+
Address byteCount,
272+
const RuntimeMemory* srcMemory) {
273+
Address finalDest = getFinalAddress(*this, dest, 0, byteCount, size());
274+
Address finalSrc = getFinalAddress(
275+
*srcMemory, src, 0, byteCount, srcMemory->size());
276+
const std::vector<uint8_t>* srcBuffer = srcMemory->getBuffer();
277+
if (!srcBuffer) {
278+
// If it's not a memory with a direct buffer, we might need another way to
279+
// access it, or we can just fail if this is the only implementation we
280+
// support for now.
281+
WASM_UNREACHABLE("unsupported srcMemory type in copy");
282+
}
283+
if (byteCount > 0) {
284+
std::memmove(&memory[finalDest], &(*srcBuffer)[finalSrc], byteCount);
285+
}
286+
}
287+
288+
void RealRuntimeMemory::fill(Address dest, uint8_t value, Address byteCount) {
289+
Address final = getFinalAddress(*this, dest, 0, byteCount, size());
290+
if (byteCount > 0) {
291+
std::memset(&memory[final], value, byteCount);
292+
}
293+
}
294+
295+
void RealRuntimeMemory::resize(size_t newSize) {
296+
intendedSize = newSize;
297+
const size_t minSize = 1 << 12;
298+
size_t oldAllocatedSize = memory.size();
299+
size_t newAllocatedSize = std::max(minSize, newSize);
300+
if (newAllocatedSize > oldAllocatedSize) {
301+
memory.resize(newAllocatedSize);
302+
std::memset(&memory[oldAllocatedSize], 0, newAllocatedSize - oldAllocatedSize);
303+
}
304+
if (newSize < oldAllocatedSize && newSize < minSize) {
305+
std::memset(&memory[newSize], 0, minSize - newSize);
306+
}
307+
}
308+
309+
template<typename T> T RealRuntimeMemory::get(size_t address) const {
310+
if (aligned<T>(&memory[address])) {
311+
return *reinterpret_cast<const T*>(&memory[address]);
312+
} else {
313+
T loaded;
314+
std::memcpy(&loaded, &memory[address], sizeof(T));
315+
return loaded;
316+
}
317+
}
318+
319+
template<typename T> void RealRuntimeMemory::set(size_t address, T value) {
320+
if (aligned<T>(&memory[address])) {
321+
*reinterpret_cast<T*>(&memory[address]) = value;
322+
} else {
323+
std::memcpy(&memory[address], &value, sizeof(T));
324+
}
325+
}
326+
327+
// Explicit instantiations for the templates
328+
template int8_t RealRuntimeMemory::get<int8_t>(size_t) const;
329+
template uint8_t RealRuntimeMemory::get<uint8_t>(size_t) const;
330+
template int16_t RealRuntimeMemory::get<int16_t>(size_t) const;
331+
template uint16_t RealRuntimeMemory::get<uint16_t>(size_t) const;
332+
template int32_t RealRuntimeMemory::get<int32_t>(size_t) const;
333+
template uint32_t RealRuntimeMemory::get<uint32_t>(size_t) const;
334+
template int64_t RealRuntimeMemory::get<int64_t>(size_t) const;
335+
template uint64_t RealRuntimeMemory::get<uint64_t>(size_t) const;
336+
template std::array<uint8_t, 16>
337+
RealRuntimeMemory::get<std::array<uint8_t, 16>>(size_t) const;
338+
339+
template void RealRuntimeMemory::set<int8_t>(size_t, int8_t);
340+
template void RealRuntimeMemory::set<uint8_t>(size_t, uint8_t);
341+
template void RealRuntimeMemory::set<int16_t>(size_t, int16_t);
342+
template void RealRuntimeMemory::set<uint16_t>(size_t, uint16_t);
343+
template void RealRuntimeMemory::set<int32_t>(size_t, int32_t);
344+
template void RealRuntimeMemory::set<uint32_t>(size_t, uint32_t);
345+
template void RealRuntimeMemory::set<int64_t>(size_t, int64_t);
346+
template void RealRuntimeMemory::set<uint64_t>(size_t, uint64_t);
347+
template void
348+
RealRuntimeMemory::set<std::array<uint8_t, 16>>(size_t, std::array<uint8_t, 16>);
349+
350+
} // namespace wasm

0 commit comments

Comments
 (0)