Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@
dist_ctrl_get_data/1,
dist_ctrl_put_data/2,
unique_integer/0,
unique_integer/1
unique_integer/1,
bump_reductions/1
]).

-export_type([
Expand Down Expand Up @@ -272,7 +273,8 @@ send_after(Time, Dest, Msg) ->
(Pid :: pid(), message_queue_len) -> {message_queue_len, non_neg_integer()};
(Pid :: pid(), memory) -> {memory, non_neg_integer()};
(Pid :: pid(), links) -> {links, [pid()]};
(Pid :: pid(), monitored_by) -> {monitored_by, [pid() | resource() | port()]}.
(Pid :: pid(), monitored_by) -> {monitored_by, [pid() | resource() | port()]};
(Pid :: pid(), reductions) -> {reductions, [pos_integer()]}.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are you sure about the spec? Reading your code it seems the function returns {reductions, non_neg_integer()}, following Erlang/OTP, instead of {reductions, [pos_integer()]}.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I guess there should be non_neg_integer() just like in the OTP.

process_info(_Pid, _Key) ->
erlang:nif_error(undefined).

Expand Down Expand Up @@ -1529,3 +1531,15 @@ unique_integer(_Options) ->
-spec nif_error(Reason :: any()) -> no_return().
nif_error(_Reason) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Reductions an integer representing a value of which the reduction counter
%% will be incremented by.
%% @returns true
%% @doc Increments the reduction counter for the calling process, a context switch is
%% forced when the counter reaches the maximum number of reductions for a process.
%% @end
%%-----------------------------------------------------------------------------
-spec bump_reductions(pos_integer()) -> true.
bump_reductions(Reductions) when is_integer(Reductions), Reductions > 1 ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

Erlang/OTP seems to allow bump_reductions(1)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes - I will look into it.

erlang:nif_error(undefined).
21 changes: 21 additions & 0 deletions src/libAtomVM/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "erl_nif.h"
#include "erl_nif_priv.h"
#include "globalcontext.h"
#include "limits.h"
#include "list.h"
#include "mailbox.h"
#include "smp.h"
Expand Down Expand Up @@ -89,6 +90,8 @@ Context *context_new(GlobalContext *glb)

ctx->native_handler = NULL;

ctx->reductions = 0;

ctx->saved_module = NULL;
ctx->saved_ip = NULL;
ctx->restore_trap_handler = NULL;
Expand Down Expand Up @@ -471,6 +474,7 @@ bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term a
case MESSAGE_QUEUE_LEN_ATOM:
case REGISTERED_NAME_ATOM:
case MEMORY_ATOM:
case REDUCTIONS_ATOM:
ret_size = TUPLE_SIZE(2);
break;
case LINKS_ATOM: {
Expand Down Expand Up @@ -626,6 +630,23 @@ bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term a
break;
}

case REDUCTIONS_ATOM: {
term_put_tuple_element(ret, 0, REDUCTIONS_ATOM);
if (UNLIKELY((uint64_t) LLONG_MAX < ctx->reductions)) {
*out = BADARG_ATOM;
return false;
}
int64_t reductions = (int64_t) ctx->reductions;
size_t reductions_size = term_boxed_integer_size(reductions);
if (UNLIKELY(memory_ensure_free_with_roots(ctx, reductions_size, 1, &ret, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
*out = OUT_OF_MEMORY_ATOM;
return false;
}
term reductions_term = term_make_maybe_boxed_int64(reductions, &ctx->heap);
term_put_tuple_element(ret, 1, reductions_term);
break;
}

default:
UNREACHABLE();
}
Expand Down
2 changes: 2 additions & 0 deletions src/libAtomVM/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ struct Context
// Ports support
native_handler_f native_handler;

uint64_t reductions;

unsigned int leader : 1;
unsigned int has_min_heap_size : 1;
unsigned int has_max_heap_size : 1;
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/defaultatoms.def
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ X(PORT_COUNT_ATOM, "\xA", "port_count")
X(ATOM_COUNT_ATOM, "\xA", "atom_count")
X(SYSTEM_ARCHITECTURE_ATOM, "\x13", "system_architecture")
X(WORDSIZE_ATOM, "\x8", "wordsize")
X(REDUCTIONS_ATOM, "\xA", "reductions")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Usually we add new atoms at the end, but I'm not sure we really have a convention here, adding in the middle reduces merge conflicts. However, the jit compiler depends on some values and it may break it.

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 if it reduces merge conflicts I would leave it as it is.


X(DECIMALS_ATOM, "\x8", "decimals")
X(SCIENTIFIC_ATOM, "\xA", "scientific")
Expand Down
15 changes: 15 additions & 0 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]);
static term nif_unicode_characters_to_binary(Context *ctx, int argc, term argv[]);
static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[]);
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[]);
static term nif_erlang_bump_reductions_1(Context *ctx, int argc, term argv[]);

#define DECLARE_MATH_NIF_FUN(moniker) \
static term nif_math_##moniker(Context *ctx, int argc, term argv[]);
Expand Down Expand Up @@ -875,6 +876,11 @@ static const struct Nif zlib_compress_nif =
};


