diff --git a/.cargo/config.toml b/.cargo/config.toml
index a5ff1d8dffa08affe3e1b20720eea65edc924813..bd46659f7991a95d853711672a6a4eed9222c5a1 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -37,3 +37,8 @@ rustflags = [
   "-Aclippy::extra-unused-type-parameters", # stylistic
   "-Aclippy::default_constructed_unit_structs", # stylistic
 ]
+
+[env]
+# Needed for musl builds so user doesn't have to install musl-tools.
+CC_x86_64_unknown_linux_musl = { value = ".cargo/musl-gcc", force = true, relative = true }
+CXX_x86_64_unknown_linux_musl = { value = ".cargo/musl-g++", force = true, relative = true }
diff --git a/.cargo/musl-g++ b/.cargo/musl-g++
new file mode 100755
index 0000000000000000000000000000000000000000..656adcc2ac1b6785b708d8828b29d93e03a77272
--- /dev/null
+++ b/.cargo/musl-g++
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Wrapper for building with musl.
+#
+# See comments for musl-gcc in this repo.
+
+g++ "$@"
diff --git a/.cargo/musl-gcc b/.cargo/musl-gcc
new file mode 100755
index 0000000000000000000000000000000000000000..5bc7852dbc6ec25f51403ae58fedc7ba094f78b7
--- /dev/null
+++ b/.cargo/musl-gcc
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# Wrapper for building with musl.
+#
+# musl unfortunately requires a musl-enabled C compiler (musl-gcc) to be
+# installed, which can be kind of a pain to get installed depending on the
+# distro. That's not a very good user experience.
+#
+# The real musl-gcc wrapper sets the correct system include paths for linking
+# with musl libc library. Since this is not actually used to link any binaries
+# it should most likely work just fine.
+
+gcc "$@"
diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml
index eb70b46af60847558f9150aaa0ed4dbf47c1210d..7d7007acd8a8bba2a266422a096a1901734771f2 100644
--- a/.gitlab/pipeline/test.yml
+++ b/.gitlab/pipeline/test.yml
@@ -503,3 +503,23 @@ cargo-hfuzz:
     - cargo hfuzz build
     - for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); do
       cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; done
