Skip to content

Commit 1cc8bf5

Browse files
committed
Reduce VM size by supporting compilers from 3 last OTP releases
Update few CI tests to optimize VM for the tested OTP version Fix accordingly conditions for following opcodes: - OP_BS_MATCH_STRING is generated by at least OTP 21 to 27 - OP_BS_TEST_UNIT is generated by at least OTP 21 to 25 Signed-off-by: Paul Guyot <[email protected]>
1 parent f660ded commit 1cc8bf5

18 files changed

+182
-21
lines changed

.github/workflows/build-and-test-macos.yaml

+5-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ jobs:
4040
os: ["macos-13", "macos-14", "macos-15"]
4141
otp: ["24", "25", "26", "27"]
4242

43+
include:
44+
- otp: "24"
45+
cmake_opts_other: "-DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=24 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=24"
46+
4347
steps:
4448
# Setup
4549
- name: "Checkout repo"
@@ -83,7 +87,7 @@ jobs:
8387
working-directory: build
8488
run: |
8589
export PATH="/usr/local/opt/erlang@${{ matrix.otp }}/bin:/opt/homebrew/opt/erlang@${{ matrix.otp }}/bin:$PATH"
86-
cmake -DAVM_WARNINGS_ARE_ERRORS=ON -G Ninja ..
90+
cmake -DAVM_WARNINGS_ARE_ERRORS=ON ${{ matrix.cmake_opts_other }} -G Ninja ..
8791
8892
- name: "Build: run ninja"
8993
working-directory: build

.github/workflows/build-and-test-other.yaml

+16-2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ jobs:
129129
name: test-modules
130130
path: build_tests
131131

132+
- name: Skip compilation of Erlang code
133+
run: |
134+
sed -e 's|add_subdirectory(libs)|# add_subdirectory(libs)|g' \
135+
-e 's|add_subdirectory(examples)|# add_subdirectory(examples)|g' \
136+
-i CMakeLists.txt
137+
sed -e 's|add_subdirectory(erlang_tests)|# add_subdirectory(erlang_tests)|g' \
138+
-e 's|add_subdirectory(libs/|# add_subdirectory(libs/|g' \
139+
-e 's|add_dependencies(test-erlang erlang_test_modules)|# add_dependencies(test-erlang erlang_test_modules)|g' \
140+
-i tests/CMakeLists.txt
141+
132142
- name: Set up QEMU
133143
uses: docker/setup-qemu-action@v3
134144