static const struct Nif erlang_bump_reductions_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_erlang_bump_reductions_1
};

#define DEFINE_MATH_NIF(moniker) \
static const struct Nif math_##moniker##_nif = \
{ \
Expand Down Expand Up @@ -5920,6 +5926,15 @@ static term nif_zlib_compress_1(Context *ctx, int argc, term argv[])
}
#endif

static term nif_erlang_bump_reductions_1(Context *ctx, int argc, term argv[])
{
UNUSED(argc);
VALIDATE_VALUE(argv[0], term_is_integer);
int64_t reductions_to_bump = term_to_int(argv[0]) - 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure why we do -1 here.

Copy link
Contributor Author

@FKubisSWM FKubisSWM Aug 28, 2025

Choose a reason for hiding this comment

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

I think its because bump_reductions is creating 1 reduction by itself just by being called.

ctx->reductions += reductions_to_bump;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should ensure we don't overflow, whatever the width of this field is.

return TRUE_ATOM;
}

//
// MAINTENANCE NOTE: Exception handling for fp operations using math
// error handling is designed to be thread-safe, as errors are specified
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,4 @@ math:sqrt/1, &math_sqrt_nif
math:tan/1, &math_tan_nif
math:tanh/1, &math_tanh_nif
zlib:compress/1, &zlib_compress_nif
erlang:bump_reductions/1, &erlang_bump_reductions_nif
48 changes: 24 additions & 24 deletions src/libAtomVM/opcodesswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -1767,7 +1767,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
Module *prev_mod;
term *x_regs;
const uint8_t *pc;
int remaining_reductions;
uint64_t max_reductions;
Copy link
Collaborator

Choose a reason for hiding this comment

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

On 32 bits platforms it's going to be a problem. Why this change?

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 see, will be reverted to int.


Context *ctx = scheduler_run(glb);

Expand All @@ -1780,7 +1780,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
code = mod->code->code;
x_regs = ctx->x;
pc = (ctx->saved_ip);
remaining_reductions = DEFAULT_REDUCTIONS_AMOUNT;
max_reductions = ctx->reductions + DEFAULT_REDUCTIONS_AMOUNT;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
Expand Down Expand Up @@ -1864,8 +1864,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
#ifdef IMPL_EXECUTE_LOOP
ctx->cp = module_address(mod->module_index, pc - code);