+
+# cf https://github.com/paritytech/polkadot-sdk/issues/1652
+test-syscalls:
+  stage: test
+  extends:
+    - .docker-env
+    - .common-refs
+    - .run-immediately
+  variables:
+    SKIP_WASM_BUILD: 1
+  script:
+    - cargo build --locked --profile production --target x86_64-unknown-linux-musl --bin polkadot-execute-worker --bin polkadot-prepare-worker
+    - cd polkadot/scripts/list-syscalls
+    - ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-execute-worker --only-used-syscalls | diff -u execute-worker-syscalls -
+    - ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-prepare-worker --only-used-syscalls | diff -u prepare-worker-syscalls -
+  after_script:
+    - if [[ "$CI_JOB_STATUS" == "failed" ]]; then
+        printf "The x86_64 syscalls used by the worker binaries have changed. Please review if this is expected and update polkadot/scripts/list-syscalls/*-worker-syscalls as needed.\n";
+      fi
+  allow_failure: true # TODO: remove this once we have an idea how often the syscall lists will change
diff --git a/polkadot/scripts/list-syscalls/execute-worker-syscalls b/polkadot/scripts/list-syscalls/execute-worker-syscalls
new file mode 100644
index 0000000000000000000000000000000000000000..05abe9ba7368c43b766d1c888cd631aa9903d6b2
--- /dev/null
+++ b/polkadot/scripts/list-syscalls/execute-worker-syscalls
@@ -0,0 +1,74 @@
+0 (read)
+1 (write)
+2 (open)
+3 (close)
+4 (stat)
+5 (fstat)
+7 (poll)
+8 (lseek)
+9 (mmap)
+10 (mprotect)
+11 (munmap)
+12 (brk)
+13 (rt_sigaction)
+14 (rt_sigprocmask)
+15 (rt_sigreturn)
+16 (ioctl)
+19 (readv)
+20 (writev)
+24 (sched_yield)
+25 (mremap)
+28 (madvise)
+39 (getpid)
+41 (socket)
+42 (connect)
+45 (recvfrom)
+46 (sendmsg)
+53 (socketpair)
+56 (clone)
+60 (exit)
+61 (wait4)
+62 (kill)
+72 (fcntl)
+79 (getcwd)
+80 (chdir)
+82 (rename)
+83 (mkdir)
+87 (unlink)
+89 (readlink)
+96 (gettimeofday)
+97 (getrlimit)
+99 (sysinfo)
+102 (getuid)
+110 (getppid)
+131 (sigaltstack)
+140 (getpriority)
+141 (setpriority)
+144 (sched_setscheduler)
+157 (prctl)
+158 (arch_prctl)
+165 (mount)
+166 (umount2)
+200 (tkill)
+202 (futex)
+204 (sched_getaffinity)
+213 (epoll_create)
+217 (getdents64)
+218 (set_tid_address)
+228 (clock_gettime)
+230 (clock_nanosleep)
+231 (exit_group)
+232 (epoll_wait)
+233 (epoll_ctl)
+257 (openat)
+262 (newfstatat)
+263 (unlinkat)
+272 (unshare)
+273 (set_robust_list)
+281 (epoll_pwait)
+284 (eventfd)
+290 (eventfd2)
+291 (epoll_create1)
+302 (prlimit64)
+318 (getrandom)
+319 (memfd_create)
diff --git a/polkadot/scripts/list-syscalls/list-syscalls.rb b/polkadot/scripts/list-syscalls/list-syscalls.rb
new file mode 100755
index 0000000000000000000000000000000000000000..9cef6f74b2ebe5588fca908f3787791a956ba50e
--- /dev/null
+++ b/polkadot/scripts/list-syscalls/list-syscalls.rb
@@ -0,0 +1,608 @@
+#!/usr/bin/ruby
+
+# A script to statically list syscalls used by a given binary.
+#
+# Syntax:  list-syscalls.rb <binary> [--only-used-syscalls]
+#
+# NOTE:    For accurate results, build the binary with musl and LTO enabled.
+# Example: ./polkadot/scripts/list-syscalls/list-syscalls.rb target/x86_64-unknown-linux-musl/production/polkadot-prepare-worker --only-used-syscalls
+#
+# Author:  @koute
+# Source:  https://gist.github.com/koute/166f82bfee5e27324077891008fca6eb
+
+require 'shellwords'
+require 'set'
+
+SYNTAX_STRING = 'Syntax: list-syscalls.rb <binary> [--only-used-syscalls]'.freeze
+
+# Generated from `libc` using the following regex:
+#   'pub const SYS_([a-z0-9_]+): ::c_long = (\d+);'
+#   '    \2 => "\1",'
+SYSCALLS = {
+  0 => 'read',
+  1 => 'write',
+  2 => 'open',
+  3 => 'close',
+  4 => 'stat',
+  5 => 'fstat',
+  6 => 'lstat',
+  7 => 'poll',
+  8 => 'lseek',
+  9 => 'mmap',
+  10 => 'mprotect',
+  11 => 'munmap',
+  12 => 'brk',
+  13 => 'rt_sigaction',
+  14 => 'rt_sigprocmask',
+  15 => 'rt_sigreturn',
+  16 => 'ioctl',
+  17 => 'pread64',
+  18 => 'pwrite64',
+  19 => 'readv',
+  20 => 'writev',
+  21 => 'access',
+  22 => 'pipe',
+  23 => 'select',
+  24 => 'sched_yield',
+  25 => 'mremap',
+  26 => 'msync',
+  27 => 'mincore',
+  28 => 'madvise',
+  29 => 'shmget',
+  30 => 'shmat',
+  31 => 'shmctl',
+  32 => 'dup',
+  33 => 'dup2',
+  34 => 'pause',
+  35 => 'nanosleep',
+  36 => 'getitimer',
+  37 => 'alarm',
+  38 => 'setitimer',
+  39 => 'getpid',
+  40 => 'sendfile',
+  41 => 'socket',
+  42 => 'connect',
+  43 => 'accept',
+  44 => 'sendto',
+  45 => 'recvfrom',
+  46 => 'sendmsg',
+  47 => 'recvmsg',
+  48 => 'shutdown',
+  49 => 'bind',
+  50 => 'listen',
+  51 => 'getsockname',
+  52 => 'getpeername',
+  53 => 'socketpair',
+  54 => 'setsockopt',
+  55 => 'getsockopt',
+  56 => 'clone',
+  57 => 'fork',
+  58 => 'vfork',
+  59 => 'execve',
+  60 => 'exit',
+  61 => 'wait4',
+  62 => 'kill',
+  63 => 'uname',
+  64 => 'semget',
+  65 => 'semop',
+  66 => 'semctl',
+  67 => 'shmdt',
+  68 => 'msgget',
+  69 => 'msgsnd',
+  70 => 'msgrcv',
+  71 => 'msgctl',
+  72 => 'fcntl',
+  73 => 'flock',
+  74 => 'fsync',
+  75 => 'fdatasync',
+  76 => 'truncate',
+  77 => 'ftruncate',
+  78 => 'getdents',
+  79 => 'getcwd',
+  80 => 'chdir',
+  81 => 'fchdir',
+  82 => 'rename',
+  83 => 'mkdir',
+  84 => 'rmdir',
+  85 => 'creat',
+  86 => 'link',
+  87 => 'unlink',
+  88 => 'symlink',
+  89 => 'readlink',
+  90 => 'chmod',
+  91 => 'fchmod',
+  92 => 'chown',
+  93 => 'fchown',
+  94 => 'lchown',
+  95 => 'umask',
+  96 => 'gettimeofday',
+  97 => 'getrlimit',
+  98 => 'getrusage',
+  99 => 'sysinfo',
+  100 => 'times',
+  101 => 'ptrace',
+  102 => 'getuid',
+  103 => 'syslog',
+  104 => 'getgid',
+  105 => 'setuid',
+  106 => 'setgid',
+  107 => 'geteuid',
+  108 => 'getegid',
+  109 => 'setpgid',
+  110 => 'getppid',
+  111 => 'getpgrp',
+  112 => 'setsid',
+  113 => 'setreuid',
+  114 => 'setregid',
+  115 => 'getgroups',
+  116 => 'setgroups',
+  117 => 'setresuid',
+  118 => 'getresuid',
+  119 => 'setresgid',
+  120 => 'getresgid',
+  121 => 'getpgid',
+  122 => 'setfsuid',
+  123 => 'setfsgid',
+  124 => 'getsid',
+  125 => 'capget',
+  126 => 'capset',
+  127 => 'rt_sigpending',
+  128 => 'rt_sigtimedwait',
+  129 => 'rt_sigqueueinfo',
+  130 => 'rt_sigsuspend',
+  131 => 'sigaltstack',
+  132 => 'utime',
+  133 => 'mknod',
+  134 => 'uselib',
+  135 => 'personality',
+  136 => 'ustat',
+  137 => 'statfs',
+  138 => 'fstatfs',
+  139 => 'sysfs',
+  140 => 'getpriority',
+  141 => 'setpriority',
+  142 => 'sched_setparam',
+  143 => 'sched_getparam',
+  144 => 'sched_setscheduler',
+  145 => 'sched_getscheduler',
+  146 => 'sched_get_priority_max',
+  147 => 'sched_get_priority_min',
+  148 => 'sched_rr_get_interval',
+  149 => 'mlock',
+  150 => 'munlock',
+  151 => 'mlockall',
+  152 => 'munlockall',
+  153 => 'vhangup',
+  154 => 'modify_ldt',
+  155 => 'pivot_root',
+  156 => '_sysctl',
+  157 => 'prctl',
+  158 => 'arch_prctl',
+  159 => 'adjtimex',
+  160 => 'setrlimit',
+  161 => 'chroot',
+  162 => 'sync',
+  163 => 'acct',
+  164 => 'settimeofday',
+  165 => 'mount',
+  166 => 'umount2',
+  167 => 'swapon',
+  168 => 'swapoff',
+  169 => 'reboot',
+  170 => 'sethostname',
+  171 => 'setdomainname',
+  172 => 'iopl',
+  173 => 'ioperm',
+  174 => 'create_module',
+  175 => 'init_module',
+  176 => 'delete_module',
+  177 => 'get_kernel_syms',
+  178 => 'query_module',
+  179 => 'quotactl',
+  180 => 'nfsservctl',
+  181 => 'getpmsg',
+  182 => 'putpmsg',
+  183 => 'afs_syscall',
+  184 => 'tuxcall',
+  185 => 'security',
+  186 => 'gettid',
+  187 => 'readahead',
+  188 => 'setxattr',
+  189 => 'lsetxattr',
+  190 => 'fsetxattr',
+  191 => 'getxattr',
+  192 => 'lgetxattr',
+  193 => 'fgetxattr',
+  194 => 'listxattr',
+  195 => 'llistxattr',
+  196 => 'flistxattr',
+  197 => 'removexattr',
+  198 => 'lremovexattr',
+  199 => 'fremovexattr',
+  200 => 'tkill',
+  201 => 'time',
+  202 => 'futex',
+  203 => 'sched_setaffinity',
+  204 => 'sched_getaffinity',
+  205 => 'set_thread_area',
+  206 => 'io_setup',
+  207 => 'io_destroy',
+  208 => 'io_getevents',
+  209 => 'io_submit',
+  210 => 'io_cancel',
+  211 => 'get_thread_area',
+  212 => 'lookup_dcookie',
+  213 => 'epoll_create',
+  214 => 'epoll_ctl_old',
+  215 => 'epoll_wait_old',
+  216 => 'remap_file_pages',
+  217 => 'getdents64',
+  218 => 'set_tid_address',
+  219 => 'restart_syscall',
+  220 => 'semtimedop',
+  221 => 'fadvise64',
+  222 => 'timer_create',
+  223 => 'timer_settime',
+  224 => 'timer_gettime',
+  225 => 'timer_getoverrun',
+  226 => 'timer_delete',
+  227 => 'clock_settime',
+  228 => 'clock_gettime',
+  229 => 'clock_getres',
+  230 => 'clock_nanosleep',
+  231 => 'exit_group',
+  232 => 'epoll_wait',
+  233 => 'epoll_ctl',
+  234 => 'tgkill',
+  235 => 'utimes',
+  236 => 'vserver',
+  237 => 'mbind',
+  238 => 'set_mempolicy',
+  239 => 'get_mempolicy',
+  240 => 'mq_open',
+  241 => 'mq_unlink',
+  242 => 'mq_timedsend',
+  243 => 'mq_timedreceive',
+  244 => 'mq_notify',
+  245 => 'mq_getsetattr',
+  246 => 'kexec_load',
+  247 => 'waitid',
+  248 => 'add_key',
+  249 => 'request_key',
+  250 => 'keyctl',
+  251 => 'ioprio_set',
+  252 => 'ioprio_get',
+  253 => 'inotify_init',
+  254 => 'inotify_add_watch',
+  255 => 'inotify_rm_watch',
+  256 => 'migrate_pages',
+  257 => 'openat',
+  258 => 'mkdirat',
+  259 => 'mknodat',
+  260 => 'fchownat',
+  261 => 'futimesat',
+  262 => 'newfstatat',
+  263 => 'unlinkat',
+  264 => 'renameat',
+  265 => 'linkat',
+  266 => 'symlinkat',
+  267 => 'readlinkat',
+  268 => 'fchmodat',
+  269 => 'faccessat',
+  270 => 'pselect6',
+  271 => 'ppoll',
+  272 => 'unshare',
+  273 => 'set_robust_list',
+  274 => 'get_robust_list',
+  275 => 'splice',
+  276 => 'tee',
+  277 => 'sync_file_range',
+  278 => 'vmsplice',
+  279 => 'move_pages',
+  280 => 'utimensat',
+  281 => 'epoll_pwait',
+  282 => 'signalfd',
+  283 => 'timerfd_create',
+  284 => 'eventfd',
+  285 => 'fallocate',
+  286 => 'timerfd_settime',
+  287 => 'timerfd_gettime',
+  288 => 'accept4',
+  289 => 'signalfd4',
+  290 => 'eventfd2',
+  291 => 'epoll_create1',
+  292 => 'dup3',
+  293 => 'pipe2',
+  294 => 'inotify_init1',
+  295 => 'preadv',
+  296 => 'pwritev',
+  297 => 'rt_tgsigqueueinfo',
+  298 => 'perf_event_open',
+  299 => 'recvmmsg',
+  300 => 'fanotify_init',
+  301 => 'fanotify_mark',
+  302 => 'prlimit64',
+  303 => 'name_to_handle_at',
+  304 => 'open_by_handle_at',
+  305 => 'clock_adjtime',
+  306 => 'syncfs',
+  307 => 'sendmmsg',
+  308 => 'setns',
+  309 => 'getcpu',
+  310 => 'process_vm_readv',
+  311 => 'process_vm_writev',
+  312 => 'kcmp',
+  313 => 'finit_module',
+  314 => 'sched_setattr',
+  315 => 'sched_getattr',
+  316 => 'renameat2',
+  317 => 'seccomp',
+  318 => 'getrandom',
+  319 => 'memfd_create',
+  320 => 'kexec_file_load',
+  321 => 'bpf',
+  322 => 'execveat',
+  323 => 'userfaultfd',
+  324 => 'membarrier',
+  325 => 'mlock2',
+  326 => 'copy_file_range',
+  327 => 'preadv2',
+  328 => 'pwritev2',
+  329 => 'pkey_mprotect',
+  330 => 'pkey_alloc',
+  331 => 'pkey_free',
+  332 => 'statx',
+  334 => 'rseq',
+  424 => 'pidfd_send_signal',
+  425 => 'io_uring_setup',
+  426 => 'io_uring_enter',
+  427 => 'io_uring_register',
+  428 => 'open_tree',
+  429 => 'move_mount',
+  430 => 'fsopen',
+  431 => 'fsconfig',
+  432 => 'fsmount',
+  433 => 'fspick',
+  434 => 'pidfd_open',
+  435 => 'clone3',
+  436 => 'close_range',
+  437 => 'openat2',
+  438 => 'pidfd_getfd',
+  439 => 'faccessat2',
+  440 => 'process_madvise',
+  441 => 'epoll_pwait2',
+  442 => 'mount_setattr',
+  443 => 'quotactl_fd',
+  444 => 'landlock_create_ruleset',
+  445 => 'landlock_add_rule',
+  446 => 'landlock_restrict_self',
+  447 => 'memfd_secret',
+  448 => 'process_mrelease',
+  449 => 'futex_waitv',
+  450 => 'set_mempolicy_home_node'
+}.map { |num, name| [num, "#{num} (#{name})"] }.to_h
+
+REGS_R64 = %w[
+  rax
+  rbx
+  rcx
+  rdx
+  rsi
+  rdi
+  rsp
+  rbp
+  r8
+  r9
+  r10
+  r11
+  r12
+  r13
+  r14
+  r15
+]
+
+REGS_R32 = %w[
+  eax
+  ebx
+  ecx
+  edx
+  esi
+  edi
+  esp
+  ebp
+  r8d
+  r9d
+  r10d
+  r11d
+  r12d
+  r13d
+  r14d
+  r15d
+]
+
+REGS_R16 = %w[
+  ax
+  bx
+  cx
+  dx
+  si
+  di
+  sp
+  bp
+  r8w
+  r9w
+  r10w
+  r11w
+  r12w
+  r13w
+  r14w
+  r15w
+]
+
+REGS_R8 = %w[
+  al
+  bl
+  cl
+  dl
+  sil
+  dil
+  spl
+  bpl
+  r8b
+  r9b
+  r10b
+  r11b
+  r12b
+  r13b
+  r14b
+  r15b
+]
+
+REG_MAP = (REGS_R64.map { |r| [r, r] } + REGS_R32.zip(REGS_R64) + REGS_R16.zip(REGS_R64) + REGS_R8.zip(REGS_R64)).to_h
+REGS_R = (REGS_R64 + REGS_R32 + REGS_R16 + REGS_R8).join('|')
+
+if ARGV.empty?
+  warn SYNTAX_STRING
+  exit 1
+end
+
+file_path = ARGV[0]
+raise "no such file: #{file_path}" unless File.exist? file_path
+
+only_used_syscalls = false
+ARGV[1..].each do |arg|
+  if arg == '--only-used-syscalls'
+    only_used_syscalls = true
+  else
+    warn "invalid argument '#{arg}':\n#{SYNTAX_STRING}"
+    exit 1
+  end
+end
+
+puts 'Running objdump...' unless only_used_syscalls
+dump = `objdump -wd -j .text -M intel #{file_path.shellescape}`
+raise 'objdump failed' unless $?.exitstatus == 0
+
+puts 'Parsing objdump output...' unless only_used_syscalls
+current_fn = nil
+code_for_fn = {}
+fns_with_syscall = Set.new
+fns_with_indirect_syscall = Set.new
+dump.split("\n").each do |line|
+  if line =~ /\A[0-9a-f]+ <(.+?)>:/
+    current_fn = Regexp.last_match(1)
+    next
+  end
+
+  next unless current_fn
+  next if %w[syscall __syscall_cp_c].include?(current_fn) # These are for indirect syscalls.
+
+  code = line.strip.split("\t")[2]
+  next if [nil, ''].include?(code)
+
+  code_for_fn[current_fn] ||= []
+  code_for_fn[current_fn] << code.gsub(/[\t ]+/, ' ')
+
+  fns_with_syscall.add(current_fn) if code == 'syscall'
+  fns_with_indirect_syscall.add(current_fn) if code =~ /<(syscall|__syscall_cp)>/
+end
+
+unless only_used_syscalls
+  puts "Found #{fns_with_syscall.length} functions doing direct syscalls"
+  puts "Found #{fns_with_indirect_syscall.length} functions doing indirect syscalls"
+end
+
+syscalls_for_fn = {}
+not_found_count = 0
+(fns_with_syscall + fns_with_indirect_syscall).each do |fn_name|
+  syscalls_for_fn[fn_name] ||= []
+
+  if fn_name =~ /_ZN11parking_lot9raw_mutex8RawMutex9lock_slow.+/
+    # Hardcode 'SYS_futex' as this function produces a really messy assembly.
+    syscalls_for_fn[fn_name] << 202
+    next
+  end
+
+  code = code_for_fn[fn_name]
+
+  found = false
+  regs = {}
+  code.each do |inst|
+    if inst =~ /mov (#{REGS_R}),(.+)/
+      reg = Regexp.last_match(1)
+      value = Regexp.last_match(2)
+      regs[REG_MAP[reg]] = if value =~ /#{REGS_R}/
+                             regs[REG_MAP[value]]
+                           elsif value =~ /0x([0-9a-f]+)/
+                             Regexp.last_match(1).to_i(16)
+                           end
+    elsif inst =~ /xor (#{REGS_R}),(#{REGS_R})/
+      reg_1 = Regexp.last_match(1)
+      reg_2 = Regexp.last_match(1)
+      regs[REG_MAP[reg_1]] = 0 if reg_1 == reg_2
+    elsif inst =~ /lea (#{REGS_R}),(.+)/
+      reg = Regexp.last_match(1)
+      value = Regexp.last_match(2)
+      regs[REG_MAP[reg]] = ('syscall' if value.strip =~ /\[rip\+0x[a-z0-9]+\]\s*#\s*[0-9a-f]+\s*<syscall>/)
+    elsif inst =~ /(call|jmp) (#{REGS_R})/
+      reg = Regexp.last_match(2)
+      if regs[REG_MAP[reg]] == 'syscall'
+        if !regs['rdi'].nil?
+          syscalls_for_fn[fn_name] << regs['rdi']
+          found = true
+        else
+          found = false
+        end
+      end
+    elsif inst =~ /(call|jmp) [0-9a-f]+ <(syscall|__syscall_cp)>/
+      if !regs['rdi'].nil?
+        syscalls_for_fn[fn_name] << regs['rdi']
+        found = true
+      else
+        found = false
+      end
+    elsif inst == 'syscall'
+      if !regs['rax'].nil?
+        syscalls_for_fn[fn_name] << regs['rax']
+        found = true
+      else
+        found = false
+      end
+    end
+  end
+
+  next if found
+
+  puts "WARN: Function triggers a syscall but couldn't figure out which one: #{fn_name}"
+  puts '  ' + code.join("\n  ")
+  puts
+  not_found_count += 1
+end
+
+puts "WARN: Failed to figure out syscall for #{not_found_count} function(s)" if not_found_count > 0
+
+fns_for_syscall = {}
+syscalls_for_fn.each do |fn_name, syscalls|
+  syscalls.each do |syscall|
+    fns_for_syscall[syscall] ||= []
+    fns_for_syscall[syscall] << fn_name
+  end
+end
+
+if only_used_syscalls
+  puts syscalls_for_fn.values.flatten.sort.uniq.map { |sc| SYSCALLS[sc] || sc }.join("\n")
+else
+  puts 'Functions per syscall:'
+  fns_for_syscall.sort_by { |sc, _| sc }.each do |syscall, fn_names|
+    fn_names = fn_names.sort.uniq
+
+    puts "    #{SYSCALLS[syscall] || syscall} [#{fn_names.length} functions]"
+    fn_names.each do |fn_name|
+      puts "        #{fn_name}"
+    end
+  end
+
+  puts
+  puts 'Used syscalls:'
+  puts '    ' + syscalls_for_fn.values.flatten.sort.uniq.map { |sc| SYSCALLS[sc] || sc }.join("\n    ")
+end
diff --git a/polkadot/scripts/list-syscalls/prepare-worker-syscalls b/polkadot/scripts/list-syscalls/prepare-worker-syscalls
new file mode 100644
index 0000000000000000000000000000000000000000..f1597f20675652a130a7e23e9039b0ad153ee306
--- /dev/null
+++ b/polkadot/scripts/list-syscalls/prepare-worker-syscalls
@@ -0,0 +1,76 @@
+0 (read)
+1 (write)
+2 (open)
+3 (close)
+4 (stat)
+5 (fstat)
+7 (poll)
+8 (lseek)
+9 (mmap)
+10 (mprotect)
+11 (munmap)
+12 (brk)
+13 (rt_sigaction)
+14 (rt_sigprocmask)
+15 (rt_sigreturn)
+16 (ioctl)
+19 (readv)
+20 (writev)
+24 (sched_yield)
+25 (mremap)
+28 (madvise)
+39 (getpid)
+41 (socket)
+42 (connect)
+45 (recvfrom)
+46 (sendmsg)
+53 (socketpair)
+56 (clone)
+60 (exit)
+61 (wait4)
+62 (kill)
+72 (fcntl)
+79 (getcwd)
+80 (chdir)
+82 (rename)
+83 (mkdir)
+87 (unlink)
+89 (readlink)
+96 (gettimeofday)
+97 (getrlimit)
+98 (getrusage)
+99 (sysinfo)
+102 (getuid)
+110 (getppid)
+131 (sigaltstack)
+140 (getpriority)
+141 (setpriority)
+144 (sched_setscheduler)
+157 (prctl)
+158 (arch_prctl)
+165 (mount)
+166 (umount2)
+200 (tkill)
+202 (futex)
+203 (sched_setaffinity)
+204 (sched_getaffinity)
+213 (epoll_create)
+217 (getdents64)
+218 (set_tid_address)
+228 (clock_gettime)
+230 (clock_nanosleep)
+231 (exit_group)
+232 (epoll_wait)
+233 (epoll_ctl)
+257 (openat)
+262 (newfstatat)
+263 (unlinkat)
+272 (unshare)
+273 (set_robust_list)
+281 (epoll_pwait)
+284 (eventfd)
+290 (eventfd2)
+291 (epoll_create1)
+302 (prlimit64)
+309 (getcpu)
+318 (getrandom)