[RFC] MLIR Dialect for WebAssembly

This is a proposal to support WebAssembly module representation in MLIR.

We have a work-in-progress implementation available here.

TL;DR: We have been working on an MLIR representation for WebAssembly programs, and tools to import such programs in MLIR and convert them up to the LLVM IR dialect, in order to be able to compile Wasm binaries to native code ahead-of-time.

The longer term goal is to provide support for Wasm usage on embedded systems.

The dialect covers a good percentage of the Wasm constructs, but is still in early stages of development. Open questions remain on what the LLVM lowering should look like. Thus, we think it’s a good time to go to the community, receive feedback on our current approach, and shift ongoing work into the community for cross-industry collaboration.

Context and Motivation

WebAssembly (abbreviated Wasm) is a well specified binary instruction format for an abstract stack machine.

An embedder is used to execute Wasm code in a given environment, sandboxing the execution.

LLVM already supports Wasm as a code generation target. This proposal is about doing the opposite: taking Wasm binary code as an input and producing native machine code for it.

Outside of web browser execution, there is also an interest in using Wasm as a binary format. There are several reasons for this.

Firstly, Wasm was designed from the ground up for safety and security. Out of the box, Wasm offers sandboxing, control flow integrity enforcement, memory safety guarantees, and so on. For developers interested in security, these guarantees are compelling.

Secondly, Wasm enables “compile once, run anywhere”; the concept that you can run a Wasm binary anywhere where its embedder is available. The embedder can be thought of as a Wasm interpreter. A close-to-home example of this is the 2024 RFC to build LLVM itself for Wasm.

However, there is a tradeoff to using Wasm as a portable binary format: this code isn’t native, and so it comes with a performance hit. For example, the YoWASP project uses Wasm to provide FPGA tools to many different platforms, but the cost of roughly a 50% performance hit.

We could keep most of that benefit if we added a lightweight AoT Wasm compiler that takes pre-compiled Wasm binaries, and compiles them directly to native code. In addition, AoT compiling allows the reduction of the amount of extra software that needs to be provided on the target platforms. The full interpreter / JIT compiler can be replaced by only a Wasm runtime that implements the sandboxing etc.This is especially relevant for safety critical applications for which the embedded software needs to undergo a qualification process (e.g. in aerospace, automotive, and other safety-critical contexts.)

Related Work

AOT compilers for Wasm already exist, such as WAMR or Wasmtime, but they are tightly coupled with their respective embedders. The proposed dialect is embedder-agnostic, and should allow for multiple lowering paths if required.

There are existing LLVM-based Wasm compilers (e.g.Wasker). However, existing solutions use LLVM as an external component. Our approach brings Wasm->native support directly into the LLVM monorepo.

High-level Approach

This is a high-level overview of how we get from a binary Wasm file to LLVM IR (and subsequently, native code.)

Suppose you have the following Wasm text format (WAT) code.

(module
(func (export "or_i32") (result i32)
i32.const 10
i32.const 3
i32.or)
)

Let’s say you have placed this WAT in the Wasm binary format as foo.wasm, using some external tool such as wat2wasm.

Via mlir-translate --import-wasm foo.wasm, we can lower this to code in the Wasm MLIR dialect like so:

wasm.func nested @or_i32() -> i32 {
%0 = wasm.const 10 : i32
%1 = wasm.const 3 : i32
%2 = wasm.or %0 %1 : i32
wasm.return %2 : i32
}

We’ve designed the Wasm dialect to mirror the opcodes, data structures, and so on that exist in Wasm. So, for example, each Wasm opcode has an associated opcode in the Wasm dialect.

Readers familiar with Wasm might be wondering at this point, “what about the Wasm stack?” The Wasm stack is tracked at parsing time to correctly map operation results to uses. So, while parsing a Wasm binary, we can verify that code like this is invalid:

(module
(func (export "or_i32") (result i32)
i32.const 10
i32.or)
)

In the above case, the importer will recognize that there are insufficient values on the stack for the i32.or, emit an error, and produce no Wasm dialect code.

Once we’ve finished translation, we can pass the Wasm dialect code to mlir-opt --convert-wasm-to-standard. This will convert the Wasm dialect code to the appropriate standard dialect(s) like so:

func.func @or_i32() -> i32 {
%c10_i32 = arith.constant 10 : i32
%c3_i32 = arith.constant 3 : i32
%0 = arith.ori %c10_i32, %c3_i32 : i32
return %0 : i32
}

