Skip to content

Conversation

@lforg37
Copy link
Contributor

@lforg37 lforg37 commented Aug 21, 2025

This is the following of PR #154452.

It extend Wasm binary to Wasm SSA importer with support of control flow operations, comparison operations and conversion operations.

lforg37 and others added 2 commits August 21, 2025 11:25
---------

Co-authored-by: Ferdinand Lemaire <[email protected]>
Co-authored-by: Jessica Paquette <[email protected]>
---------

Co-authored-by: Ferdinand Lemaire <[email protected]>
Co-authored-by: Jessica Paquette <[email protected]>
@lforg37 lforg37 force-pushed the lforget.wasm_importer_controlf_flow_conv_comp branch from 3f3d3ef to 77ad742 Compare August 21, 2025 05:44
Add label level tracking system to the importer.
Add import for all the control flow related ops.

---------

Co-authored-by: Ferdinand Lemaire <[email protected]>
Co-authored-by: Jessica Paquette <[email protected]>
@lforg37 lforg37 force-pushed the lforget.wasm_importer_controlf_flow_conv_comp branch from 77ad742 to f56e2a2 Compare August 21, 2025 05:56
@lforg37 lforg37 marked this pull request as ready for review August 21, 2025 05:56
@llvmbot llvmbot added the mlir label Aug 21, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 21, 2025

@llvm/pr-subscribers-mlir

Author: Luc Forget (lforg37)

Changes

Patch is 113.33 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/154674.diff

65 Files Affected:

  • (modified) mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h (+71)
  • (modified) mlir/lib/Target/Wasm/TranslateFromWasm.cpp (+438-3)
  • (modified) mlir/test/Target/Wasm/abs.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/add_div.mlir (+40)
  • (modified) mlir/test/Target/Wasm/and.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/block.mlir (+16)
  • (added) mlir/test/Target/Wasm/block_complete_type.mlir (+24)
  • (added) mlir/test/Target/Wasm/block_value_type.mlir (+19)
  • (added) mlir/test/Target/Wasm/branch_if.mlir (+29)
  • (added) mlir/test/Target/Wasm/call.mlir (+17)
  • (modified) mlir/test/Target/Wasm/clz.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/comparison_ops.mlir (+269)
  • (added) mlir/test/Target/Wasm/convert.mlir (+85)
  • (modified) mlir/test/Target/Wasm/copysign.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/ctz.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/demote.mlir (+15)
  • (modified) mlir/test/Target/Wasm/div.mlir (+10-10)
  • (added) mlir/test/Target/Wasm/double_nested_loop.mlir (+63)
  • (added) mlir/test/Target/Wasm/empty_blocks_list_and_stack.mlir (+53)
  • (added) mlir/test/Target/Wasm/eq.mlir (+56)
  • (added) mlir/test/Target/Wasm/eqz.mlir (+21)
  • (added) mlir/test/Target/Wasm/extend.mlir (+69)
  • (added) mlir/test/Target/Wasm/if.mlir (+112)
  • (added) mlir/test/Target/Wasm/inputs/add_div.yaml.wasm (+50)
  • (added) mlir/test/Target/Wasm/inputs/block.yaml.wasm (+22)
  • (added) mlir/test/Target/Wasm/inputs/block_complete_type.yaml.wasm (+23)
  • (added) mlir/test/Target/Wasm/inputs/block_value_type.yaml.wasm (+18)
  • (added) mlir/test/Target/Wasm/inputs/branch_if.yaml.wasm (+18)
  • (added) mlir/test/Target/Wasm/inputs/call.yaml.wasm (+26)
  • (added) mlir/test/Target/Wasm/inputs/comparison_ops.yaml.wasm (+88)
  • (added) mlir/test/Target/Wasm/inputs/convert.yaml.wasm (+69)
  • (added) mlir/test/Target/Wasm/inputs/demote.yaml.wasm (+18)
  • (added) mlir/test/Target/Wasm/inputs/double_nested_loop.yaml.wasm (+19)
  • (added) mlir/test/Target/Wasm/inputs/empty_blocks_list_and_stack.yaml.wasm (+21)
  • (added) mlir/test/Target/Wasm/inputs/eq.yaml.wasm (+27)
  • (added) mlir/test/Target/Wasm/inputs/eqz.yaml.wasm (+29)
  • (added) mlir/test/Target/Wasm/inputs/extend.yaml.wasm (+40)
  • (added) mlir/test/Target/Wasm/inputs/if.yaml.wasm (+25)
  • (added) mlir/test/Target/Wasm/inputs/loop.yaml.wasm (+17)
  • (added) mlir/test/Target/Wasm/inputs/loop_with_inst.yaml.wasm (+20)
  • (added) mlir/test/Target/Wasm/inputs/ne.yaml.wasm (+27)
  • (added) mlir/test/Target/Wasm/inputs/promote.yaml.wasm (+18)
  • (added) mlir/test/Target/Wasm/inputs/reinterpret.yaml.wasm (+53)
  • (added) mlir/test/Target/Wasm/inputs/rounding.yaml.wasm (+37)
  • (added) mlir/test/Target/Wasm/inputs/wrap.yaml.wasm (+24)
  • (added) mlir/test/Target/Wasm/loop.mlir (+17)
  • (added) mlir/test/Target/Wasm/loop_with_inst.mlir (+33)
  • (modified) mlir/test/Target/Wasm/max.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/min.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/ne.mlir (+52)
  • (modified) mlir/test/Target/Wasm/neg.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/or.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/popcnt.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/promote.mlir (+14)
  • (added) mlir/test/Target/Wasm/reinterpret.mlir (+46)
  • (modified) mlir/test/Target/Wasm/rem.mlir (+4-4)
  • (modified) mlir/test/Target/Wasm/rotl.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/rotr.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/rounding.mlir (+50)
  • (modified) mlir/test/Target/Wasm/shl.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/shr_s.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/shr_u.mlir (+2-2)
  • (modified) mlir/test/Target/Wasm/sqrt.mlir (+2-2)
  • (added) mlir/test/Target/Wasm/wrap.mlir (+15)
  • (modified) mlir/test/Target/Wasm/xor.mlir (+2-2)