remaining_reductions--;
if (LIKELY(remaining_reductions)) {
++ctx->reductions;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is going to slow us down as opposed to keeping the value in a local variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Local variable from opcodesswitch is unobtainable in nif. Also every context should have its own reductions counter if we want the reductions to work exactly like the OTP version.

if (LIKELY(ctx->reductions < max_reductions)) {
TRACE_CALL(ctx, mod, "call", label, arity);
JUMP_TO_ADDRESS(mod->labels[label]);
} else {
Expand Down Expand Up @@ -1895,8 +1895,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)

DEBUG_DUMP_STACK(ctx);

remaining_reductions--;
if (LIKELY(remaining_reductions)) {
++ctx->reductions;
if (LIKELY(ctx->reductions < max_reductions)) {
TRACE_CALL(ctx, mod, "call_last", label, arity);
JUMP_TO_ADDRESS(mod->labels[label]);
} else {
Expand All @@ -1917,8 +1917,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
USED_BY_TRACE(label);

#ifdef IMPL_EXECUTE_LOOP
remaining_reductions--;
if (LIKELY(remaining_reductions)) {
++ctx->reductions;
if (LIKELY(ctx->reductions < max_reductions)) {
TRACE_CALL(ctx, mod, "call_only", label, arity);
JUMP_TO_ADDRESS(mod->labels[label]);
} else {
Expand All @@ -1933,8 +1933,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
// save pc in case of error
const uint8_t *orig_pc = pc - 1;

remaining_reductions--;
if (UNLIKELY(!remaining_reductions)) {
++ctx->reductions;
if (UNLIKELY(ctx->reductions >= max_reductions)) {
SCHEDULE_NEXT(mod, orig_pc);
}
#endif
Expand Down Expand Up @@ -2041,8 +2041,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
// save pc in case of error
const uint8_t *orig_pc = pc - 1;

remaining_reductions--;
if (UNLIKELY(!remaining_reductions)) {
++ctx->reductions;
if (UNLIKELY(ctx->reductions >= max_reductions)) {
SCHEDULE_NEXT(mod, orig_pc);
}
#endif
Expand Down Expand Up @@ -3240,8 +3240,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
USED_BY_TRACE(label);

#ifdef IMPL_EXECUTE_LOOP
remaining_reductions--;
if (LIKELY(remaining_reductions)) {
++ctx->reductions;
if (LIKELY(ctx->reductions < max_reductions)) {
JUMP_TO_ADDRESS(mod->labels[label]);
} else {
SCHEDULE_NEXT(mod, mod->labels[label]);
Expand Down Expand Up @@ -3477,8 +3477,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)

case OP_CALL_FUN: {
#ifdef IMPL_EXECUTE_LOOP
remaining_reductions--;
if (UNLIKELY(!remaining_reductions)) {
++ctx->reductions;
if (UNLIKELY(ctx->reductions >= max_reductions)) {
SCHEDULE_NEXT(mod, pc - 1);
}
#endif
Expand Down Expand Up @@ -3533,8 +3533,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)

case OP_CALL_EXT_ONLY: {
#ifdef IMPL_EXECUTE_LOOP
remaining_reductions--;
if (UNLIKELY(!remaining_reductions)) {
++ctx->reductions;
if (UNLIKELY(ctx->reductions >= max_reductions)) {
SCHEDULE_NEXT(mod, pc - 1);
}
#endif
Expand Down Expand Up @@ -5233,8 +5233,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
// save pc in case of error
const uint8_t *orig_pc = pc - 1;

remaining_reductions--;
if (UNLIKELY(!remaining_reductions)) {
++ctx->reductions;
if (UNLIKELY(ctx->reductions >= max_reductions)) {
SCHEDULE_NEXT(mod, orig_pc);
}
#endif
Expand Down Expand Up @@ -5286,8 +5286,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)

case OP_APPLY_LAST: {
#ifdef IMPL_EXECUTE_LOOP
remaining_reductions--;
if (UNLIKELY(!remaining_reductions)) {
++ctx->reductions;
if (UNLIKELY(ctx->reductions >= max_reductions)) {
SCHEDULE_NEXT(mod, pc - 1);
}
#endif
Expand Down Expand Up @@ -6793,8 +6793,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)

case OP_CALL_FUN2: {
#ifdef IMPL_EXECUTE_LOOP
remaining_reductions--;
if (UNLIKELY(!remaining_reductions)) {
++ctx->reductions;
if (UNLIKELY(ctx->reductions >= max_reductions)) {
SCHEDULE_NEXT(mod, pc - 1);
}
#endif
Expand Down
5 changes: 5 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,8 @@ compile_erlang(test_raw_raise)
compile_erlang(test_ets)
compile_erlang(test_node)

compile_erlang(test_bump_reductions)

compile_erlang(test_op_bs_start_match)
compile_assembler(test_op_bs_start_match_asm)
compile_erlang(test_op_bs_create_bin)
Expand Down Expand Up @@ -1068,6 +1070,9 @@ add_custom_target(erlang_test_modules DEPENDS
test_raw_raise.beam

test_ets.beam

test_bump_reductions.beam

test_node.beam

test_op_bs_start_match.beam
Expand Down
49 changes: 49 additions & 0 deletions tests/erlang_tests/test_bump_reductions.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
%
% This file is part of AtomVM.
%
% Copyright 2025 Franciszek Kubis <[email protected]>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(test_bump_reductions).

-export([start/0, process_test/0]).

start() ->
{reductions, 1} = erlang:process_info(self(), reductions),
erlang:bump_reductions(500),
{reductions, 502} = erlang:process_info(self(), reductions),
Pid = erlang:spawn_opt(fun() -> process_test() end, [link]),
Pid ! {ready, self()},
receive
{r1, {reductions, Reductions}} ->
2 = Reductions
end,
receive
{r2, {reductions, Reductions2}} ->
1029 = Reductions2
end,
erlang:bump_reductions(2000),
{reductions, 2505} = erlang:process_info(self(), reductions),
0.

process_test() ->
receive
{ready, Pid} ->
Pid ! {r1, erlang:process_info(self(), reductions)},
erlang:bump_reductions(1025),
Pid ! {r2, erlang:process_info(self(), reductions)}
end.
5 changes: 5 additions & 0 deletions tests/test-structs.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ static const char *const bounded_free_atom = "\xC" "bounded_free";
static const char *const minimum_atom = "\x7" "minimum";
static const char *const fibonacci_atom = "\x9" "fibonacci";

static const char *const reductions_atom = "\xA" "reductions";

void test_valueshashtable()
{
struct ValuesHashTable *htable = valueshashtable_new();
Expand Down Expand Up @@ -429,6 +431,9 @@ atom_index_t insert_atoms_into_atom_table(struct AtomTable *table)
assert(r == AtomTableEnsureAtomOk);
r = atom_table_ensure_atom(table, (const uint8_t *) fibonacci_atom + 1, fibonacci_atom[0], AtomTableNoOpts, &global_atom_index);

assert(r == AtomTableEnsureAtomOk);
r = atom_table_ensure_atom(table, (const uint8_t *) reductions_atom + 1, reductions_atom[0], AtomTableNoOpts, &global_atom_index);

return decimals_index;
}

Expand Down
2 changes: 2 additions & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,8 @@ struct Test tests[] = {
TEST_CASE(test_ets),
TEST_CASE(test_node),

TEST_CASE(test_bump_reductions),

// TEST CRASHES HERE: TEST_CASE(memlimit),

{ NULL, 0, false, false }
Expand Down
Loading