This provides a path to LLVM IR, and subsequently, native code on the user’s platform of choice.

For each implemented Wasm construct, we have included

  • Translation unit tests in mlir/test/Target/WebAssembly
  • Conversion unit tests in mlir/test/Conversion/WasmToStandard

To automate this process, we also have implemented a simple driver, called Wasabi that performs the translation and conversion phases.

Via Wasabi, the user can automatically convert from a Wasm binary to the MLIR dialect, standard dialects, or LLVM IR.

# Translate foo.wasm to Wasm MLIR and output the result.
$ wasabi /path/to/foo.wasm --dump --output-type wasm-mlir

# Translate foo.wasm to Wasm MLIR, convert to standard dialects, and output the result.
$ wasabi /path/to/foo.wasm --dump --output-type std-mlir

# Translate foo.wasm to Wasm MIR, convert to standard dialects, lower to LLVM IR, and output the result.
$ wasabi /path/to/foo.wasm --dump

We’ve included LLVM lit tests which serve as additional examples for all components.

What Will Be Upstreamed?

We propose that we upstream the

  • Wasm dialect
  • Translation
  • Conversions
  • Wasabi driver

Into llvm-project as quickly as possible. We have been following LLVM and MLIR coding standards and conventions with the plan of upstreaming from the beginning.

We are happy to make any changes to the implementation that are necessary to bring this work in-tree.

Why Now?

We believe that multiple parties can and will benefit from this work in the embedded space and beyond. With the first Asian LLVM conference happening this week, we think that this is an excellent time to begin pushing this work into the open.

The work is not 100% complete, but is in a “mostly LLVM OSS programming style state”. We think it’s at a state where it’s easy for contributors to join the effort and push it forward, and we think there is enough work there that it can definitely be pushed to completion in OSS.

Git Integration Approach

We’ve put the current code up for community consideration and review on this fork branch.

We don’t have strong opinions on how this should be upstreamed into LLVM, so we would like community input on the best path forward.

Code Layout

The current implementation is structured like so:

Dialect

Translation

Conversion

Driver

Status and plan

In its actual state, the project consists of :

  • A new WebAssembly dialect for MLIR which represents Wasm programs
  • A translation target which allows to import Wasm binary to this dialect with mlir-translate
  • A (set of) conversions to lower this dialect to standard MLIR dialects (memref, arith, math, cf, func, …) that can themselves be lowered to the LLVM dialect and then compiled to native code. At the moment this is a monolithic conversion, targeting multiple standard dialects at the same time.

The proposed implementation covers Wasm 2.0 specification

  • Structural constructs of Wasm that are represented in the dialect and can be imported:
    • Functions (type definitions and partial support of Wasm instructions for function code, see below for more detail)
    • Globals
    • Memory (no initializers at the moment)
    • Tables (no initializer at the moment)
    • Imports
    • Exports
    • Start point → not yet supported
  • Instruction support:
    • Numeric instructions on scalar types: Almost 100% coverage
    • Vector instructions: no support at the moment
    • Global and local variable instructions: 100% support
    • Control flow instructions: 100% support
  • Trap: embedder dependent, not yet supported
  • Memory and Table instructions: not implemented yet, as it can also be embedder dependent.

Technical Debt

There are some validations in the Wasm Binary importer for which no negative tests have been implemented. Existing tools to generate Wasm binaries from WAT verify that the input WAT is valid. Therefore, it is difficult to generate broken Wasm binaries today.

Plan for this is to first reach some level of agreement on what should be tested in the importer vs delegated to dialect verifier, specify this somewhere and write the test cases accordingly afterwards.

It might be interesting to actually create a fuzzing tool for this, but a first implementation can be as simple as manually editing valid Wasm binary files to produce invalid ones.

Currently we’re not consistent on where we call it “WebAssembly” and where we call it “Wasm”. Since naming things is one of the hardest problems in computer science, we would like to ask the community for assistance in choosing the most appropriate name. :slight_smile:

Design Considerations

Representing the Wasm Stack

While Wasm targets an abstract stack machine, the proposed WebAssembly dialect abstracts away the stack and uses an SSA representation of the program.

It is of the importer’s responsibility to track the stack state during the import in order to map correctly results to consumers.

As a result, the WebAssembly Dialect can represent incorrect Wasm programs.
For instance, this code snippet

%0 = wasm.const 1: i32
%1 = wasm.add %0 %0 : i32

Would represent an invalid program, as the stack machine semantics forbids reusing the same value twice (an operation consumes its operands), so here a second distinct wasm.const 1 would be necessary to have a program with valid stack state.

There is however no conceptual difficulty to implement additional verification to enforce the validity of a represented module. Part of the validation would be to ensure that operands of Wasm operations that are taken on the stack have exactly one consumer.

An alternative consisting in having operations producing and consuming an opaque-typed stack value has been considered, but this doesn’t solve the issue (a !wasm.stack value can be reused multiple times if no verification forbids it) and just adds complexity for tracking which result is reused where, makes implementing the conversion to standard dialects challenging (a shared state to track which is the actual type of the value(s) added / consumed on the stack would be required, which isn’t easily done within the Conversion Rewriter framework) and make the type checking more complex.

Embedder-Dependent Constructs

In addition, open questions remain on how to lower some constructs that are dependent on the embedder. Indeed, while arithmetic operations poses no great difficulty as they are “naturally” mapped to operations from Math or Arith dialects, operations on memory or function table for instance (indirect call of a function stored in a function table) is trickier as it is actually dependent on the embedder API.

For instance:

  • the memory management could be done internally to the module, with a memref global storing a pointer to a memref storing a vector of bytes, but in the case the embedder manages the memory it should be lowered to call to the embedder management API instead
  • The way to register an initialization function isn’t clear either. Registration of the entry point function is also something dependent on the embedder mechanism.

Detailed Wasm Dialect Example

This example shows how Wasm concepts such as locals, function arguments, control flow operations, and arithmetic operations are represented in SSA form via the Wasm dialect.

Given the following WAT code:

(module
  (func (param $i i32) (result i32)
    (loop $my_loop (result i32)
      local.get $i
      i32.const 1
      i32.add
      local.set $i
      local.get $i
      i32.const 10
      i32.lt_s
      br_if $my_loop
      local.get $i
    )
  )
)

We can produce the following Wasm dialect code:

module {
wasm.func nested @func_0(%arg0: i32) -> i32 {
  %0 = wasm.local_from_arg %arg0 : i32
  wasm.loop : {
    %2 = wasm.local_get %0 : memref<i32>
    %3 = wasm.const 1 : i32
    %4 = wasm.add %2 %3 : i32
    wasm.local_set %0 : memref<i32> to %4 : i32
    %5 = wasm.local_get %0 : memref<i32>
    %6 = wasm.const 10 : i32
    %7 = wasm.lt_si %5 %6 : i32 -> i32
    wasm.branch_if %7 to level 0 else ^bb1
  ^bb1: // pred: ^bb0
    %8 = wasm.local_get %0 : memref<i32>
    wasm.block_return %8 : i32
  }> ^bb1

^bb1(%1: i32): // pred: ^bb0
  wasm.return %1 : i32
}
};

Local Variables

Since Wasm “local” values are variables that can be overwritten with “get” and “set” on the stack, they can’t be represented as normal SSA values. We have chosen to represent them as memref in order to benefit from the pointer-like semantic.

%2 = wasm.local_get %0 : memref<i32>

Function Arguments

Function arguments are mapped to Wasm locals using the local_from_arg operation. A function argument is effectively a value that “comes in” on the Wasm stack, passed along by the caller to the callee function.

%0 = wasm.local_from_arg %arg0 : i32

Control Flow Operations

Blocks, If, and Loops, are terminator operations that take some operands based on their function type. They define regions containing the operations that are subject to the control flow (e.g. conditionally executed for If)..

Control flow operations have a successor block which is the usual block to execute when the loop / block / if has finished, and which will be the destination of the jump of the block_return operation.
Control flow operations also implement the WasmLabelLevelInterface which defines a label target block, which is the one that should be reached when branch instructions bubble up to this operation.

Branch instructions define an integer attribute that specifies how many nesting levels they should exit. They are terminator instructions, but due to the multi-region level exiting capabilities, storing the destination block as successor was not an option.

wasm.loop : {
// ...
    wasm.branch_if %7 to level 0 else ^bb1
  ^bb1: // pred: ^bb0
    %8 = wasm.local_get %0 : memref<i32>
    wasm.block_return %8 : i32
}> ^bb1

^bb1(%1: i32): // pred: ^bb0
  wasm.return %1 : i32
}

Arithmetic Operations and Constants

For arithmetic operations, we tried to stay as close as possible to existing standard dialects. This makes it trivial to convert to other dialects in a majority of cases.

%3 = wasm.const 1 : i32
// ...
%4 = wasm.add %2 %3 : i32

For operations with no representation available in the standard dialects (for example, rotates), we translate into bit-twiddling operations within the Wasm dialect that do have representations within the standard dialects. The simpler Wasm dialect operations are then subsequently lowered to operations within the target dialect.

The testcase mlir/test/Conversion/WasmToStandard/wasm-rotr-to-arith.mlir serves as an example of this. The RotateOpConversion struct in WasmToStandard.cpp shows a concrete implementation.

Future and Ongoing Work

Future work includes of course the implementation of Wasm operations that are not supported in the current state of the project (vector instructions, table operations, …).

In addition, conversion to standard dialects for the embedder-specific operations should be rethought, taking into consideration how the integration should be done.

As mentioned in the technical debt section, some negative tests for validation need to be implemented. Currently, the project does not contain googletest-style tests. As the project matures, we would like to add broader testing.

Finally, another missing feature is the ability to import Wasm text format, as currently only import from binary Wasm files is supported. Adding a WAT parser would make it much easier to validate the project via LLVM lit style tests, and also increase the breadth of the project.

Thank you for reading,

Luc Forget <[email protected]>
Ferdinand Lemaire <[email protected]>
Jessica Paquette <[email protected]>

14 Likes

This is fantastic work! Thank you for bringing this to MLIR.

I have not yet given much thoughts about the details but here are a couple comments:

  • About embedder-dependent constructs, since they depend on how exactly you are targeting your lowering, how about leaving them to the user to implement? It’s useful to have a conversion of arithmetics to arith, but when it comes to more complex things I don’t find it inappropriate to simply not provide anything, as it highly depends on user intent. You can provide an opinionated lowering on the side if you want, but it does not seem necessary to me.

  • I’m not a huge fan of using memref in the canonical representation of the wasm dialect as it is way more powerful than what you are trying to model. Have you considered adding a simple local<T> type for your function arguments? This would also save you from having a wrapping op at the start of functions, and the caller op would be abstractly responsible of that type wrapping.

  • I think the control flow signal proposal would make modeling the control flow in this much easier (where operations can encode in IR arbitrary parent region successors without being a terminator). Hopefully once it is available in MLIR this could be simplified!

Thank you for the feedback!

About embedder-dependent constructs […]

Just for making sure I understood correctlty, your recommendation would be to split the mono conversion WasmToStandard into finer grain conversions, e.g. WasmToArith, so user can do a mix and match and add their own for e.g. converting the memory op depending on their specific needs? That sounds indeed like a good idea.

Have you considered adding a simple local<T> type for your function arguments?

We did at some point, but moved back from it at some point and I can’t recall why, as it makes sense.
The wasm.func function type would still be having value types in the inputs but the entry block should wrap it in local<T>, correct?

I think the control flow signal proposal would make modeling the control flow in this much easier

Thanks for pointing it out, I’ll have a look!

Yes exactly!

This could be an approach. I would write the wrapped types (local<i32>) in the function header signature, but not in the caller op. Those types are not required to match, since you can take that into account in verification and lowering. With this, you do not need to have explicit wrapping so there is no way to forget it, it will be valid by default.

I’ve been thinking a bit more about the SSA constraint you are introducing. Are you sure it is needed? Why not convert back and forth from a stack machine to proper SSA instead of requiring this middle ground? You could even have two dialects, one for SSA wasm and one for stack-machine wasm, if needed (not sure it is).

Thanks for the proposal! The code looks quite advanced and seems to follow the upstream style at a glance. The rationale to have a WebAssembly dialect makes sense to me personally.

A couple of high-level questions:

  • We systematically ask what is the community served by the new dialect, could you please elaborate on this? Specifically, I see all the proposers are from the same company, are there other actors interested in this work?
  • How does the dialect want to handle versioning and potential divergence with the Wasm specification?
  • What will be needed for proper testing, especially integration testing?

More technical questions / suggestions:

  • Don’t use memref unless you need a multi-dimensional array, use !ptr.ptr, !llvm.ptr or introduce a new type.
  • Please avoid referencing “standard” dialects :slight_smile: We spent a lot of time breaking that into pieces and some outdated references still remain, unfortunately. The grouping of arith/math/cf may require some new name, but it’s not necessarily your job to find it.
  • You can consider individual dialects or wasm.prefixed.operations via the extension mechanism similar to that of the transform dialect to support different embedders.
  • Also take a look at the GPU compilation pipeline as an inspiration for separate / multi-target compilation.
  • For operations not representable directly in arith/math but representable in llvm, it’s okay to mix llvm with higher-level dialects IMO. As long as there’s no need to lower to something that is not llvm.
  • I’d err on the side of having a single raising pass -convert-wasm-to-<find-a-good-name> even if we provide a -test-convert-wasm-to-arith, -test-convert-wasm-to-math and co. for testing purposes. If the process is based on passes, we can use a dialect interface mechanism similar to that used by -convert-to-llvm that collects relevant patterns from multiple dialects and applies them simultaneously. Having multiple passes makes it harder to understand and construct pass pipelines.