diff --git a/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h b/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h
index 21adde878994e..cd9ef5b2132a4 100644
--- a/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h
+++ b/mlir/include/mlir/Target/Wasm/WasmBinaryEncoding.h
@@ -19,6 +19,14 @@ namespace mlir {
 struct WasmBinaryEncoding {
   /// Byte encodings for Wasm instructions.
   struct OpCode {
+    // Control instructions.
+    static constexpr std::byte block{0x02};
+    static constexpr std::byte loop{0x03};
+    static constexpr std::byte ifOpCode{0x04};
+    static constexpr std::byte elseOpCode{0x05};
+    static constexpr std::byte branchIf{0x0D};
+    static constexpr std::byte call{0x10};
+
     // Locals, globals, constants.
     static constexpr std::byte localGet{0x20};
     static constexpr std::byte localSet{0x21};
@@ -29,6 +37,42 @@ struct WasmBinaryEncoding {
     static constexpr std::byte constFP32{0x43};
     static constexpr std::byte constFP64{0x44};
 
+    // Comparisons.
+    static constexpr std::byte eqzI32{0x45};
+    static constexpr std::byte eqI32{0x46};
+    static constexpr std::byte neI32{0x47};
+    static constexpr std::byte ltSI32{0x48};
+    static constexpr std::byte ltUI32{0x49};
+    static constexpr std::byte gtSI32{0x4A};
+    static constexpr std::byte gtUI32{0x4B};
+    static constexpr std::byte leSI32{0x4C};
+    static constexpr std::byte leUI32{0x4D};
+    static constexpr std::byte geSI32{0x4E};
+    static constexpr std::byte geUI32{0x4F};
+    static constexpr std::byte eqzI64{0x50};
+    static constexpr std::byte eqI64{0x51};
+    static constexpr std::byte neI64{0x52};
+    static constexpr std::byte ltSI64{0x53};
+    static constexpr std::byte ltUI64{0x54};
+    static constexpr std::byte gtSI64{0x55};
+    static constexpr std::byte gtUI64{0x56};
+    static constexpr std::byte leSI64{0x57};
+    static constexpr std::byte leUI64{0x58};
+    static constexpr std::byte geSI64{0x59};
+    static constexpr std::byte geUI64{0x5A};
+    static constexpr std::byte eqF32{0x5B};
+    static constexpr std::byte neF32{0x5C};
+    static constexpr std::byte ltF32{0x5D};
+    static constexpr std::byte gtF32{0x5E};
+    static constexpr std::byte leF32{0x5F};
+    static constexpr std::byte geF32{0x60};
+    static constexpr std::byte eqF64{0x61};
+    static constexpr std::byte neF64{0x62};
+    static constexpr std::byte ltF64{0x63};
+    static constexpr std::byte gtF64{0x64};
+    static constexpr std::byte leF64{0x65};
+    static constexpr std::byte geF64{0x66};
+
     // Numeric operations.
     static constexpr std::byte clzI32{0x67};
     static constexpr std::byte ctzI32{0x68};
@@ -93,6 +137,33 @@ struct WasmBinaryEncoding {
     static constexpr std::byte maxF64{0xA5};
     static constexpr std::byte copysignF64{0xA6};
     static constexpr std::byte wrap{0xA7};
+
+    // Conversion operations
+    static constexpr std::byte extendS{0xAC};
+    static constexpr std::byte extendU{0xAD};
+    static constexpr std::byte convertSI32F32{0xB2};
+    static constexpr std::byte convertUI32F32{0xB3};
+    static constexpr std::byte convertSI64F32{0xB4};
+    static constexpr std::byte convertUI64F32{0xB5};
+
+    static constexpr std::byte demoteF64ToF32{0xB6};
+
+    static constexpr std::byte convertSI32F64{0xB7};
+    static constexpr std::byte convertUI32F64{0xB8};
+    static constexpr std::byte convertSI64F64{0xB9};
+    static constexpr std::byte convertUI64F64{0xBA};
+
+    static constexpr std::byte promoteF32ToF64{0xBB};
+    static constexpr std::byte reinterpretF32AsI32{0xBC};
+    static constexpr std::byte reinterpretF64AsI64{0xBD};
+    static constexpr std::byte reinterpretI32AsF32{0xBE};
+    static constexpr std::byte reinterpretI64AsF64{0xBF};
+
+    static constexpr std::byte extendI328S{0xC0};
+    static constexpr std::byte extendI3216S{0xC1};
+    static constexpr std::byte extendI648S{0xC2};
+    static constexpr std::byte extendI6416S{0xC3};
+    static constexpr std::byte extendI6432S{0xC4};
   };
 
   /// Byte encodings of types in Wasm binaries
diff --git a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
index 6afbe0505e649..66655cd367958 100644
--- a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
+++ b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
@@ -138,6 +138,10 @@ using ImportDesc =
 
 using parsed_inst_t = FailureOr<SmallVector<Value>>;
 
+struct EmptyBlockMarker {};
+using BlockTypeParseResult =
+    std::variant<EmptyBlockMarker, TypeIdxRecord, Type>;
+
 struct WasmModuleSymbolTables {
   SmallVector<FunctionSymbolRefContainer> funcSymbols;
   SmallVector<GlobalSymbolRefContainer> globalSymbols;
@@ -206,6 +210,16 @@ class ValueStack {
   ///   if an error occurs.
   LogicalResult pushResults(ValueRange results, Location *opLoc);
 
+  void addLabelLevel(LabelLevelOpInterface levelOp) {
+    labelLevel.push_back({values.size(), levelOp});
+    LDBG() << "Adding a new frame context to ValueStack";
+  }
+
+  void dropLabelLevel() {
+    assert(!labelLevel.empty() && "Trying to drop a frame from empty context");
+    auto newSize = labelLevel.pop_back_val().stackIdx;
+    values.truncate(newSize);
+  }
 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
   /// A simple dump function for debugging.
   /// Writes output to llvm::dbgs().
@@ -214,6 +228,7 @@ class ValueStack {
 
 private:
   SmallVector<Value> values;
+  SmallVector<LabelLevel> labelLevel;
 };
 
 using local_val_t = TypedValue<wasmssa::LocalRefType>;
@@ -248,6 +263,19 @@ class ExpressionParser {
   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...);
+
   /// This function generates a dispatch tree to associate an opcode with a
   /// parser. Parsers are registered by specialising the
   /// `parseSpecificInstruction` function for the op code to handle.
@@ -280,11 +308,102 @@ class ExpressionParser {
     }
   }
 
+  struct NestingContext {
+    NestingContext(ExpressionParser &parser, LabelLevelOpInterface levelOp)
+        : parser{parser} {
+      parser.addNestingContextLevel(levelOp);
+    }
+    NestingContext(NestingContext &&other) : parser{other.parser} {
+      other.shouldDropOnDestruct = false;
+    }
+    NestingContext(NestingContext const &) = delete;
+    ~NestingContext() {
+      if (shouldDropOnDestruct)
+        parser.dropNestingContextLevel();
+    }
+    ExpressionParser &parser;
+    bool shouldDropOnDestruct = true;
+  };
+
+  void addNestingContextLevel(LabelLevelOpInterface levelOp) {
+    valueStack.addLabelLevel(levelOp);
+  }
+
+  void dropNestingContextLevel() {
+    // Should always succeed as we are droping the frame that was previously
+    // created.
+    valueStack.dropLabelLevel();
+  }
+
+  llvm::FailureOr<FunctionType> getFuncTypeFor(OpBuilder &builder,
+                                               EmptyBlockMarker) {
+    return builder.getFunctionType({}, {});
+  }
+
+  llvm::FailureOr<FunctionType> getFuncTypeFor(OpBuilder &builder,
+                                               TypeIdxRecord type) {
+    if (type.id > symbols.moduleFuncTypes.size())
+      return emitError(*currentOpLoc,
+                       "Type index references nonexistent type: ")
+             << type.id << ". Only " << symbols.moduleFuncTypes.size()
+             << " types are registered.";
+    return symbols.moduleFuncTypes[type.id];
+  }
+
+  llvm::FailureOr<FunctionType> getFuncTypeFor(OpBuilder &builder,
+                                               Type valType) {
+    return builder.getFunctionType({}, {valType});
+  }
+
+  llvm::FailureOr<FunctionType>
+  getFuncTypeFor(OpBuilder &builder, BlockTypeParseResult parseResult) {
+    return std::visit(
+        [this, &builder](auto value) { return getFuncTypeFor(builder, value); },
+        parseResult);
+  }
+
+  llvm::FailureOr<FunctionType>
+  getFuncTypeFor(OpBuilder &builder,
+                 llvm::FailureOr<BlockTypeParseResult> parseResult) {
+    if (llvm::failed(parseResult))
+      return failure();
+    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();
+    builder.create<BlockReturnOp>(opLoc, *returnOperands);
+    LDBG() << "End of parsing of a block";
+    return bodyParsingRes->endingByte;
+  }
+
 public:
   template <std::byte ParseEndByte = WasmBinaryEncoding::endByte>
   parsed_inst_t parse(OpBuilder &builder, UniqueByte<ParseEndByte> = {});
@@ -294,7 +413,11 @@ class ExpressionParser {
   parse(OpBuilder &builder,
         ByteSequence<ExpressionParseEnd...> parsingEndFilters);
 
-  FailureOr<SmallVector<Value>> popOperands(TypeRange operandTypes) {
+  NestingContext addNesting(LabelLevelOpInterface levelOp) {
+    return NestingContext{*this, levelOp};
+  }
+
+  FailureOr<llvm::SmallVector<Value>> popOperands(TypeRange operandTypes) {
     return valueStack.popOperands(operandTypes, &currentOpLoc.value());
   }
 
@@ -308,6 +431,12 @@ 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 &);
+
 private:
   std::optional<Location> currentOpLoc;
   ParserHead &parser;
@@ -586,6 +715,29 @@ class ParserHead {
     return success();
   }
 
+  llvm::FailureOr<BlockTypeParseResult> parseBlockType(MLIRContext *ctx) {
+    auto loc = getLocation();
+    auto blockIndicator = peek();
+    if (failed(blockIndicator))
+      return failure();
+    if (*blockIndicator == WasmBinaryEncoding::Type::emptyBlockType) {
+      offset += 1;
+      return {EmptyBlockMarker{}};
+    }
+    if (isValueOneOf(*blockIndicator, valueTypesEncodings))
+      return parseValueType(ctx);
+    /// Block type idx is a 32 bit positive integer encoded as a 33 bit signed
+    /// value
+    auto typeIdx = parseI64();
+    if (failed(typeIdx))
+      return failure();
+    if (*typeIdx < 0 || *typeIdx > std::numeric_limits<uint32_t>::max())
+      return emitError(loc, "type ID should be representable with an unsigned "
+                            "32 bits integer. Got ")
+             << *typeIdx;
+    return {TypeIdxRecord{static_cast<uint32_t>(*typeIdx)}};
+  }
+
   bool end() const { return curHead().empty(); }
 
   ParserHead copy() const { return *this; }
@@ -701,17 +853,41 @@ inline parsed_inst_t ExpressionParser::parseSpecificInstruction(OpBuilder &) {
 void ValueStack::dump() const {
   llvm::dbgs() << "================= Wasm ValueStack =======================\n";
   llvm::dbgs() << "size: " << size() << "\n";
+  llvm::dbgs() << "nbFrames: " << labelLevel.size() << '\n';
   llvm::dbgs() << "<Top>"
                << "\n";
   // Stack is pushed to via push_back. Therefore the top of the stack is the
   // end of the vector. Iterate in reverse so that the first thing we print
   // is the top of the stack.
+  auto indexGetter = [this]() {
+    size_t idx = labelLevel.size();
+    return [this, idx]() mutable -> std::optional<std::pair<size_t, size_t>> {
+      llvm::dbgs() << "IDX: " << idx << '\n';
+      if (idx == 0)
+        return std::nullopt;
+      auto frameId = idx - 1;
+      auto frameLimit = labelLevel[frameId].stackIdx;
+      idx -= 1;
+      return {{frameId, frameLimit}};
+    };
+  };
+  auto getNextFrameIndex = indexGetter();
+  auto nextFrameIdx = getNextFrameIndex();
   size_t stackSize = size();
-  for (size_t idx = 0; idx < stackSize; idx++) {
+  for (size_t idx = 0; idx < stackSize;) {
     size_t actualIdx = stackSize - 1 - idx;
+    while (nextFrameIdx && (nextFrameIdx->second > actualIdx)) {
+      llvm::dbgs() << "  --------------- Frame (" << nextFrameIdx->first
+                   << ")\n";
+      nextFrameIdx = getNextFrameIndex();
+    }
     llvm::dbgs() << "  ";
     values[actualIdx].dump();
   }
+  while (nextFrameIdx) {
+    llvm::dbgs() << "  --------------- Frame (" << nextFrameIdx->first << ")\n";
+    nextFrameIdx = getNextFrameIndex();
+  }
   llvm::dbgs() << "<Bottom>"
                << "\n";
   llvm::dbgs() << "=========================================================\n";
@@ -792,6 +968,151 @@ ExpressionParser::parse(OpBuilder &builder,
   }
 }
 
+llvm::FailureOr<FunctionType>
+ExpressionParser::parseBlockFuncType(OpBuilder &builder) {
+  return getFuncTypeFor(builder, parser.parseBlockType(builder.getContext()));
+}
+
+template <typename OpToCreate>
+parsed_inst_t ExpressionParser::parseBlockLikeOp(OpBuilder &builder) {
+  auto opLoc = currentOpLoc;
+  auto funcType = parseBlockFuncType(builder);
+  if (failed(funcType))
+    return failure();
+
+  auto inputTypes = funcType->getInputs();
+  auto inputOps = popOperands(inputTypes);
+  if (failed(inputOps))
+    return failure();
+
+  Block *curBlock = builder.getBlock();
+  Region *curRegion = curBlock->getParent();
+  auto resTypes = funcType->getResults();
+  llvm::SmallVector<Location> locations{};
+  locations.resize(resTypes.size(), *currentOpLoc);
+  auto *successor =
+      builder.createBlock(curRegion, curRegion->end(), resTypes, locations);
+  builder.setInsertionPointToEnd(curBlock);
+  auto blockOp =
+      builder.create<OpToCreate>(*currentOpLoc, *inputOps, successor);
+  auto *blockBody = blockOp.createBlock();
+  if (failed(parseBlockContent(builder, blockBody, resTypes, *opLoc, blockOp)))
+    return failure();
+  builder.setInsertionPointToStart(successor);
+  return {ValueRange{successor->getArguments()}};
+}
+
+template <>
+inline parsed_inst_t
+ExpressionParser::parseSpecificInstruction<WasmBinaryEncoding::OpCode::block>(
+    OpBuilder &builder) {
+  return parseBlockLikeOp<BlockOp>(builder);
+}
+
+template <>
+inline parsed_inst_t
+ExpressionParser::parseSpecificInstruction<WasmBinaryEncoding::OpCode::loop>(
+    OpBuilder &builder) {
+  return parseBlockLikeOp<LoopOp>(builder);
+}
+
+template <>
+inline parsed_inst_t ExpressionParser::parseSpecificInstruction<
+    WasmBinaryEncoding::OpCode::ifOpCode>(OpBuilder &builder) {
+  auto opLoc = currentOpLoc;
+  auto funcType = parseBlockFuncType(builder);
+  if (failed(funcType))
+    return failure();
+
+  LDBG() << "Parsing an if instruction of type " << *funcType;
+  auto inputTypes = funcType->getInputs();
+  auto conditionValue = popOperands(builder.getI32Type());
+  if (failed(conditionValue))
+    return failure();
+  auto inputOps = popOperands(inputTypes);
+  if (failed(inputOps))
+    return failure();
+
+  Block *curBlock = builder.getBlock();
+  Region *curRegion = curBlock->getParent();
+  auto resTypes = funcType->getResults();
+  llvm::SmallVector<Location> locations{};
+  locations.resize(resTypes.size(), *currentOpLoc);
+  auto *successor =
+      builder.createBlock(curRegion, curRegion->end(), resTypes, locations);
+  builder.setInsertionPointToEnd(curBlock);
+  auto ifOp = builder.create<IfOp>(*currentOpLoc, conditionValue->front(),
+                                   *inputOps, successor);
+  auto *ifEntryBlock = ifOp.createIfBlock();
+  constexpr auto ifElseFilter =
+      ByteSequence<WasmBinaryEncoding::endByte,
+                   WasmBinaryEncoding::OpCode::elseOpCode>{};
+  auto parseIfRes = parseBlockContent(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 =
+        parseBlockContent(builder, elseEntryBlock, resTypes, *opLoc, ifOp);
+    if (failed(parseElseRes))
+      return failure();
+  }
+  builder.setInsertionPointToStart(successor);
+  return {ValueRange{successor->getArguments()}};
+}
+
+template <>
+inline parsed_inst_t ExpressionParser::parseSpecificInstruction<
+    WasmBinaryEncoding::OpCode::branchIf>(OpBuilder &builder) {
+  auto level = parser.parseLiteral<uint32_t>();
+  if (failed(level))
+    return failure();
+  Block *curBlock = builder.getBlock();
+  Region *curRegion = curBlock->getParent();
+  auto sip = builder.saveInsertionPoint();
+  Block *elseBlock = builder.createBlock(curRegion, curRegion->end());
+  auto condition = popOperands(builder.getI32Type());
+  if (failed(condition))
+    return failure();
+  builder.restoreInsertionPoint(sip);
+  auto targetOp =
+      LabelBranchingOpInterface::getTargetOpFromBlock(curBlock, *level);
+  if (failed(targetOp))
+    return failure();
+  auto inputTypes = targetOp->getLabelTarget()->getArgumentTypes();
+  auto branchArgs = popOperands(inputTypes);
+  if (failed(branchArgs))
+    return failure();
+  builder.create<BranchIfOp>(*currentOpLoc, condition->front(),
+                             builder.getUI32IntegerAttr(*level), *branchArgs,
+                             elseBlock);
+  builder.setInsertionPointToStart(elseBlock);
+  return {*branchArgs};
+}
+
+template <>
+inline parsed_inst_t
+ExpressionParser::parseSpecificInstruction<WasmBinaryEncoding::OpCode::call>(
+    OpBuilder &builder) {
+  auto loc = *currentOpLoc;
+  auto funcIdx = parser.parseLiteral<uint32_t>();
+  if (failed(funcIdx))
+    return failure();
+  if (*funcIdx >= symbols.funcSymbols.size())
+    return emitError(loc, "Invalid function index: ") << *funcIdx;
+  auto callee = symbols.funcSymbols[*funcIdx];
+  llvm::ArrayRef<Type> inTypes = callee.functionType.getInputs();
+  llvm::ArrayRef<Type> resTypes = callee.functionType.getResults();
+  parsed_inst_t inOperands = popOperands(inTypes);
+  if (failed(inOperands))
+    return failure();
+  auto callOp =
+      builder.create<FuncCallOp>(loc, resTypes, callee.symbol, *inOperands);
+  return {callOp.getResults()};
+}
+
 template <>
 inline parsed_inst_t ExpressionParser::parseSpecificInstruction<
     WasmBinaryEncoding::OpCode::localGet>(OpBuilder &builder) {
@@ -1000,11 +1321,23 @@ inline parsed_inst_t ExpressionParser::buildNumericOp(
 
 BUILD_NUMERIC_BINOP_FP(CopySignOp, copysign)
 BUILD_NUMERIC_BINOP_FP(DivOp, div)
+BUILD_NUMERIC_BINOP_FP(GeOp, ge)
+BUILD_NUMERIC_BINOP_FP(GtOp, gt)
+BUILD_NUMERIC_BINOP_FP(LeOp, le)
+BUILD_NUMERIC_BINOP_FP(LtOp, lt)
 BUILD_NUMERIC_BINOP_FP(MaxOp, max)
 BUILD_NUMERIC_BINOP_FP(MinOp, min)
 BUILD_NUMERIC_BINOP_INT(AndOp, and)
 BUILD_NUMERIC_BINOP_INT(DivSIOp, divS)
 BUILD_NUMERIC_BINOP_INT(DivUIOp, divU)
+BUILD_NUMERIC_BINOP_INT(GeSIOp, geS)
+BUILD_NUMERIC_BINOP_INT(GeUIOp, geU)
+BUILD_NUMERIC_BINOP_INT(GtSIOp, gtS)
+BUILD_NUMERIC_BINOP_INT(GtUIOp, gtU)
+BUILD_NUME...
[truncated]

@lforg37
Copy link
Contributor Author

lforg37 commented Aug 21, 2025

@joker-eph could you have a look?

@lforg37 lforg37 changed the title Lforget.wasm importer controlf flow conv comp [MLIR][Wasm] Add control flow, conversion and comparison ops to wasm importer Aug 21, 2025
@lforg37 lforg37 changed the title [MLIR][Wasm] Add control flow, conversion and comparison ops to wasm importer [MLIR][Wasm] Control flow, conversion and comparison in Wasm importer Aug 21, 2025
@lforg37 lforg37 changed the title [MLIR][Wasm] Control flow, conversion and comparison in Wasm importer [MLIR][WASM] Control flow, conversion and comparison in Wasm importer Aug 22, 2025
@flemairen6
Copy link
Contributor

@jpienaar since @joker-eph seems to be off, can you have a look at this PR? Thanks a lot :)

@joker-eph
Copy link
Collaborator

The change is really too wasm-specific for me, it would be great if someone else who is interested in these could review.
I saw that @MattPD was reviewing, I thought they would be able to approve this?

@MattPD
Copy link
Member

MattPD commented Aug 30, 2025

Can't make any promises, but can try taking a look next week. Would be good to get another Wasm-specific review, too!

@lforg37
Copy link
Contributor Author

lforg37 commented Sep 12, 2025

@MattPD how can we help you review the PR? Or do you know someone else who might be able to review it?

@MattPD
Copy link
Member

MattPD commented Sep 15, 2025

@lforg37 Thanks, it's a matter of finding some spare cycles (limited bandwidth at the moment)

@tamaroning
Copy link

@kateinoigakukun
Hello, could you review this?

@jpienaar
Copy link
Member

jpienaar commented Oct 1, 2025

Apologies I completely missed the ping. I know nothing about the WASM side, but can do a general review/try best to page in soon. Is there a roundtrip test or the like which would catch behavioral changes?

Copy link
Member

@jpienaar jpienaar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some comments from first scan. If I recall from the ODM, the yaml with binary strings is the best currently given import support, correct?

@lforg37 lforg37 force-pushed the lforget.wasm_importer_controlf_flow_conv_comp branch from 30cdc05 to be7943a Compare October 2, 2025 02:19
Copy link
Member

@kateinoigakukun kateinoigakukun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed Wasm-specific changes, and it looks good to me :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind clarifying why SymbolTable::setSymbolVisibility helper didn't work for this case, and leave comments if needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is because we rely on the fact that the visibility attribute stream should be present somewhere else, and setSymbolVisibility remove the attribute for public symbols.

I'll investigate to see where we have this assumption to see if we can instead have follow the usual assumption that no attribute means public visibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the only reason we are doing it is to enforce explicit presence of the public keyword in the printer of function op.
The rationale was that public is non default for Wasm function (as it requires an export).

If this is something we want to keep, we need to enforce the existence of the visibility symbol in the verifier of wasmssa function op I think.

Otherwise, we can just have the absent attribute mean public.

Do you have any opinion on this @kateinoigakukun or @jpienaar ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have much opinion on MLIR side but it would be nice to add some comments explaining that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I will wait for @jpienaar's opinion then and either add comments or revert to using SymbolTable::setSymbolVisibility depending onn the result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We proposed a third option, visible here using a unitattr to mark the symbol which are exported.
This allow to be closer to Wasm semantics (unless explicitely exported, operation are "nested") , while not having a confusing SymVisibility attribute that wouldn't have the same behavior as in other part of MLIR.

@jpienaar @joker-eph we could use your help here to determine what design should be used to be consistent with the rest of the project (that's an MLIR question :-) ) (and also if this last change should be added to this PR or to a separate one).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use interfaces specifically to not rely on specific attributes: you can use another source of information to fit the interface just like you're doing.

I would update the description of you're FuncOp accordingly though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My apologies, the question was unclear. My concern was that if we use the public vs nested in the textual assembly with nested being the default, it might be confusing to user used to e.g. the func dialect where public is the default.

I will update the doc and add the commit to this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit has been added. @kateinoigakukun can you have a second look to it to give your opinion on the wasm part of it?

@lforg37 lforg37 force-pushed the lforget.wasm_importer_controlf_flow_conv_comp branch from be7943a to 49532e8 Compare October 2, 2025 04:48
@lforg37 lforg37 requested a review from jpienaar October 3, 2025 00:42
Wasm has the concept of export operation that makes a symbol available for use in other modules.
By default an operation has nested visibility only.

This commit changes the way visibility is represented in `wasmssa` dialect to align with Wasm semantics.

Operation that defines "exportable" symbols (memory, global, functions or table) can have an `UnitAttr` that signal they should be exported (and have public visibility). 

When they do not have this attribute, visibility is nested.

---------

Co-authored-by: Luc Forget <[email protected]>
@lforg37 lforg37 requested a review from MattPD October 15, 2025 07:45
@flemairen6
Copy link
Contributor

With the approval and @lforg37's work for the comment, can this be merged @joker-eph?

@joker-eph joker-eph merged commit e40f215 into llvm:main Oct 17, 2025
10 checks passed
@joker-eph
Copy link
Collaborator

@flemairen6 feel free to follow this process to request commit access to be able to merge your PR without my involvement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants