diff --git a/.ci/run_test_suite.sh b/.ci/run_test_suite.sh index 668757cec..721db2b2e 100644 --- a/.ci/run_test_suite.sh +++ b/.ci/run_test_suite.sh @@ -18,6 +18,8 @@ # TestSuite=microsuite # # 'cpu' or 'check' # CPU_EXCEPTION_TYPE='cpu' +# # Enable bulk memory operations proposal +# ENABLE_BULK_MEMORY=true set -e @@ -93,6 +95,16 @@ case $CPU_EXCEPTION_TYPE in ;; esac +# Enable bulk memory operations proposal (default: true) +if [ -z "$ENABLE_BULK_MEMORY" ]; then + ENABLE_BULK_MEMORY=true +fi +if [ ${ENABLE_BULK_MEMORY} = true ]; then + CMAKE_OPTIONS="$CMAKE_OPTIONS -DZEN_ENABLE_BULK_MEMORY=ON" +else + CMAKE_OPTIONS="$CMAKE_OPTIONS -DZEN_ENABLE_BULK_MEMORY=OFF" +fi + STACK_TYPES=("-DZEN_ENABLE_VIRTUAL_STACK=ON" "-DZEN_ENABLE_VIRTUAL_STACK=OFF") if [[ $RUN_MODE == "interpreter" ]]; then STACK_TYPES=("-DZEN_ENABLE_VIRTUAL_STACK=OFF") diff --git a/.github/workflows/dtvm_wasm_test_x86.yml b/.github/workflows/dtvm_wasm_test_x86.yml index 4fd9c4896..3142a917c 100644 --- a/.github/workflows/dtvm_wasm_test_x86.yml +++ b/.github/workflows/dtvm_wasm_test_x86.yml @@ -56,6 +56,7 @@ jobs: export TestSuite=microsuite export CPU_EXCEPTION_TYPE='check' export ENABLE_GAS_METER=false + export ENABLE_BULK_MEMORY=true bash .ci/run_test_suite.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 49275bea2..9fcc2e10e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,9 @@ option(ZEN_ENABLE_MULTIPASS_JIT "Enable multipass JIT" OFF) option(ZEN_ENABLE_CLI "Enable command line interface" ON) option(ZEN_ENABLE_BUILTIN_WASI "Enable builtin wasi" ON) option(ZEN_ENABLE_BUILTIN_LIBC "Enable builtin libc (partial)" ON) + +# WebAssembly proposal options +option(ZEN_ENABLE_BULK_MEMORY "Enable bulk memory operations proposal" ON) option(ZEN_ENABLE_LIBEVM "Enable evmc library build" OFF) # Feature options diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bbe49d442..9e6cff0d8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,10 @@ if(ZEN_ENABLE_BUILTIN_WASI) add_definitions(-DZEN_ENABLE_BUILTIN_WASI) endif() +if(ZEN_ENABLE_BULK_MEMORY) + add_definitions(-DZEN_ENABLE_BULK_MEMORY) +endif() + if(ZEN_ENABLE_BUILTIN_ENV) add_definitions(-DZEN_ENABLE_BUILTIN_ENV) endif() diff --git a/src/action/bytecode_visitor.h b/src/action/bytecode_visitor.h index 5d22dd711..8f373dee1 100644 --- a/src/action/bytecode_visitor.h +++ b/src/action/bytecode_visitor.h @@ -722,6 +722,52 @@ template class WASMByteCodeVisitor { handleIntExtend(); break; + case common::WASM_PREFIX_FC: { // Bulk memory operations prefix +#ifdef ZEN_ENABLE_BULK_MEMORY + uint32_t SubOpcode; + Ip = readSafeLEBNumber(Ip, SubOpcode); + // Skip operands based on sub-opcode + switch (SubOpcode) { + case common::FC_MEMORY_INIT: // memory.init: dataidx(LEB) + memidx(1 + // byte) + Ip = utils::skipLEBNumber(Ip, IpEnd); + Ip++; // skip memidx + break; + case common::FC_DATA_DROP: // data.drop: dataidx(LEB) + Ip = utils::skipLEBNumber(Ip, IpEnd); + break; + case common::FC_MEMORY_COPY: // memory.copy: 2 bytes + Ip += 2; + break; + case common::FC_MEMORY_FILL: // memory.fill: 1 byte + Ip++; + break; + case common::FC_TABLE_INIT: // table.init: elemidx(LEB) + tableidx(LEB) + Ip = utils::skipLEBNumber(Ip, IpEnd); + Ip = utils::skipLEBNumber(Ip, IpEnd); + break; + case common::FC_ELEM_DROP: // elem.drop: elemidx(LEB) + Ip = utils::skipLEBNumber(Ip, IpEnd); + break; + case common::FC_TABLE_COPY: // table.copy: dst_tableidx(LEB) + + // src_tableidx(LEB) + Ip = utils::skipLEBNumber(Ip, IpEnd); + Ip = utils::skipLEBNumber(Ip, IpEnd); + break; + default: + break; + } + throw getErrorWithExtraMessage( + ErrorCode::UnsupportedOpcode, + "bulk memory operations not supported in JIT mode"); +#else + throw getErrorWithExtraMessage( + ErrorCode::UnsupportedOpcode, + "bulk memory operations not enabled (compile with " + "ZEN_ENABLE_BULK_MEMORY=ON)"); +#endif + } + default: throw getErrorWithExtraMessage(ErrorCode::UnsupportedOpcode, std::to_string(Opcode)); diff --git a/src/action/function_loader.cpp b/src/action/function_loader.cpp index 3cb3c295b..cec4910fc 100644 --- a/src/action/function_loader.cpp +++ b/src/action/function_loader.cpp @@ -671,6 +671,120 @@ void FunctionLoader::load() { case I64_EXTEND32_S: popAndPushValueType(1, WASMType::I64, WASMType::I64); break; + case WASM_PREFIX_FC: { +#ifdef ZEN_ENABLE_BULK_MEMORY + // Multi-byte opcode prefix for bulk memory operations + uint32_t SubOpcode = readU32(); + switch (SubOpcode) { + case FC_MEMORY_INIT: { // memory.init: dataidx + 0x00 + uint32_t DataIdx = readU32(); + uint8_t MemIdx = to_underlying(readByte()); + if (MemIdx != 0x00) { + throw getError(ErrorCode::ZeroFlagExpected); + } + if (Mod.DataCount == -1u) { + throw getError(ErrorCode::UnknownDataSegment); + } + if (!Mod.isValidDataSegment(DataIdx)) { + throw getError(ErrorCode::UnknownDataSegment); + } + if (!hasMemory()) { + throw getError(ErrorCode::UnknownMemory); + } + popValueType(WASMType::I32); // n + popValueType(WASMType::I32); // s + popValueType(WASMType::I32); // d + FuncCodeEntry.Stats |= Module::SF_memory; + break; + } + case FC_DATA_DROP: { // data.drop: dataidx + uint32_t DataIdx = readU32(); + if (Mod.DataCount == -1u) { + throw getError(ErrorCode::UnknownDataSegment); + } + if (!Mod.isValidDataSegment(DataIdx)) { + throw getError(ErrorCode::UnknownDataSegment); + } + break; + } + case FC_MEMORY_COPY: { // memory.copy: 0x00 0x00 + uint8_t DstMemIdx = to_underlying(readByte()); + uint8_t SrcMemIdx = to_underlying(readByte()); + if (DstMemIdx != 0x00 || SrcMemIdx != 0x00) { + throw getError(ErrorCode::ZeroFlagExpected); + } + if (!hasMemory()) { + throw getError(ErrorCode::UnknownMemory); + } + popValueType(WASMType::I32); // n + popValueType(WASMType::I32); // s + popValueType(WASMType::I32); // d + FuncCodeEntry.Stats |= Module::SF_memory; + break; + } + case FC_MEMORY_FILL: { // memory.fill: 0x00 + uint8_t MemIdx = to_underlying(readByte()); + if (MemIdx != 0x00) { + throw getError(ErrorCode::ZeroFlagExpected); + } + if (!hasMemory()) { + throw getError(ErrorCode::UnknownMemory); + } + popValueType(WASMType::I32); // n + popValueType(WASMType::I32); // val + popValueType(WASMType::I32); // d + FuncCodeEntry.Stats |= Module::SF_memory; + break; + } + case FC_TABLE_INIT: { // table.init: elemidx + tableidx + uint32_t ElemIdx = readU32(); + uint32_t TableIdx = readU32(); + if (!Mod.isValidElemSegment(ElemIdx)) { + throw getError(ErrorCode::UnknownElemSegment); + } + if (!Mod.isValidTable(TableIdx)) { + throw getError(ErrorCode::UnknownTable); + } + popValueType(WASMType::I32); // n + popValueType(WASMType::I32); // s + popValueType(WASMType::I32); // d + FuncCodeEntry.Stats |= Module::SF_table; + break; + } + case FC_ELEM_DROP: { // elem.drop: elemidx + uint32_t ElemIdx = readU32(); + if (!Mod.isValidElemSegment(ElemIdx)) { + throw getError(ErrorCode::UnknownElemSegment); + } + break; + } + case FC_TABLE_COPY: { // table.copy: dst_tableidx + src_tableidx + uint32_t DstTableIdx = readU32(); + uint32_t SrcTableIdx = readU32(); + if (!Mod.isValidTable(DstTableIdx)) { + throw getError(ErrorCode::UnknownTable); + } + if (!Mod.isValidTable(SrcTableIdx)) { + throw getError(ErrorCode::UnknownTable); + } + popValueType(WASMType::I32); // n + popValueType(WASMType::I32); // s + popValueType(WASMType::I32); // d + FuncCodeEntry.Stats |= Module::SF_table; + break; + } + default: + throw getErrorWithExtraMessage(ErrorCode::UnsupportedOpcode, + "0xFC " + std::to_string(SubOpcode)); + } +#else + throw getErrorWithExtraMessage( + ErrorCode::UnsupportedOpcode, + "bulk memory operations not enabled (compile with " + "ZEN_ENABLE_BULK_MEMORY=ON)"); +#endif + break; + } case I32_LOAD: case I64_LOAD: case F32_LOAD: diff --git a/src/action/instantiator.cpp b/src/action/instantiator.cpp index 6d94f0b66..2aeae21c9 100644 --- a/src/action/instantiator.cpp +++ b/src/action/instantiator.cpp @@ -166,6 +166,10 @@ void Instantiator::instantiateTables(Instance &Inst) { for (uint32_t I = 0; I < Mod.NumElementSegments; ++I) { const auto &Element = Mod.ElementTable[I]; +#ifdef ZEN_ENABLE_BULK_MEMORY + if (Element.Mode != 0) + continue; // skip non-active segments +#endif TableInstance &TableInst = Inst.Tables[Element.TableIdx]; uint32_t Offset = 0; if (Element.InitExprKind == GET_GLOBAL) { @@ -209,6 +213,10 @@ void Instantiator::initMemoryByDataSegments(Instance &Inst) { const Module *Mod = Inst.Mod; for (uint32_t I = 0; I < Mod->NumDataSegments; ++I) { const auto &DataSeg = Mod->DataTable[I]; +#ifdef ZEN_ENABLE_BULK_MEMORY + if (DataSeg.Mode != 0) + continue; // skip non-active segments +#endif uint32_t MemIdx = DataSeg.MemIdx; // should checked if MemIndex is valid in loader MemoryInstance &MemInst = Inst.Memories[MemIdx]; @@ -349,6 +357,30 @@ void Instantiator::instantiate(Instance &Inst) { instantiateMemories(Inst); +#ifdef ZEN_ENABLE_BULK_MEMORY + // Initialize dropped segment tracking arrays + if (Mod.NumDataSegments > 0) { + Inst.DroppedDataSegments = + (bool *)Inst.allocateZeros(sizeof(bool) * Mod.NumDataSegments); + // Mark active data segments as dropped after instantiation (per spec) + for (uint32_t I = 0; I < Mod.NumDataSegments; ++I) { + if (Mod.DataTable[I].Mode == 0) { + Inst.DroppedDataSegments[I] = true; + } + } + } + if (Mod.NumElementSegments > 0) { + Inst.DroppedElemSegments = + (bool *)Inst.allocateZeros(sizeof(bool) * Mod.NumElementSegments); + // Mark active element segments as dropped after instantiation (per spec) + for (uint32_t I = 0; I < Mod.NumElementSegments; ++I) { + if (Mod.ElementTable[I].Mode == 0) { + Inst.DroppedElemSegments[I] = true; + } + } + } +#endif + #ifdef ZEN_ENABLE_BUILTIN_WASI if (!Inst.getRuntime()->getConfig().DisableWASI) { instantiateWasi(Inst); diff --git a/src/action/interpreter.cpp b/src/action/interpreter.cpp index dbfa494b4..32f962d15 100644 --- a/src/action/interpreter.cpp +++ b/src/action/interpreter.cpp @@ -538,6 +538,41 @@ class BaseInterpreterImpl { case I64_EXTEND16_S: case I64_EXTEND32_S: break; + case WASM_PREFIX_FC: { // Bulk memory operations prefix +#ifdef ZEN_ENABLE_BULK_MEMORY + uint32_t SubOpcode = 0; + Ptr = readSafeLEBNumber(Ptr, SubOpcode); + switch (SubOpcode) { + case FC_MEMORY_INIT: // memory.init: dataidx(LEB) + memidx(1 byte) + Ptr = skipLEBNumber(Ptr, End); + Ptr++; // skip memidx + break; + case FC_DATA_DROP: // data.drop: dataidx(LEB) + Ptr = skipLEBNumber(Ptr, End); + break; + case FC_MEMORY_COPY: // memory.copy: 2 bytes + Ptr += 2; + break; + case FC_MEMORY_FILL: // memory.fill: 1 byte + Ptr++; + break; + case FC_TABLE_INIT: // table.init: elemidx(LEB) + tableidx(LEB) + Ptr = skipLEBNumber(Ptr, End); + Ptr = skipLEBNumber(Ptr, End); + break; + case FC_ELEM_DROP: // elem.drop: elemidx(LEB) + Ptr = skipLEBNumber(Ptr, End); + break; + case FC_TABLE_COPY: // table.copy: dst_tableidx(LEB) + src_tableidx(LEB) + Ptr = skipLEBNumber(Ptr, End); + Ptr = skipLEBNumber(Ptr, End); + break; + default: + break; + } +#endif + break; + } case I32_LOAD: case I64_LOAD: case F32_LOAD: @@ -2093,8 +2128,146 @@ void BaseInterpreterImpl::interpret() { BREAK; } DEFAULT : { - ZEN_LOG_ERROR("munimplemented opcode: 0x%x", Opcode); - ZEN_ASSERT_TODO(); + if (Opcode == WASM_PREFIX_FC) { +#ifdef ZEN_ENABLE_BULK_MEMORY + // Bulk memory operations prefix + uint32_t SubOpcode = 0; + Ip = readSafeLEBNumber(Ip, SubOpcode); + switch (SubOpcode) { + case FC_MEMORY_INIT: { // memory.init + uint32_t DataIdx = 0; + Ip = readSafeLEBNumber(Ip, DataIdx); + Ip++; // skip memidx (0x00) + uint32_t N = Frame->valuePop(ValStackPtr); + uint32_t S = Frame->valuePop(ValStackPtr); + uint32_t D = Frame->valuePop(ValStackPtr); + const DataEntry *DataSeg = Mod->getDataEntry(DataIdx); + bool Dropped = ModInst->isDataSegmentDropped(DataIdx); + if (Dropped) { + if (N == 0 && S == 0) { + // Zero-length init with offset 0 on dropped segment is OK + // but still need to check D <= MemSize + if ((uint64_t)D > LinearMemSize) { + throw getError(ErrorCode::OutOfBoundsMemory); + } + BREAK; + } + throw getError(ErrorCode::OutOfBoundsMemory); + } + if ((uint64_t)S + (uint64_t)N > (uint64_t)DataSeg->Size) { + throw getError(ErrorCode::OutOfBoundsMemory); + } + if ((uint64_t)D + (uint64_t)N > LinearMemSize) { + throw getError(ErrorCode::OutOfBoundsMemory); + } + if (N > 0) { + std::memcpy(Memory->MemBase + D, + Mod->getWASMBytecode() + DataSeg->Offset + S, N); + } + BREAK; + } + case FC_DATA_DROP: { // data.drop + uint32_t DataIdx = 0; + Ip = readSafeLEBNumber(Ip, DataIdx); + ModInst->dropDataSegment(DataIdx); + BREAK; + } + case FC_MEMORY_COPY: { // memory.copy + Ip += 2; // skip dst_memidx and src_memidx (both 0x00) + uint32_t N = Frame->valuePop(ValStackPtr); + uint32_t S = Frame->valuePop(ValStackPtr); + uint32_t D = Frame->valuePop(ValStackPtr); + if ((uint64_t)S + (uint64_t)N > LinearMemSize || + (uint64_t)D + (uint64_t)N > LinearMemSize) { + throw getError(ErrorCode::OutOfBoundsMemory); + } + if (N > 0) { + std::memmove(Memory->MemBase + D, Memory->MemBase + S, N); + } + BREAK; + } + case FC_MEMORY_FILL: { // memory.fill + Ip++; // skip memidx (0x00) + uint32_t N = Frame->valuePop(ValStackPtr); + uint32_t Val = Frame->valuePop(ValStackPtr); + uint32_t D = Frame->valuePop(ValStackPtr); + if ((uint64_t)D + (uint64_t)N > LinearMemSize) { + throw getError(ErrorCode::OutOfBoundsMemory); + } + if (N > 0) { + std::memset(Memory->MemBase + D, (uint8_t)Val, N); + } + BREAK; + } + case FC_TABLE_INIT: { // table.init + uint32_t ElemIdx = 0, TableIdx = 0; + Ip = readSafeLEBNumber(Ip, ElemIdx); + Ip = readSafeLEBNumber(Ip, TableIdx); + uint32_t N = Frame->valuePop(ValStackPtr); + uint32_t S = Frame->valuePop(ValStackPtr); + uint32_t D = Frame->valuePop(ValStackPtr); + const ElemEntry *ElemSeg = Mod->getElemEntry(ElemIdx); + TableInstance *Table = ModInst->getTableInst(TableIdx); + bool Dropped = ModInst->isElemSegmentDropped(ElemIdx); + if (Dropped) { + if (N == 0 && S == 0) { + if (D > Table->CurSize) { + throw getError(ErrorCode::OutOfBoundsTable); + } + BREAK; + } + throw getError(ErrorCode::OutOfBoundsTable); + } + if ((uint64_t)S + (uint64_t)N > (uint64_t)ElemSeg->NumFuncIdxs) { + throw getError(ErrorCode::OutOfBoundsTable); + } + if ((uint64_t)D + (uint64_t)N > (uint64_t)Table->CurSize) { + throw getError(ErrorCode::OutOfBoundsTable); + } + if (N > 0) { + std::memcpy(Table->Elements + D, ElemSeg->FuncIdxs + S, + N * sizeof(uint32_t)); + } + BREAK; + } + case FC_ELEM_DROP: { // elem.drop + uint32_t ElemIdx = 0; + Ip = readSafeLEBNumber(Ip, ElemIdx); + ModInst->dropElemSegment(ElemIdx); + BREAK; + } + case FC_TABLE_COPY: { // table.copy + uint32_t DstTableIdx = 0, SrcTableIdx = 0; + Ip = readSafeLEBNumber(Ip, DstTableIdx); + Ip = readSafeLEBNumber(Ip, SrcTableIdx); + uint32_t N = Frame->valuePop(ValStackPtr); + uint32_t S = Frame->valuePop(ValStackPtr); + uint32_t D = Frame->valuePop(ValStackPtr); + TableInstance *DstTable = ModInst->getTableInst(DstTableIdx); + TableInstance *SrcTable = ModInst->getTableInst(SrcTableIdx); + if ((uint64_t)S + (uint64_t)N > (uint64_t)SrcTable->CurSize || + (uint64_t)D + (uint64_t)N > (uint64_t)DstTable->CurSize) { + throw getError(ErrorCode::OutOfBoundsTable); + } + if (N > 0) { + std::memmove(DstTable->Elements + D, SrcTable->Elements + S, + N * sizeof(uint32_t)); + } + BREAK; + } + default: + ZEN_LOG_ERROR("unimplemented 0xFC sub-opcode: 0x%x", SubOpcode); + ZEN_ASSERT_TODO(); + } +#else + ZEN_LOG_ERROR("bulk memory operations not enabled (compile with " + "ZEN_ENABLE_BULK_MEMORY=ON)"); + ZEN_ASSERT_TODO(); +#endif + } else { + ZEN_LOG_ERROR("unimplemented opcode: 0x%x", Opcode); + ZEN_ASSERT_TODO(); + } } } // TODO: write back ValueStackPtr, Ip, CtrlStackPtr to Frame diff --git a/src/action/module_loader.cpp b/src/action/module_loader.cpp index 79331d561..3eed48035 100644 --- a/src/action/module_loader.cpp +++ b/src/action/module_loader.cpp @@ -810,33 +810,81 @@ void ModuleLoader::loadElementSection() { ElemEntry *Entry = Mod.initElemTable(NumElemSegments); for (uint32_t I = 0; I < NumElemSegments; ++I) { - uint32_t TableIdx = readU32(); - if (!Mod.isValidTable(TableIdx)) { - throw getError(ErrorCode::UnknownTable); - } - - const auto [ExprKind, Expr] = readConstExpr(WASMType::I32); + uint32_t SegFlags = readU32(); - uint32_t NumFuncIdxs = readU32(); - // Set the field `NumFuncIdxs` and `FuncIdxs` implicitly - uint32_t *FuncIdxs = Mod.initFuncIdxTable(NumFuncIdxs, Entry); - for (uint32_t J = 0; J < NumFuncIdxs; ++J) { - uint32_t FuncIdx = readU32(); - if (!Mod.isValidFunc(FuncIdx)) { - throw getError(ErrorCode::UnknownFunction); + if (SegFlags == 0) { + // Format 0: active, table 0, offset expr, vec(funcidx) + // Validate that table 0 exists (required for active element segments) + if (!Mod.isValidTable(0)) { + throw getError(ErrorCode::UnknownTable); } - FuncIdxs[J] = FuncIdx; + + const auto [ExprKind, Expr] = readConstExpr(WASMType::I32); + + uint32_t NumFuncIdxs = readU32(); + uint32_t *FuncIdxs = Mod.initFuncIdxTable(NumFuncIdxs, Entry); + for (uint32_t J = 0; J < NumFuncIdxs; ++J) { + uint32_t FuncIdx = readU32(); + if (!Mod.isValidFunc(FuncIdx)) { + throw getError(ErrorCode::UnknownFunction); + } + FuncIdxs[J] = FuncIdx; #ifdef ZEN_ENABLE_MULTIPASS_JIT - if (ZEN_LIKELY(FuncIdx >= Mod.NumImportFunctions)) { - uint32_t TypeIdx = Mod.getFunctionTypeIdx(FuncIdx); - Mod.TypedFuncRefs[TypeIdx].push_back(FuncIdx); + if (ZEN_LIKELY(FuncIdx >= Mod.NumImportFunctions)) { + uint32_t TypeIdx = Mod.getFunctionTypeIdx(FuncIdx); + Mod.TypedFuncRefs[TypeIdx].push_back(FuncIdx); + } +#endif } + + Entry->TableIdx = 0; + Entry->InitExprKind = ExprKind; + Entry->InitExprVal = Expr; +#ifdef ZEN_ENABLE_BULK_MEMORY + Entry->Mode = 0; // active #endif } +#ifdef ZEN_ENABLE_BULK_MEMORY + else if (SegFlags == 1) { + // Format 1: passive, elemkind(0x00), vec(funcidx) + uint8_t ElemKind = to_underlying(readByte()); + if (ElemKind != 0x00) { + throw getError(ErrorCode::InvalidType); + } - Entry->TableIdx = TableIdx; - Entry->InitExprKind = ExprKind; - Entry->InitExprVal = Expr; + uint32_t NumFuncIdxs = readU32(); + uint32_t *FuncIdxs = Mod.initFuncIdxTable(NumFuncIdxs, Entry); + for (uint32_t J = 0; J < NumFuncIdxs; ++J) { + uint32_t FuncIdx = readU32(); + if (!Mod.isValidFunc(FuncIdx)) { + throw getError(ErrorCode::UnknownFunction); + } + FuncIdxs[J] = FuncIdx; +#ifdef ZEN_ENABLE_MULTIPASS_JIT + if (ZEN_LIKELY(FuncIdx >= Mod.NumImportFunctions)) { + uint32_t TypeIdx = Mod.getFunctionTypeIdx(FuncIdx); + Mod.TypedFuncRefs[TypeIdx].push_back(FuncIdx); + } +#endif + } + + Entry->TableIdx = 0; + Entry->InitExprKind = 0; + Entry->InitExprVal = {}; + Entry->Mode = 1; // passive + } +#endif + else { + // SegFlags 2 and 3 are valid in the WebAssembly spec but not yet + // supported: + // - 2: active with explicit table index + // - 3: declarative element segment + // For now, reject with a clear error indicating unsupported feature + throw getErrorWithExtraMessage( + ErrorCode::UnsupportedOpcode, + "element segment flags " + std::to_string(SegFlags) + + " not supported (only flags 0 and 1 are supported)"); + } ++Entry; } @@ -991,29 +1039,89 @@ void ModuleLoader::loadDataSection() { uint32_t TotalDataSize = 0; DataEntry *Entry = Mod.initDataTable(NumDataSegments); for (uint32_t I = 0; I < NumDataSegments; ++I) { - uint32_t MemIdx = readU32(); - if (!Mod.isValidMem(MemIdx)) { - throw getError(ErrorCode::UnknownMemory); - } + uint32_t SegFlags = readU32(); + + if (SegFlags == 0) { + // Format 0: active, memory 0, offset expr, data (MVP compatible) + if (!Mod.isValidMem(0)) { + throw getError(ErrorCode::UnknownMemory); + } + + const auto [ExprKind, Expr] = readConstExpr(WASMType::I32); + + uint32_t DataSegmentSize = readU32(); + if (DataSegmentSize > PresetMaxDataSegmentSize || + addOverflow(TotalDataSize, DataSegmentSize, TotalDataSize)) { + throw getError(ErrorCode::DataSegmentTooLarge); + } - const auto [ExprKind, Expr] = readConstExpr(WASMType::I32); + uint32_t DataPtrOffset = Ptr - Start; + if (addOverflow(Ptr, DataSegmentSize, Ptr) || Ptr > End) { + throw getError(ErrorCode::UnexpectedEnd); + } - uint32_t DataSegmentSize = readU32(); - if (DataSegmentSize > PresetMaxDataSegmentSize || - addOverflow(TotalDataSize, DataSegmentSize, TotalDataSize)) { - throw getError(ErrorCode::DataSegmentTooLarge); + Entry->MemIdx = 0; + Entry->Size = DataSegmentSize; + Entry->Offset = DataPtrOffset; + Entry->InitExprKind = ExprKind; + Entry->InitExprVal = Expr; +#ifdef ZEN_ENABLE_BULK_MEMORY + Entry->Mode = 0; // active +#endif } +#ifdef ZEN_ENABLE_BULK_MEMORY + else if (SegFlags == 1) { + // Format 1: passive, data only + uint32_t DataSegmentSize = readU32(); + if (DataSegmentSize > PresetMaxDataSegmentSize || + addOverflow(TotalDataSize, DataSegmentSize, TotalDataSize)) { + throw getError(ErrorCode::DataSegmentTooLarge); + } - uint32_t DataPtrOffset = Ptr - Start; - if (addOverflow(Ptr, DataSegmentSize, Ptr) || Ptr > End) { - throw getError(ErrorCode::UnexpectedEnd); + uint32_t DataPtrOffset = Ptr - Start; + if (addOverflow(Ptr, DataSegmentSize, Ptr) || Ptr > End) { + throw getError(ErrorCode::UnexpectedEnd); + } + + Entry->MemIdx = 0; + Entry->Size = DataSegmentSize; + Entry->Offset = DataPtrOffset; + Entry->InitExprKind = 0; + Entry->InitExprVal = {}; + Entry->Mode = 1; // passive } +#endif + else if (SegFlags == 2) { + // Format 2: active, explicit memory index, offset expr, data + uint32_t MemIdx = readU32(); + if (!Mod.isValidMem(MemIdx)) { + throw getError(ErrorCode::UnknownMemory); + } - Entry->MemIdx = MemIdx; - Entry->Size = DataSegmentSize; - Entry->Offset = DataPtrOffset; - Entry->InitExprKind = ExprKind; - Entry->InitExprVal = Expr; + const auto [ExprKind, Expr] = readConstExpr(WASMType::I32); + + uint32_t DataSegmentSize = readU32(); + if (DataSegmentSize > PresetMaxDataSegmentSize || + addOverflow(TotalDataSize, DataSegmentSize, TotalDataSize)) { + throw getError(ErrorCode::DataSegmentTooLarge); + } + + uint32_t DataPtrOffset = Ptr - Start; + if (addOverflow(Ptr, DataSegmentSize, Ptr) || Ptr > End) { + throw getError(ErrorCode::UnexpectedEnd); + } + + Entry->MemIdx = MemIdx; + Entry->Size = DataSegmentSize; + Entry->Offset = DataPtrOffset; + Entry->InitExprKind = ExprKind; + Entry->InitExprVal = Expr; +#ifdef ZEN_ENABLE_BULK_MEMORY + Entry->Mode = 0; // active +#endif + } else { + throw getError(ErrorCode::UnsupportedOpcode); + } ++Entry; } diff --git a/src/common/enums.h b/src/common/enums.h index 74abc1607..4d25117b3 100644 --- a/src/common/enums.h +++ b/src/common/enums.h @@ -46,6 +46,21 @@ enum Opcode { #undef DEFINE_WASM_OPCODE }; // Opcode +// Multi-byte opcode prefix +constexpr uint8_t WASM_PREFIX_FC = 0xFC; + +// 0xFC sub-opcodes for bulk memory operations +// Note: These are always defined to allow code to compile even when +// ZEN_ENABLE_BULK_MEMORY is disabled. Runtime checks ensure these +// opcodes are properly rejected when the feature is disabled. +constexpr uint8_t FC_MEMORY_INIT = 0x08; +constexpr uint8_t FC_DATA_DROP = 0x09; +constexpr uint8_t FC_MEMORY_COPY = 0x0A; +constexpr uint8_t FC_MEMORY_FILL = 0x0B; +constexpr uint8_t FC_TABLE_INIT = 0x0C; +constexpr uint8_t FC_ELEM_DROP = 0x0D; +constexpr uint8_t FC_TABLE_COPY = 0x0E; + enum LabelType { LABEL_BLOCK, LABEL_LOOP, diff --git a/src/common/errors.def b/src/common/errors.def index 94a29ea1f..054671c47 100644 --- a/src/common/errors.def +++ b/src/common/errors.def @@ -83,6 +83,8 @@ DEFINE_ERROR(Load, None, InvalidNameSectionPosition, "name section must DEFINE_ERROR(Load, None, DuplicateExportName, "duplicate export name") DEFINE_ERROR(Load, None, FuncCodeInconsistent, "function and code section have inconsistent lengths") DEFINE_ERROR(Load, None, DataSegAndDataCountInconsistent, "data count and data section have inconsistent lengths") +DEFINE_ERROR(Load, None, UnknownDataSegment, "unknown data segment") +DEFINE_ERROR(Load, None, UnknownElemSegment, "unknown elem segment") // Link Error: Import DEFINE_ERROR(Load, None, UnknownImport, "unknown import") @@ -152,6 +154,7 @@ DEFINE_ERROR(Execution, None, IndirectCallTypeMismatch, "indirect call t DEFINE_ERROR(Execution, None, UndefinedElement, "undefined element") DEFINE_ERROR(Execution, None, Unreachable, "unreachable") DEFINE_ERROR(Execution, None, UninitializedElement, "uninitialized element") +DEFINE_ERROR(Execution, None, OutOfBoundsTable, "out of bounds table access") DEFINE_ERROR(Execution, None, GasLimitExceeded, "out of gas") DEFINE_ERROR(Execution, None, InstanceExit, "instance exit") diff --git a/src/common/wasm_defs/opcode.def b/src/common/wasm_defs/opcode.def index 5f530b5b5..5a2abb549 100644 --- a/src/common/wasm_defs/opcode.def +++ b/src/common/wasm_defs/opcode.def @@ -210,4 +210,13 @@ DEFINE_WASM_OPCODE(I64_EXTEND32_S, 0xc4, "i64_extend32_s") DEFINE_WASM_OPCODE(DROP_64, 0xc5, "drop_64") DEFINE_WASM_OPCODE(SELECT_64, 0xc6, "select_64") +// Bulk Memory Operations (encoded as 0xFC prefix + sub-opcode in wasm binary) +DEFINE_WASM_OPCODE(MEMORY_INIT, 0xc7, "memory.init") +DEFINE_WASM_OPCODE(DATA_DROP, 0xc8, "data.drop") +DEFINE_WASM_OPCODE(MEMORY_COPY, 0xc9, "memory.copy") +DEFINE_WASM_OPCODE(MEMORY_FILL, 0xca, "memory.fill") +DEFINE_WASM_OPCODE(TABLE_INIT, 0xcb, "table.init") +DEFINE_WASM_OPCODE(ELEM_DROP, 0xcc, "elem.drop") +DEFINE_WASM_OPCODE(TABLE_COPY, 0xcd, "table.copy") + #endif diff --git a/src/runtime/instance.cpp b/src/runtime/instance.cpp index 746e7e90f..cb0bb6918 100644 --- a/src/runtime/instance.cpp +++ b/src/runtime/instance.cpp @@ -142,6 +142,18 @@ InstanceUniquePtr Instance::newInstance(Isolation &Iso, const Module &Mod, } Instance::~Instance() { +#ifdef ZEN_ENABLE_BULK_MEMORY + // Free dropped segment tracking arrays + if (DroppedDataSegments) { + deallocate(DroppedDataSegments); + DroppedDataSegments = nullptr; + } + if (DroppedElemSegments) { + deallocate(DroppedElemSegments); + DroppedElemSegments = nullptr; + } +#endif + auto *MemAllocator = getWasmMemoryAllocator(); for (uint32_t I = 0; I < NumTotalMemories; ++I) { if (Memories[I].MemBase) { diff --git a/src/runtime/instance.h b/src/runtime/instance.h index ba04a6193..504746886 100644 --- a/src/runtime/instance.h +++ b/src/runtime/instance.h @@ -186,6 +186,34 @@ class Instance final : public RuntimeObject { return Globals[GlobalIdx].Type; } + // ==================== Dropped Segment Methods ==================== +#ifdef ZEN_ENABLE_BULK_MEMORY + + bool isDataSegmentDropped(uint32_t DataIdx) const { + if (!DroppedDataSegments) + return false; + return DroppedDataSegments[DataIdx]; + } + + bool isElemSegmentDropped(uint32_t ElemIdx) const { + if (!DroppedElemSegments) + return false; + return DroppedElemSegments[ElemIdx]; + } + + void dropDataSegment(uint32_t DataIdx) { + if (DroppedDataSegments) { + DroppedDataSegments[DataIdx] = true; + } + } + + void dropElemSegment(uint32_t ElemIdx) { + if (DroppedElemSegments) { + DroppedElemSegments[ElemIdx] = true; + } + } +#endif + // ==================== Error/Exception Methods ==================== void setError(const Error &NewErr) { Err = NewErr; } @@ -358,6 +386,11 @@ class Instance final : public RuntimeObject { bool DataSegsInited = false; +#ifdef ZEN_ENABLE_BULK_MEMORY + bool *DroppedDataSegments = nullptr; + bool *DroppedElemSegments = nullptr; +#endif + #ifdef ZEN_ENABLE_VIRTUAL_STACK // one instance maybe called by hostapi( instanceA -> hostapi -> instanceA ) std::queue VirtualStacks; diff --git a/src/runtime/module.h b/src/runtime/module.h index fead2c0aa..de3c1a64b 100644 --- a/src/runtime/module.h +++ b/src/runtime/module.h @@ -240,6 +240,9 @@ struct ElemEntry { InitExpr InitExprVal; uint32_t NumFuncIdxs; uint32_t *FuncIdxs; +#ifdef ZEN_ENABLE_BULK_MEMORY + uint8_t Mode; // 0=active, 1=passive, 2=declarative +#endif }; struct CodeEntry { @@ -268,6 +271,9 @@ struct DataEntry { uint32_t Offset; uint8_t InitExprKind; InitExpr InitExprVal; +#ifdef ZEN_ENABLE_BULK_MEMORY + uint8_t Mode; // 0=active, 1=passive +#endif }; class Module final : public BaseModule { @@ -382,6 +388,8 @@ class Module final : public BaseModule { uint32_t getNumDataSegments() const { return NumDataSegments; } + uint32_t getNumElementSegments() const { return NumElementSegments; } + // ==================== Validating Methods ==================== bool isValidType(uint32_t TypeIdx) const { return TypeIdx < NumTypes; } @@ -406,6 +414,17 @@ class Module final : public BaseModule { return GlobalIdx < getNumTotalGlobals(); } + bool isValidDataSegment(uint32_t DataIdx) const { + // During code validation, NumDataSegments may not be set yet + // (Data section comes after Code section), so use DataCount if available + uint32_t Count = (DataCount != -1u) ? DataCount : NumDataSegments; + return DataIdx < Count; + } + + bool isValidElemSegment(uint32_t ElemIdx) const { + return ElemIdx < NumElementSegments; + } + // ==================== Segment Accessing Methods ==================== TypeEntry *getDeclaredType(uint32_t TypeIdx) const { @@ -463,6 +482,11 @@ class Module final : public BaseModule { return DataTable + DataSegIdx; } + ElemEntry *getElemEntry(uint32_t ElemSegIdx) const { + ZEN_ASSERT(ElemSegIdx < NumElementSegments); + return ElementTable + ElemSegIdx; + } + // ==================== Layout Methods ==================== const InstanceLayout &getLayout() const { return Layout; } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 89a1c2f37..bbc48b9f0 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -11,8 +11,7 @@ function(PROCESS_SPEC_FILES SPEC_CATEGORY_DIR) add_custom_command( OUTPUT ${OUTPUT_SPEC_JSON} COMMAND mkdir -vp ${OUTPUT_SPEC_SUBDIR} - COMMAND wast2json --disable-bulk-memory -o ${OUTPUT_SPEC_JSON} - ${SPEC_FILE_PATH} + COMMAND wast2json -o ${OUTPUT_SPEC_JSON} ${SPEC_FILE_PATH} DEPENDS ${SPEC_FILE_PATH} VERBATIM ) diff --git a/src/tests/spec_unit_tests.cpp b/src/tests/spec_unit_tests.cpp index d6adce389..8fc3fdbd6 100644 --- a/src/tests/spec_unit_tests.cpp +++ b/src/tests/spec_unit_tests.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -112,8 +113,26 @@ void testWithUnitName(const std::pair &UnitPair) { T.run(UnitPair); } +// Bulk memory operations tests that require the feature to be enabled +static const std::unordered_set BulkMemoryTests = { + "memory_fill", "memory_copy", "memory_init", "data_drop", + "table_copy", "table_init", "elem_drop", +}; + TEST_P(SpecUnitTest, TestSpec) { const auto &UnitPair = GetParam(); +#ifdef ZEN_ENABLE_BULK_MEMORY + // Skip bulk memory tests in JIT mode (not supported) + if (T.getConfig().Mode != RunMode::InterpMode && + UnitPair.first == "proposals" && BulkMemoryTests.count(UnitPair.second)) { + GTEST_SKIP() << "Bulk memory operations not supported in JIT mode"; + } +#else + // Skip bulk memory tests when feature is disabled + if (UnitPair.first == "proposals" && BulkMemoryTests.count(UnitPair.second)) { + GTEST_SKIP() << "Bulk memory operations not enabled"; + } +#endif testWithUnitName(UnitPair); } diff --git a/src/utils/wasm.cpp b/src/utils/wasm.cpp index 03d8b2afe..13035e042 100644 --- a/src/utils/wasm.cpp +++ b/src/utils/wasm.cpp @@ -290,6 +290,42 @@ const uint8_t *skipCurrentBlock(const uint8_t *Ip, const uint8_t *End) { case I64_EXTEND32_S: break; +#ifdef ZEN_ENABLE_BULK_MEMORY + case WASM_PREFIX_FC: { // Bulk memory operations prefix + uint32_t SubOpcode; + Ip = readLEBNumber(Ip, End, SubOpcode); + switch (SubOpcode) { + case FC_MEMORY_INIT: // memory.init: dataidx(LEB) + memidx(1 byte) + Ip = skipLEBNumber(Ip, End); + Ip++; // skip memidx + break; + case FC_DATA_DROP: // data.drop: dataidx(LEB) + Ip = skipLEBNumber(Ip, End); + break; + case FC_MEMORY_COPY: // memory.copy: 2 bytes + Ip += 2; + break; + case FC_MEMORY_FILL: // memory.fill: 1 byte + Ip++; + break; + case FC_TABLE_INIT: // table.init: elemidx(LEB) + tableidx(LEB) + Ip = skipLEBNumber(Ip, End); + Ip = skipLEBNumber(Ip, End); + break; + case FC_ELEM_DROP: // elem.drop: elemidx(LEB) + Ip = skipLEBNumber(Ip, End); + break; + case FC_TABLE_COPY: // table.copy: dst_tableidx(LEB) + src_tableidx(LEB) + Ip = skipLEBNumber(Ip, End); + Ip = skipLEBNumber(Ip, End); + break; + default: + break; + } + break; + } +#endif + } // switch opcode } // while ip < end return nullptr; diff --git a/tests/wast/proposals/data_drop.wast b/tests/wast/proposals/data_drop.wast new file mode 100644 index 000000000..f1dbf5108 --- /dev/null +++ b/tests/wast/proposals/data_drop.wast @@ -0,0 +1,33 @@ +;; Test data.drop instruction + +(module + (memory 1) + (data (i32.const 0) "\01\02") ;; active segment 0 + (data "\aa\bb\cc") ;; passive segment 1 + + (func (export "drop_passive") + (data.drop 1) + ) + + (func (export "init_passive") (param $dest i32) (param $src i32) (param $size i32) + (memory.init 1 + (local.get $dest) + (local.get $src) + (local.get $size)) + ) +) + +;; Init from passive segment works before drop +(invoke "init_passive" (i32.const 0) (i32.const 0) (i32.const 3)) + +;; Drop passive segment +(invoke "drop_passive") + +;; Double drop should succeed +(invoke "drop_passive") + +;; Init after drop should trap +(assert_trap (invoke "init_passive" (i32.const 0) (i32.const 0) (i32.const 1)) "out of bounds memory access") + +;; Zero-length init after drop with offset 0 should succeed +(invoke "init_passive" (i32.const 0) (i32.const 0) (i32.const 0)) diff --git a/tests/wast/proposals/elem_drop.wast b/tests/wast/proposals/elem_drop.wast new file mode 100644 index 000000000..9c6a968bc --- /dev/null +++ b/tests/wast/proposals/elem_drop.wast @@ -0,0 +1,38 @@ +;; Test elem.drop instruction + +(module + (table 10 funcref) + (type $sig (func (result i32))) + + (func $f0 (result i32) (i32.const 0)) + (func $f1 (result i32) (i32.const 1)) + (func $f2 (result i32) (i32.const 2)) + + (elem func $f0 $f1 $f2) + + (func (export "drop_elem") + (elem.drop 0) + ) + + (func (export "init_elem") (param $dest i32) (param $src i32) (param $size i32) + (table.init 0 + (local.get $dest) + (local.get $src) + (local.get $size)) + ) +) + +;; Init from passive element segment works before drop +(invoke "init_elem" (i32.const 0) (i32.const 0) (i32.const 3)) + +;; Drop element segment +(invoke "drop_elem") + +;; Double drop should succeed +(invoke "drop_elem") + +;; Init after drop should trap +(assert_trap (invoke "init_elem" (i32.const 0) (i32.const 0) (i32.const 1)) "out of bounds table access") + +;; Zero-length init after drop with offset 0 should succeed +(invoke "init_elem" (i32.const 0) (i32.const 0) (i32.const 0)) diff --git a/tests/wast/proposals/memory_copy.wast b/tests/wast/proposals/memory_copy.wast new file mode 100644 index 000000000..b1c17e485 --- /dev/null +++ b/tests/wast/proposals/memory_copy.wast @@ -0,0 +1,50 @@ +;; Test memory.copy instruction + +(module + (memory 1) + + (func (export "copy") (param $dest i32) (param $src i32) (param $size i32) + (memory.copy + (local.get $dest) + (local.get $src) + (local.get $size)) + ) + + (func (export "store8") (param $addr i32) (param $val i32) + (i32.store8 (local.get $addr) (local.get $val)) + ) + + (func (export "load8_u") (param $addr i32) (result i32) + (i32.load8_u (local.get $addr)) + ) +) + +;; Setup: store some values +(invoke "store8" (i32.const 0) (i32.const 0xAA)) +(invoke "store8" (i32.const 1) (i32.const 0xBB)) +(invoke "store8" (i32.const 2) (i32.const 0xCC)) +(invoke "store8" (i32.const 3) (i32.const 0xDD)) + +;; Basic copy (non-overlapping) +(invoke "copy" (i32.const 10) (i32.const 0) (i32.const 4)) +(assert_return (invoke "load8_u" (i32.const 10)) (i32.const 0xAA)) +(assert_return (invoke "load8_u" (i32.const 11)) (i32.const 0xBB)) +(assert_return (invoke "load8_u" (i32.const 12)) (i32.const 0xCC)) +(assert_return (invoke "load8_u" (i32.const 13)) (i32.const 0xDD)) + +;; Overlapping copy (forward: dest > src) +(invoke "copy" (i32.const 1) (i32.const 0) (i32.const 3)) +(assert_return (invoke "load8_u" (i32.const 0)) (i32.const 0xAA)) +(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xAA)) +(assert_return (invoke "load8_u" (i32.const 2)) (i32.const 0xBB)) +(assert_return (invoke "load8_u" (i32.const 3)) (i32.const 0xCC)) + +;; Zero-length copy (should succeed) +(invoke "copy" (i32.const 0) (i32.const 0) (i32.const 0)) + +;; Zero-length copy at end of memory (should succeed) +(invoke "copy" (i32.const 65536) (i32.const 65536) (i32.const 0)) + +;; Out of bounds copy +(assert_trap (invoke "copy" (i32.const 65534) (i32.const 0) (i32.const 3)) "out of bounds memory access") +(assert_trap (invoke "copy" (i32.const 0) (i32.const 65534) (i32.const 3)) "out of bounds memory access") diff --git a/tests/wast/proposals/memory_fill.wast b/tests/wast/proposals/memory_fill.wast new file mode 100644 index 000000000..48eb0513b --- /dev/null +++ b/tests/wast/proposals/memory_fill.wast @@ -0,0 +1,41 @@ +;; Test memory.fill instruction + +(module + (memory 1) + + (func (export "fill") (param $dest i32) (param $val i32) (param $size i32) + (memory.fill + (local.get $dest) + (local.get $val) + (local.get $size)) + ) + + (func (export "load8_u") (param $addr i32) (result i32) + (i32.load8_u (local.get $addr)) + ) +) + +;; Basic fill +(invoke "fill" (i32.const 1) (i32.const 0xFF) (i32.const 3)) +(assert_return (invoke "load8_u" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xFF)) +(assert_return (invoke "load8_u" (i32.const 2)) (i32.const 0xFF)) +(assert_return (invoke "load8_u" (i32.const 3)) (i32.const 0xFF)) +(assert_return (invoke "load8_u" (i32.const 4)) (i32.const 0)) + +;; Fill value is truncated to single byte +(invoke "fill" (i32.const 0) (i32.const 0xABCD) (i32.const 2)) +(assert_return (invoke "load8_u" (i32.const 0)) (i32.const 0xCD)) +(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xCD)) + +;; Zero-length fill (should succeed) +(invoke "fill" (i32.const 0) (i32.const 0) (i32.const 0)) + +;; Zero-length fill at end of memory (should succeed) +(invoke "fill" (i32.const 65536) (i32.const 0) (i32.const 0)) + +;; Out of bounds fill +(assert_trap (invoke "fill" (i32.const 65534) (i32.const 0) (i32.const 3)) "out of bounds memory access") + +;; Way out of bounds +(assert_trap (invoke "fill" (i32.const 65537) (i32.const 0) (i32.const 1)) "out of bounds memory access") diff --git a/tests/wast/proposals/memory_init.wast b/tests/wast/proposals/memory_init.wast new file mode 100644 index 000000000..d8db943f1 --- /dev/null +++ b/tests/wast/proposals/memory_init.wast @@ -0,0 +1,44 @@ +;; Test memory.init instruction + +(module + (memory 1) + (data "\aa\bb\cc\dd\ee") + + (func (export "init") (param $dest i32) (param $src i32) (param $size i32) + (memory.init 0 + (local.get $dest) + (local.get $src) + (local.get $size)) + ) + + (func (export "load8_u") (param $addr i32) (result i32) + (i32.load8_u (local.get $addr)) + ) +) + +;; The module above declares a passive data segment using `(data "...")`. +;; Under the bulk-memory proposal, segments without an explicit offset +;; expression are passive; active segments have an offset expression. + +;; Basic init from passive segment +(invoke "init" (i32.const 0) (i32.const 0) (i32.const 5)) +(assert_return (invoke "load8_u" (i32.const 0)) (i32.const 0xAA)) +(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xBB)) +(assert_return (invoke "load8_u" (i32.const 2)) (i32.const 0xCC)) +(assert_return (invoke "load8_u" (i32.const 3)) (i32.const 0xDD)) +(assert_return (invoke "load8_u" (i32.const 4)) (i32.const 0xEE)) + +;; Partial init with offset +(invoke "init" (i32.const 10) (i32.const 2) (i32.const 3)) +(assert_return (invoke "load8_u" (i32.const 10)) (i32.const 0xCC)) +(assert_return (invoke "load8_u" (i32.const 11)) (i32.const 0xDD)) +(assert_return (invoke "load8_u" (i32.const 12)) (i32.const 0xEE)) + +;; Zero-length init (should succeed) +(invoke "init" (i32.const 0) (i32.const 0) (i32.const 0)) + +;; Out of bounds init (source) +(assert_trap (invoke "init" (i32.const 0) (i32.const 3) (i32.const 3)) "out of bounds memory access") + +;; Out of bounds init (dest) +(assert_trap (invoke "init" (i32.const 65534) (i32.const 0) (i32.const 3)) "out of bounds memory access") diff --git a/tests/wast/proposals/table_copy.wast b/tests/wast/proposals/table_copy.wast new file mode 100644 index 000000000..5383958e4 --- /dev/null +++ b/tests/wast/proposals/table_copy.wast @@ -0,0 +1,44 @@ +;; Test table.copy instruction + +(module + (table 10 funcref) + (elem (i32.const 0) $f0 $f1 $f2) + (type $sig (func (result i32))) + + (func $f0 (result i32) (i32.const 0)) + (func $f1 (result i32) (i32.const 1)) + (func $f2 (result i32) (i32.const 2)) + + (func (export "copy") (param $dest i32) (param $src i32) (param $size i32) + (table.copy + (local.get $dest) + (local.get $src) + (local.get $size)) + ) + + (func (export "call_indirect") (param $idx i32) (result i32) + (call_indirect (type $sig) (local.get $idx)) + ) +) + +;; Basic copy +(invoke "copy" (i32.const 3) (i32.const 0) (i32.const 3)) +(assert_return (invoke "call_indirect" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "call_indirect" (i32.const 4)) (i32.const 1)) +(assert_return (invoke "call_indirect" (i32.const 5)) (i32.const 2)) + +;; Overlapping copy (forward) +(invoke "copy" (i32.const 1) (i32.const 0) (i32.const 3)) +(assert_return (invoke "call_indirect" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "call_indirect" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "call_indirect" (i32.const 3)) (i32.const 2)) + +;; Zero-length copy (should succeed) +(invoke "copy" (i32.const 0) (i32.const 0) (i32.const 0)) + +;; Zero-length copy at end of table (should succeed) +(invoke "copy" (i32.const 10) (i32.const 10) (i32.const 0)) + +;; Out of bounds copy +(assert_trap (invoke "copy" (i32.const 8) (i32.const 0) (i32.const 3)) "out of bounds table access") +(assert_trap (invoke "copy" (i32.const 0) (i32.const 8) (i32.const 3)) "out of bounds table access") diff --git a/tests/wast/proposals/table_init.wast b/tests/wast/proposals/table_init.wast new file mode 100644 index 000000000..f8dbd23ec --- /dev/null +++ b/tests/wast/proposals/table_init.wast @@ -0,0 +1,54 @@ +;; Test table.init instruction + +(module + (table 10 funcref) + (type $sig (func (result i32))) + + (func $f0 (result i32) (i32.const 0)) + (func $f1 (result i32) (i32.const 1)) + (func $f2 (result i32) (i32.const 2)) + + (elem func $f0 $f1 $f2) + + (func (export "init") (param $dest i32) (param $src i32) (param $size i32) + (table.init 0 + (local.get $dest) + (local.get $src) + (local.get $size)) + ) + + (func (export "drop") + (elem.drop 0) + ) + + (func (export "call_indirect") (param $idx i32) (result i32) + (call_indirect (type $sig) (local.get $idx)) + ) +) + +;; Basic init from passive element segment +(invoke "init" (i32.const 0) (i32.const 0) (i32.const 3)) +(assert_return (invoke "call_indirect" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "call_indirect" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "call_indirect" (i32.const 2)) (i32.const 2)) + +;; Partial init with offset +(invoke "init" (i32.const 5) (i32.const 1) (i32.const 2)) +(assert_return (invoke "call_indirect" (i32.const 5)) (i32.const 1)) +(assert_return (invoke "call_indirect" (i32.const 6)) (i32.const 2)) + +;; Zero-length init (should succeed) +(invoke "init" (i32.const 0) (i32.const 0) (i32.const 0)) + +;; Out of bounds init (source) +(assert_trap (invoke "init" (i32.const 0) (i32.const 2) (i32.const 2)) "out of bounds table access") + +;; Out of bounds init (dest) +(assert_trap (invoke "init" (i32.const 9) (i32.const 0) (i32.const 2)) "out of bounds table access") + +;; Drop then init should trap +(invoke "drop") +(assert_trap (invoke "init" (i32.const 0) (i32.const 0) (i32.const 1)) "out of bounds table access") + +;; Zero-length init after drop with offset 0 should succeed +(invoke "init" (i32.const 0) (i32.const 0) (i32.const 0)) diff --git a/tests/wast/spec.patch b/tests/wast/spec.patch index 577698802..47ccb3dc1 100644 --- a/tests/wast/spec.patch +++ b/tests/wast/spec.patch @@ -1,5 +1,5 @@ diff --git a/test/core/binary.wast b/test/core/binary.wast -index e748bbe8..1d97737a 100644 +index e748bbe8c..1d97737ab 100644 --- a/test/core/binary.wast +++ b/test/core/binary.wast @@ -633,10 +633,8 @@ @@ -24,10 +24,42 @@ index e748bbe8..1d97737a 100644 "\02\40" ;; block 0 "\41\01" ;; condition of if 0 diff --git a/test/core/elem.wast b/test/core/elem.wast -index 1ea2b061..6dcb451a 100644 +index 1ea2b0618..afeb060ec 100644 --- a/test/core/elem.wast +++ b/test/core/elem.wast -@@ -354,28 +354,28 @@ +@@ -12,10 +12,10 @@ + (elem 0x0 (i32.const 0) $f $f) + (elem 0x000 (offset (i32.const 0))) + (elem 0 (offset (i32.const 0)) $f $f) +- (elem $t (i32.const 0)) +- (elem $t (i32.const 0) $f $f) +- (elem $t (offset (i32.const 0))) +- (elem $t (offset (i32.const 0)) $f $f) ++ (elem 0 (i32.const 0)) ++ (elem 0 (i32.const 0) $f $f) ++ (elem 0 (offset (i32.const 0))) ++ (elem 0 (offset (i32.const 0)) $f $f) + ) + + ;; Basic use +@@ -242,16 +242,6 @@ + "elements segment does not fit" + ) + +-;; Element without table +- +-(assert_invalid +- (module +- (func $f) +- (elem (i32.const 0) $f) +- ) +- "unknown table" +-) +- + ;; Invalid offsets + + (assert_invalid +@@ -354,28 +344,28 @@ (assert_return (invoke $module1 "call-8") (i32.const 65)) (assert_return (invoke $module1 "call-9") (i32.const 66)) @@ -79,9 +111,23 @@ index 1ea2b061..6dcb451a 100644 +;; (assert_return (invoke $module1 "call-7") (i32.const 67)) +;; (assert_return (invoke $module1 "call-8") (i32.const 69)) +;; (assert_return (invoke $module1 "call-9") (i32.const 70)) +diff --git a/test/core/func_ptrs.wast b/test/core/func_ptrs.wast +index f6f8e2c42..47dae65c4 100644 +--- a/test/core/func_ptrs.wast ++++ b/test/core/func_ptrs.wast +@@ -29,9 +29,6 @@ + (assert_return (invoke "three" (i32.const 13)) (i32.const 11)) + (invoke "four" (i32.const 83)) + +-(assert_invalid (module (elem (i32.const 0))) "unknown table") +-(assert_invalid (module (elem (i32.const 0) 0) (func)) "unknown table") +- + (assert_invalid + (module (table 1 funcref) (elem (i64.const 0))) + "type mismatch" diff --git a/test/core/imports.wast b/test/core/imports.wast deleted file mode 100644 -index 2f0200dc..00000000 +index 2f0200dc3..000000000 --- a/test/core/imports.wast +++ /dev/null @@ -1,593 +0,0 @@ @@ -680,14 +726,14 @@ index 2f0200dc..00000000 -) diff --git a/test/core/inline-module.wast b/test/core/inline-module.wast deleted file mode 100644 -index dc7ead77..00000000 +index dc7ead776..000000000 --- a/test/core/inline-module.wast +++ /dev/null @@ -1 +0,0 @@ -(func) (memory 0) (func (export "f")) diff --git a/test/core/linking.wast b/test/core/linking.wast deleted file mode 100644 -index 6868e8b7..00000000 +index 6868e8b70..000000000 --- a/test/core/linking.wast +++ /dev/null @@ -1,388 +0,0 @@