@@ -159,13 +169,17 @@ jobs:
159169
mkdir -p build &&
160170
cd build &&
161171
cmake .. ${{ matrix.cmake_opts }} &&
172+
mkdir -p tests/erlang_tests/code_load/beams/ &&
162173
cp ../build_tests/tests/erlang_tests/*.beam tests/erlang_tests/ &&
163174
cp ../build_tests/tests/erlang_tests/code_load/*.{avm,beam,hrl} tests/erlang_tests/code_load/ &&
164-
mkdir -p tests/erlang_tests/code_load/beams/ &&
165175
cp ../build_tests/tests/erlang_tests/code_load/beams/*.beam tests/erlang_tests/code_load/beams/ &&
176+
mkdir -p tests/libs/etest/ &&
166177
cp ../build_tests/tests/libs/etest/*.avm tests/libs/etest/ &&
167-
cp ../build_tests/tests/libs/estdlib/*.avm tests/libs/estdlib/ &&
178+
mkdir -p tests/libs/estdlib/ &&
179+
cp ../build_tests/tests/libs/estdlib/*.avm tests/libs/estdlib/ &&
180+
mkdir -p tests/libs/eavmlib/ &&
168181
cp ../build_tests/tests/libs/eavmlib/*.avm tests/libs/eavmlib/ &&
182+
mkdir -p tests/libs/alisp/ &&
169183
cp ../build_tests/tests/libs/alisp/*.avm tests/libs/alisp/ &&
170184
make AtomVM &&
171185
make test-erlang &&

.github/workflows/build-and-test.yaml

+7-3
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ jobs:
204204
elixir_version: "1.7"
205205
rebar3_version: "3.15.2"
206206
compiler_pkgs: "g++"
207+
cmake_opts_other: "-DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=21 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=21"
207208

208209
- container: "ubuntu:20.04"
209210
cc: "cc"
@@ -213,6 +214,7 @@ jobs:
213214
elixir_version: "1.8"
214215
rebar3_version: "3.18.0"
215216
compiler_pkgs: "g++"
217+
cmake_opts_other: "-DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=22 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=22"
216218

217219
- container: "ubuntu:20.04"
218220
cc: "cc"
@@ -222,6 +224,7 @@ jobs:
222224
elixir_version: "1.11"
223225
rebar3_version: "3.20.0"
224226
compiler_pkgs: "g++"
227+
cmake_opts_other: "-DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=23 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=23"
225228

226229
- container: "ubuntu:20.04"
227230
cc: "cc"
@@ -231,6 +234,7 @@ jobs:
231234
elixir_version: "1.14"
232235
rebar3_version: "3.23.0"
233236
compiler_pkgs: "g++"
237+
cmake_opts_other: "-DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=24 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=24"
234238

235239
- os: "ubuntu-24.04"
236240
cc: "cc"
@@ -281,9 +285,9 @@ jobs:
281285
cc: "gcc-10"
282286
cxx: "g++-10"
283287
cflags: "-m32 -O3"
284-
otp: "23"
285-
elixir_version: "1.11"
286-
rebar3_version: "3.20.0"
288+
otp: "27"
289+
elixir_version: "1.17"
290+
rebar3_version: "3.24.0"
287291
# Use Werror so we get an error with 32 bit specific warnings
288292
cmake_opts_other: "-DAVM_CREATE_STACKTRACES=off -DAVM_WARNINGS_ARE_ERRORS=ON"
289293
arch: "i386"

.github/workflows/esp32-build.yaml

+17-4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ jobs:
5454
idf-version: 'v5.0.7'
5555
- esp-idf-target: "esp32c3"
5656
idf-version: 'v5.1.5'
57+
58+
# ESP-IDF 5.0.7 and 5.1.5 containers are based on Ubuntu focal which has Erlang/OTP 22
59+
# ESP-IDF 5.2.3 and 5.3.2 containers are based on Ubuntu jammy which has Erlang/OTP 24
60+
include:
61+
- idf-version: 'v5.0.7'
62+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=22 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=22
63+
- idf-version: 'v5.1.5'
64+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=22 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=22
65+
- idf-version: 'v5.2.3'
66+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=24 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=24
67+
- idf-version: 'v5.3.2'
68+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=24 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=24
69+
5770
steps:
5871
- name: Checkout repo
5972
uses: actions/checkout@v4
@@ -170,8 +183,8 @@ jobs:
170183
echo "CONFIG_ESP_WIFI_RX_IRAM_OPT=n" >> sdkconfig.defaults
171184
. $IDF_PATH/export.sh
172185
export IDF_TARGET=${{matrix.esp-idf-target}}
173-
idf.py set-target ${{matrix.esp-idf-target}}
174-
idf.py build
186+
idf.py ${{matrix.cmake_opts}} set-target ${{matrix.esp-idf-target}}
187+
idf.py ${{matrix.cmake_opts}} build
175188
176189
- name: Run ESP32 tests using qemu with memory checks build
177190
working-directory: ./src/platforms/esp32/test/
@@ -191,8 +204,8 @@ jobs:
191204
. $IDF_PATH/export.sh
192205
export IDF_TARGET=${{matrix.esp-idf-target}}
193206
export PATH=${PATH}:${HOME}/.cache/rebar3/bin
194-
idf.py set-target ${{matrix.esp-idf-target}}
195-
idf.py build
207+
idf.py ${{matrix.cmake_opts}} set-target ${{matrix.esp-idf-target}}
208+
idf.py ${{matrix.cmake_opts}} build
196209
197210
- name: Run ESP32 tests using qemu
198211
working-directory: ./src/platforms/esp32/test/

.github/workflows/run-tests-with-beam.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -43,45 +43,55 @@ jobs:
4343
- os: "ubuntu-24.04"
4444
test_erlang_opts: "-s prime_smp"
4545
container: erlang:21
46+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=21 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=21
4647

4748
- os: "ubuntu-24.04"
4849
test_erlang_opts: "-s prime_smp"
4950
container: erlang:22
51+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=22 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=22
5052

5153
- os: "ubuntu-24.04"
5254
test_erlang_opts: "-s prime_smp"
5355
container: erlang:23
56+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=23 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=23
5457

5558
- os: "ubuntu-24.04"
5659
test_erlang_opts: "-s prime_smp"
5760
container: erlang:24
61+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=24 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=24
5862

5963
- os: "ubuntu-24.04"
6064
test_erlang_opts: "-s prime_smp"
6165
container: erlang:25
66+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=25 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=25
6267

6368
- os: "ubuntu-24.04"
6469
test_erlang_opts: "-s prime_smp"
6570
otp: "26"
6671
container: erlang:26
72+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=26 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=26
6773

6874
- os: "ubuntu-24.04"
6975
test_erlang_opts: "-s prime_smp"
7076
otp: "27"
7177
container: erlang:27
78+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=27 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=27
7279

7380
# This is ARM64
7481
- os: "macos-14"
7582
otp: "25"
7683
path_prefix: "/opt/homebrew/opt/erlang@25/bin:"
84+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=25 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=25
7785

7886
- os: "macos-14"
7987
otp: "26"
8088
path_prefix: "/opt/homebrew/opt/erlang@26/bin:"
89+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=26 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=26
8190

8291
- os: "macos-14"
8392
otp: "27"
8493
path_prefix: "/opt/homebrew/opt/erlang@27/bin:"
94+
cmake_opts: -DAVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION=27 -DAVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION=27
8595
steps:
8696
# Setup
8797
- name: "Checkout repo"

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ option(AVM_RELEASE "Build an AtomVM release" OFF)
3636
option(AVM_CREATE_STACKTRACES "Create stacktraces" ON)
3737
option(AVM_BUILD_RUNTIME_ONLY "Only build the AtomVM runtime" OFF)
3838
option(COVERAGE "Build for code coverage" OFF)
39+
set(AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION "25" CACHE STRING "Minimum supported OTP Release for Erlang compiler")
40+
set(AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION "28" CACHE STRING "Maximum supported OTP Release for Erlang compiler")
3941

4042
if((${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") OR
4143
(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") OR

CMakeModules/FindElixir.cmake

+6-7
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
1919
#
2020

21-
find_program(ELIXIRC_PATH elixirc)
21+
find_program(ELIXIRC_EXECUTABLE elixirc)
2222

23-
if (ELIXIRC_PATH)
24-
message("Found Elixir")
25-
set(Elixir_FOUND TRUE)
26-
elseif(Elixir_FIND_REQUIRED)
27-
message(FATAL_ERROR "Elixir compiler not found")
28-
endif()
23+
include(FindPackageHandleStandardArgs)
24+
find_package_handle_standard_args(Elixir
25+
FOUND_VAR Elixir_FOUND
26+
REQUIRED_VARS ELIXIRC_EXECUTABLE
27+
)

CMakeModules/FindErlang.cmake

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#
2+
# This file is part of AtomVM.
3+
#
4+
# Copyright 2025 Paul Guyot <[email protected]>
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
#
20+
21+
cmake_minimum_required(VERSION 3.13)
22+
23+
find_program(ERLC_EXECUTABLE erlc)
24+
find_program(ERL_EXECUTABLE erl)
25+
26+
if (ERLC_EXECUTABLE AND ERL_EXECUTABLE)
27+
execute_process(COMMAND erl -eval "io:put_chars(erlang:system_info(otp_release))." -s init stop -noshell OUTPUT_VARIABLE Erlang_VERSION)
28+
endif()
29+
30+
include(FindPackageHandleStandardArgs)
31+
32+
if (CMAKE_VERSION VERSION_LESS 3.19)
33+
# Handle version range ourselves
34+
if (Erlang_FIND_VERSION AND ERL_EXECUTABLE)
35+
string(REPLACE "..." ";" Erlang_FIND_VERSION_LIST ${Erlang_FIND_VERSION})
36+
list(LENGTH Erlang_FIND_VERSION_LIST Erlang_FIND_VERSION_LIST_LEN)
37+
if (Erlang_FIND_VERSION_LIST_LEN EQUAL 1)
38+
find_package_handle_standard_args(Erlang
39+
FOUND_VAR Erlang_FOUND
40+
REQUIRED_VARS ERLC_EXECUTABLE ERL_EXECUTABLE
41+
VERSION_VAR Erlang_VERSION
42+
)
43+
elseif(${Erlang_FIND_VERSION_LIST_LEN} EQUAL 2)
44+
list(GET Erlang_FIND_VERSION_LIST 0 Erlang_FIND_VERSION_MIN)
45+
list(GET Erlang_FIND_VERSION_LIST 1 Erlang_FIND_VERSION_MAX)
46+
if (${Erlang_VERSION} LESS Erlang_FIND_VERSION_MIN)
47+
message(FATAL_ERROR "-- Found Erlang: ${ERL_EXECUTABLE} but OTP Release ${Erlang_VERSION} is less than required ${Erlang_FIND_VERSION_MIN}")
48+
endif()
49+
if (${Erlang_VERSION} GREATER Erlang_FIND_VERSION_MAX)
50+
message(FATAL_ERROR "-- Found Erlang: ${ERL_EXECUTABLE} but OTP Release ${Erlang_VERSION} is greater than supported ${Erlang_FIND_VERSION_MAX}")
51+
endif()
52+
find_package_handle_standard_args(Erlang
53+
FOUND_VAR Erlang_FOUND
54+
REQUIRED_VARS ERLC_EXECUTABLE ERL_EXECUTABLE
55+
)
56+
else()
57+
message(FATAL_ERROR "-- Found Erlang: ${ERL_EXECUTABLE} but version range syntax is incorrect ${Erlang_FIND_VERSION}")
58+
endif()
59+
else()
60+
find_package_handle_standard_args(Erlang
61+
FOUND_VAR Erlang_FOUND
62+
REQUIRED_VARS ERLC_EXECUTABLE ERL_EXECUTABLE
63+
)
64+
endif()
65+
else()
66+
find_package_handle_standard_args(Erlang
67+
FOUND_VAR Erlang_FOUND
68+
HANDLE_VERSION_RANGE
69+
REQUIRED_VARS ERLC_EXECUTABLE ERL_EXECUTABLE
70+
VERSION_VAR Erlang_VERSION
71+
)
72+
endif()

examples/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ if (Gleam_FOUND)
3636
else()
3737
message(WARNING "-- gleam not found, skipping Gleam examples")
3838
endif()
39+
40+
# Ensure erlang version is compatible with VM
41+
find_package(Erlang ${AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION}...${AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION} REQUIRED)

libs/CMakeLists.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
1919
#
2020

21-
cmake_minimum_required (VERSION 3.12)
21+
cmake_minimum_required (VERSION 3.13)
2222
project(libs)
2323

2424
add_subdirectory(estdlib/src)
@@ -65,6 +65,9 @@ else()
6565
message("Dialyzer was not found -- skipping PLT build")
6666
endif()
6767

68+
# Ensure erlang version is compatible with VM
69+
find_package(Erlang ${AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION}...${AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION} REQUIRED)
70+
6871
install(
6972
FILES ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib.avm
7073
DESTINATION lib/atomvm

src/libAtomVM/CMakeLists.txt

+6
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ endif()
134134
if (ADVANCED_TRACING)
135135
target_compile_definitions(libAtomVM PUBLIC ENABLE_ADVANCED_TRACE)
136136
endif()
137+
if (AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION)
138+
target_compile_definitions(libAtomVM PUBLIC MINIMUM_OTP_COMPILER_VERSION=${AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION})
139+
endif()
140+
if (AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION)
141+
target_compile_definitions(libAtomVM PUBLIC MAXIMUM_OTP_COMPILER_VERSION=${AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION})
142+
endif()
137143

138144
target_link_libraries(libAtomVM PUBLIC m)
139145
include(CheckCSourceCompiles)

src/libAtomVM/opcodesswitch.h

+7-3
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@
4747

4848
// These constants can be used to reduce the size of the VM for a specific
4949
// range of compiler versions
50-
#define MINIMUM_OTP_COMPILER_VERSION 21
51-
#define MAXIMUM_OTP_COMPILER_VERSION 26
50+
#ifndef MINIMUM_OTP_COMPILER_VERSION
51+
#define MINIMUM_OTP_COMPILER_VERSION 25
52+
#endif
53+
#ifndef MAXIMUM_OTP_COMPILER_VERSION
54+
#define MAXIMUM_OTP_COMPILER_VERSION 28
55+
#endif
5256

5357
#ifdef __cplusplus
5458
extern "C" {
@@ -5071,7 +5075,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
50715075
break;
50725076
}
50735077

5074-
#if MINIMUM_OTP_COMPILER_VERSION <= 24
5078+
#if MINIMUM_OTP_COMPILER_VERSION <= 25
50755079
case OP_BS_TEST_UNIT: {
50765080
uint32_t fail;
50775081
DECODE_LABEL(fail, pc)

src/platforms/emscripten/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ endif ()
3232
option(AVM_DISABLE_SMP "Disable SMP." OFF)
3333
option(AVM_USE_32BIT_FLOAT "Use 32 bit floats." OFF)
3434
option(AVM_VERBOSE_ABORT "Print module and line number on VM abort" OFF)
35+
set(AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION "25" CACHE STRING "Minimum supported OTP Release for Erlang compiler")
36+
set(AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION "28" CACHE STRING "Maximum supported OTP Release for Erlang compiler")
3537

3638
set(
3739
PLATFORM_LIB_SUFFIX

src/platforms/esp32/test/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ set(AVM_SELECT_IN_TASK ON)
5959

6060
project(atomvm-esp32-test)
6161

62+
# Ensure erlang version is compatible with VM
63+
set(AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION "25" CACHE STRING "Minimum supported OTP Release for Erlang compiler")
64+
set(AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION "28" CACHE STRING "Maximum supported OTP Release for Erlang compiler")
65+
find_package(Erlang ${AVM_MINIMUM_OTP_RELEASE_COMPILER_VERSION}...${AVM_MAXIMUM_OTP_RELEASE_COMPILER_VERSION} REQUIRED)
66+
6267
# esp-idf does not use compile_feature but instead sets version in
6368
# c_compile_options
6469
# Ensure project is compiled with at least C11

0 commit comments

Comments
 (0)