elixir-lang/elixir

internal error in pass beam_ssa_opt

Closed this issue · 6 comments

cr0t commented

Elixir and Erlang/OTP versions

Erlang/OTP 28 [erts-16.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

Elixir 1.18.4 (compiled with Erlang/OTP 28)

Operating system

macOS

Current behavior

If in the module below, we leave all decode/3 clauses definitions public, it compiles (and works) fine.

However, if we define decode/3 using defp, it fails to compile (see the console output below).

Note

We checked on Erlang 28.0.2 and 27.3.4.2, both fails to compile.

defmodule Transmission do
  defmodule Codec do
    # ...

    def decode(input), do: decode(input, 0, {:ok, <<>>})

    # NOTE: if we change all the next defp decode to def decode, module compiles fine

    defp decode(_, _, {:error, msg}), do: {:error, msg}
    defp decode(<<>>, _, {:ok, acc}), do: {:ok, acc}

    # the last byte
    defp decode(<<last_byte::8-bitstring>>, bytes_read, {:ok, acc}) do
      if valid?(last_byte) do
        msg_bits = rem(bytes_read, 8)
        <<msg::size(msg_bits), _::bitstring>> = last_byte

        decode(<<>>, bytes_read + 1, {:ok, <<acc::bitstring, msg::size(msg_bits)>>})
      else
        decode(<<>>, bytes_read, {:error, "wrong parity"})
      end
    end

    # non-last bytes
    defp decode(<<byte::8-bitstring, rest::bitstring>>, bytes_read, {:ok, acc}) do
      if valid?(byte) do
        <<msg::7-bitstring, _::1>> = byte

        decode(rest, bytes_read + 1, {:ok, <<acc::bitstring, msg::bitstring>>})
      else
        decode(<<>>, bytes_read, {:error, "wrong parity"})
      end
    end

    defp parity(msg) when bit_size(msg) == 7,
      do: for(<<bit::1 <- msg>>, bit == 1, do: bit) |> length() |> rem(2)

    defp valid?(<<msg::7-bitstring, parity_bit::1>>),
      do: parity(msg) == parity_bit
  end

  # ...
end

The failing compilation output:

$ mix compile
Compiling 1 file (.ex)
Sub pass ssa_opt_destructive_update

== Compilation error in file lib/transmission.ex ==
** (CompileError) lib/transmission.ex: internal error in pass beam_ssa_opt:
exception error: no function clause matching beam_ssa_destructive_update:patch_literal_term(<<"wrong parity">>,{self,init_writable},167)
  in function  beam_ssa_destructive_update:patch_literal_tuple/6 (beam_ssa_destructive_update.erl:1045)
  in call from beam_ssa_destructive_update:patch_opargs/6 (beam_ssa_destructive_update.erl:933)
  in call from beam_ssa_destructive_update:patch_opargs/3 (beam_ssa_destructive_update.erl:926)
  in call from beam_ssa_destructive_update:patch_is/5 (beam_ssa_destructive_update.erl:843)
  in call from beam_ssa_destructive_update:patch_f/5 (beam_ssa_destructive_update.erl:820)
  in call from beam_ssa_destructive_update:'-patch_instructions/5-anonymous-4-'/3 (beam_ssa_destructive_update.erl:295)
  in call from maps:fold_1/4 (maps.erl:894)
    (stdlib 7.0.2) lists.erl:2641: :lists.foreach_1/2

Expected behavior

It compiles successfully:

$ mix compile
Compiling 1 file (.ex)
Generated transmission app

Thanks @cr0t for the report 💜
I was able to reproduce it on 1.18.4 (compiled on OTP 27) as well.

This is most likely a bug in erlang itself, will report it upstream.

Reported in erlang/otp#10077.

Note: the bug doesn't manifest if you use an atom for error, {:error, :wrong_parity}.

Closing as it has been reported upstream.

cr0t commented

Thanks, folks, for directing it to the Erlang/OTP directly!

@josevalim while I was working on the code (the snippet is a part of the solution to some Exercism task), I also found a non-documented feature regarding the bitstring pattern-matching.

Correct me if I am wrong, but I was unable to find in the official Elixir documentation examples of this syntax:

def decode(<<byte::8-bitstring, rest::bitstring>>, # ...

I am referring to the -bitstring suffix, which is telling the compiler that I want byte to stay as a bitstring and not be converted to an integer. This syntax I've got from Claude, when I asked it what was wrong with my initial approach (without the -bistring).

Is it really an undocumented feature, or am I bad at googling? 😄

If it's undocumented, shall the docs be updated (I can try)?

cr0t commented

Oh, yes… Now I get it! Probably, I missed the bistring listed in the Types section 🤦

…it didn't click in my head that it can be used as a type definition for the bitstring segment. There are examples with other types, but not with bits or bitstrings.

Thanks.