-
Notifications
You must be signed in to change notification settings - Fork 24
feat(wasm): support bulk memory operations proposal (interpreter mode) #415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -671,6 +671,113 @@ void FunctionLoader::load() { | |
| case I64_EXTEND32_S: | ||
| popAndPushValueType(1, WASMType::I64, WASMType::I64); | ||
| break; | ||
| case WASM_PREFIX_FC: { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The entire opcode handler block needs to be wrapped in a macro, otherwise the original WASM_PREFIX_FC opcode will cause an error: getErrorWithExtraMessage(ErrorCode::UnsupportedOpcode, std::to_string(Opcode)). Now it will throw an error: getErrorWithExtraMessage(ErrorCode::UnsupportedOpcode, "bulk memory operations not supported in JIT mode") |
||
| // 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)); | ||
| } | ||
| break; | ||
| } | ||
| case I32_LOAD: | ||
| case I64_LOAD: | ||
| case F32_LOAD: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -538,6 +538,39 @@ class BaseInterpreterImpl { | |
| case I64_EXTEND16_S: | ||
| case I64_EXTEND32_S: | ||
| break; | ||
| case WASM_PREFIX_FC: { // Bulk memory operations prefix | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrap with macro |
||
| uint32_t SubOpcode = 0; | ||
| Ptr = readSafeLEBNumber(Ptr, SubOpcode); | ||
| switch (SubOpcode) { | ||
| case FC_MEMORY_INIT: // memory.init: dataidx(LEB) + memidx(1 byte) | ||
| Ptr = skipLEBNumber<uint32_t>(Ptr, End); | ||
| Ptr++; // skip memidx | ||
| break; | ||
| case FC_DATA_DROP: // data.drop: dataidx(LEB) | ||
| Ptr = skipLEBNumber<uint32_t>(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<uint32_t>(Ptr, End); | ||
| Ptr = skipLEBNumber<uint32_t>(Ptr, End); | ||
| break; | ||
| case FC_ELEM_DROP: // elem.drop: elemidx(LEB) | ||
| Ptr = skipLEBNumber<uint32_t>(Ptr, End); | ||
| break; | ||
| case FC_TABLE_COPY: // table.copy: dst_tableidx(LEB) + src_tableidx(LEB) | ||
| Ptr = skipLEBNumber<uint32_t>(Ptr, End); | ||
| Ptr = skipLEBNumber<uint32_t>(Ptr, End); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| break; | ||
| } | ||
| case I32_LOAD: | ||
| case I64_LOAD: | ||
| case F32_LOAD: | ||
|
|
@@ -2093,8 +2126,140 @@ void BaseInterpreterImpl::interpret() { | |
| BREAK; | ||
| } | ||
| DEFAULT : { | ||
| ZEN_LOG_ERROR("munimplemented opcode: 0x%x", Opcode); | ||
| ZEN_ASSERT_TODO(); | ||
| if (Opcode == WASM_PREFIX_FC) { | ||
| // 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<uint32_t>(ValStackPtr); | ||
| uint32_t S = Frame->valuePop<uint32_t>(ValStackPtr); | ||
| uint32_t D = Frame->valuePop<uint32_t>(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<uint32_t>(ValStackPtr); | ||
| uint32_t S = Frame->valuePop<uint32_t>(ValStackPtr); | ||
| uint32_t D = Frame->valuePop<uint32_t>(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<uint32_t>(ValStackPtr); | ||
| uint32_t Val = Frame->valuePop<uint32_t>(ValStackPtr); | ||
| uint32_t D = Frame->valuePop<uint32_t>(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<uint32_t>(ValStackPtr); | ||
| uint32_t S = Frame->valuePop<uint32_t>(ValStackPtr); | ||
| uint32_t D = Frame->valuePop<uint32_t>(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<uint32_t>(ValStackPtr); | ||
| uint32_t S = Frame->valuePop<uint32_t>(ValStackPtr); | ||
| uint32_t D = Frame->valuePop<uint32_t>(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("munimplemented opcode: 0x%x", Opcode); | ||
|
|
||
| ZEN_ASSERT_TODO(); | ||
| } | ||
| } | ||
| } | ||
| // TODO: write back ValueStackPtr, Ip, CtrlStackPtr to Frame | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entire opcode handler block needs to be wrapped in a macro, otherwise the original WASM_PREFIX_FC opcode will cause an error: getErrorWithExtraMessage(ErrorCode::UnsupportedOpcode, std::to_string(Opcode)). Now it will throw an error: getErrorWithExtraMessage(ErrorCode::UnsupportedOpcode, "bulk memory operations not supported in JIT mode")