Understanding the "memory-safe" dialect

I read the assembly docs but I am still a bit unsure regarding when I should apply the ("memory-safe") dialect, and when not.

For instance, take the following examples taken from PRBMath:

function frac(UD60x18 x) pure returns (UD60x18 result) {
    assembly {
        result := mod(x, uUNIT)
    }
}

And:

function msb(uint256 x) pure returns (uint256 result) {
    // 2^128
    assembly {
        let factor := shl(7, gt(x, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // ...
}

The docs say this:

Inline assembly that neither involves any operations that access memory nor assigns to any Solidity variables in memory is automatically considered memory-safe and does not need to be annotated.

The first example simply reads a variable from the stack and a const, so my instinct would say there is no need for the dialect because the code is memory safe by default. But the second example assigns to result, which is part of returndata, so here I am not so sure. My questions are:

  1. Do the examples above count as code snippets that do not involve any operations that access memory?
  2. Does it hurt if I add the ("memory-safe") dialect to a code snippet that doesn’t actually need it (e.g. a code that “neither involves any operations that access memory nor assigns to any Solidity variables in memory”)?
6 Likes

A return variable is just like any other local variable, so in your case, it is a stack variable that you can assign in the inline assembly block and it is still memory-safe.

Both your inline assembly blocks do not need an annotation and are still considered memory-safe by the compiler. Adding it does not hurt, though.

In general, the only inline assembly statements that require “memory-safe” (and also could be memory-unsafe!) are opcodes that concern memory (mload, mstore, calldatacopy, call, msize, …) or access to local variables outside of the inline assembly block that are of memory reference type.

Code that only touches storage or storage reference type is fine as well, as is calldata.

5 Likes

Thanks for your answer, Chris, all clear now!

This observation is so clarifying that it might be worth including in the documentation website!

Ditto for this!

2 Likes

Does using Memory-safe dialect in assembly actually has any effects on the memory. Can we find any difference with and without the dialect in memory?

Using the memory-safe dialect in assembly does have effects on memory safety in Solidity contracts, but it does not directly affect the underlying memory in terms of performance or storage. The memory-safe dialect primarily affects how you write and handle assembly code in Solidity, providing additional safety features and restrictions to prevent common programming errors.

This is completely wrong. Using memory-safe assembly does not enable any safety checks or restrictions. Quite the opposite. The compiler cannot determine on its own whether the code is memory-safe or not. By using this feature, you’re declaring that it is and signaling that the compiler can do things that would not be safe to do otherwise. If it turns out it’s not memory-safe, bad things can happen.

5 Likes