I also propose you to hold an ODM to socialize the idea better and potentially find collaborators. We have open slots, please let me know.

2 Likes

I’m also working on this proposal along with Luc, I’ll try to answer some of those points.
First of all, thank you very much for the comments and suggestions, we’ll be working towards implementing them to our proposal in the upcoming days.

As you mentioned, the three of us working on this are from the same company, and we’ve been working on this because our company has a use for it. However, we know that efforts for AOT compilation of Wasm currently have traction especially in the embedded system space. For instance, there was a talk two days ago at AsiaLLVM by the people from wanco which aims for a similar goal but doesn’t use MLIR.

We were not aware of those meetings but it looks like a great opportunity to discuss this topic so we would be very interested in participating in one (or more!).

This is an interesting topic that, to be honest, we haven’t thought too much about yet. Are there currently any best practice regarding this topic? My experience on this topic is mostly how onnx-mlir handles it, by versioning operations with their standard version in the opname (like AveragePool from the standard version 13 is called AveragePoolV13 once it’s not the latest version of AveragePool anymore) The Wasm specification doesn’t evolve very quickly so I don’t think this way of doing it makes too much sense for our case.

Do you mean, in terms of dependencies? Resources? Currently we don’t have any additional dependencies. In the future there might be a need to add an embedder as a dependency for real end to end tests but nothing has been decided on this area yet.

1 Like

Please send me a direct message or email so we can arrange one. From commit timestamps it looks like you folks are based in Japan and I suppose the current time slot wouldn’t work well for you… :slight_smile:

You can look at TOSA, which is an upstream dialect reflecting an external specification. We also have an op versioning mechanism that I believe is underused upstream that you may find useful.

Sounds good. You probably know that LLVM doesn’t like taking external dependencies, so thinking about testing strategy should that be needed is helpful. If you need specific hardware or some configuration, or have significant testing load compared to the rest of MLIR, you can get in touch with the LLVM infrastructure team.

On that topic, I am very interested in this work for the WebAssembly community. Specifically, while languages like C/C++/Rust use LLVM to compile to WebAssembly, other languages do so without LLVM (Go, Grain, AssemblyScript, Dart, Java, etc.). If we could:

  • input WebAssembly to LLVM (the new ability proposed here),
  • run LLVM’s existing optimizations, and then
  • re-emit a WebAssembly file (using the existing WebAssembly backend)

then all those non-LLVM users could start to benefit from LLVM’s optimizations, perhaps substantially.

See Integrating LLVM optimizations with wasm-opt by xuruiyang2002 · Pull Request #7634 · WebAssembly/binaryen · GitHub for more background on how we might use this - we’ve considered other ways to optimize parts of WebAssembly using LLVM, and this proposal could make things far more practical.

As background, I am a WebAssembly toolchain developer, mostly Binaryen (a WebAssembly optimizer), Emscripten (C++ WebAssembly toolchain based on LLVM), and rarely also LLVM itself, and I’ve helped with several language ports to WebAssembly (e.g. Java, Dart). I just heard about this proposal now, and I am pretty excited about it!

- Alon Zakai

8 Likes

On the subject of interested users, I know that when I was at the University of Cambridge in the team led by Tobias Grosser we were interested in developing WebAssembly support in an MLIR-compatible way to use in other research projects. While I’m sure there was no doubt about it, there is definitely wide appeal for this.

When you have time I’m really curious about your rationale against separating stack machine concerns from SSA (by integrating full stack machine to SSA transformation in the translations, or by having two dialects). I guess the sort of linear typing constraint works, but it’s a bit unfortunately inconvenient (and I guess a bit unusual)!

1 Like

Regarding:

Did you mean only in the RFC or in the code too? At least for pass names I see it’s already pretty common as a target like in AffineToStandard ComplexToStandard or LinalgToStandard.

You mentioned:

I agree it’s more convenient to have a single raising pass in our case, but then what would be a good target name if standard isn’t - since it’s already a relatively common target name?

