blob: 18fad752193b15281f345966c0b7550b6419fb27 [file] [log] [blame]
/*
* Copyright 2026 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ir/runtime-memory.h"
#include "fp16.h"
#include "interpreter/exception.h"
#include <iostream>
namespace wasm {
namespace {
[[noreturn]] void trap(std::string_view reason) {
std::cout << "[trap " << reason << "]\n";
throw TrapException{};
}
Address getFinalAddress(Address addr,
Address offset,
Index bytes,
Address memorySizeBytes) {
if (offset > memorySizeBytes) {
std::string msg = "offset > memory: ";
msg += std::to_string(uint64_t(offset));
msg += " > ";
msg += std::to_string(uint64_t(memorySizeBytes));
trap(msg);
}
if (addr > memorySizeBytes - offset) {
std::string msg = "final > memory: ";
msg += std::to_string(uint64_t(addr));
msg += " > ";
msg += std::to_string(uint64_t(memorySizeBytes - offset));
trap(msg);
}
addr = size_t(addr) + offset;
if (bytes > memorySizeBytes - addr) {
std::string msg = "highest > memory: ";
msg += std::to_string(uint64_t(addr));
msg += " + ";
msg += std::to_string(uint64_t(bytes));
msg += " > ";
msg += std::to_string(uint64_t(memorySizeBytes));
trap(msg);
}
return addr;
}
void checkLoadAddress(Address addr,
Index bytes,
Address memorySizeBytes) {
if (addr > memorySizeBytes || bytes > memorySizeBytes - addr) {
std::string msg = "highest > memory: ";
msg += std::to_string(uint64_t(addr));
msg += " + ";
msg += std::to_string(uint64_t(bytes));
msg += " > ";
msg += std::to_string(uint64_t(memorySizeBytes));
trap(msg);
}
}
void checkAtomicAddress(Address addr,
Index bytes,
Address memorySizeBytes) {
checkLoadAddress(addr, bytes, memorySizeBytes);
// Unaligned atomics trap.
if (bytes > 1) {
if (addr & (bytes - 1)) {
trap("unaligned atomic operation");
}
}
}
template<typename T> bool aligned(const uint8_t* address) {
static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2");
return 0 == (reinterpret_cast<uintptr_t>(address) & (sizeof(T) - 1));
}
} // namespace
RealRuntimeMemory::RealRuntimeMemory(Memory memory)
: RuntimeMemory(std::move(memory)) {
resize(memoryDefinition.initialByteSize());
}
Literal RealRuntimeMemory::load(Address addr,
Address offset,
uint8_t byteCount,
MemoryOrder order,
Type type,
bool signed_) const {
Address final = getFinalAddress(addr, offset, byteCount, size());
if (order != MemoryOrder::Unordered) {
checkAtomicAddress(final, byteCount, size());
}
switch (type.getBasic()) {
case Type::i32: {
switch (byteCount) {
case 1:
return signed_ ? Literal((int32_t)get<int8_t>(final))
: Literal((int32_t)get<uint8_t>(final));
case 2:
return signed_ ? Literal((int32_t)get<int16_t>(final))
: Literal((int32_t)get<uint16_t>(final));
case 4:
return Literal((int32_t)get<int32_t>(final));
default:
WASM_UNREACHABLE("invalid size");
}
}
case Type::i64: {
switch (byteCount) {
case 1:
return signed_ ? Literal((int64_t)get<int8_t>(final))
: Literal((int64_t)get<uint8_t>(final));
case 2:
return signed_ ? Literal((int64_t)get<int16_t>(final))
: Literal((int64_t)get<uint16_t>(final));
case 4:
return signed_ ? Literal((int64_t)get<int32_t>(final))
: Literal((int64_t)get<uint32_t>(final));
case 8:
return Literal((int64_t)get<int64_t>(final));
default:
WASM_UNREACHABLE("invalid size");
}
}
case Type::f32: {
switch (byteCount) {
case 2:
return Literal(bit_cast<int32_t>(
fp16_ieee_to_fp32_value(get<uint16_t>(final))))
.castToF32();
case 4:
return Literal(get<uint32_t>(final)).castToF32();
default:
WASM_UNREACHABLE("invalid size");
}
}
case Type::f64:
return Literal(get<uint64_t>(final)).castToF64();
case Type::v128:
return Literal(get<std::array<uint8_t, 16>>(final).data());
default:
WASM_UNREACHABLE("unexpected type");
}
}
void RealRuntimeMemory::store(Address addr,
Address offset,
uint8_t byteCount,
MemoryOrder order,
Literal value,
Type type) {
Address final = getFinalAddress(addr, offset, byteCount, size());
if (order != MemoryOrder::Unordered) {
checkAtomicAddress(final, byteCount, size());
}
switch (type.getBasic()) {
case Type::i32: {
switch (byteCount) {
case 1:
set<int8_t>(final, value.geti32());
break;
case 2:
set<int16_t>(final, value.geti32());
break;
case 4:
set<int32_t>(final, value.geti32());
break;
default:
WASM_UNREACHABLE("invalid size");
}
break;
}
case Type::i64: {
switch (byteCount) {
case 1:
set<int8_t>(final, value.geti64());
break;
case 2:
set<int16_t>(final, value.geti64());
break;
case 4:
set<int32_t>(final, value.geti64());
break;
case 8:
set<int64_t>(final, value.geti64());
break;
default:
WASM_UNREACHABLE("invalid size");
}
break;
}
case Type::f32: {
switch (byteCount) {
case 2:
set<uint16_t>(final,
fp16_ieee_from_fp32_value(
bit_cast<float>(value.reinterpreti32())));
break;
case 4:
set<int32_t>(final, value.reinterpreti32());
break;
default:
WASM_UNREACHABLE("invalid size");
}
break;
}
case Type::f64:
set<int64_t>(final, value.reinterpreti64());
break;
case Type::v128:
set<std::array<uint8_t, 16>>(final, value.getv128());
break;
default:
WASM_UNREACHABLE("unexpected type");
}
}
bool RealRuntimeMemory::grow(Address delta) {
Address pageSize = memoryDefinition.pageSize();
Address oldPages = intendedSize / pageSize;
Address newPages = oldPages + delta;
if (newPages > memoryDefinition.max && memoryDefinition.hasMax()) {
return false;
}
// Apply a reasonable limit on memory size, 1GB, to avoid DOS on the
// interpreter.
if (newPages * pageSize > 1024 * 1024 * 1024) {
return false;
}
resize(newPages * pageSize);
return true;
}
Address RealRuntimeMemory::size() const { return intendedSize; }
void RealRuntimeMemory::init(Address dest,
Address src,
Address byteCount,
const DataSegment* data) {
if (src > data->data.size() || byteCount > data->data.size() - src) {
trap("out of bounds segment access in memory.init");
}
Address final = getFinalAddress(dest, 0, byteCount, size());
if (byteCount > 0) {
std::memcpy(&memory[final], &data->data[src], byteCount);
}
}
void RealRuntimeMemory::copy(Address dest,
Address src,
Address byteCount,
const RuntimeMemory* srcMemory) {
Address finalDest = getFinalAddress(dest, 0, byteCount, size());
Address finalSrc = getFinalAddress(src, 0, byteCount, srcMemory->size());
const std::vector<uint8_t>* srcBuffer = srcMemory->getBuffer();
if (!srcBuffer) {
// If it's not a memory with a direct buffer, we might need another way to
// access it, or we can just fail if this is the only implementation we
// support for now.
WASM_UNREACHABLE("unsupported srcMemory type in copy");
}
if (byteCount > 0) {
std::memmove(&memory[finalDest], &(*srcBuffer)[finalSrc], byteCount);
}
}
void RealRuntimeMemory::fill(Address dest, uint8_t value, Address byteCount) {
Address final = getFinalAddress(dest, 0, byteCount, size());
if (byteCount > 0) {
std::memset(&memory[final], value, byteCount);
}
}
void RealRuntimeMemory::resize(size_t newSize) {
intendedSize = newSize;
const size_t minSize = 1 << 12;
size_t oldAllocatedSize = memory.size();
size_t newAllocatedSize = std::max(minSize, newSize);
if (newAllocatedSize > oldAllocatedSize) {
memory.resize(newAllocatedSize);
std::memset(&memory[oldAllocatedSize], 0, newAllocatedSize - oldAllocatedSize);
}
if (newSize < oldAllocatedSize && newSize < minSize) {
std::memset(&memory[newSize], 0, minSize - newSize);
}
}
template<typename T> T RealRuntimeMemory::get(size_t address) const {
if (aligned<T>(&memory[address])) {
return *reinterpret_cast<const T*>(&memory[address]);
} else {
T loaded;
std::memcpy(&loaded, &memory[address], sizeof(T));
return loaded;
}
}
template<typename T> void RealRuntimeMemory::set(size_t address, T value) {
if (aligned<T>(&memory[address])) {
*reinterpret_cast<T*>(&memory[address]) = value;
} else {
std::memcpy(&memory[address], &value, sizeof(T));
}
}
// Explicit instantiations for the templates
template int8_t RealRuntimeMemory::get<int8_t>(size_t) const;
template uint8_t RealRuntimeMemory::get<uint8_t>(size_t) const;
template int16_t RealRuntimeMemory::get<int16_t>(size_t) const;
template uint16_t RealRuntimeMemory::get<uint16_t>(size_t) const;
template int32_t RealRuntimeMemory::get<int32_t>(size_t) const;
template uint32_t RealRuntimeMemory::get<uint32_t>(size_t) const;
template int64_t RealRuntimeMemory::get<int64_t>(size_t) const;
template uint64_t RealRuntimeMemory::get<uint64_t>(size_t) const;
template std::array<uint8_t, 16>
RealRuntimeMemory::get<std::array<uint8_t, 16>>(size_t) const;
template void RealRuntimeMemory::set<int8_t>(size_t, int8_t);
template void RealRuntimeMemory::set<uint8_t>(size_t, uint8_t);
template void RealRuntimeMemory::set<int16_t>(size_t, int16_t);
template void RealRuntimeMemory::set<uint16_t>(size_t, uint16_t);
template void RealRuntimeMemory::set<int32_t>(size_t, int32_t);
template void RealRuntimeMemory::set<uint32_t>(size_t, uint32_t);
template void RealRuntimeMemory::set<int64_t>(size_t, int64_t);
template void RealRuntimeMemory::set<uint64_t>(size_t, uint64_t);
template void
RealRuntimeMemory::set<std::array<uint8_t, 16>>(size_t, std::array<uint8_t, 16>);
} // namespace wasm