[Mlir-commits] [mlir] [mlir][wasm] Support for saturating FP truncations (PR #212709)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Wed Jul 29 02:06:30 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Luc Forget (lforg37)
<details>
<summary>Changes</summary>
Add support for saturate truncation of float to int operations.
This also requires the support of parser with sub-opcodes.
This is handled by supporting parser with extra argument for sub-opcode.
---
Patch is 47.08 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/212709.diff
5 Files Affected:
- (modified) mlir/include/mlir/Dialect/WasmSSA/IR/WasmSSAOps.td (+30)
- (modified) mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h (+2)
- (modified) mlir/lib/Target/Wasm/TranslateFromWasm.cpp (+344-261)
- (added) mlir/test/Target/Wasm/inputs/trunc_sat.yaml.wasm (+69)
- (added) mlir/test/Target/Wasm/trunc_sat.mlir (+94)
``````````diff
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:
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 cea6bd2816dc1..a700a875e1caa 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"
@@ -30,6 +31,7 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
+#include <type_traits>
#include <variant>
#define DEBUG_TYPE "wasm-translate"
@@ -247,6 +249,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 +260,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 +327,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 +361,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 +996,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 +1012,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 +1069,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 +1084,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 +1113,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().funcSymbo...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/212709
More information about the Mlir-commits
mailing list