diff --git a/.github/actions/make-snapshot/action.yml b/.github/actions/make-snapshot/action.yml index cd144d2bf6d1d0..f8d26dd989f0d0 100644 --- a/.github/actions/make-snapshot/action.yml +++ b/.github/actions/make-snapshot/action.yml @@ -14,9 +14,6 @@ inputs: fetch-branch: description: 'fetch branch' required: false - generate-tar-bz2: - description: 'Generate .tar.bz2' - required: false srcdir: description: 'srcdir for tool/make-snapshot. Empty = clone ruby/ruby into ./ruby.' required: false @@ -59,15 +56,10 @@ runs: env: ARCHNAME: ${{ inputs.archname }} SRCDIR: ${{ inputs.srcdir }} - GENERATE_TAR_BZ2: ${{ inputs.generate-tar-bz2 }} VERSION: ${{ inputs.version }} run: | [ -z "$SRCDIR" ] && SRCDIR=ruby - if [ -n "$GENERATE_TAR_BZ2" ]; then - ruby "$SRCDIR/tool/make-snapshot" "-archname=$ARCHNAME" -srcdir="$SRCDIR" pkg $VERSION - else - ruby "$SRCDIR/tool/make-snapshot" "-archname=$ARCHNAME" -srcdir="$SRCDIR" -packages=gzip,xz,zip pkg $VERSION - fi + ruby "$SRCDIR/tool/make-snapshot" "-archname=$ARCHNAME" -srcdir="$SRCDIR" -packages=gzip,xz,zip pkg $VERSION shell: bash - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 5a7b30fc3d8983..8369815ff7bfcb 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -65,14 +65,6 @@ jobs: ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems >> $GITHUB_OUTPUT if: ${{ env.UPDATE_ENABLED == 'true' }} - - name: Update spec/bundler/support/builders.rb - run: | - #!ruby - rake_version = File.read("gems/bundled_gems")[/^rake\s+(\S+)/, 1] - print ARGF.read.sub(/^ *def rake_version\s*\K".*?"/) {rake_version.dump} - shell: ruby -i~ {0} spec/bundler/support/builders.rb - if: ${{ env.UPDATE_ENABLED == 'true' }} - - name: Maintain updated gems list in NEWS run: | ruby tool/update-NEWS-gemlist.rb bundled @@ -88,7 +80,6 @@ jobs: git diff --color --no-ext-diff --ignore-submodules --exit-code -- gems/bundled_gems || gems=true git add -- NEWS.md gems/bundled_gems - git add -- spec/bundler/support/builders.rb echo news=$news >> $GITHUB_OUTPUT echo gems=$gems >> $GITHUB_OUTPUT echo update=${news:-$gems} >> $GITHUB_OUTPUT diff --git a/.github/workflows/tarball-macos.yml b/.github/workflows/tarball-macos.yml index d6552d516a7f12..02d04ac66b5d7d 100644 --- a/.github/workflows/tarball-macos.yml +++ b/.github/workflows/tarball-macos.yml @@ -35,6 +35,7 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} env: + ARCHNAME: ${{ inputs.archname }} TEST_BUNDLED_GEMS_ALLOW_FAILURES: ${{ inputs.allow-failures }} steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -47,7 +48,7 @@ jobs: run: | set -x curl -sSL "${RUBY_PATCH_URL}" -o ruby.patch - cd snapshot-*/ + cd "$ARCHNAME/" git apply ../ruby.patch shell: bash env: @@ -71,26 +72,26 @@ jobs: run: | echo "JOBS=-j$((1 + $(sysctl -n hw.activecpu)))" >> $GITHUB_ENV - name: configure - run: cd snapshot-*/ && ./configure --with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml) + run: cd "$ARCHNAME/" && ./configure --with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml) - name: make - run: cd snapshot-*/ && make $JOBS + run: cd "$ARCHNAME/" && make $JOBS - name: Reinstall Homebrew Ruby run: brew install ruby if: inputs.rebuild-homebrew-ruby && matrix.os == 'macos-15-intel' && (matrix.test_task == 'test-bundled-gems' || matrix.test_task == 'test-bundler-parallel') - name: Tests - run: cd snapshot-*/ && make $JOBS -s ${{ matrix.test_task }} + run: cd "$ARCHNAME/" && make $JOBS -s ${{ matrix.test_task }} env: RUBY_TESTOPTS: "-q --tty=no" RUBY_DEBUG_TEST_NO_REMOTE: "1" # leaked-globals since 2.7 - name: Leaked Globals - run: cd snapshot-*/ && make -s leaked-globals + run: cd "$ARCHNAME/" && make -s leaked-globals if: matrix.test_task == 'check' - name: make install without root privilege - run: cd snapshot-*/ && make $JOBS install DESTDIR="/tmp/destdir" + run: cd "$ARCHNAME/" && make $JOBS install DESTDIR="/tmp/destdir" if: matrix.test_task == 'check' - name: make install - run: cd snapshot-*/ && sudo make $JOBS install + run: cd "$ARCHNAME/" && sudo make $JOBS install if: matrix.test_task == 'check' - name: ruby -v run: /usr/local/bin/ruby -v diff --git a/.github/workflows/tarball-test-schedule.yml b/.github/workflows/tarball-test-schedule.yml new file mode 100644 index 00000000000000..225aeed633e9eb --- /dev/null +++ b/.github/workflows/tarball-test-schedule.yml @@ -0,0 +1,25 @@ +name: tarball-test-schedule +on: + schedule: + - cron: '30 18 * * *' # Daily at 18:30 UTC + workflow_dispatch: + +permissions: {} + +jobs: + dispatch: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + branch: + - master + - ruby_4_0 + - ruby_3_4 + - ruby_3_3 + steps: + - name: Trigger tarball-test on ${{ matrix.branch }} + run: gh workflow run tarball-test.yml --ref "$BRANCH" --repo "$GITHUB_REPOSITORY" + env: + BRANCH: ${{ matrix.branch }} + GH_TOKEN: ${{ secrets.MATZBOT_GITHUB_ACTION_TOKEN }} diff --git a/.github/workflows/tarball-test.yml b/.github/workflows/tarball-test.yml index 49a3800e540b9f..5b06b466fe0a90 100644 --- a/.github/workflows/tarball-test.yml +++ b/.github/workflows/tarball-test.yml @@ -12,8 +12,7 @@ on: # Do not use paths-ignore for required status checks # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks merge_group: - schedule: - - cron: '30 18 * * *' # Daily at 18:30 UTC + workflow_dispatch: concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -73,7 +72,6 @@ jobs: uses: ./.github/workflows/tarball-windows.yml with: archname: snapshot-${{ needs.tarball.outputs.branch }} - mode: modern secrets: inherit non_development: diff --git a/.github/workflows/tarball-ubuntu.yml b/.github/workflows/tarball-ubuntu.yml index 6568942055e5f4..3ab0dc407960c7 100644 --- a/.github/workflows/tarball-ubuntu.yml +++ b/.github/workflows/tarball-ubuntu.yml @@ -62,6 +62,7 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} env: + ARCHNAME: ${{ inputs.archname }} TEST_BUNDLED_GEMS_ALLOW_FAILURES: ${{ inputs.allow-failures }} steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -74,7 +75,7 @@ jobs: run: | set -x curl -sSL "${RUBY_PATCH_URL}" -o ruby.patch - cd snapshot-*/ + cd "$ARCHNAME/" git apply ../ruby.patch shell: bash env: @@ -154,13 +155,13 @@ jobs: run: | echo "JOBS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - name: configure - run: cd snapshot-*/ && ./configure + run: cd "$ARCHNAME/" && ./configure - name: make - run: cd snapshot-*/ && make $JOBS + run: cd "$ARCHNAME/" && make $JOBS - name: Save stats of HOME run: | set -euxo pipefail - cd snapshot-*/ + cd "$ARCHNAME/" cat >test.rb <<'EOF' require 'pathname' require 'digest' @@ -187,7 +188,7 @@ jobs: make runruby rm -f test.rb - name: Tests - run: cd snapshot-*/ && make $JOBS -s ${{ matrix.test_task }} + run: cd "$ARCHNAME/" && make $JOBS -s ${{ matrix.test_task }} env: RUBY_TESTOPTS: "-q --tty=no" # not sure why ~/.gnupg is remained @@ -198,7 +199,7 @@ jobs: - name: Diff stats of HOME run: | set -euxo pipefail - cd snapshot-*/ + cd "$ARCHNAME/" cat >test.rb <<'EOF' require 'pathname' require 'digest' @@ -227,13 +228,13 @@ jobs: diff -u /tmp/stat-before-tests.txt /tmp/stat-after-tests.txt # leaked-globals since 2.7 - name: Leaked Globals - run: cd snapshot-*/ && make -s leaked-globals + run: cd "$ARCHNAME/" && make -s leaked-globals if: matrix.test_task == 'check' - name: make install without root privilege - run: cd snapshot-*/ && make $JOBS install DESTDIR="/tmp/destdir" + run: cd "$ARCHNAME/" && make $JOBS install DESTDIR="/tmp/destdir" if: matrix.test_task == 'check' - name: make install - run: cd snapshot-*/ && sudo make $JOBS install + run: cd "$ARCHNAME/" && sudo make $JOBS install if: matrix.test_task == 'check' - name: ruby -v env: @@ -256,7 +257,7 @@ jobs: if: failure() && github.event_name == 'schedule' - name: Get ruby/ruby sha id: ruby_sha - run: cd snapshot-*/ && ./ruby -e 'puts "sha=#{RUBY_REVISION}"' >> $GITHUB_OUTPUT + run: cd "$ARCHNAME/" && ./ruby -e 'puts "sha=#{RUBY_REVISION}"' >> $GITHUB_OUTPUT if: failure() && inputs.notify-ruby-sha && github.event_name == 'schedule' - uses: ruby/action-slack@54175162371f1f7c8eb94d7c8644ee2479fcd375 # v3.2.2 with: diff --git a/.github/workflows/tarball-windows.yml b/.github/workflows/tarball-windows.yml index 9c7940e1729477..52a28f006e6447 100644 --- a/.github/workflows/tarball-windows.yml +++ b/.github/workflows/tarball-windows.yml @@ -12,19 +12,16 @@ on: required: false type: string default: '' - mode: - description: '"modern" (vssetup + 2022/2025-vs2026) or "legacy" (vs2022 + vcvars 14.2 + test-all/test-spec split)' - required: false - type: string - default: 'modern' jobs: windows: strategy: matrix: - include: ${{ fromJSON(inputs.mode == 'legacy' - && '[{"os":"2022","vs":2022,"vcvars":"10.0.22621.0 -vcvars_ver=14.2","test_task":"check"}]' - || '[{"os":"2022","test_task":"check"},{"os":"2025-vs2026","test_task":"check"}]') }} + include: + - os: '2022' + test_task: check + - os: '2025-vs2026' + test_task: check fail-fast: false runs-on: windows-${{ matrix.os }} defaults: @@ -94,7 +91,7 @@ jobs: path: snapshot-*/.downloaded-cache key: downloaded-cache - - name: setup env (modern) + - name: setup env # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 env: @@ -106,20 +103,6 @@ jobs: set TEMP=%USERPROFILE%\AppData\Local\Temp set /a TEST_JOBS=(15 * %NUMBER_OF_PROCESSORS% / 10) > nul set > new.env - if: inputs.mode == 'modern' - - name: setup env (legacy) - run: | - if not "%VCVARS%" == "" goto :vcset - set VCVARS="C:\Program Files (x86)\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - if not exist %VCVARS% set VCVARS="C:\Program Files\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - :vcset - set > old.env - call %VCVARS% ${{ matrix.vcvars }} - set TMP=%USERPROFILE%\AppData\Local\Temp - set TEMP=%USERPROFILE%\AppData\Local\Temp - set /a TEST_JOBS=(15 * %NUMBER_OF_PROCESSORS% / 10) > nul - set > new.env - if: inputs.mode == 'legacy' - name: update env shell: pwsh diff --git a/NEWS.md b/NEWS.md index 08a2d65e07dbf2..3d88e29ddf28c1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -75,9 +75,9 @@ releases. ### The following default gems are updated. * RubyGems 4.1.0.dev - * 4.0.3 to [v4.0.4][RubyGems-v4.0.4], [v4.0.5][RubyGems-v4.0.5], [v4.0.6][RubyGems-v4.0.6], [v4.0.7][RubyGems-v4.0.7], [v4.0.8][RubyGems-v4.0.8], [v4.0.9][RubyGems-v4.0.9], [v4.0.10][RubyGems-v4.0.10], [v4.0.11][RubyGems-v4.0.11] + * 4.0.3 to [v4.0.4][RubyGems-v4.0.4], [v4.0.5][RubyGems-v4.0.5], [v4.0.6][RubyGems-v4.0.6], [v4.0.7][RubyGems-v4.0.7], [v4.0.8][RubyGems-v4.0.8], [v4.0.9][RubyGems-v4.0.9], [v4.0.10][RubyGems-v4.0.10], [v4.0.11][RubyGems-v4.0.11], [v4.0.12][RubyGems-v4.0.12] * bundler 4.1.0.dev - * 4.0.3 to [v4.0.4][bundler-v4.0.4], [v4.0.5][bundler-v4.0.5], [v4.0.6][bundler-v4.0.6], [v4.0.7][bundler-v4.0.7], [v4.0.8][bundler-v4.0.8], [v4.0.9][bundler-v4.0.9], [v4.0.10][bundler-v4.0.10], [v4.0.11][bundler-v4.0.11] + * 4.0.3 to [v4.0.4][bundler-v4.0.4], [v4.0.5][bundler-v4.0.5], [v4.0.6][bundler-v4.0.6], [v4.0.7][bundler-v4.0.7], [v4.0.8][bundler-v4.0.8], [v4.0.9][bundler-v4.0.9], [v4.0.10][bundler-v4.0.10], [v4.0.11][bundler-v4.0.11], [v4.0.12][bundler-v4.0.12] * erb 6.0.4 * 6.0.1 to [v6.0.1.1][erb-v6.0.1.1], [v6.0.2][erb-v6.0.2], [v6.0.3][erb-v6.0.3], [v6.0.4][erb-v6.0.4] * ipaddr 1.2.9 @@ -186,6 +186,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [RubyGems-v4.0.9]: https://github.com/rubygems/rubygems/releases/tag/v4.0.9 [RubyGems-v4.0.10]: https://github.com/rubygems/rubygems/releases/tag/v4.0.10 [RubyGems-v4.0.11]: https://github.com/rubygems/rubygems/releases/tag/v4.0.11 +[RubyGems-v4.0.12]: https://github.com/rubygems/rubygems/releases/tag/v4.0.12 [bundler-v4.0.4]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.4 [bundler-v4.0.5]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.5 [bundler-v4.0.6]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.6 @@ -194,6 +195,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [bundler-v4.0.9]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.9 [bundler-v4.0.10]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.10 [bundler-v4.0.11]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.11 +[bundler-v4.0.12]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.12 [erb-v6.0.1.1]: https://github.com/ruby/erb/releases/tag/v6.0.1.1 [erb-v6.0.2]: https://github.com/ruby/erb/releases/tag/v6.0.2 [erb-v6.0.3]: https://github.com/ruby/erb/releases/tag/v6.0.3 diff --git a/configure.ac b/configure.ac index cfa78394d40b7d..2de91209d61919 100644 --- a/configure.ac +++ b/configure.ac @@ -288,6 +288,11 @@ AS_CASE(["/${rb_CC} "], RUBY_CHECK_PROG_FOR_CC([OBJDUMP], [clang], [${llvm_prefix}objdump]) RUBY_CHECK_PROG_FOR_CC([RANLIB], [clang], [${llvm_prefix}ranlib]) RUBY_CHECK_PROG_FOR_CC([STRIP], [clang], [${llvm_prefix}strip]) + + # These -Wno-* flags silence clang-specific diagnostics that don't exist + # in GCC. GCC silently accepts unknown -Wno-* flags but later emits noisy + # "unrecognized command-line option" notes whenever another warning fires. + clang_warnflags="-Wno-constant-logical-operand -Wno-parentheses-equality -Wno-self-assign" ]) AS_UNSET(rb_CC) AS_UNSET(rb_dummy) @@ -787,13 +792,10 @@ AS_CASE(["$GCC:${warnflags+set}:${extra_warnflags:+set}:"], -Wimplicit-fallthrough=0 \ -Wmissing-noreturn \ -Wno-cast-function-type \ - -Wno-constant-logical-operand \ -Wno-long-long \ -Wno-missing-field-initializers \ -Wno-overlength-strings \ -Wno-packed-bitfield-compat \ - -Wno-parentheses-equality \ - -Wno-self-assign \ -Wno-tautological-compare \ -Wno-unused-parameter \ -Wno-unused-value \ @@ -801,6 +803,7 @@ AS_CASE(["$GCC:${warnflags+set}:${extra_warnflags:+set}:"], -Wsuggest-attribute=noreturn \ -Wunused-variable \ -diag-disable=175,188,1684,2259,2312 \ + $clang_warnflags \ $extra_warnflags \ ; do AS_IF([test "$particular_werror_flags" != yes], [ diff --git a/gc/default/default.c b/gc/default/default.c index e47c31d08d506e..9791236002f5bd 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -196,18 +196,6 @@ static RB_THREAD_LOCAL_SPECIFIER int malloc_increase_local; SLOT(32) SLOT(64) SLOT(128) SLOT(256) SLOT(512) #endif -/* Precomputed reciprocals for fast slot index calculation. - * For slot size d: reciprocal = ceil(2^48 / d). - * Then offset / d == (uint32_t)((offset * reciprocal) >> 48) - * for all offset < HEAP_PAGE_SIZE. */ -#define SLOT_RECIPROCAL_SHIFT 48 -#define SLOT_RECIPROCAL(size) (((1ULL << SLOT_RECIPROCAL_SHIFT) + (size) - 1) / (size)) - -static const uint64_t heap_slot_reciprocal_table[HEAP_COUNT] = { -#define SLOT(size) SLOT_RECIPROCAL(size), - EACH_POOL_SLOT_SIZE(SLOT) -#undef SLOT -}; typedef struct ractor_newobj_heap_cache { struct free_slot *freelist; struct heap_page *using_page; @@ -711,11 +699,23 @@ size_t rb_gc_impl_obj_slot_size(VALUE obj); #define RVALUE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD) static const size_t pool_slot_sizes[HEAP_COUNT] = { -#define SLOT(size) size, +#define SLOT(size) ((size) + RVALUE_OVERHEAD), EACH_POOL_SLOT_SIZE(SLOT) #undef SLOT }; +/* Precomputed reciprocals for fast slot index calculation. + * For slot size d: reciprocal = ceil(2^48 / d). + * Then offset / d == (uint32_t)((offset * reciprocal) >> 48) + * for all offset < HEAP_PAGE_SIZE. */ +#define SLOT_RECIPROCAL_SHIFT 48 +#define SLOT_RECIPROCAL(size) (((1ULL << SLOT_RECIPROCAL_SHIFT) + (size) - 1) / (size)) + +static const uint64_t heap_slot_reciprocal_table[HEAP_COUNT] = { +#define SLOT(size) SLOT_RECIPROCAL((size) + RVALUE_OVERHEAD), + EACH_POOL_SLOT_SIZE(SLOT) +#undef SLOT +}; #if SIZEOF_VALUE >= 8 static uint8_t size_to_heap_idx[1024 / 8 + 1]; diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 24f05676b11038..96e9e32ef6a340 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -475,6 +475,7 @@ rb_mmtk_special_const_p(MMTk_ObjectReference object) } RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) +RBIMPL_ATTR_NORETURN() static void rb_mmtk_gc_thread_bug(const char *msg, ...) { @@ -505,6 +506,7 @@ rb_mmtk_gc_thread_panic_handler(void) rb_mmtk_gc_thread_bug("MMTk GC thread panicked"); } +RBIMPL_ATTR_NORETURN() static void rb_mmtk_mutator_thread_panic_handler(void) { diff --git a/io.c b/io.c index cb1c95460adc0b..15a05c930b8485 100644 --- a/io.c +++ b/io.c @@ -2536,7 +2536,7 @@ interpret_seek_whence(VALUE vwhence) * f.tell # => 12 * f.close * - * - +:SET+ or IO:SEEK_SET: + * - +:SET+ or IO::SEEK_SET: * Repositions the stream to the given +offset+: * * f = File.open('t.txt') diff --git a/test/.excludes/TestThread.rb b/test/.excludes/TestThread.rb index f26ea420a60662..63f193e484ee56 100644 --- a/test/.excludes/TestThread.rb +++ b/test/.excludes/TestThread.rb @@ -15,4 +15,6 @@ if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # to avoid "`failed to allocate memory (NoMemoryError)" error exclude(:test_thread_interrupt_for_killed_thread, 'TODO') + # timeout only on mswin, not mingw + exclude(:test_thread_join_during_finalizers, 'Timeout') end diff --git a/tool/make-snapshot b/tool/make-snapshot index 912cb9ce9242b3..dff636d601482c 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -54,7 +54,7 @@ PACKAGES = { "xz" => %w".tar.xz xz -c", "zip" => %w".zip zip -Xqr", } -DEFAULT_PACKAGES = PACKAGES.keys - ["tar"] +DEFAULT_PACKAGES = PACKAGES.keys - ["tar", "bzip"] if !$no7z and system("7z", out: IO::NULL) PACKAGES["gzip"] = %w".tar.gz 7z a dummy -tgzip -mx -so" PACKAGES["zip"] = %w".zip 7z a -tzip -mx -mtc=off" << {out: IO::NULL}