-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Describe the bug
When compiling a specific recursive function pattern, the OTP 28 compiler crashes with an internal bad key
error in the beam_ssa_pre_codegen
pass. The issue appears to be a regression, as the same code compiles successfully on OTP 27.
While the bug is triggered by Elixir source code, the crash occurs deep within the Erlang compiler's SSA backend (beam_ssa_pre_codegen.erl
), which is why this issue is being reported to the Erlang/OTP team.
To Reproduce
A complete repository with the reproduction code and further analysis is available here:
https://github.com/neilberkman/otp28_bug_repro
-
Set up the environment:
- Erlang/OTP: 28.0.1 (or latest
main
branch) - Elixir: 1.18.x (built on OTP 28)
- Erlang/OTP: 28.0.1 (or latest
-
Clone the reproduction repository:
git clone https://github.com/neilberkman/otp28_bug_repro.git cd otp28_bug_repro
-
Run the Elixir compiler on the minimal reproduction file:
elixirc accurate_minimal_repro.ex
Expected behavior
The code should compile successfully, producing a .beam
file without any errors.
Affected versions
- Erlang/OTP 28.0.1
Note: The bug is not present in Erlang/OTP 27.
Additional context
The crash is consistently reproduced when a function has the following combination of features:
- A recursive tail call.
- Pattern-matched function clauses for the base and recursive cases.
- Nested conditional logic (a
case
statement containing anif
statement). - An intermediate variable created within the
if
block that is immediately passed to another function.
Minimal Reproduction Code (accurate_minimal_repro.ex
):
defmodule AccurateMinimalRepro do
defmodule Cursor do
defstruct [:current, :position]
def from_list(list, opts \\ []) do
position = Keyword.get(opts, :position, 0)
%__MODULE__{current: Enum.at(list, position), position: position}
end
def to_list(%__MODULE__{}), do: []
def move_forward(%__MODULE__{} = cursor), do: cursor
def delete_before(%__MODULE__{} = cursor, _count), do: cursor
def insert_before(%__MODULE__{} = cursor, _items), do: cursor
end
def test_function do
diffs = Cursor.from_list([{:equal, "test"}])
line_mode_loop(diffs, {0, 0, "", ""}, :never)
end
defp line_mode_loop(
%Cursor{current: nil} = diffs,
_acc,
_deadline
),
do: Cursor.to_list(diffs)
defp line_mode_loop(
%Cursor{current: this_diff} = diffs,
{count_delete, count_insert, text_delete, text_insert},
deadline
) do
{op, text} = this_diff
result =
case op do
:insert ->
{diffs, count_delete, count_insert + 1, text_delete, text_insert <> text}
:delete ->
{diffs, count_delete + 1, count_insert, text_delete <> text, text_insert}
:equal ->
diffs2 =
if count_delete > 0 && count_insert > 0 do
temp_diffs = Cursor.delete_before(diffs, count_delete + count_insert)
sub_diff = main_impl(text_delete, text_insert, false, deadline)
Cursor.insert_before(temp_diffs, sub_diff)
else
diffs
end
{diffs2, 0, 0, "", ""}
_ ->
raise RuntimeError, "Invalid operation"
end
{cursor, count_delete, count_insert, text_delete, text_insert} = result
line_mode_loop(
Cursor.move_forward(cursor),
{count_delete, count_insert, text_delete, text_insert},
deadline
)
end
defp main_impl(_text1, _text2, _check_lines, _deadline) do
[{:equal, "stub"}]
end
end
Compiler Crash Output:
== Compilation error in file accurate_minimal_repro.ex ==
** (CompileError) accurate_minimal_repro.ex: internal error in pass beam_ssa_pre_codegen:
exception error: bad key: {b_var,144}
in function map_get/2
called as map_get({b_var,144},
#{{b_var,63} => {y,147},
{b_var,106} => {y,153},
{b_var,108} => {y,152},
{b_var,112} => {y,151},
{b_var,114} => {y,150},
{b_var,120} => {y,149},
{b_var,122} => {y,148},
{b_var,154} => {y,158},
{b_var,155} => {y,159},
{b_var,156} => {y,160},
{b_var,157} => {y,161}})
*** argument 1: not present in map
in call from beam_ssa_pre_codegen:init_interval/2 (beam_ssa_pre_codegen.erl:3185)
in call from beam_ssa_pre_codegen:init_intervals/2 (beam_ssa_pre_codegen.erl:3153)
in call from beam_ssa_pre_codegen:linear_scan/1 (beam_ssa_pre_codegen.erl:2697)
in call from beam_ssa_pre_codegen:ssa_pre_codegen/3 (beam_ssa_pre_codegen.erl:173)
in call from beam_ssa_codegen:module/3 (beam_ssa_codegen.erl:259)
in call from compile:fold_comp/4 (compile.erl:374)
in call from compile:'-internal_comp/5-anonymous-1-'/5 (compile.erl:386)
in call from compile:do_comp/4 (compile.erl:261)
(elixir 1.18.0-dev) lib/kernel/parallel_compiler.ex:483: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8