[Mlir-commits] [mlir] [mlir][wasm] Support for saturating FP truncations (PR #212709)
Luc Forget
llvmlistbot at llvm.org
Wed Jul 29 02:05:21 PDT 2026
https://github.com/lforg37 updated https://github.com/llvm/llvm-project/pull/212709
>From 32fbd6618724839d0e635f9b762860ff1abcceaf Mon Sep 17 00:00:00 2001
From: Luc Forget <dev at alias.lforget.fr>
Date: Mon, 27 Jul 2026 20:49:53 +0900
Subject: [PATCH 1/5] [mlir][wasm] Add saturated truncation in WasmSSA dialect
---
.../mlir/Dialect/WasmSSA/IR/WasmSSAOps.td | 30 +++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/mlir/include/mlir/Dialect/WasmSSA/IR/WasmSSAOps.td b/mlir/include/mlir/Dialect/WasmSSA/IR/WasmSSAOps.td
index 3b3c3e8825fef..c206ea8c05583 100644
--- a/mlir/include/mlir/Dialect/WasmSSA/IR/WasmSSAOps.td
+++ b/mlir/include/mlir/Dialect/WasmSSA/IR/WasmSSAOps.td
@@ -1101,6 +1101,36 @@ def WasmSSA_TruncUIOp : WasmSSA_ConversionOp<"trunc_ui",
[WasmSSA_FPType],
[WasmSSA_IntegerType]>{}
+def WasmSSA_TruncSatSIOp : WasmSSA_ConversionOp<"trunc_sat_si",
+ [{Truncate floating point value to signed integer, saturating.
+
+ Consume a floating point value and produces an unsigned integer holding the value truncated toward zero.
+ NaN is mapped to 0, infinities are mapped to minimal and maximal representable values.
+ Normal values are clamped to the representable range and truncated.}],
+ [{Example:
+
+ ```mlir
+ %a = wasmssa.trunc_sat_si %b : f32 to i32
+ ```
+ }],
+ [WasmSSA_FPType],
+ [WasmSSA_IntegerType]>{}
+
+def WasmSSA_TruncSatUIOp : WasmSSA_ConversionOp<"trunc_sat_ui",
+ [{Truncate floating point value to unsigned integer, saturating.
+
+ Consume a floating point value and produces an unsigned integer holding the value truncated toward zero.
+ Negative infinity and NaN are mapped to 0, positive infinity is mapped to maximal representable values.
+ Normal values are clamped to the representable range and truncated.}],
+ [{Example:
+
+ ```mlir
+ %a = wasmssa.trunc_sat_ui %b : f32 to i32
+ ```
+ }],
+ [WasmSSA_FPType],
+ [WasmSSA_IntegerType]>{}
+
def WasmSSA_DemoteOp : WasmSSA_ConversionOp<"demote",
"Convert a f64 value to f32",
[{Example:
>From c4773c87a274a5f097debbc716e89cef5b5e1de5 Mon Sep 17 00:00:00 2001
From: Luc Forget <dev at alias.lforget.fr>
Date: Tue, 28 Jul 2026 22:10:41 +0900
Subject: [PATCH 2/5] [mlir][wasm] Clean the opcode / parser mapping mechanism
in Wasm parser
---
mlir/lib/Target/Wasm/TranslateFromWasm.cpp | 542 +++++++++++----------
1 file changed, 281 insertions(+), 261 deletions(-)
diff --git a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
index cea6bd2816dc1..22e5f56e9068a 100644
--- a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
+++ b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
@@ -30,6 +30,7 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
+#include <type_traits>
#include <variant>
#define DEBUG_TYPE "wasm-translate"
@@ -247,6 +248,9 @@ constexpr ByteSequence<std::byte{IS}...>
constexpr auto all8bitsBytes =
castIndexSequenceToBytes(std::make_index_sequence<256>());
+template <std::byte>
+struct OpCode {};
+
class ExpressionParser {
public:
using locals_t = SmallVector<local_val_t>;
@@ -255,38 +259,6 @@ class ExpressionParser {
: parser{parser}, symbols{symbols}, locals{initLocal} {}
private:
- template <typename valueT>
- parsed_inst_t
- parseConstInst(OpBuilder &builder,
- std::enable_if_t<std::is_arithmetic_v<valueT>> * = nullptr);
-
- /// Construct an operation with \p numOperands operands and a single result.
- /// Each operand must have the same type. Suitable for e.g. binops, unary
- /// ops, etc.
- ///
- /// \p opcode - The WASM opcode to build.
- /// \p valueType - The operand and result type for the built instruction.
- /// \p numOperands - The number of operands for the built operation.
- ///
- /// \returns The parsed instruction result, or failure.
- template <typename opcode, typename valueType, unsigned int numOperands>
- inline parsed_inst_t
- buildNumericOp(OpBuilder &builder,
- std::enable_if_t<std::is_arithmetic_v<valueType>> * = nullptr);
-
- /// Construct a conversion operation of type \p opType that takes a value from
- /// type \p inputType on the stack and will produce a value of type
- /// \p outputType.
- ///
- /// \p opType - The WASM dialect operation to build.
- /// \p inputType - The operand type for the built instruction.
- /// \p outputType - The result type for the built instruction.
- ///
- /// \returns The parsed instruction result, or failure.
- template <typename opType, typename inputType, typename outputType,
- typename... extraArgsT>
- inline parsed_inst_t buildConvertOp(OpBuilder &builder, extraArgsT...);
-
inline parsed_inst_t dispatchToInstParser(std::byte opCode,
OpBuilder &builder);
///
@@ -354,39 +326,12 @@ class ExpressionParser {
return getFuncTypeFor(builder, *parseResult);
}
- llvm::FailureOr<FunctionType> parseBlockFuncType(OpBuilder &builder);
-
struct ParseResultWithInfo {
SmallVector<Value> opResults;
std::byte endingByte;
};
- template <typename FilterT = ByteSequence<WasmBinaryEncoding::endByte>>
- /// @param blockToFill: the block which content will be populated
- /// @param resType: the type that this block is supposed to return
- llvm::FailureOr<std::byte>
- parseBlockContent(OpBuilder &builder, Block *blockToFill, TypeRange resTypes,
- Location opLoc, LabelLevelOpInterface levelOp,
- FilterT parseEndBytes = {}) {
- OpBuilder::InsertionGuard guard{builder};
- builder.setInsertionPointToStart(blockToFill);
- LDBG() << "parsing a block of type "
- << builder.getFunctionType(blockToFill->getArgumentTypes(),
- resTypes);
- auto nC = addNesting(levelOp);
-
- if (failed(pushResults(blockToFill->getArguments())))
- return failure();
- auto bodyParsingRes = parse(builder, parseEndBytes);
- if (failed(bodyParsingRes))
- return failure();
- auto returnOperands = popOperands(resTypes);
- if (failed(returnOperands))
- return failure();
- BlockReturnOp::create(builder, opLoc, *returnOperands);
- LDBG() << "end of parsing of a block";
- return bodyParsingRes->endingByte;
- }
+ llvm::FailureOr<FunctionType> parseBlockFuncType(OpBuilder &builder);
public:
template <std::byte ParseEndByte = WasmBinaryEncoding::endByte>
@@ -415,98 +360,117 @@ class ExpressionParser {
template <typename OpToCreate>
parsed_inst_t parseSetOrTee(OpBuilder &);
- /// Blocks and Loops have a similar format and differ only in how their exit
- /// is handled which doesn´t matter at parsing time. Factorizes in one
- /// function.
- template <typename OpToCreate>
- parsed_inst_t parseBlockLikeOp(OpBuilder &);
-
Location getCurrentOpLoc() {
assert(currentOpLoc.has_value() &&
"expects current opcode location to be set");
return *currentOpLoc;
}
- class TopLevelInstParserRegistry {
+ struct ExprParserProxy {
public:
- template <std::byte opCode>
- static constexpr bool hasParserForOpcode = false;
+ friend ExpressionParser;
+ inline auto parseBlockFuncType(OpBuilder &builder) {
+ return exprParser.parseBlockFuncType(builder);
+ }
- template <std::byte opCode>
- static parsed_inst_t parseInstrWithOpCode(OpBuilder &,
- ExpressionParser &) = delete;
- };
+ template <typename FilterT = ByteSequence<WasmBinaryEncoding::endByte>>
+ /// @param blockToFill: the block which content will be populated
+ /// @param resType: the type that this block is supposed to return
+ llvm::FailureOr<std::byte>
+ parseBlockContent(OpBuilder &builder, Block *blockToFill,
+ TypeRange resTypes, Location opLoc,
+ LabelLevelOpInterface levelOp,
+ FilterT parseEndBytes = {}) {
+ OpBuilder::InsertionGuard guard{builder};
+ builder.setInsertionPointToStart(blockToFill);
+ LDBG() << "parsing a block of type "
+ << builder.getFunctionType(blockToFill->getArgumentTypes(),
+ resTypes);
+ auto nC = exprParser.addNesting(levelOp);
+
+ if (failed(exprParser.pushResults(blockToFill->getArguments())))
+ return failure();
+ auto bodyParsingRes = exprParser.parse(builder, parseEndBytes);
+ if (failed(bodyParsingRes))
+ return failure();
+ auto returnOperands = exprParser.popOperands(resTypes);
+ if (failed(returnOperands))
+ return failure();
+ BlockReturnOp::create(builder, opLoc, *returnOperands);
+ LDBG() << "end of parsing of a block";
+ return bodyParsingRes->endingByte;
+ }
-private:
- std::optional<Location> currentOpLoc;
- ParserHead &parser;
- WasmModuleSymbolTables const &symbols;
- locals_t locals;
- ValueStack valueStack;
-};
+ inline ParserHead &parser() { return exprParser.parser; }
-static inline parsed_inst_t
-unreachableHandler(OpBuilder &, ExpressionParser &expressionParser) {
- llvm_unreachable("Failure in opcode parser dispatch logic.");
- return mlir::failure();
-}
+ /// Blocks and Loops have a similar format and differ only in how their exit
+ /// is handled which doesn´t matter at parsing time. Factorizes in one
+ /// function.
+ template <typename OpToCreate>
+ parsed_inst_t parseBlockLikeOp(OpBuilder &);
-template <typename ParserRegistry>
-class InstDispatcher {
-private:
- using dispatch_t = parsed_inst_t (*)(OpBuilder &, ExpressionParser &);
+ inline auto getCurrentOpLoc() { return exprParser.getCurrentOpLoc(); }
- template <std::byte opCode>
- static constexpr dispatch_t getHandlerForOpCode() {
- if constexpr (ParserRegistry::template hasParserForOpcode<opCode>)
- return ParserRegistry::template parseInstrWithOpCode<opCode>;
- else
- return unreachableHandler;
- }
+ inline auto popOperands(TypeRange operandTypes) {
+ return exprParser.popOperands(operandTypes);
+ }
-public:
- template <std::byte opCode>
- static constexpr bool isValidInst =
- ParserRegistry::template hasParserForOpcode<opCode>;
+ inline auto &symbols() { return exprParser.symbols; }
-private:
- static inline parsed_inst_t
- invalidOpcodeDiag(OpBuilder &, ExpressionParser &expressionParser,
- std::byte opCode) {
- return emitError(expressionParser.getCurrentOpLoc(),
- "unknown instruction opcode: ")
- << static_cast<int>(opCode);
- }
+ inline auto &locals() { return exprParser.locals; }
- template <std::byte... opCodes>
- static inline parsed_inst_t dispatchImpl(std::byte opCode, OpBuilder &builder,
- ExpressionParser &exprParser,
- ByteSequence<opCodes...>) {
- static constexpr std::array<bool, 256> opcodeValidityMap{
- isValidInst<opCodes>...};
- static constexpr std::array<dispatch_t, 256> dispatchTable{
- getHandlerForOpCode<opCodes>()...};
- if (opcodeValidityMap[static_cast<size_t>(opCode)]) {
- return dispatchTable[static_cast<size_t>(opCode)](builder, exprParser);
+ template <typename OpToCreate>
+ parsed_inst_t parseSetOrTee(OpBuilder &builder) {
+ return exprParser.parseSetOrTee<OpToCreate>(builder);
}
- return invalidOpcodeDiag(builder, exprParser, opCode);
- }
-public:
- ///
- /// @brief dispatch control flow to the sub parser registered for opCode in
- /// `ParserRegistry`
- ///
- /// @param opCode opCode of the instruction to be Parsed
- /// @param builder builder that will be passed to the parser
- /// @param exprParser the generic parser passed to the sub parser
- ///
- /// @return the result of the parser or an error if there is no parser
- /// registered for the opcode (emits a diagnostic)
- static parsed_inst_t dispatch(std::byte opCode, OpBuilder &builder,
- ExpressionParser &exprParser) {
- return dispatchImpl(opCode, builder, exprParser, all8bitsBytes);
- }
+ template <typename valueT>
+ parsed_inst_t
+ parseConstInst(OpBuilder &builder,
+ std::enable_if_t<std::is_arithmetic_v<valueT>> * = nullptr);
+
+ /// Construct an operation with \p numOperands operands and a single result.
+ /// Each operand must have the same type. Suitable for e.g. binops, unary
+ /// ops, etc.
+ ///
+ /// \p opcode - The WASM opcode to build.
+ /// \p valueType - The operand and result type for the built instruction.
+ /// \p numOperands - The number of operands for the built operation.
+ ///
+ /// \returns The parsed instruction result, or failure.
+ template <typename opcode, typename valueType, unsigned int numOperands>
+ inline parsed_inst_t buildNumericOp(
+ OpBuilder &builder,
+ std::enable_if_t<std::is_arithmetic_v<valueType>> * = nullptr);
+
+ /// Construct a conversion operation of type \p opType that takes a value
+ /// from
+ /// type \p inputType on the stack and will produce a value of type
+ /// \p outputType.
+ ///
+ /// \p opType - The WASM dialect operation to build.
+ /// \p inputType - The operand type for the built instruction.
+ /// \p outputType - The result type for the built instruction.
+ ///
+ /// \returns The parsed instruction result, or failure.
+ template <typename opType, typename inputType, typename outputType,
+ typename... extraArgsT>
+ inline parsed_inst_t buildConvertOp(OpBuilder &builder, extraArgsT...);
+
+ private:
+ explicit ExprParserProxy(ExpressionParser &exprParser)
+ : exprParser{exprParser} {};
+
+ private:
+ ExpressionParser &exprParser;
+ };
+
+private:
+ std::optional<Location> currentOpLoc;
+ ParserHead &parser;
+ WasmModuleSymbolTables const &symbols;
+ locals_t locals;
+ ValueStack valueStack;
};
class ParserHead {
@@ -1031,8 +995,9 @@ ExpressionParser::parseBlockFuncType(OpBuilder &builder) {
}
template <typename OpToCreate>
-parsed_inst_t ExpressionParser::parseBlockLikeOp(OpBuilder &builder) {
- auto opLoc = currentOpLoc;
+parsed_inst_t
+ExpressionParser::ExprParserProxy::parseBlockLikeOp(OpBuilder &builder) {
+ auto opLoc = getCurrentOpLoc();
auto funcType = parseBlockFuncType(builder);
if (failed(funcType))
return failure();
@@ -1046,49 +1011,35 @@ parsed_inst_t ExpressionParser::parseBlockLikeOp(OpBuilder &builder) {
Region *curRegion = curBlock->getParent();
auto resTypes = funcType->getResults();
llvm::SmallVector<Location> locations{};
- locations.resize(resTypes.size(), *currentOpLoc);
+ locations.resize(resTypes.size(), getCurrentOpLoc());
auto *successor =
builder.createBlock(curRegion, curRegion->end(), resTypes, locations);
builder.setInsertionPointToEnd(curBlock);
auto blockOp =
- OpToCreate::create(builder, *currentOpLoc, *inputOps, successor);
+ OpToCreate::create(builder, getCurrentOpLoc(), *inputOps, successor);
auto *blockBody = blockOp.createBlock();
- if (failed(parseBlockContent(builder, blockBody, resTypes, *opLoc, blockOp)))
+ if (failed(parseBlockContent(builder, blockBody, resTypes, opLoc, blockOp)))
return failure();
builder.setInsertionPointToStart(successor);
return {ValueRange{successor->getArguments()}};
}
-// We can't use SFINAE in combination with deleted default instantiation
-// to identify which parsers are registered due to GCC < 14.1 bug, so we
-// use a constexpr variable to register them.
-// This is in order to avoid having to have only one "registration" of the
-// opcode.
-#define REGISTER_PARSER_OPCODE_PARSER(parserType, opcode, builderName, \
- parserName) \
- template <> \
- constexpr bool parserType::hasParserForOpcode<opcode> = true; \
- template <> \
- inline parsed_inst_t parserType::parseInstrWithOpCode<opcode>( \
- OpBuilder & (builderName), ExpressionParser & (parserName))
-
-#define REGISTER_PRIMARY_WASM_INST_PARSER(opcode, builderName, parserName) \
- REGISTER_PARSER_OPCODE_PARSER(ExpressionParser::TopLevelInstParserRegistry, \
- opcode, builderName, parserName)
-
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::block, builder,
- exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::block>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseBlockLikeOp<BlockOp>(builder);
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::loop, builder,
- exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::loop>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseBlockLikeOp<LoopOp>(builder);
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::ifOpCode, builder,
- exprParser) {
- auto opLoc = exprParser.currentOpLoc;
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::ifOpCode>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ auto opLoc = exprParser.getCurrentOpLoc();
auto funcType = exprParser.parseBlockFuncType(builder);
if (failed(funcType))
return failure();
@@ -1117,14 +1068,14 @@ REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::ifOpCode, builder,
ByteSequence<WasmBinaryEncoding::endByte,
WasmBinaryEncoding::OpCode::elseOpCode>{};
auto parseIfRes = exprParser.parseBlockContent(
- builder, ifEntryBlock, resTypes, *opLoc, ifOp, ifElseFilter);
+ builder, ifEntryBlock, resTypes, opLoc, ifOp, ifElseFilter);
if (failed(parseIfRes))
return failure();
if (*parseIfRes == WasmBinaryEncoding::OpCode::elseOpCode) {
LDBG() << " else block is present.";
Block *elseEntryBlock = ifOp.createElseBlock();
auto parseElseRes = exprParser.parseBlockContent(builder, elseEntryBlock,
- resTypes, *opLoc, ifOp);
+ resTypes, opLoc, ifOp);
if (failed(parseElseRes))
return failure();
}
@@ -1132,9 +1083,10 @@ REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::ifOpCode, builder,
return {ValueRange{successor->getArguments()}};
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::branchIf, builder,
- exprParser) {
- auto level = exprParser.parser.parseLiteral<uint32_t>();
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::branchIf>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ auto level = exprParser.parser().parseLiteral<uint32_t>();
if (failed(level))
return failure();
Block *curBlock = builder.getBlock();
@@ -1160,15 +1112,16 @@ REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::branchIf, builder,
return {*branchArgs};
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::call, builder,
- exprParser) {
- auto loc = *exprParser.currentOpLoc;
- auto funcIdx = exprParser.parser.parseLiteral<uint32_t>();
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::call>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ auto loc = exprParser.getCurrentOpLoc();
+ auto funcIdx = exprParser.parser().parseLiteral<uint32_t>();
if (failed(funcIdx))
return failure();
- if (*funcIdx >= exprParser.symbols.funcSymbols.size())
+ if (*funcIdx >= exprParser.symbols().funcSymbols.size())
return emitError(loc, "Invalid function index: ") << *funcIdx;
- auto callee = exprParser.symbols.funcSymbols[*funcIdx];
+ auto callee = exprParser.symbols().funcSymbols[*funcIdx];
llvm::ArrayRef<Type> inTypes = callee.functionType.getInputs();
llvm::ArrayRef<Type> resTypes = callee.functionType.getResults();
parsed_inst_t inOperands = exprParser.popOperands(inTypes);
@@ -1179,31 +1132,33 @@ REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::call, builder,
return {callOp.getResults()};
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::localGet, builder,
- exprParser) {
- FailureOr<uint32_t> id = exprParser.parser.parseLiteral<uint32_t>();
- Location instLoc = *exprParser.currentOpLoc;
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::localGet>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ FailureOr<uint32_t> id = exprParser.parser().parseLiteral<uint32_t>();
+ Location instLoc = exprParser.getCurrentOpLoc();
if (failed(id))
return failure();
- if (*id >= exprParser.locals.size())
+ if (*id >= exprParser.locals().size())
return emitError(instLoc, "invalid local index. function has ")
- << exprParser.locals.size() << " accessible locals, received index "
- << *id;
- return {{LocalGetOp::create(builder, instLoc, exprParser.locals[*id])
+ << exprParser.locals().size()
+ << " accessible locals, received index " << *id;
+ return {{LocalGetOp::create(builder, instLoc, exprParser.locals()[*id])
.getResult()}};
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::globalGet,
- builder, exprParser) {
- FailureOr<uint32_t> id = exprParser.parser.parseLiteral<uint32_t>();
- Location instLoc = *exprParser.currentOpLoc;
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::globalGet>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ FailureOr<uint32_t> id = exprParser.parser().parseLiteral<uint32_t>();
+ Location instLoc = exprParser.getCurrentOpLoc();
if (failed(id))
return failure();
- if (*id >= exprParser.symbols.globalSymbols.size())
+ if (*id >= exprParser.symbols().globalSymbols.size())
return emitError(instLoc, "invalid global index. function has ")
- << exprParser.symbols.globalSymbols.size()
+ << exprParser.symbols().globalSymbols.size()
<< " accessible globals, received index " << *id;
- GlobalSymbolRefContainer globalVar = exprParser.symbols.globalSymbols[*id];
+ GlobalSymbolRefContainer globalVar = exprParser.symbols().globalSymbols[*id];
auto globalOp = GlobalGetOp::create(builder, instLoc, globalVar.globalType,
globalVar.symbol);
@@ -1231,13 +1186,15 @@ parsed_inst_t ExpressionParser::parseSetOrTee(OpBuilder &builder) {
->getResults()};
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::localSet, builder,
- exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::localSet>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseSetOrTee<LocalSetOp>(builder);
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::localTee, builder,
- exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::localTee>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseSetOrTee<LocalTeeOp>(builder);
}
@@ -1298,39 +1255,43 @@ attr_holder_t<ValT> buildLiteralAttr(OpBuilder &builder, ValT val) {
}
template <typename valueT>
-parsed_inst_t ExpressionParser::parseConstInst(
+parsed_inst_t ExpressionParser::ExprParserProxy::parseConstInst(
OpBuilder &builder, std::enable_if_t<std::is_arithmetic_v<valueT>> *) {
- auto parsedConstant = parser.parseLiteral<valueT>();
+ auto parsedConstant = parser().parseLiteral<valueT>();
if (failed(parsedConstant))
return failure();
auto constOp =
- ConstOp::create(builder, *currentOpLoc,
+ ConstOp::create(builder, getCurrentOpLoc(),
buildLiteralAttr<valueT>(builder, *parsedConstant));
return {{constOp.getResult()}};
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::constI32, builder,
- exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::constI32>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseConstInst<int32_t>(builder);
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::constI64, builder,
- exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::constI64>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseConstInst<int64_t>(builder);
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::constFP32,
- builder, exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::constFP32>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseConstInst<float>(builder);
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::constFP64,
- builder, exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::constFP64>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.parseConstInst<double>(builder);
}
template <typename opcode, typename valueType, unsigned int numOperands>
-inline parsed_inst_t ExpressionParser::buildNumericOp(
+inline parsed_inst_t ExpressionParser::ExprParserProxy::buildNumericOp(
OpBuilder &builder, std::enable_if_t<std::is_arithmetic_v<valueType>> *) {
auto ty = buildLiteralType<valueType>(builder);
LDBG() << "*** buildNumericOp: numOperands = " << numOperands
@@ -1341,20 +1302,16 @@ inline parsed_inst_t ExpressionParser::buildNumericOp(
auto operands = popOperands(tysToPop);
if (failed(operands))
return failure();
- auto op = opcode::create(builder, *currentOpLoc, *operands).getResult();
+ auto op = opcode::create(builder, getCurrentOpLoc(), *operands).getResult();
LDBG() << "Built operation: " << op;
return {{op}};
}
// Convenience macro for generating numerical operations.
#define BUILD_NUMERIC_OP(OP_NAME, N_ARGS, PREFIX, SUFFIX, TYPE) \
- template <> \
- constexpr bool ExpressionParser::TopLevelInstParserRegistry:: \
- hasParserForOpcode<WasmBinaryEncoding::OpCode::PREFIX##SUFFIX> = true; \
- template <> \
- inline parsed_inst_t ExpressionParser::TopLevelInstParserRegistry:: \
- parseInstrWithOpCode<WasmBinaryEncoding::OpCode::PREFIX##SUFFIX>( \
- OpBuilder & builder, ExpressionParser & exprParser) { \
+ inline parsed_inst_t parse( \
+ OpCode<WasmBinaryEncoding::OpCode::PREFIX##SUFFIX>, OpBuilder &builder, \
+ ExpressionParser::ExprParserProxy &exprParser) { \
return exprParser.buildNumericOp<OP_NAME, TYPE, N_ARGS>(builder); \
}
@@ -1439,8 +1396,9 @@ BUILD_NUMERIC_UNARY_OP_INT(PopCntOp, popcnt)
template <typename opType, typename inputType, typename outputType,
typename... extraArgsT>
-inline parsed_inst_t ExpressionParser::buildConvertOp(OpBuilder &builder,
- extraArgsT... extraArgs) {
+inline parsed_inst_t
+ExpressionParser::ExprParserProxy::buildConvertOp(OpBuilder &builder,
+ extraArgsT... extraArgs) {
static_assert(std::is_arithmetic_v<inputType>,
"InputType should be an arithmetic type");
static_assert(std::is_arithmetic_v<outputType>,
@@ -1450,30 +1408,28 @@ inline parsed_inst_t ExpressionParser::buildConvertOp(OpBuilder &builder,
auto operand = popOperands(intype);
if (failed(operand))
return failure();
- auto op = opType::create(builder, *currentOpLoc, outType, operand->front(),
- extraArgs...);
+ auto op = opType::create(builder, getCurrentOpLoc(), outType,
+ operand->front(), extraArgs...);
LDBG() << "Built operation: " << op;
return {{op.getResult()}};
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::demoteF64ToF32,
- builder, exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::demoteF64ToF32>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.buildConvertOp<DemoteOp, double, float>(builder);
}
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::wrap, builder,
- exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::wrap>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.buildConvertOp<WrapOp, int64_t, int32_t>(builder);
}
#define BUILD_CONVERSION_OP(IN_T, OUT_T, SOURCE_OP, TARGET_OP) \
- template <> \
- constexpr bool ExpressionParser::TopLevelInstParserRegistry:: \
- hasParserForOpcode<WasmBinaryEncoding::OpCode::SOURCE_OP> = true; \
- template <> \
- inline parsed_inst_t ExpressionParser::TopLevelInstParserRegistry:: \
- parseInstrWithOpCode<WasmBinaryEncoding::OpCode::SOURCE_OP>( \
- OpBuilder & builder, ExpressionParser & exprParser) { \
+ inline parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::SOURCE_OP>, \
+ OpBuilder &builder, \
+ ExpressionParser::ExprParserProxy &exprParser) { \
return exprParser.buildConvertOp<TARGET_OP, IN_T, OUT_T>(builder); \
}
@@ -1505,16 +1461,9 @@ BUILD_CONVERSION_OP(int32_t, int64_t, extendU, ExtendUI32Op)
#undef BUILD_CONVERSION_OP
#define BUILD_SLICE_EXTEND_PARSER(IT_WIDTH, EXTRACT_WIDTH) \
- template <> \
- constexpr bool \
- ExpressionParser::TopLevelInstParserRegistry::hasParserForOpcode< \
- WasmBinaryEncoding::OpCode::extendI##IT_WIDTH##EXTRACT_WIDTH##S> = \
- true; \
- template <> \
- parsed_inst_t \
- ExpressionParser::TopLevelInstParserRegistry::parseInstrWithOpCode< \
- WasmBinaryEncoding::OpCode::extendI##IT_WIDTH##EXTRACT_WIDTH##S>( \
- OpBuilder & builder, ExpressionParser & exprParser) { \
+ parsed_inst_t parse( \
+ OpCode<WasmBinaryEncoding::OpCode::extendI##IT_WIDTH##EXTRACT_WIDTH##S>, \
+ OpBuilder &builder, ExpressionParser::ExprParserProxy &exprParser) { \
using inout_t = int##IT_WIDTH##_t; \
auto attr = builder.getUI32IntegerAttr(EXTRACT_WIDTH); \
return exprParser.buildConvertOp<ExtendLowBitsSOp, inout_t, inout_t>( \
@@ -1529,34 +1478,23 @@ BUILD_SLICE_EXTEND_PARSER(64, 32)
#undef BUILD_SLICE_EXTEND_PARSER
-REGISTER_PRIMARY_WASM_INST_PARSER(WasmBinaryEncoding::OpCode::promoteF32ToF64,
- builder, exprParser) {
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::promoteF32ToF64>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
return exprParser.buildConvertOp<PromoteOp, float, double>(builder);
}
#define BUILD_REINTERPRET_PARSER(WIDTH, FP_TYPE) \
- template <> \
- constexpr bool \
- ExpressionParser::TopLevelInstParserRegistry::hasParserForOpcode< \
- WasmBinaryEncoding::OpCode::reinterpretF##WIDTH##AsI##WIDTH> = true; \
- template <> \
- inline parsed_inst_t \
- ExpressionParser::TopLevelInstParserRegistry::parseInstrWithOpCode< \
- WasmBinaryEncoding::OpCode::reinterpretF##WIDTH##AsI##WIDTH>( \
- OpBuilder & builder, ExpressionParser & exprParser) { \
+ inline parsed_inst_t parse( \
+ OpCode<WasmBinaryEncoding::OpCode::reinterpretF##WIDTH##AsI##WIDTH>, \
+ OpBuilder &builder, ExpressionParser::ExprParserProxy &exprParser) { \
return exprParser.buildConvertOp<ReinterpretOp, FP_TYPE, int##WIDTH##_t>( \
builder); \
} \
\
- template <> \
- constexpr bool \
- ExpressionParser::TopLevelInstParserRegistry::hasParserForOpcode< \
- WasmBinaryEncoding::OpCode::reinterpretI##WIDTH##AsF##WIDTH> = true; \
- template <> \
- inline parsed_inst_t \
- ExpressionParser::TopLevelInstParserRegistry::parseInstrWithOpCode< \
- WasmBinaryEncoding::OpCode::reinterpretI##WIDTH##AsF##WIDTH>( \
- OpBuilder & builder, ExpressionParser & exprParser) { \
+ inline parsed_inst_t parse( \
+ OpCode<WasmBinaryEncoding::OpCode::reinterpretI##WIDTH##AsF##WIDTH>, \
+ OpBuilder &builder, ExpressionParser::ExprParserProxy &exprParser) { \
return exprParser.buildConvertOp<ReinterpretOp, int##WIDTH##_t, FP_TYPE>( \
builder); \
}
@@ -1566,10 +1504,92 @@ BUILD_REINTERPRET_PARSER(64, double)
#undef BUILD_REINTERPRET_PARSER
+class InstDispatcher {
+private:
+ template <std::byte OpCode, typename = void>
+ struct HasParserRegistered : std::false_type {};
+
+ template <std::byte opCode>
+ struct HasParserRegistered<
+ opCode, std::void_t<decltype(parse(
+ std::declval<OpCode<opCode>>(), std::declval<OpBuilder &>(),
+ std::declval<ExpressionParser::ExprParserProxy &>()))>>
+ : std::true_type {};
+
+ template <std::byte opCode>
+ static constexpr bool hasParseredRegistered =
+ HasParserRegistered<opCode>::value;
+ using dispatch_t = parsed_inst_t (*)(OpBuilder &,
+ ExpressionParser::ExprParserProxy &);
+
+ static inline parsed_inst_t
+ unreachableHandler(OpBuilder &,
+ ExpressionParser::ExprParserProxy &expressionParser) {
+ llvm_unreachable("Failure in opcode parser dispatch logic.");
+ return mlir::failure();
+ }
+
+ template <std::byte opCode>
+ static parsed_inst_t
+ forwardToParser(OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ return parse(OpCode<opCode>{}, builder, exprParser);
+ }
+
+ template <std::byte opCode>
+ static constexpr dispatch_t getHandlerForOpCode() {
+ if constexpr (hasParseredRegistered<opCode>)
+ return forwardToParser<opCode>;
+ else
+ return unreachableHandler;
+ }
+
+private:
+ static inline parsed_inst_t
+ invalidOpcodeDiag(OpBuilder &,
+ ExpressionParser::ExprParserProxy &expressionParser,
+ std::byte opCode) {
+ return emitError(expressionParser.getCurrentOpLoc(),
+ "unknown instruction opcode: ")
+ << static_cast<int>(opCode);
+ }
+
+ template <std::byte... opCodes>
+ static inline parsed_inst_t
+ dispatchImpl(std::byte opCode, OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser,
+ ByteSequence<opCodes...>) {
+ static constexpr std::array<bool, 256> opcodeValidityMap{
+ hasParseredRegistered<opCodes>...};
+ static constexpr std::array<dispatch_t, 256> dispatchTable{
+ getHandlerForOpCode<opCodes>()...};
+ if (opcodeValidityMap[static_cast<size_t>(opCode)]) {
+ return dispatchTable[static_cast<size_t>(opCode)](builder, exprParser);
+ }
+ return invalidOpcodeDiag(builder, exprParser, opCode);
+ }
+
+public:
+ ///
+ /// @brief dispatch control flow to the sub parser registered for opCode in
+ /// `ParserRegistry`
+ ///
+ /// @param opCode opCode of the instruction to be Parsed
+ /// @param builder builder that will be passed to the parser
+ /// @param exprParser the generic parser passed to the sub parser
+ ///
+ /// @return the result of the parser or an error if there is no parser
+ /// registered for the opcode (emits a diagnostic)
+ static parsed_inst_t dispatch(std::byte opCode, OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ return dispatchImpl(opCode, builder, exprParser, all8bitsBytes);
+ }
+};
+
inline parsed_inst_t
ExpressionParser::dispatchToInstParser(std::byte opCode, OpBuilder &builder) {
- return InstDispatcher<ExpressionParser::TopLevelInstParserRegistry>::dispatch(
- opCode, builder, *this);
+ ExpressionParser::ExprParserProxy exprParser{*this};
+ return InstDispatcher::dispatch(opCode, builder, exprParser);
}
class WasmBinaryParser {
private:
>From 641480930997c2e241882b04e416afc14b895b92 Mon Sep 17 00:00:00 2001
From: Luc Forget <dev at alias.lforget.fr>
Date: Wed, 29 Jul 2026 16:44:43 +0900
Subject: [PATCH 3/5] [mlir][wasm] Add support for auto registration of parser
with sub-opcode
---
mlir/lib/Target/Wasm/TranslateFromWasm.cpp | 36 +++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
index 22e5f56e9068a..58de04e779e30 100644
--- a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
+++ b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
@@ -16,6 +16,7 @@
#include "mlir/IR/BuiltinAttributeInterfaces.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Location.h"
#include "mlir/Support/LLVM.h"
#include "mlir/Target/Wasm/WasmBinaryEncoding.h"
@@ -1519,6 +1520,25 @@ class InstDispatcher {
template <std::byte opCode>
static constexpr bool hasParseredRegistered =
HasParserRegistered<opCode>::value;
+
+ template <std::byte OpCode, typename = void>
+ struct HasParserWthSubOpCodeRegistered : std::false_type {};
+
+ template <std::byte opCode>
+ struct HasParserWthSubOpCodeRegistered<
+ opCode, std::void_t<decltype(parse(
+ std::declval<OpCode<opCode>>(), std::declval<OpBuilder &>(),
+ std::declval<ExpressionParser::ExprParserProxy &>(),
+ std::declval<std::uint32_t>()))>> : std::true_type {
+ static_assert(!hasParseredRegistered<opCode>,
+ "plain parser and parser with sub-opcode can't be registered "
+ "for the same opcode");
+ };
+
+ template <std::byte opCode>
+ static constexpr bool hasParseredWithSubOpCodeRegistered =
+ HasParserWthSubOpCodeRegistered<opCode>::value;
+
using dispatch_t = parsed_inst_t (*)(OpBuilder &,
ExpressionParser::ExprParserProxy &);
@@ -1536,10 +1556,23 @@ class InstDispatcher {
return parse(OpCode<opCode>{}, builder, exprParser);
}
+ template <std::byte opCode>
+ static parsed_inst_t
+ forwardToParserWithSubOpCode(OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &exprParser) {
+ auto loc = exprParser.getCurrentOpLoc();
+ auto subOpCode = exprParser.parser().parseLiteral<std::uint32_t>();
+ if (failed(subOpCode))
+ return emitError(loc) << "expecting sub opcode for opcode " << opCode;
+ return parse(OpCode<opCode>{}, builder, exprParser, subOpCode.value());
+ }
+
template <std::byte opCode>
static constexpr dispatch_t getHandlerForOpCode() {
if constexpr (hasParseredRegistered<opCode>)
return forwardToParser<opCode>;
+ else if constexpr (hasParseredWithSubOpCodeRegistered<opCode>)
+ return forwardToParserWithSubOpCode<opCode>;
else
return unreachableHandler;
}
@@ -1560,7 +1593,8 @@ class InstDispatcher {
ExpressionParser::ExprParserProxy &exprParser,
ByteSequence<opCodes...>) {
static constexpr std::array<bool, 256> opcodeValidityMap{
- hasParseredRegistered<opCodes>...};
+ (hasParseredRegistered<opCodes> ||
+ hasParseredWithSubOpCodeRegistered<opCodes>)...};
static constexpr std::array<dispatch_t, 256> dispatchTable{
getHandlerForOpCode<opCodes>()...};
if (opcodeValidityMap[static_cast<size_t>(opCode)]) {
>From 7b712193faea130a6edf69b0a3bebb8aa804a2e0 Mon Sep 17 00:00:00 2001
From: Luc Forget <dev at alias.lforget.fr>
Date: Wed, 29 Jul 2026 17:03:34 +0900
Subject: [PATCH 4/5] [mlir][wasm] Add parser support for saturating truncation
---
.../mlir/Target/Wasm/WasmBinaryEncoding.h | 2 ++
mlir/lib/Target/Wasm/TranslateFromWasm.cpp | 31 ++++++++++++++++++-
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h b/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h
index fcaec612f913d..66506ed19b1a7 100644
--- a/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h
+++ b/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h
@@ -174,6 +174,8 @@ struct WasmBinaryEncoding {
static constexpr std::byte extendI648S{0xC2};
static constexpr std::byte extendI6416S{0xC3};
static constexpr std::byte extendI6432S{0xC4};
+
+ static constexpr std::byte saturatedTruncate{0xFC};
};
/// Byte encodings of types in Wasm binaries
diff --git a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
index 58de04e779e30..a700a875e1caa 100644
--- a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
+++ b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
@@ -1461,6 +1461,34 @@ BUILD_CONVERSION_OP(int32_t, int64_t, extendU, ExtendUI32Op)
#undef BUILD_CONVERSION_OP
+parsed_inst_t parse(OpCode<WasmBinaryEncoding::OpCode::saturatedTruncate>,
+ OpBuilder &builder,
+ ExpressionParser::ExprParserProxy &parser,
+ std::uint32_t subOpCode) {
+ if (subOpCode > 7)
+ return emitError(parser.getCurrentOpLoc())
+ << "invalid sub-opcode for trunc_saturate: " << subOpCode;
+ LDBG() << "Sub subOpcode for operation: " << subOpCode;
+ bool isDestUnsigned = subOpCode & 1;
+ bool isSrcF64 = subOpCode & 2;
+ bool isDestI64 = subOpCode & 4;
+ auto srcTypeBuilder =
+ isSrcF64 ? buildLiteralType<double> : buildLiteralType<float>;
+ auto destTypeBuilder =
+ isDestI64 ? buildLiteralType<uint64_t> : buildLiteralType<int32_t>;
+ auto srcOp = parser.popOperands(srcTypeBuilder(builder));
+ if (failed(srcOp))
+ return failure();
+ Operation *op =
+ isDestUnsigned
+ ? TruncSatUIOp::create(builder, parser.getCurrentOpLoc(),
+ destTypeBuilder(builder), srcOp->front())
+ : TruncSatSIOp::create(builder, parser.getCurrentOpLoc(),
+ destTypeBuilder(builder), srcOp->front());
+ LDBG() << "Built operation: " << op;
+ return {{op->getResult(0)}};
+}
+
#define BUILD_SLICE_EXTEND_PARSER(IT_WIDTH, EXTRACT_WIDTH) \
parsed_inst_t parse( \
OpCode<WasmBinaryEncoding::OpCode::extendI##IT_WIDTH##EXTRACT_WIDTH##S>, \
@@ -1563,7 +1591,8 @@ class InstDispatcher {
auto loc = exprParser.getCurrentOpLoc();
auto subOpCode = exprParser.parser().parseLiteral<std::uint32_t>();
if (failed(subOpCode))
- return emitError(loc) << "expecting sub opcode for opcode " << opCode;
+ return emitError(loc) << "expecting sub opcode for opcode "
+ << static_cast<uint8_t>(opCode);
return parse(OpCode<opCode>{}, builder, exprParser, subOpCode.value());
}
>From f7efd8bfc4e451d4ce561fc1d7df99c05a343a18 Mon Sep 17 00:00:00 2001
From: Ferdinand Lemaire <ferdinand.lemaire at woven-planet.global>
Date: Wed, 29 Jul 2026 17:42:23 +0900
Subject: [PATCH 5/5] [mlir][wasm] Add tests for trunc saturate
---
.../Target/Wasm/inputs/trunc_sat.yaml.wasm | 69 ++++++++++++++
mlir/test/Target/Wasm/trunc_sat.mlir | 94 +++++++++++++++++++
2 files changed, 163 insertions(+)
create mode 100644 mlir/test/Target/Wasm/inputs/trunc_sat.yaml.wasm
create mode 100644 mlir/test/Target/Wasm/trunc_sat.mlir
diff --git a/mlir/test/Target/Wasm/inputs/trunc_sat.yaml.wasm b/mlir/test/Target/Wasm/inputs/trunc_sat.yaml.wasm
new file mode 100644
index 0000000000000..e145072abb98a
--- /dev/null
+++ b/mlir/test/Target/Wasm/inputs/trunc_sat.yaml.wasm
@@ -0,0 +1,69 @@
+--- !WASM
+FileHeader:
+ Version: 0x1
+Sections:
+ - Type: TYPE
+ Signatures:
+ - Index: 0
+ ParamTypes: []
+ ReturnTypes:
+ - I32
+ - Index: 1
+ ParamTypes: []
+ ReturnTypes:
+ - I64
+ - Type: FUNCTION
+ FunctionTypes: [ 0, 0, 0, 0, 1, 1, 1, 1 ]
+ - Type: EXPORT
+ Exports:
+ - Name: trunc_sat_f32_s
+ Kind: FUNCTION
+ Index: 0
+ - Name: trunc_sat_f32_u
+ Kind: FUNCTION
+ Index: 1
+ - Name: trunc_sat_f64_s
+ Kind: FUNCTION
+ Index: 2
+ - Name: trunc_sat_f64_u
+ Kind: FUNCTION
+ Index: 3
+ - Name: i64_trunc_sat_f32_s
+ Kind: FUNCTION
+ Index: 4
+ - Name: i64_trunc_sat_f32_u
+ Kind: FUNCTION
+ Index: 5
+ - Name: i64_trunc_sat_f64_s
+ Kind: FUNCTION
+ Index: 6
+ - Name: i64_trunc_sat_f64_u
+ Kind: FUNCTION
+ Index: 7
+ - Type: CODE
+ Functions:
+ - Index: 0
+ Locals: []
+ Body: 439A994141FC000B
+ - Index: 1
+ Locals: []
+ Body: 439A994141FC010B
+ - Index: 2
+ Locals: []
+ Body: 443333333333332840FC020B
+ - Index: 3
+ Locals: []
+ Body: 443333333333332840FC030B
+ - Index: 4
+ Locals: []
+ Body: 439A994141FC040B
+ - Index: 5
+ Locals: []
+ Body: 439A994141FC050B
+ - Index: 6
+ Locals: []
+ Body: 443333333333332840FC060B
+ - Index: 7
+ Locals: []
+ Body: 443333333333332840FC070B
+...
diff --git a/mlir/test/Target/Wasm/trunc_sat.mlir b/mlir/test/Target/Wasm/trunc_sat.mlir
new file mode 100644
index 0000000000000..4fa531caab74d
--- /dev/null
+++ b/mlir/test/Target/Wasm/trunc_sat.mlir
@@ -0,0 +1,94 @@
+// RUN: yaml2obj %S/inputs/trunc_sat.yaml.wasm -o - | mlir-translate --import-wasm | FileCheck %s
+
+/* Source code used to generate this test:
+(module
+ (func (export "trunc_sat_f32_s") (result i32)
+ f32.const 12.1
+ i32.trunc_sat_f32_s
+ )
+
+ (func (export "trunc_sat_f32_u") (result i32)
+ f32.const 12.1
+ i32.trunc_sat_f32_u
+ )
+
+ (func (export "trunc_sat_f64_s") (result i32)
+ f64.const 12.1
+ i32.trunc_sat_f64_s
+ )
+
+ (func (export "trunc_sat_f64_u") (result i32)
+ f64.const 12.1
+ i32.trunc_sat_f64_u
+ )
+
+ (func (export "i64_trunc_sat_f32_s") (result i64)
+ f32.const 12.1
+ i64.trunc_sat_f32_s
+ )
+
+ (func (export "i64_trunc_sat_f32_u") (result i64)
+ f32.const 12.1
+ i64.trunc_sat_f32_u
+ )
+
+ (func (export "i64_trunc_sat_f64_s") (result i64)
+ f64.const 12.1
+ i64.trunc_sat_f64_s
+ )
+
+ (func (export "i64_trunc_sat_f64_u") (result i64)
+ f64.const 12.1
+ i64.trunc_sat_f64_u
+ )
+)
+*/
+
+
+// CHECK-LABEL: wasmssa.func exported @trunc_sat_f32_s() -> i32 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f32
+// CHECK: %[[TRUNC_SAT_SI_0:.*]] = wasmssa.trunc_sat_si %[[CONST_0]] : f32 to i32
+// CHECK: wasmssa.return %[[TRUNC_SAT_SI_0]] : i32
+// CHECK: }
+
+// CHECK-LABEL: wasmssa.func exported @trunc_sat_f32_u() -> i32 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f32
+// CHECK: %[[TRUNC_SAT_UI_0:.*]] = wasmssa.trunc_sat_ui %[[CONST_0]] : f32 to i32
+// CHECK: wasmssa.return %[[TRUNC_SAT_UI_0]] : i32
+// CHECK: }
+
+// CHECK-LABEL: wasmssa.func exported @trunc_sat_f64_s() -> i32 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f64
+// CHECK: %[[TRUNC_SAT_SI_0:.*]] = wasmssa.trunc_sat_si %[[CONST_0]] : f64 to i32
+// CHECK: wasmssa.return %[[TRUNC_SAT_SI_0]] : i32
+// CHECK: }
+
+// CHECK-LABEL: wasmssa.func exported @trunc_sat_f64_u() -> i32 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f64
+// CHECK: %[[TRUNC_SAT_UI_0:.*]] = wasmssa.trunc_sat_ui %[[CONST_0]] : f64 to i32
+// CHECK: wasmssa.return %[[TRUNC_SAT_UI_0]] : i32
+// CHECK: }
+
+// CHECK-LABEL: wasmssa.func exported @i64_trunc_sat_f32_s() -> i64 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f32
+// CHECK: %[[TRUNC_SAT_SI_0:.*]] = wasmssa.trunc_sat_si %[[CONST_0]] : f32 to i64
+// CHECK: wasmssa.return %[[TRUNC_SAT_SI_0]] : i64
+// CHECK: }
+
+// CHECK-LABEL: wasmssa.func exported @i64_trunc_sat_f32_u() -> i64 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f32
+// CHECK: %[[TRUNC_SAT_UI_0:.*]] = wasmssa.trunc_sat_ui %[[CONST_0]] : f32 to i64
+// CHECK: wasmssa.return %[[TRUNC_SAT_UI_0]] : i64
+// CHECK: }
+
+// CHECK-LABEL: wasmssa.func exported @i64_trunc_sat_f64_s() -> i64 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f64
+// CHECK: %[[TRUNC_SAT_SI_0:.*]] = wasmssa.trunc_sat_si %[[CONST_0]] : f64 to i64
+// CHECK: wasmssa.return %[[TRUNC_SAT_SI_0]] : i64
+// CHECK: }
+
+// CHECK-LABEL: wasmssa.func exported @i64_trunc_sat_f64_u() -> i64 {
+// CHECK: %[[CONST_0:.*]] = wasmssa.const 1.210000e+01 : f64
+// CHECK: %[[TRUNC_SAT_UI_0:.*]] = wasmssa.trunc_sat_ui %[[CONST_0]] : f64 to i64
+// CHECK: wasmssa.return %[[TRUNC_SAT_UI_0]] : i64
+// CHECK: }
More information about the Mlir-commits
mailing list