When you have time I’m really curious about your rationale against separating stack machine concerns from SSA (by integrating full stack machine to SSA transformation in the translations, or by having two dialects). I guess the sort of linear typing constraint works, but it’s a bit unfortunately inconvenient (and I guess a bit unusual)!

I think the wording was unclear.
Currently the importer does the conversion Stack Machine to SSA. Programs produced by the importer respects the stack machine semantics “de facto” as they are translation from it.
We just wanted in the original post to highlight that the dialect allows to represent a superset to what Wasm can represent.
If this is a concern, it is possible to have a validation to check for strict stack semantics.

At the moment there is no direct exporter from the Wasm dialect to Wasm binary, and going through the LLVM path shouldn’t pose any issue for non strictly “Wasm representable” Wasm dialect program, as the Wasm backend will do the required transformation to make it work on the Wasm stack machine.

The question was more like "do we want a strict Wasm representation in MLIR, so that we can have “dumb translations” with 1-1 mappings from Wasm to the dialect, or are we OK with a superset, and the eventual MLIR->Wasm translation would have to be a bit more complex than just mapping MLIR constructs to Wasm constructs.

To make it clear, at the moment a verification for “stack machine validity” is not implemented.

1 Like

Everywhere. You are most welcome to update the remaining usages in the codebase too. The notion of “standard dialect” has been deprecated since 2020: [RFC] Splitting the Standard dialect.

It is not common, it is outdated and used by other outdated things or things with no maintainer that has enough time to update them.

If I had had a different name in mind, I would have suggested it already. --raise-wasm would be perfectly reasonable for me.

2 Likes

I was looking forward to a WASM dialect but was surprised to see that this seems to be completely independent from https://www.youtube.com/watch?v=z2xmzf8f5Ac (which is afaik not yet published), is that correct?

We were indeed not aware of this initiative.
If I understood correctly the presentation, their focus is on providing a translation from the MLIR space to Wasm space, where we were focused on the opposite.

It would be interesting to see the code in order to have an idea on how close the two dialects are.

I mailed the presenter at some point and I believe they will publish a paper this year. I don’t think there is anything online at this point actually showing what their dialect looks like so it’s hard to say how it compares.

In any case I am really happy this exists now so thanks for making it. I already have a use case in mind although it will probably take me forever with the pace my current project is progressing.

I also wonder if there would be community interest in supporting an EVM dialect. Obviously way more special purpose than WASM but at least vaguely similar due to being stack based and important commercially.

That makes sense! This sounds good to me: in a view where we have a stack-machine based (no SSA) dialect, and a separate dialect, this is akin to just having the separate dialect for now, which I think is a great first step. The more I think about it the more I appreciate the two dialect approach, but I think having two will be easier once the mechanization of dialect generation will be more mature (it would be amazing to be able to lower the WebAssembly SpecTec into IRDL, but we are not there yet).

I think it’s okay that the dialect supports more than WebAssembly for now. But do consider to adjust it so that adding a strict wasm dialect on top is not impossible (maybe reducing the amount of breaking changes that would be required).

I am more interested about lowering other mlir dialect to standard WASM. And this proposal looks like SSA based superset of WASM. I’m very curious whether there is a namespace conflict between these two?

1 Like

I’m not completely sure I understood correctly your proposal.
You suggest to have a dialect that would specifically represent a stack machine in the future, which would be able to explicitely represent stack and pop of values on the stack machine ?

I am more interested about lowering other mlir dialect to standard WASM. And this proposal looks like SSA based superset of WASM. I’m very curious whether there is a namespace conflict between these two?

I am not sure I get what would be “these two” ?
In any case, the current dialect could be used as a base for a translation to Wasm binary.
Basically the flow would be the following:

  • Check if the module is “valid” stack machine program (e.g. SSA which are not references to local or global have a single consumer).
  • If not → the module should undergo some transformation (duplication of expression tree or variable creation to store results which are reused for instance) to become a valid stack machine program
  • When a state of valid stack program is reached, 1-1 translation can be performed.

Basically I think that in the future it would be nice to have:

Proper WebAssembly --translate--> wasm_stack --lowers--> wasm_ssa --> ...

right now the dialect you are proposing is wasm_ssa, and you jump directly from WebAssembly to wasm_ssa (which is fine, but the reverse would be a bit difficult). The wasm_stack dialect would be a dialect mapping very closely to WebAssembly operations, where there would be no SSA values, only an implicit abstract stack.